@calltelemetry/ct-lab-mcp 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.
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/dist/bin/ct-lab-mcp.d.ts +60 -0
- package/dist/bin/ct-lab-mcp.d.ts.map +1 -0
- package/dist/bin/ct-lab-mcp.js +261 -0
- package/dist/bin/ct-lab-mcp.js.map +1 -0
- package/dist/bin/proxmox-mcp.d.ts +58 -0
- package/dist/bin/proxmox-mcp.d.ts.map +1 -0
- package/dist/bin/proxmox-mcp.js +248 -0
- package/dist/bin/proxmox-mcp.js.map +1 -0
- package/dist/src/appliance/commands.d.ts +33 -0
- package/dist/src/appliance/commands.d.ts.map +1 -0
- package/dist/src/appliance/commands.js +241 -0
- package/dist/src/appliance/commands.js.map +1 -0
- package/dist/src/appliance/executor.d.ts +34 -0
- package/dist/src/appliance/executor.d.ts.map +1 -0
- package/dist/src/appliance/executor.js +214 -0
- package/dist/src/appliance/executor.js.map +1 -0
- package/dist/src/appliance/index.d.ts +8 -0
- package/dist/src/appliance/index.d.ts.map +1 -0
- package/dist/src/appliance/index.js +8 -0
- package/dist/src/appliance/index.js.map +1 -0
- package/dist/src/appliance/mock.d.ts +66 -0
- package/dist/src/appliance/mock.d.ts.map +1 -0
- package/dist/src/appliance/mock.js +339 -0
- package/dist/src/appliance/mock.js.map +1 -0
- package/dist/src/appliance/types.d.ts +83 -0
- package/dist/src/appliance/types.d.ts.map +1 -0
- package/dist/src/appliance/types.js +58 -0
- package/dist/src/appliance/types.js.map +1 -0
- package/dist/src/credentials/doppler.d.ts +25 -0
- package/dist/src/credentials/doppler.d.ts.map +1 -0
- package/dist/src/credentials/doppler.js +68 -0
- package/dist/src/credentials/doppler.js.map +1 -0
- package/dist/src/credentials/index.d.ts +4 -0
- package/dist/src/credentials/index.d.ts.map +1 -0
- package/dist/src/credentials/index.js +4 -0
- package/dist/src/credentials/index.js.map +1 -0
- package/dist/src/credentials/resolver.d.ts +36 -0
- package/dist/src/credentials/resolver.d.ts.map +1 -0
- package/dist/src/credentials/resolver.js +427 -0
- package/dist/src/credentials/resolver.js.map +1 -0
- package/dist/src/credentials/types.d.ts +73 -0
- package/dist/src/credentials/types.d.ts.map +1 -0
- package/dist/src/credentials/types.js +5 -0
- package/dist/src/credentials/types.js.map +1 -0
- package/dist/src/guardrails/index.d.ts +3 -0
- package/dist/src/guardrails/index.d.ts.map +1 -0
- package/dist/src/guardrails/index.js +3 -0
- package/dist/src/guardrails/index.js.map +1 -0
- package/dist/src/guardrails/safety.d.ts +45 -0
- package/dist/src/guardrails/safety.d.ts.map +1 -0
- package/dist/src/guardrails/safety.js +217 -0
- package/dist/src/guardrails/safety.js.map +1 -0
- package/dist/src/guardrails/types.d.ts +36 -0
- package/dist/src/guardrails/types.d.ts.map +1 -0
- package/dist/src/guardrails/types.js +20 -0
- package/dist/src/guardrails/types.js.map +1 -0
- package/dist/src/hypervisors/esxi/client.d.ts +103 -0
- package/dist/src/hypervisors/esxi/client.d.ts.map +1 -0
- package/dist/src/hypervisors/esxi/client.js +395 -0
- package/dist/src/hypervisors/esxi/client.js.map +1 -0
- package/dist/src/hypervisors/esxi/mock.d.ts +45 -0
- package/dist/src/hypervisors/esxi/mock.d.ts.map +1 -0
- package/dist/src/hypervisors/esxi/mock.js +202 -0
- package/dist/src/hypervisors/esxi/mock.js.map +1 -0
- package/dist/src/hypervisors/index.d.ts +10 -0
- package/dist/src/hypervisors/index.d.ts.map +1 -0
- package/dist/src/hypervisors/index.js +10 -0
- package/dist/src/hypervisors/index.js.map +1 -0
- package/dist/src/hypervisors/manager.d.ts +78 -0
- package/dist/src/hypervisors/manager.d.ts.map +1 -0
- package/dist/src/hypervisors/manager.js +415 -0
- package/dist/src/hypervisors/manager.js.map +1 -0
- package/dist/src/hypervisors/proxmox/client.d.ts +29 -0
- package/dist/src/hypervisors/proxmox/client.d.ts.map +1 -0
- package/dist/src/hypervisors/proxmox/client.js +278 -0
- package/dist/src/hypervisors/proxmox/client.js.map +1 -0
- package/dist/src/hypervisors/proxmox/mock.d.ts +17 -0
- package/dist/src/hypervisors/proxmox/mock.d.ts.map +1 -0
- package/dist/src/hypervisors/proxmox/mock.js +26 -0
- package/dist/src/hypervisors/proxmox/mock.js.map +1 -0
- package/dist/src/hypervisors/types.d.ts +159 -0
- package/dist/src/hypervisors/types.d.ts.map +1 -0
- package/dist/src/hypervisors/types.js +6 -0
- package/dist/src/hypervisors/types.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/proxmox/client.d.ts +37 -0
- package/dist/src/proxmox/client.d.ts.map +1 -0
- package/dist/src/proxmox/client.js +282 -0
- package/dist/src/proxmox/client.js.map +1 -0
- package/dist/src/proxmox/index.d.ts +53 -0
- package/dist/src/proxmox/index.d.ts.map +1 -0
- package/dist/src/proxmox/index.js +109 -0
- package/dist/src/proxmox/index.js.map +1 -0
- package/dist/src/proxmox/mock.d.ts +40 -0
- package/dist/src/proxmox/mock.d.ts.map +1 -0
- package/dist/src/proxmox/mock.js +362 -0
- package/dist/src/proxmox/mock.js.map +1 -0
- package/dist/src/proxmox/poller.d.ts +29 -0
- package/dist/src/proxmox/poller.d.ts.map +1 -0
- package/dist/src/proxmox/poller.js +107 -0
- package/dist/src/proxmox/poller.js.map +1 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +2 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/mcp-server.d.ts +5 -0
- package/dist/src/server/mcp-server.d.ts.map +1 -0
- package/dist/src/server/mcp-server.js +5 -0
- package/dist/src/server/mcp-server.js.map +1 -0
- package/dist/src/server.d.ts +35 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +95 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/slot/gc.d.ts +42 -0
- package/dist/src/slot/gc.d.ts.map +1 -0
- package/dist/src/slot/gc.js +79 -0
- package/dist/src/slot/gc.js.map +1 -0
- package/dist/src/slot/index.d.ts +4 -0
- package/dist/src/slot/index.d.ts.map +1 -0
- package/dist/src/slot/index.js +4 -0
- package/dist/src/slot/index.js.map +1 -0
- package/dist/src/slot/manager.d.ts +92 -0
- package/dist/src/slot/manager.d.ts.map +1 -0
- package/dist/src/slot/manager.js +650 -0
- package/dist/src/slot/manager.js.map +1 -0
- package/dist/src/slot/types.d.ts +184 -0
- package/dist/src/slot/types.d.ts.map +1 -0
- package/dist/src/slot/types.js +67 -0
- package/dist/src/slot/types.js.map +1 -0
- package/dist/src/tools/appliance-exec.d.ts +81 -0
- package/dist/src/tools/appliance-exec.d.ts.map +1 -0
- package/dist/src/tools/appliance-exec.js +134 -0
- package/dist/src/tools/appliance-exec.js.map +1 -0
- package/dist/src/tools/claim-slot.d.ts +5 -0
- package/dist/src/tools/claim-slot.d.ts.map +1 -0
- package/dist/src/tools/claim-slot.js +5 -0
- package/dist/src/tools/claim-slot.js.map +1 -0
- package/dist/src/tools/index.d.ts +26 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +63 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/list-hosts.d.ts +41 -0
- package/dist/src/tools/list-hosts.d.ts.map +1 -0
- package/dist/src/tools/list-hosts.js +91 -0
- package/dist/src/tools/list-hosts.js.map +1 -0
- package/dist/src/tools/list-inventory.d.ts +50 -0
- package/dist/src/tools/list-inventory.d.ts.map +1 -0
- package/dist/src/tools/list-inventory.js +112 -0
- package/dist/src/tools/list-inventory.js.map +1 -0
- package/dist/src/tools/list-slots.d.ts +5 -0
- package/dist/src/tools/list-slots.d.ts.map +1 -0
- package/dist/src/tools/list-slots.js +5 -0
- package/dist/src/tools/list-slots.js.map +1 -0
- package/dist/src/tools/provision-tools.d.ts +231 -0
- package/dist/src/tools/provision-tools.d.ts.map +1 -0
- package/dist/src/tools/provision-tools.js +289 -0
- package/dist/src/tools/provision-tools.js.map +1 -0
- package/dist/src/tools/release-slot.d.ts +5 -0
- package/dist/src/tools/release-slot.d.ts.map +1 -0
- package/dist/src/tools/release-slot.js +5 -0
- package/dist/src/tools/release-slot.js.map +1 -0
- package/dist/src/tools/slot-tools.d.ts +173 -0
- package/dist/src/tools/slot-tools.d.ts.map +1 -0
- package/dist/src/tools/slot-tools.js +296 -0
- package/dist/src/tools/slot-tools.js.map +1 -0
- package/dist/src/tools/vm-action.d.ts +189 -0
- package/dist/src/tools/vm-action.d.ts.map +1 -0
- package/dist/src/tools/vm-action.js +300 -0
- package/dist/src/tools/vm-action.js.map +1 -0
- package/dist/src/transports/index.d.ts +3 -0
- package/dist/src/transports/index.d.ts.map +1 -0
- package/dist/src/transports/index.js +3 -0
- package/dist/src/transports/index.js.map +1 -0
- package/dist/src/transports/sse.d.ts +38 -0
- package/dist/src/transports/sse.d.ts.map +1 -0
- package/dist/src/transports/sse.js +167 -0
- package/dist/src/transports/sse.js.map +1 -0
- package/dist/src/transports/stdio.d.ts +15 -0
- package/dist/src/transports/stdio.d.ts.map +1 -0
- package/dist/src/transports/stdio.js +34 -0
- package/dist/src/transports/stdio.js.map +1 -0
- package/dist/src/types/config.d.ts +17 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +5 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/index.d.ts +4 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +4 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/mcp.d.ts +38 -0
- package/dist/src/types/mcp.d.ts.map +1 -0
- package/dist/src/types/mcp.js +46 -0
- package/dist/src/types/mcp.js.map +1 -0
- package/dist/src/types/proxmox.d.ts +181 -0
- package/dist/src/types/proxmox.d.ts.map +1 -0
- package/dist/src/types/proxmox.js +5 -0
- package/dist/src/types/proxmox.js.map +1 -0
- package/dist/src/types/slot.d.ts +5 -0
- package/dist/src/types/slot.d.ts.map +1 -0
- package/dist/src/types/slot.js +5 -0
- package/dist/src/types/slot.js.map +1 -0
- package/dist/src/utils/formatters.d.ts +71 -0
- package/dist/src/utils/formatters.d.ts.map +1 -0
- package/dist/src/utils/formatters.js +480 -0
- package/dist/src/utils/formatters.js.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +6 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/logger.d.ts +28 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +74 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/mutex.d.ts +32 -0
- package/dist/src/utils/mutex.d.ts.map +1 -0
- package/dist/src/utils/mutex.js +86 -0
- package/dist/src/utils/mutex.js.map +1 -0
- package/dist/src/utils/redact.d.ts +6 -0
- package/dist/src/utils/redact.d.ts.map +1 -0
- package/dist/src/utils/redact.js +6 -0
- package/dist/src/utils/redact.js.map +1 -0
- package/dist/src/utils/redactor.d.ts +50 -0
- package/dist/src/utils/redactor.d.ts.map +1 -0
- package/dist/src/utils/redactor.js +170 -0
- package/dist/src/utils/redactor.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Appliance SSH Executor using pure-JS ssh2 Client
|
|
3
|
+
*
|
|
4
|
+
* Connects to Call Telemetry appliances on Port 2222 using Doppler-resolved credentials.
|
|
5
|
+
* Handles connect & execution timeouts, and guarantees output sanitization & secret redaction.
|
|
6
|
+
*/
|
|
7
|
+
import { Client } from "ssh2";
|
|
8
|
+
import { ApplianceConnectionError, ApplianceAuthenticationError, ApplianceTimeoutError, ApplianceError } from "./types.js";
|
|
9
|
+
import { buildApplianceCommand, normalizeCommandType } from "./commands.js";
|
|
10
|
+
import { credentialResolver } from "../credentials/resolver.js";
|
|
11
|
+
import { defaultRedactor, registerSecret } from "../utils/redactor.js";
|
|
12
|
+
import { logger } from "../utils/logger.js";
|
|
13
|
+
export class ApplianceSshExecutor {
|
|
14
|
+
resolver;
|
|
15
|
+
redactor;
|
|
16
|
+
defaultPort;
|
|
17
|
+
defaultConnectTimeoutMs;
|
|
18
|
+
defaultExecutionTimeoutMs;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.resolver = options.resolver || credentialResolver;
|
|
21
|
+
this.redactor = options.redactor || defaultRedactor;
|
|
22
|
+
this.defaultPort = options.defaultPort || 2222;
|
|
23
|
+
this.defaultConnectTimeoutMs = options.defaultConnectTimeoutMs || 10000;
|
|
24
|
+
this.defaultExecutionTimeoutMs = options.defaultExecutionTimeoutMs || 30000;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Execute a mediated command on the target appliance over SSH.
|
|
28
|
+
*/
|
|
29
|
+
async execute(params) {
|
|
30
|
+
const targetIp = (params.targetIp || params.target || "").trim();
|
|
31
|
+
if (!targetIp) {
|
|
32
|
+
throw new ApplianceError("Appliance target IP or hostname is required.");
|
|
33
|
+
}
|
|
34
|
+
// Build and validate mediated shell command
|
|
35
|
+
const command = buildApplianceCommand(params);
|
|
36
|
+
const action = normalizeCommandType(params);
|
|
37
|
+
const port = params.port || this.defaultPort;
|
|
38
|
+
const connectTimeoutMs = params.connectTimeoutMs || this.defaultConnectTimeoutMs;
|
|
39
|
+
const executionTimeoutMs = params.timeoutMs || this.defaultExecutionTimeoutMs;
|
|
40
|
+
// Resolve Doppler / Environment VM credentials
|
|
41
|
+
const resolution = await this.resolver.resolve();
|
|
42
|
+
const vmCreds = resolution.vmCredentials;
|
|
43
|
+
const username = vmCreds.username || "admin";
|
|
44
|
+
const password = vmCreds.password;
|
|
45
|
+
const privateKey = vmCreds.privateKey;
|
|
46
|
+
const passphrase = vmCreds.passphrase;
|
|
47
|
+
// Register credentials with redactor to ensure zero leakage
|
|
48
|
+
if (password) {
|
|
49
|
+
this.redactor.registerSecret(password);
|
|
50
|
+
registerSecret(password);
|
|
51
|
+
}
|
|
52
|
+
if (privateKey && typeof privateKey === "string") {
|
|
53
|
+
this.redactor.registerSecret(privateKey);
|
|
54
|
+
registerSecret(privateKey);
|
|
55
|
+
}
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
let isSettled = false;
|
|
59
|
+
let connectTimer = null;
|
|
60
|
+
let execTimer = null;
|
|
61
|
+
const client = new Client();
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
if (connectTimer) {
|
|
64
|
+
clearTimeout(connectTimer);
|
|
65
|
+
connectTimer = null;
|
|
66
|
+
}
|
|
67
|
+
if (execTimer) {
|
|
68
|
+
clearTimeout(execTimer);
|
|
69
|
+
execTimer = null;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
client.end();
|
|
73
|
+
client.destroy();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// ignore socket teardown error
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const safeReject = (err) => {
|
|
80
|
+
if (isSettled)
|
|
81
|
+
return;
|
|
82
|
+
isSettled = true;
|
|
83
|
+
cleanup();
|
|
84
|
+
const redactedErr = this.redactor.redactError(err);
|
|
85
|
+
reject(redactedErr);
|
|
86
|
+
};
|
|
87
|
+
const safeResolve = (res) => {
|
|
88
|
+
if (isSettled)
|
|
89
|
+
return;
|
|
90
|
+
isSettled = true;
|
|
91
|
+
cleanup();
|
|
92
|
+
resolve(res);
|
|
93
|
+
};
|
|
94
|
+
// Set connect timeout timer
|
|
95
|
+
connectTimer = setTimeout(() => {
|
|
96
|
+
safeReject(new ApplianceTimeoutError(`SSH connection to ${targetIp}:${port} timed out after ${connectTimeoutMs}ms.`));
|
|
97
|
+
}, connectTimeoutMs);
|
|
98
|
+
client.on("error", (err) => {
|
|
99
|
+
const msg = err.message || "";
|
|
100
|
+
if (msg.includes("authentication") ||
|
|
101
|
+
msg.includes("All configured authentication methods failed") ||
|
|
102
|
+
err.level === "client-authentication") {
|
|
103
|
+
safeReject(new ApplianceAuthenticationError(`SSH authentication failed for user '${username}' on ${targetIp}:${port}: ${msg}`));
|
|
104
|
+
}
|
|
105
|
+
else if (err.code === "ECONNREFUSED" ||
|
|
106
|
+
err.code === "ENOTFOUND" ||
|
|
107
|
+
err.code === "EHOSTUNREACH" ||
|
|
108
|
+
err.code === "ETIMEDOUT" ||
|
|
109
|
+
msg.includes("ECONNREFUSED")) {
|
|
110
|
+
safeReject(new ApplianceConnectionError(`SSH connection failed to ${targetIp}:${port} (${err.code || "CONNECTION_ERROR"}): ${msg}`));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
safeReject(new ApplianceConnectionError(`SSH error on ${targetIp}:${port}: ${msg}`));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
client.on("ready", () => {
|
|
117
|
+
// Clear connect timer once connection is established
|
|
118
|
+
if (connectTimer) {
|
|
119
|
+
clearTimeout(connectTimer);
|
|
120
|
+
connectTimer = null;
|
|
121
|
+
}
|
|
122
|
+
// Set execution timeout timer
|
|
123
|
+
execTimer = setTimeout(() => {
|
|
124
|
+
safeReject(new ApplianceTimeoutError(`Appliance command execution on ${targetIp}:${port} timed out after ${executionTimeoutMs}ms: ${command}`));
|
|
125
|
+
}, executionTimeoutMs);
|
|
126
|
+
const execOptions = {
|
|
127
|
+
pty: false
|
|
128
|
+
};
|
|
129
|
+
client.exec(command, execOptions, (err, stream) => {
|
|
130
|
+
if (err) {
|
|
131
|
+
safeReject(new ApplianceConnectionError(`Failed to spawn exec channel on ${targetIp}:${port}: ${err.message}`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let rawStdout = "";
|
|
135
|
+
let rawStderr = "";
|
|
136
|
+
stream.on("data", (data) => {
|
|
137
|
+
rawStdout += data.toString();
|
|
138
|
+
});
|
|
139
|
+
stream.stderr.on("data", (data) => {
|
|
140
|
+
rawStderr += data.toString();
|
|
141
|
+
});
|
|
142
|
+
stream.on("close", (code, _signal) => {
|
|
143
|
+
const durationMs = Date.now() - startTime;
|
|
144
|
+
const exitCode = code ?? 0;
|
|
145
|
+
const redactedStdout = this.redactor.redactString(rawStdout);
|
|
146
|
+
const redactedStderr = this.redactor.redactString(rawStderr);
|
|
147
|
+
const isRedacted = redactedStdout !== rawStdout || redactedStderr !== rawStderr;
|
|
148
|
+
safeResolve({
|
|
149
|
+
targetIp,
|
|
150
|
+
target: targetIp,
|
|
151
|
+
action,
|
|
152
|
+
command,
|
|
153
|
+
exitCode,
|
|
154
|
+
stdout: redactedStdout,
|
|
155
|
+
stderr: redactedStderr,
|
|
156
|
+
durationMs,
|
|
157
|
+
redacted: isRedacted
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
stream.on("error", (streamErr) => {
|
|
161
|
+
safeReject(new ApplianceConnectionError(`SSH stream error during command execution on ${targetIp}:${port}: ${streamErr.message}`));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
// Prepare ssh2 connection config
|
|
166
|
+
const connectConfig = {
|
|
167
|
+
host: targetIp,
|
|
168
|
+
port,
|
|
169
|
+
username,
|
|
170
|
+
readyTimeout: connectTimeoutMs,
|
|
171
|
+
keepaliveInterval: 5000,
|
|
172
|
+
keepaliveCountMax: 3
|
|
173
|
+
};
|
|
174
|
+
if (privateKey) {
|
|
175
|
+
connectConfig.privateKey = privateKey;
|
|
176
|
+
if (passphrase) {
|
|
177
|
+
connectConfig.passphrase = passphrase;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (password) {
|
|
181
|
+
connectConfig.password = password;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
client.connect(connectConfig);
|
|
185
|
+
}
|
|
186
|
+
catch (connErr) {
|
|
187
|
+
const msg = connErr instanceof Error ? connErr.message : String(connErr);
|
|
188
|
+
safeReject(new ApplianceConnectionError(`Failed to initiate SSH connection to ${targetIp}:${port}: ${msg}`));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Test SSH connectivity and authentication against the target appliance.
|
|
194
|
+
*/
|
|
195
|
+
async testConnection(targetIp, port, timeoutMs) {
|
|
196
|
+
try {
|
|
197
|
+
const result = await this.execute({
|
|
198
|
+
targetIp,
|
|
199
|
+
port: port || this.defaultPort,
|
|
200
|
+
commandType: "status",
|
|
201
|
+
timeoutMs: timeoutMs || 5000,
|
|
202
|
+
connectTimeoutMs: timeoutMs || 5000
|
|
203
|
+
});
|
|
204
|
+
return result.exitCode === 0;
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
208
|
+
logger.warn(`Appliance SSH testConnection failed for ${targetIp}:${port || this.defaultPort}: ${msg}`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export const applianceSshExecutor = new ApplianceSshExecutor();
|
|
214
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/appliance/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/appliance/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/appliance/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Fidelity Mock Appliance Executor
|
|
3
|
+
*
|
|
4
|
+
* Provides offline simulation of mediated Call Telemetry appliance CLI execution,
|
|
5
|
+
* realistic output payloads, dynamic secret redaction, and simulated error states.
|
|
6
|
+
*/
|
|
7
|
+
import { ApplianceExecParams, ApplianceExecResult, IApplianceExecutor } from "./types.js";
|
|
8
|
+
import { SecretRedactor } from "../utils/redactor.js";
|
|
9
|
+
export type MockFailureType = "connect_timeout" | "exec_timeout" | "conn_refused" | "auth_failed" | "exit_code_error" | null;
|
|
10
|
+
export type CustomCommandHandler = (params: ApplianceExecParams) => Partial<ApplianceExecResult> | Promise<Partial<ApplianceExecResult>>;
|
|
11
|
+
export declare class MockApplianceExecutor implements IApplianceExecutor {
|
|
12
|
+
private redactor;
|
|
13
|
+
private history;
|
|
14
|
+
private simulatedFailure;
|
|
15
|
+
private simulatedExitCode;
|
|
16
|
+
private simulatedStderr;
|
|
17
|
+
private simulatedLatencyMs;
|
|
18
|
+
private customHandlers;
|
|
19
|
+
constructor(redactor?: SecretRedactor);
|
|
20
|
+
/**
|
|
21
|
+
* Set simulated failure mode for testing error handling.
|
|
22
|
+
*/
|
|
23
|
+
setSimulatedFailure(failure: MockFailureType): void;
|
|
24
|
+
/**
|
|
25
|
+
* Set simulated non-zero exit code and stderr message.
|
|
26
|
+
*/
|
|
27
|
+
setSimulatedExitCode(exitCode: number, stderr?: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set simulated execution delay.
|
|
30
|
+
*/
|
|
31
|
+
setLatency(latencyMs: number): void;
|
|
32
|
+
/**
|
|
33
|
+
* Register a custom handler for a specific command action.
|
|
34
|
+
*/
|
|
35
|
+
setCustomHandler(action: string, handler: CustomCommandHandler): void;
|
|
36
|
+
/**
|
|
37
|
+
* Clear all custom handlers.
|
|
38
|
+
*/
|
|
39
|
+
clearCustomHandlers(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get all executed command parameter history.
|
|
42
|
+
*/
|
|
43
|
+
getHistory(): ApplianceExecParams[];
|
|
44
|
+
/**
|
|
45
|
+
* Get the last executed command parameters.
|
|
46
|
+
*/
|
|
47
|
+
getLastExecuted(): ApplianceExecParams | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Clear command execution history.
|
|
50
|
+
*/
|
|
51
|
+
clearHistory(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Reset mock executor to default clean state.
|
|
54
|
+
*/
|
|
55
|
+
reset(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Execute mediated appliance command in mock environment.
|
|
58
|
+
*/
|
|
59
|
+
execute(params: ApplianceExecParams): Promise<ApplianceExecResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Test connection in mock environment.
|
|
62
|
+
*/
|
|
63
|
+
testConnection(targetIp: string, port?: number, timeoutMs?: number): Promise<boolean>;
|
|
64
|
+
}
|
|
65
|
+
export declare const mockApplianceExecutor: MockApplianceExecutor;
|
|
66
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../src/appliance/mock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAKnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AAEvE,MAAM,MAAM,eAAe,GACvB,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,aAAa,GACb,iBAAiB,GACjB,IAAI,CAAC;AAET,MAAM,MAAM,oBAAoB,GAAG,CACjC,MAAM,EAAE,mBAAmB,KACxB,OAAO,CAAC,mBAAmB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;AAE1E,qBAAa,qBAAsB,YAAW,kBAAkB;IAC9D,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,kBAAkB,CAAc;IACxC,OAAO,CAAC,cAAc,CAAgD;gBAE1D,QAAQ,CAAC,EAAE,cAAc;IAIrC;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAInD;;OAEG;IACH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAiD,GAAG,IAAI;IAKvG;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAInC;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI;IAIrE;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;OAEG;IACH,UAAU,IAAI,mBAAmB,EAAE;IAInC;;OAEG;IACH,eAAe,IAAI,mBAAmB,GAAG,SAAS;IAIlD;;OAEG;IACH,YAAY,IAAI,IAAI;IAIpB;;OAEG;IACH,KAAK,IAAI,IAAI;IASb;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA2SxE;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAM5F;AAED,eAAO,MAAM,qBAAqB,uBAA8B,CAAC"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Fidelity Mock Appliance Executor
|
|
3
|
+
*
|
|
4
|
+
* Provides offline simulation of mediated Call Telemetry appliance CLI execution,
|
|
5
|
+
* realistic output payloads, dynamic secret redaction, and simulated error states.
|
|
6
|
+
*/
|
|
7
|
+
import { ApplianceConnectionError, ApplianceAuthenticationError, ApplianceTimeoutError, ApplianceError } from "./types.js";
|
|
8
|
+
import { buildApplianceCommand, normalizeCommandType } from "./commands.js";
|
|
9
|
+
import { defaultRedactor } from "../utils/redactor.js";
|
|
10
|
+
export class MockApplianceExecutor {
|
|
11
|
+
redactor;
|
|
12
|
+
history = [];
|
|
13
|
+
simulatedFailure = null;
|
|
14
|
+
simulatedExitCode = 0;
|
|
15
|
+
simulatedStderr = "";
|
|
16
|
+
simulatedLatencyMs = 10;
|
|
17
|
+
customHandlers = new Map();
|
|
18
|
+
constructor(redactor) {
|
|
19
|
+
this.redactor = redactor || defaultRedactor;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set simulated failure mode for testing error handling.
|
|
23
|
+
*/
|
|
24
|
+
setSimulatedFailure(failure) {
|
|
25
|
+
this.simulatedFailure = failure;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Set simulated non-zero exit code and stderr message.
|
|
29
|
+
*/
|
|
30
|
+
setSimulatedExitCode(exitCode, stderr = "Command execution failed on appliance.") {
|
|
31
|
+
this.simulatedExitCode = exitCode;
|
|
32
|
+
this.simulatedStderr = stderr;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Set simulated execution delay.
|
|
36
|
+
*/
|
|
37
|
+
setLatency(latencyMs) {
|
|
38
|
+
this.simulatedLatencyMs = Math.max(0, latencyMs);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Register a custom handler for a specific command action.
|
|
42
|
+
*/
|
|
43
|
+
setCustomHandler(action, handler) {
|
|
44
|
+
this.customHandlers.set(action.toLowerCase().trim().replace(/\s+/g, "_"), handler);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Clear all custom handlers.
|
|
48
|
+
*/
|
|
49
|
+
clearCustomHandlers() {
|
|
50
|
+
this.customHandlers.clear();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get all executed command parameter history.
|
|
54
|
+
*/
|
|
55
|
+
getHistory() {
|
|
56
|
+
return [...this.history];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the last executed command parameters.
|
|
60
|
+
*/
|
|
61
|
+
getLastExecuted() {
|
|
62
|
+
return this.history[this.history.length - 1];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Clear command execution history.
|
|
66
|
+
*/
|
|
67
|
+
clearHistory() {
|
|
68
|
+
this.history = [];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reset mock executor to default clean state.
|
|
72
|
+
*/
|
|
73
|
+
reset() {
|
|
74
|
+
this.history = [];
|
|
75
|
+
this.simulatedFailure = null;
|
|
76
|
+
this.simulatedExitCode = 0;
|
|
77
|
+
this.simulatedStderr = "";
|
|
78
|
+
this.simulatedLatencyMs = 10;
|
|
79
|
+
this.customHandlers.clear();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Execute mediated appliance command in mock environment.
|
|
83
|
+
*/
|
|
84
|
+
async execute(params) {
|
|
85
|
+
const targetIp = (params.targetIp || params.target || "").trim();
|
|
86
|
+
if (!targetIp) {
|
|
87
|
+
throw new ApplianceError("Appliance target IP or hostname is required.");
|
|
88
|
+
}
|
|
89
|
+
// Build and strictly validate command
|
|
90
|
+
const command = buildApplianceCommand(params);
|
|
91
|
+
const action = normalizeCommandType(params);
|
|
92
|
+
this.history.push({ ...params, targetIp });
|
|
93
|
+
if (this.simulatedLatencyMs > 0) {
|
|
94
|
+
await new Promise((r) => setTimeout(r, this.simulatedLatencyMs));
|
|
95
|
+
}
|
|
96
|
+
// Handle simulated failure states
|
|
97
|
+
if (this.simulatedFailure === "connect_timeout") {
|
|
98
|
+
throw new ApplianceTimeoutError(`SSH connection to ${targetIp}:${params.port || 2222} timed out after ${params.connectTimeoutMs || 10000}ms.`);
|
|
99
|
+
}
|
|
100
|
+
if (this.simulatedFailure === "exec_timeout") {
|
|
101
|
+
throw new ApplianceTimeoutError(`Appliance command execution on ${targetIp}:${params.port || 2222} timed out after ${params.timeoutMs || 30000}ms: ${command}`);
|
|
102
|
+
}
|
|
103
|
+
if (this.simulatedFailure === "conn_refused") {
|
|
104
|
+
throw new ApplianceConnectionError(`SSH connection failed to ${targetIp}:${params.port || 2222} (ECONNREFUSED): connect ECONNREFUSED ${targetIp}:${params.port || 2222}`);
|
|
105
|
+
}
|
|
106
|
+
if (this.simulatedFailure === "auth_failed") {
|
|
107
|
+
throw new ApplianceAuthenticationError(`SSH authentication failed for user 'admin' on ${targetIp}:${params.port || 2222}: All configured authentication methods failed`);
|
|
108
|
+
}
|
|
109
|
+
if (this.simulatedFailure === "exit_code_error" || this.simulatedExitCode !== 0) {
|
|
110
|
+
const exitCode = this.simulatedExitCode || 1;
|
|
111
|
+
const rawStderr = this.simulatedStderr || `Error executing command: ${command}`;
|
|
112
|
+
const redactedStderr = this.redactor.redactString(rawStderr);
|
|
113
|
+
return {
|
|
114
|
+
targetIp,
|
|
115
|
+
target: targetIp,
|
|
116
|
+
action,
|
|
117
|
+
command,
|
|
118
|
+
exitCode,
|
|
119
|
+
stdout: "",
|
|
120
|
+
stderr: redactedStderr,
|
|
121
|
+
durationMs: this.simulatedLatencyMs,
|
|
122
|
+
redacted: redactedStderr !== rawStderr
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Check custom handler (check normalized action, raw params.action, or raw params.commandType)
|
|
126
|
+
const rawActionKey = (params.action || "").toLowerCase().trim().replace(/\s+/g, "_");
|
|
127
|
+
const rawCommandTypeKey = (params.commandType || "").toLowerCase().trim().replace(/\s+/g, "_");
|
|
128
|
+
const matchedHandler = this.customHandlers.get(action) ||
|
|
129
|
+
(rawActionKey ? this.customHandlers.get(rawActionKey) : undefined) ||
|
|
130
|
+
(rawCommandTypeKey ? this.customHandlers.get(rawCommandTypeKey) : undefined);
|
|
131
|
+
if (matchedHandler) {
|
|
132
|
+
const customRes = await matchedHandler(params);
|
|
133
|
+
const rawStdout = customRes.stdout || "";
|
|
134
|
+
const rawStderr = customRes.stderr || "";
|
|
135
|
+
const redactedStdout = this.redactor.redactString(rawStdout);
|
|
136
|
+
const redactedStderr = this.redactor.redactString(rawStderr);
|
|
137
|
+
return {
|
|
138
|
+
targetIp,
|
|
139
|
+
target: targetIp,
|
|
140
|
+
action,
|
|
141
|
+
command,
|
|
142
|
+
exitCode: customRes.exitCode ?? 0,
|
|
143
|
+
stdout: redactedStdout,
|
|
144
|
+
stderr: redactedStderr,
|
|
145
|
+
durationMs: customRes.durationMs ?? this.simulatedLatencyMs,
|
|
146
|
+
redacted: customRes.redacted ?? (redactedStdout !== rawStdout || redactedStderr !== rawStderr)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Generate realistic Call Telemetry CLI outputs
|
|
150
|
+
let rawStdout = "";
|
|
151
|
+
let rawStderr = "";
|
|
152
|
+
let exitCode = 0;
|
|
153
|
+
const args = params.args || params.params || {};
|
|
154
|
+
switch (action) {
|
|
155
|
+
case "status":
|
|
156
|
+
rawStdout = [
|
|
157
|
+
"Call Telemetry Appliance Stack Status:",
|
|
158
|
+
" calltelemetry-web-1: Up (healthy) - Port 443 -> 4000",
|
|
159
|
+
" calltelemetry-db-1: Up (healthy) - PostgreSQL 16.2",
|
|
160
|
+
" calltelemetry-nats-1: Up (healthy) - NATS JetStream 2.10",
|
|
161
|
+
" calltelemetry-jtapi-sidecar-1: Up (healthy) - Connected to CUCM 192.168.125.10",
|
|
162
|
+
" calltelemetry-seaweedfs-1: Up (healthy) - S3 Storage",
|
|
163
|
+
" calltelemetry-grafana-1: Up (healthy) - Metrics Dashboard",
|
|
164
|
+
" calltelemetry-caddy-1: Up (healthy) - Reverse Proxy",
|
|
165
|
+
"System Health: GREEN (All services running)",
|
|
166
|
+
`Appliance IP: ${targetIp}`
|
|
167
|
+
].join("\n");
|
|
168
|
+
break;
|
|
169
|
+
case "jtapi_status":
|
|
170
|
+
case "jtapi status":
|
|
171
|
+
rawStdout = JSON.stringify({
|
|
172
|
+
status: "connected",
|
|
173
|
+
sidecar_healthy: true,
|
|
174
|
+
cucm_host: "192.168.125.10",
|
|
175
|
+
monitored_devices_count: 42,
|
|
176
|
+
cti_port_status: "in_service",
|
|
177
|
+
rtp_streamer_status: "ready",
|
|
178
|
+
active_calls: 0
|
|
179
|
+
}, null, 2);
|
|
180
|
+
break;
|
|
181
|
+
case "health":
|
|
182
|
+
case "health_report":
|
|
183
|
+
rawStdout = [
|
|
184
|
+
"=== Call Telemetry Health Diagnostic Report ===",
|
|
185
|
+
"CPU Load: 12% (16 cores)",
|
|
186
|
+
"Memory: 4120 MB / 8192 MB (50.3% used)",
|
|
187
|
+
"Disk (/data): 14.2 GB / 60.0 GB (23.7% used)",
|
|
188
|
+
"Database Connections: 8 / 100 active",
|
|
189
|
+
"NATS Memory: 42 MB / 512 MB",
|
|
190
|
+
"Overall Diagnostic Verdict: HEALTHY"
|
|
191
|
+
].join("\n");
|
|
192
|
+
break;
|
|
193
|
+
case "diag":
|
|
194
|
+
case "diag_network":
|
|
195
|
+
case "diag network":
|
|
196
|
+
rawStdout = "Gateway 192.168.124.1: Reachable (0.4ms)\nDNS 1.1.1.1: OK\nCUCM 192.168.125.10:2748: REACHABLE";
|
|
197
|
+
break;
|
|
198
|
+
case "diag_database":
|
|
199
|
+
case "diag database":
|
|
200
|
+
rawStdout = "PostgreSQL Ping: 0.8ms\nCache Hit Ratio: 99.4%\nDeadlocks: 0\nActive Queries: 3";
|
|
201
|
+
break;
|
|
202
|
+
case "diag_service":
|
|
203
|
+
case "diag service":
|
|
204
|
+
rawStdout = "docker-compose-app.service: active (running) since Wed 2026-08-19 18:00:00 UTC";
|
|
205
|
+
break;
|
|
206
|
+
case "db_status":
|
|
207
|
+
case "db status":
|
|
208
|
+
rawStdout = "Database: calltelemetry_prod\nSize: 4.2 GB\nConnections: 8\nStatus: ONLINE";
|
|
209
|
+
break;
|
|
210
|
+
case "db_size":
|
|
211
|
+
case "db size":
|
|
212
|
+
rawStdout = "4509715660 bytes (4.2 GB)";
|
|
213
|
+
break;
|
|
214
|
+
case "db_tables":
|
|
215
|
+
case "db tables": {
|
|
216
|
+
const table = args.table;
|
|
217
|
+
if (table) {
|
|
218
|
+
rawStdout = `Table: ${table} (Size: 1.2 GB, Rows: 850,000, Total Index Size: 340 MB)`;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
rawStdout = [
|
|
222
|
+
"Table: cdr_records (Size: 2.1 GB, Rows: 1,450,200)",
|
|
223
|
+
"Table: audit_logs (Size: 320 MB, Rows: 120,000)",
|
|
224
|
+
"Table: phone_devices (Size: 45 MB, Rows: 8,400)",
|
|
225
|
+
"Table: slot_leases (Size: 12 MB, Rows: 1,200)"
|
|
226
|
+
].join("\n");
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "db_vacuum":
|
|
231
|
+
case "db vacuum": {
|
|
232
|
+
const table = args.table || "all";
|
|
233
|
+
const fullFlag = args.vacuumFull ? " (FULL)" : "";
|
|
234
|
+
const analyzeFlag = args.vacuumAnalyze ? " (ANALYZE)" : "";
|
|
235
|
+
rawStdout = `VACUUM${fullFlag}${analyzeFlag} on table '${table}' completed in 1.42s (lock_timeout=30s injected). 0 dead rows removed.`;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "db_compact":
|
|
239
|
+
case "db compact":
|
|
240
|
+
rawStdout = "Database compaction completed. Reclaimed 1.2 GB disk space.";
|
|
241
|
+
break;
|
|
242
|
+
case "db_backup":
|
|
243
|
+
case "db backup":
|
|
244
|
+
rawStdout = "Backup created: /data/backups/calltelemetry_backup_20260819_190000.pgdump (Size: 840 MB)";
|
|
245
|
+
break;
|
|
246
|
+
case "db_list":
|
|
247
|
+
case "db list":
|
|
248
|
+
rawStdout = [
|
|
249
|
+
"- calltelemetry_backup_20260819_190000.pgdump (840 MB)",
|
|
250
|
+
"- calltelemetry_backup_20260818_190000.pgdump (832 MB)"
|
|
251
|
+
].join("\n");
|
|
252
|
+
break;
|
|
253
|
+
case "migrate":
|
|
254
|
+
case "migrate_status":
|
|
255
|
+
case "migrate status":
|
|
256
|
+
rawStdout = "All 42 Ecto migrations up-to-date.";
|
|
257
|
+
break;
|
|
258
|
+
case "migrate_legacy":
|
|
259
|
+
case "migrate legacy":
|
|
260
|
+
rawStdout = JSON.stringify({
|
|
261
|
+
legacy_drains: { completed: true, drained_partitions: 12, pending: 0 },
|
|
262
|
+
oban_rollups: { active: false, backfilled: 45000 }
|
|
263
|
+
}, null, 2);
|
|
264
|
+
break;
|
|
265
|
+
case "migrate_run":
|
|
266
|
+
case "migrate run":
|
|
267
|
+
rawStdout = "Running migrations: 20260819000000_add_ct_lab_tables.exs ... Done (0.042s)";
|
|
268
|
+
break;
|
|
269
|
+
case "migrate_drain":
|
|
270
|
+
case "migrate drain":
|
|
271
|
+
rawStdout = "Partition drain executed. 0 stale partitions remaining.";
|
|
272
|
+
break;
|
|
273
|
+
case "docker":
|
|
274
|
+
case "docker_ps":
|
|
275
|
+
case "docker ps":
|
|
276
|
+
rawStdout = [
|
|
277
|
+
"NAMES\tSTATUS\tIMAGE\tPORTS",
|
|
278
|
+
"calltelemetry-web-1\tUp 4 hours (healthy)\tcalltelemetry/web:0.8.7\t443->4000",
|
|
279
|
+
"calltelemetry-db-1\tUp 4 hours (healthy)\tpostgres:16.2\t5432/tcp",
|
|
280
|
+
"calltelemetry-nats-1\tUp 4 hours (healthy)\tnats:2.10\t4222/tcp",
|
|
281
|
+
"calltelemetry-jtapi-sidecar-1\tUp 4 hours (healthy)\tcalltelemetry/jtapi:0.8.7\t8080/tcp"
|
|
282
|
+
].join("\n");
|
|
283
|
+
break;
|
|
284
|
+
case "docker_logs":
|
|
285
|
+
case "logs": {
|
|
286
|
+
const container = args.container || "calltelemetry-web-1";
|
|
287
|
+
const tail = args.logTail || 100;
|
|
288
|
+
rawStdout = [
|
|
289
|
+
`=== Docker logs for ${container} (tail ${tail}) ===`,
|
|
290
|
+
`[info] Application started successfully on ${targetIp}`,
|
|
291
|
+
`[info] Secret token dp.pt.secret_token_from_doppler_xyz loaded into memory.`,
|
|
292
|
+
`[info] Connected to postgresql://postgres:super-secret-postgres-password-12345@db:5432/calltelemetry_prod`,
|
|
293
|
+
`[info] Ready to accept requests.`
|
|
294
|
+
].join("\n");
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "journalctl":
|
|
298
|
+
case "system_logs": {
|
|
299
|
+
const tail = args.logTail || 100;
|
|
300
|
+
rawStdout = `Systemd service docker-compose-app (last ${tail} lines): active (running) since 2026-08-19 18:00:00 UTC.`;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case "version":
|
|
304
|
+
rawStdout = "cli.sh version 0.8.7\nCT_RELEASE_VERSION=0.8.7-stable";
|
|
305
|
+
break;
|
|
306
|
+
case "raw_whitelisted":
|
|
307
|
+
case "raw":
|
|
308
|
+
rawStdout = `Output of raw command: ${args.rawCommand || "echo ok"}`;
|
|
309
|
+
break;
|
|
310
|
+
default:
|
|
311
|
+
rawStdout = `Executed ${action} on ${targetIp}`;
|
|
312
|
+
}
|
|
313
|
+
const redactedStdout = this.redactor.redactString(rawStdout);
|
|
314
|
+
const redactedStderr = this.redactor.redactString(rawStderr);
|
|
315
|
+
const isRedacted = redactedStdout !== rawStdout || redactedStderr !== rawStderr;
|
|
316
|
+
return {
|
|
317
|
+
targetIp,
|
|
318
|
+
target: targetIp,
|
|
319
|
+
action,
|
|
320
|
+
command,
|
|
321
|
+
exitCode,
|
|
322
|
+
stdout: redactedStdout,
|
|
323
|
+
stderr: redactedStderr,
|
|
324
|
+
durationMs: this.simulatedLatencyMs,
|
|
325
|
+
redacted: isRedacted
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Test connection in mock environment.
|
|
330
|
+
*/
|
|
331
|
+
async testConnection(targetIp, port, timeoutMs) {
|
|
332
|
+
if (this.simulatedFailure === "conn_refused" || this.simulatedFailure === "connect_timeout" || this.simulatedFailure === "auth_failed") {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return Boolean(targetIp && targetIp.trim().length > 0);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export const mockApplianceExecutor = new MockApplianceExecutor();
|
|
339
|
+
//# sourceMappingURL=mock.js.map
|