@hyzyn/dsh-docker 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.
@@ -0,0 +1,8 @@
1
+ # dsh-plugin-kit/docker bundle patch:把本插件行插入 profile 阵容。
2
+ # 作为 profile bundle 层(package.json 的 dsh.bundle.patch 字段)叠加到
3
+ # dsh-base 之上;启用方式:
4
+ # dsh plugin --profile web add link:$(pwd)/packages/docker
5
+ # 行内容为按包名注册的裸插件行:exports "." 的 node 半体运行在宿主进程。
6
+ - insert:
7
+ - id: docker
8
+ name: '@hyzyn/dsh-docker'
@@ -0,0 +1,237 @@
1
+ /**
2
+ * @hyzyn/dsh-docker — Docker CLI 封装与输出解析。
3
+ *
4
+ * 设计要点:
5
+ * - **命令一律以 argv 数组构造**,远程经 shJoin 单引号转义、本机直接
6
+ * spawn(不经 shell);用户输入(容器名/ID/命令)先过 assertRef 白名单。
7
+ * - **解析容错优先**:docker CLI 的 `--format '{{json .}}'` 字段随版本增减,
8
+ * 解析器对缺字段一律降级(State 缺失就从 Status 推导),不抛异常;
9
+ * 需要权威数据时用 `docker inspect`。
10
+ * - 纯函数(parse*)单独导出,供 scripts/smoke.mjs 用固定输出做回归。
11
+ */
12
+ import type { ExecResult, HostKeyStore, SshSpec, ExecLogger } from './ssh-exec.js';
13
+ import { RemoteExec } from './ssh-exec.js';
14
+ /** 校验一个 docker 引用(容器名 / ID / 镜像)。不合法直接抛错,绝不拼接进命令。 */
15
+ export declare function assertRef(value: unknown, field: string): string;
16
+ /** docker CLI 可执行文件白名单(argv[0],不设默认值以免误用其他程序)。 */
17
+ export declare function assertBin(value: unknown): string;
18
+ /** 逐行 JSON 解析:兼容 `{{json .}}`(每行一个对象)与整体 JSON 数组。 */
19
+ export declare function parseJsonLines(text: string): Record<string, unknown>[];
20
+ /** `12.3MiB` / `1.2kB` / `0B` → 字节数(解析失败返回 null)。 */
21
+ export declare function parseDockerSize(text: string): number | null;
22
+ /** `12.34%` → 12.34(解析失败返回 null)。 */
23
+ export declare function parsePercent(text: string): number | null;
24
+ /** `1.2kB / 0B` → { rx, tx }(解析失败一侧为 null)。 */
25
+ export declare function parseIOPair(text: string): {
26
+ rx: number | null;
27
+ tx: number | null;
28
+ };
29
+ export interface PortMapping {
30
+ hostIp?: string;
31
+ hostPort?: number;
32
+ containerPort: number;
33
+ protocol: string;
34
+ }
35
+ export interface ContainerSummary {
36
+ id: string;
37
+ shortId: string;
38
+ name: string;
39
+ image: string;
40
+ state: string;
41
+ status: string;
42
+ health: string | null;
43
+ createdAt: string | null;
44
+ runningFor: string;
45
+ ports: PortMapping[];
46
+ /** compose 项目 / 服务(有标签时)。 */
47
+ composeProject: string | null;
48
+ composeService: string | null;
49
+ /** ps 的 `.Size`(需 --size,缺省不请求,通常为空)。 */
50
+ size: string;
51
+ }
52
+ /** 从 ps 的 `.Status`(`Up 2 hours (healthy)`)推导状态。健康态单独由 deriveHealth 提供。 */
53
+ export declare function deriveState(status: string): string;
54
+ /** 从 ps 的 `.Status` 提取健康态(`Up 2 hours (healthy)` → healthy)。 */
55
+ export declare function deriveHealth(status: string): string | null;
56
+ /** 解析 ps 的 `.Ports` 串:`0.0.0.0:8080->80/tcp, [::]:8080->80/tcp, 9000/tcp`。 */
57
+ export declare function parsePorts(text: string): PortMapping[];
58
+ /** `docker ps --format '{{json .}}'` → ContainerSummary[]。 */
59
+ export declare function parsePsJson(text: string): ContainerSummary[];
60
+ /** ps 的 `.Labels` 是 `k=v,k2=v2` 串。 */
61
+ export declare function parseLabels(text: string): Record<string, string>;
62
+ export interface ContainerStats {
63
+ id: string;
64
+ shortId: string;
65
+ name: string;
66
+ cpuPercent: number | null;
67
+ memPercent: number | null;
68
+ memUsed: number | null;
69
+ memLimit: number | null;
70
+ memUsage: string;
71
+ netRx: number | null;
72
+ netTx: number | null;
73
+ netIO: string;
74
+ blockRead: number | null;
75
+ blockWrite: number | null;
76
+ blockIO: string;
77
+ pids: number | null;
78
+ }
79
+ /** `docker stats --no-stream --format '{{json .}}'` → ContainerStats[]。 */
80
+ export declare function parseStatsJson(text: string): ContainerStats[];
81
+ export interface ImageSummary {
82
+ id: string;
83
+ shortId: string;
84
+ repository: string;
85
+ tag: string;
86
+ /** `repository:tag`(dangling 时 ` <none>:<none>`)。 */
87
+ reference: string;
88
+ size: number | null;
89
+ sizeText: string;
90
+ createdAt: string;
91
+ createdSince: string;
92
+ dangling: boolean;
93
+ }
94
+ /** `docker images --format '{{json .}}'` → ImageSummary[]。 */
95
+ export declare function parseImagesJson(text: string): ImageSummary[];
96
+ export interface MountInfo {
97
+ type: string;
98
+ source: string;
99
+ destination: string;
100
+ mode: string;
101
+ readWrite: boolean;
102
+ }
103
+ export interface ContainerDetail {
104
+ id: string;
105
+ shortId: string;
106
+ name: string;
107
+ image: string;
108
+ imageId: string;
109
+ state: string;
110
+ status: string;
111
+ health: string | null;
112
+ healthLogTail: string | null;
113
+ created: string | null;
114
+ startedAt: string | null;
115
+ finishedAt: string | null;
116
+ exitCode: number | null;
117
+ oomKilled: boolean;
118
+ restartCount: number | null;
119
+ restartPolicy: string | null;
120
+ platform: string;
121
+ pid: number | null;
122
+ ports: PortMapping[];
123
+ mounts: MountInfo[];
124
+ networks: {
125
+ name: string;
126
+ ip: string | null;
127
+ }[];
128
+ command: string;
129
+ entrypoint: string;
130
+ workingDir: string;
131
+ user: string;
132
+ composeProject: string | null;
133
+ composeService: string | null;
134
+ /** 镜像标签(截断展示用;值可能很长,仅回传前 50 条)。 */
135
+ labels: Record<string, string>;
136
+ }
137
+ /** `docker inspect <id…>` 的 JSON 数组 → ContainerDetail[]。 */
138
+ export declare function parseInspectJson(text: string): ContainerDetail[];
139
+ /** inspect 的 `NetworkSettings.Ports`:`{ "80/tcp": [{HostIp, HostPort}] }`。 */
140
+ export declare function parseInspectPorts(value: unknown): PortMapping[];
141
+ /** 一个目标(target)背后的命令执行通道。 */
142
+ export interface Runner {
143
+ /** 展示用标签:`本机` 或 `user@host`。 */
144
+ readonly label: string;
145
+ run(argv: readonly string[], options?: {
146
+ timeoutMs?: number;
147
+ maxBytes?: number;
148
+ }): Promise<ExecResult>;
149
+ }
150
+ export interface DockerAction {
151
+ action: 'start' | 'stop' | 'restart' | 'remove';
152
+ id: string;
153
+ }
154
+ export interface LogsOptions {
155
+ tail?: number;
156
+ timestamps?: boolean;
157
+ since?: string;
158
+ }
159
+ export interface ProbeResult {
160
+ ok: boolean;
161
+ bin: string;
162
+ /** 服务端版本(`docker version --format {{.Server.Version}}`)。 */
163
+ serverVersion: string | null;
164
+ /** 失败原因(daemon 未运行 / 未安装 / 权限不足)。 */
165
+ error: string | null;
166
+ target: string;
167
+ }
168
+ /** 单个目标上的 Docker 操作集合。 */
169
+ export declare class DockerApi {
170
+ private readonly runner;
171
+ private readonly bin;
172
+ private readonly limits;
173
+ constructor(runner: Runner, bin: string, limits: {
174
+ timeoutMs: number;
175
+ maxBytes: number;
176
+ });
177
+ /** 探测:docker CLI 是否可用 + daemon 是否可达。 */
178
+ probe(): Promise<ProbeResult>;
179
+ listContainers(all: boolean): Promise<ContainerSummary[]>;
180
+ inspect(ids: readonly string[]): Promise<ContainerDetail[]>;
181
+ stats(ids: readonly string[]): Promise<ContainerStats[]>;
182
+ images(): Promise<ImageSummary[]>;
183
+ /** 日志:stdout / stderr 分别收,再按到达顺序合并(docker logs 两者都有内容)。 */
184
+ logs(id: string, options?: LogsOptions): Promise<{
185
+ id: string;
186
+ text: string;
187
+ truncated: boolean;
188
+ }>;
189
+ /** 生命周期操作;调用方负责 readOnly / allowMutations 门禁。 */
190
+ action(request: DockerAction): Promise<{
191
+ id: string;
192
+ action: string;
193
+ message: string;
194
+ }>;
195
+ /** 一次性 exec(无 TTY):`docker exec <id> sh -c <command>`。 */
196
+ exec(id: string, command: string, timeoutMs?: number): Promise<{
197
+ id: string;
198
+ command: string;
199
+ code: number | null;
200
+ stdout: string;
201
+ stderr: string;
202
+ truncated: boolean;
203
+ durationMs: number;
204
+ }>;
205
+ private assertOk;
206
+ }
207
+ /** 目标定义(settings 里的 `targets[]`)。 */
208
+ export interface DockerTarget {
209
+ /** 展示名(唯一)。 */
210
+ name: string;
211
+ /** 本机 / SSH 远程。 */
212
+ kind: 'local' | 'ssh';
213
+ /** kind=ssh 时引用 tty 连接簿条目名(可选;也可直接给内联字段)。 */
214
+ book?: string;
215
+ host?: string;
216
+ port?: number;
217
+ username?: string;
218
+ auth?: 'agent' | 'key' | 'password';
219
+ keyPath?: string;
220
+ password?: string;
221
+ passphrase?: string;
222
+ agentForward?: boolean;
223
+ }
224
+ /** 已解析出 SSH 规格的目标(连接簿查找完成)。 */
225
+ export interface ResolvedTarget {
226
+ name: string;
227
+ kind: 'local' | 'ssh';
228
+ spec?: SshSpec;
229
+ }
230
+ /** 为一个目标构造 Runner。 */
231
+ export declare function createRunner(options: {
232
+ target: ResolvedTarget;
233
+ remote: RemoteExec;
234
+ logger: ExecLogger;
235
+ }): Runner;
236
+ /** 供宿主半体复用:把 HostKeyStore 与 logger 绑到 RemoteExec。 */
237
+ export declare function createRemoteExec(logger: ExecLogger, store: HostKeyStore): RemoteExec;