@elyracode/docker 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +85 -0
- package/extensions/index.ts +461 -0
- package/package.json +37 -0
- package/skills/elyra-docker/SKILL.md +42 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.5.11] - 2026-05-16
|
|
4
|
+
|
|
5
|
+
## [0.5.10] - 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release
|
|
9
|
+
- `docker_exec` tool: run commands inside containers
|
|
10
|
+
- `docker_logs` tool: tail container logs with grep filtering
|
|
11
|
+
- `docker_status` tool: running containers, resource usage, ports
|
|
12
|
+
- `docker_compose` tool: up/down/restart/build operations
|
|
13
|
+
- `docker_env_check` tool: compare host .env with container environment
|
|
14
|
+
- `/docker` command for container status dashboard
|
|
15
|
+
- Auto-injects compose context into system prompt on session start
|
|
16
|
+
- Skill file for automatic Docker tool selection
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @elyracode/docker
|
|
2
|
+
|
|
3
|
+
Docker-aware development for Elyra. Container exec routing, log tailing, compose operations, environment sync checks, and container health monitoring.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
elyra install npm:@elyracode/docker
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
| Tool | Description |
|
|
14
|
+
|------|-------------|
|
|
15
|
+
| `docker_exec` | Run a command inside a Docker container |
|
|
16
|
+
| `docker_logs` | Read recent logs from a container |
|
|
17
|
+
| `docker_status` | List running containers with resource usage, ports, and health |
|
|
18
|
+
| `docker_compose` | Run docker compose operations (up, down, restart, build) |
|
|
19
|
+
| `docker_env_check` | Compare host .env with container environment variables |
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
- `/docker` -- Container status dashboard (running containers, ports, health)
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Container execution
|
|
28
|
+
|
|
29
|
+
The agent automatically routes commands through `docker exec` when it detects a Docker environment:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
> Run the database migrations
|
|
33
|
+
> Run the test suite
|
|
34
|
+
> Check the PHP version in the app container
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or be explicit:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
> Run "php artisan migrate" in the app container
|
|
41
|
+
> Execute "npm run build" in the node container
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Log reading
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
> Show me the last 50 lines of the app container logs
|
|
48
|
+
> What errors are in the nginx logs?
|
|
49
|
+
> Tail the database logs and look for slow queries
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Compose operations
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
> Rebuild the app container
|
|
56
|
+
> Restart the database service
|
|
57
|
+
> Bring up the entire stack
|
|
58
|
+
> Shut down all containers
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Environment sync
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
> Check if my .env matches what the app container sees
|
|
65
|
+
> Compare environment variables between host and container
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Status
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
/docker
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Shows running containers, CPU/memory usage, port mappings, volume mounts, and health check status.
|
|
75
|
+
|
|
76
|
+
## How it works
|
|
77
|
+
|
|
78
|
+
On session start, the extension reads `docker-compose.yml` (or `compose.yml`) and injects container context into the system prompt. The agent knows which services exist, which ports are mapped, and which volumes are mounted.
|
|
79
|
+
|
|
80
|
+
When commands need to run inside a container, `docker_exec` handles the routing. The agent doesn't need to be told "use docker exec" -- it knows from context.
|
|
81
|
+
|
|
82
|
+
## Requirements
|
|
83
|
+
|
|
84
|
+
- Docker CLI installed and running
|
|
85
|
+
- Docker Compose v2 (for compose operations)
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { ExtensionAPI } from "@elyracode/coding-agent";
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
|
|
7
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
function exec(cmd: string, cwd?: string, timeoutMs = 15000): string {
|
|
10
|
+
try {
|
|
11
|
+
return execSync(cmd, {
|
|
12
|
+
cwd,
|
|
13
|
+
encoding: "utf-8",
|
|
14
|
+
timeout: timeoutMs,
|
|
15
|
+
maxBuffer: 1024 * 1024,
|
|
16
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17
|
+
}).trim();
|
|
18
|
+
} catch (error: any) {
|
|
19
|
+
const stderr = error.stderr?.toString().trim() ?? "";
|
|
20
|
+
const stdout = error.stdout?.toString().trim() ?? "";
|
|
21
|
+
return stderr || stdout || error.message;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function dockerAvailable(): boolean {
|
|
26
|
+
try {
|
|
27
|
+
execSync("docker info", { stdio: "pipe", timeout: 5000 });
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function findComposeFile(cwd: string): string | undefined {
|
|
35
|
+
const candidates = ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"];
|
|
36
|
+
for (const name of candidates) {
|
|
37
|
+
if (existsSync(join(cwd, name))) return name;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getRunningContainers(cwd?: string): string {
|
|
43
|
+
return exec(
|
|
44
|
+
'docker ps --format "{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"',
|
|
45
|
+
cwd,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Extension ───────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
export default function (elyra: ExtensionAPI): void {
|
|
52
|
+
// Inject Docker context into system prompt on session start
|
|
53
|
+
elyra.on("before_agent_start", async (_event, ctx) => {
|
|
54
|
+
const cwd = ctx.cwd;
|
|
55
|
+
if (!dockerAvailable()) return;
|
|
56
|
+
|
|
57
|
+
const composeFile = findComposeFile(cwd);
|
|
58
|
+
if (!composeFile) return;
|
|
59
|
+
|
|
60
|
+
const composeContent = readFileSync(join(cwd, composeFile), "utf-8");
|
|
61
|
+
const running = getRunningContainers(cwd);
|
|
62
|
+
|
|
63
|
+
const contextBlock =
|
|
64
|
+
`## Docker Environment\n\n` +
|
|
65
|
+
`Compose file: ${composeFile}\n` +
|
|
66
|
+
`\`\`\`yaml\n${composeContent}\n\`\`\`\n\n` +
|
|
67
|
+
(running
|
|
68
|
+
? `Running containers:\n\`\`\`\n${running}\n\`\`\`\n\n`
|
|
69
|
+
: "No containers currently running.\n\n") +
|
|
70
|
+
`When running commands (migrations, tests, builds), use docker_exec to route them ` +
|
|
71
|
+
`to the appropriate container instead of running on the host.`;
|
|
72
|
+
|
|
73
|
+
const currentPrompt = ctx.getSystemPrompt();
|
|
74
|
+
return {
|
|
75
|
+
systemPrompt: currentPrompt + "\n\n" + contextBlock,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── Tool: docker_exec ───────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
elyra.registerTool({
|
|
82
|
+
name: "docker_exec",
|
|
83
|
+
label: "Docker Exec",
|
|
84
|
+
description:
|
|
85
|
+
"Run a command inside a Docker container. Use this instead of bash when the project " +
|
|
86
|
+
"uses Docker and the command should run in the container environment (e.g., migrations, " +
|
|
87
|
+
"tests, package installs, artisan commands). Detects the right container from the service name.",
|
|
88
|
+
parameters: Type.Object({
|
|
89
|
+
container: Type.String({
|
|
90
|
+
description:
|
|
91
|
+
"Container or service name (e.g., 'app', 'node', 'web', 'php'). " +
|
|
92
|
+
"Use docker_status to list running containers if unsure.",
|
|
93
|
+
}),
|
|
94
|
+
command: Type.String({
|
|
95
|
+
description: "Command to execute inside the container",
|
|
96
|
+
}),
|
|
97
|
+
workdir: Type.Optional(
|
|
98
|
+
Type.String({
|
|
99
|
+
description: "Working directory inside the container (e.g., /var/www/html)",
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
102
|
+
user: Type.Optional(
|
|
103
|
+
Type.String({
|
|
104
|
+
description: "User to run as inside the container (e.g., 'www-data', 'node')",
|
|
105
|
+
}),
|
|
106
|
+
),
|
|
107
|
+
}),
|
|
108
|
+
execute: async (_toolCallId, params) => {
|
|
109
|
+
if (!dockerAvailable()) {
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: "text", text: "Docker is not running or not installed." }],
|
|
112
|
+
details: {},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parts = ["docker exec"];
|
|
117
|
+
if (params.workdir) parts.push(`-w "${params.workdir}"`);
|
|
118
|
+
if (params.user) parts.push(`-u "${params.user}"`);
|
|
119
|
+
parts.push(params.container);
|
|
120
|
+
parts.push(params.command);
|
|
121
|
+
|
|
122
|
+
const cmd = parts.join(" ");
|
|
123
|
+
const output = exec(cmd, undefined, 60000);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `$ ${cmd}\n\n${output || "(no output)"}`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
details: { container: params.container, command: params.command },
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ── Tool: docker_logs ───────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
elyra.registerTool({
|
|
140
|
+
name: "docker_logs",
|
|
141
|
+
label: "Docker Logs",
|
|
142
|
+
description:
|
|
143
|
+
"Read recent logs from a Docker container. Use this to diagnose errors, " +
|
|
144
|
+
"check application output, or monitor service behavior. " +
|
|
145
|
+
"Always check logs before debugging -- the answer is often there.",
|
|
146
|
+
parameters: Type.Object({
|
|
147
|
+
container: Type.String({
|
|
148
|
+
description: "Container or service name",
|
|
149
|
+
}),
|
|
150
|
+
tail: Type.Optional(
|
|
151
|
+
Type.Number({
|
|
152
|
+
description: "Number of lines to show (default: 100)",
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
since: Type.Optional(
|
|
156
|
+
Type.String({
|
|
157
|
+
description: "Show logs since timestamp or duration (e.g., '10m', '1h', '2024-01-01')",
|
|
158
|
+
}),
|
|
159
|
+
),
|
|
160
|
+
grep: Type.Optional(
|
|
161
|
+
Type.String({
|
|
162
|
+
description: "Filter logs by pattern (grep)",
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
}),
|
|
166
|
+
execute: async (_toolCallId, params) => {
|
|
167
|
+
if (!dockerAvailable()) {
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: "text", text: "Docker is not running or not installed." }],
|
|
170
|
+
details: {},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const tail = params.tail ?? 100;
|
|
175
|
+
let cmd = `docker logs --tail ${tail}`;
|
|
176
|
+
if (params.since) cmd += ` --since "${params.since}"`;
|
|
177
|
+
cmd += ` ${params.container}`;
|
|
178
|
+
if (params.grep) cmd += ` 2>&1 | grep -i "${params.grep}"`;
|
|
179
|
+
|
|
180
|
+
const output = exec(cmd, undefined, 15000);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: output || "(no logs)",
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
details: { container: params.container, lines: tail },
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ── Tool: docker_status ─────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
elyra.registerTool({
|
|
197
|
+
name: "docker_status",
|
|
198
|
+
label: "Docker Status",
|
|
199
|
+
description:
|
|
200
|
+
"List running Docker containers with status, ports, and resource usage. " +
|
|
201
|
+
"Use this to check what's running, find container names, and verify health.",
|
|
202
|
+
parameters: Type.Object({}),
|
|
203
|
+
execute: async () => {
|
|
204
|
+
if (!dockerAvailable()) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: "Docker is not running or not installed." }],
|
|
207
|
+
details: {},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const ps = exec(
|
|
212
|
+
'docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.Size}}"',
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Get resource usage
|
|
216
|
+
let stats = "";
|
|
217
|
+
try {
|
|
218
|
+
stats = exec(
|
|
219
|
+
'docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"',
|
|
220
|
+
undefined,
|
|
221
|
+
10000,
|
|
222
|
+
);
|
|
223
|
+
} catch {
|
|
224
|
+
// stats might timeout on slow systems
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const parts = ["# Docker Status\n"];
|
|
228
|
+
parts.push("## Running Containers\n```\n" + (ps || "No containers running") + "\n```\n");
|
|
229
|
+
if (stats) {
|
|
230
|
+
parts.push("## Resource Usage\n```\n" + stats + "\n```\n");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
235
|
+
details: {},
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ── Tool: docker_compose ────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
elyra.registerTool({
|
|
243
|
+
name: "docker_compose",
|
|
244
|
+
label: "Docker Compose",
|
|
245
|
+
description:
|
|
246
|
+
"Run docker compose operations: up, down, restart, build, pull. " +
|
|
247
|
+
"Use this to manage the container stack -- start services, rebuild after " +
|
|
248
|
+
"Dockerfile changes, restart after config changes.",
|
|
249
|
+
parameters: Type.Object({
|
|
250
|
+
action: Type.String({
|
|
251
|
+
description: "Compose action: up, down, restart, build, pull, ps",
|
|
252
|
+
}),
|
|
253
|
+
service: Type.Optional(
|
|
254
|
+
Type.String({
|
|
255
|
+
description: "Target specific service (e.g., 'app', 'db'). Omit for all services.",
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
258
|
+
flags: Type.Optional(
|
|
259
|
+
Type.String({
|
|
260
|
+
description: "Additional flags (e.g., '-d' for detached, '--build' for up with build)",
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
263
|
+
}),
|
|
264
|
+
execute: async (_toolCallId, params) => {
|
|
265
|
+
if (!dockerAvailable()) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: "text", text: "Docker is not running or not installed." }],
|
|
268
|
+
details: {},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const allowed = ["up", "down", "restart", "build", "pull", "ps", "stop", "start"];
|
|
273
|
+
if (!allowed.includes(params.action)) {
|
|
274
|
+
return {
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: `Action "${params.action}" not allowed. Use: ${allowed.join(", ")}`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
details: {},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let cmd = `docker compose ${params.action}`;
|
|
286
|
+
if (params.flags) cmd += ` ${params.flags}`;
|
|
287
|
+
if (params.service) cmd += ` ${params.service}`;
|
|
288
|
+
|
|
289
|
+
// Add -d for up if not specified
|
|
290
|
+
if (params.action === "up" && !params.flags?.includes("-d")) {
|
|
291
|
+
cmd = `docker compose up -d${params.service ? ` ${params.service}` : ""}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const cwd = process.cwd();
|
|
295
|
+
const output = exec(cmd, cwd, 120000);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: `$ ${cmd}\n\n${output || "(done)"}`,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
details: { action: params.action, service: params.service },
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// ── Tool: docker_env_check ──────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
elyra.registerTool({
|
|
312
|
+
name: "docker_env_check",
|
|
313
|
+
label: "Docker Env Check",
|
|
314
|
+
description:
|
|
315
|
+
"Compare host .env file with the actual environment variables inside a container. " +
|
|
316
|
+
"Finds mismatches that cause 'works on my machine' bugs: missing vars, different values, " +
|
|
317
|
+
"vars in .env but not in container, vars in container but not in .env.",
|
|
318
|
+
parameters: Type.Object({
|
|
319
|
+
container: Type.String({
|
|
320
|
+
description: "Container or service name to check",
|
|
321
|
+
}),
|
|
322
|
+
env_file: Type.Optional(
|
|
323
|
+
Type.String({
|
|
324
|
+
description: "Path to .env file on host (default: .env)",
|
|
325
|
+
}),
|
|
326
|
+
),
|
|
327
|
+
}),
|
|
328
|
+
execute: async (_toolCallId, params) => {
|
|
329
|
+
if (!dockerAvailable()) {
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: "text", text: "Docker is not running or not installed." }],
|
|
332
|
+
details: {},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const envFile = params.env_file ?? ".env";
|
|
337
|
+
const cwd = process.cwd();
|
|
338
|
+
const envPath = join(cwd, envFile);
|
|
339
|
+
|
|
340
|
+
if (!existsSync(envPath)) {
|
|
341
|
+
return {
|
|
342
|
+
content: [{ type: "text", text: `File not found: ${envFile}` }],
|
|
343
|
+
details: {},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Parse host .env
|
|
348
|
+
const hostEnv = new Map<string, string>();
|
|
349
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
350
|
+
for (const line of envContent.split("\n")) {
|
|
351
|
+
const trimmed = line.trim();
|
|
352
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
353
|
+
const eqIdx = trimmed.indexOf("=");
|
|
354
|
+
if (eqIdx === -1) continue;
|
|
355
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
356
|
+
const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
357
|
+
hostEnv.set(key, value);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Get container env
|
|
361
|
+
const containerEnvRaw = exec(`docker exec ${params.container} env`, undefined, 10000);
|
|
362
|
+
const containerEnv = new Map<string, string>();
|
|
363
|
+
for (const line of containerEnvRaw.split("\n")) {
|
|
364
|
+
const eqIdx = line.indexOf("=");
|
|
365
|
+
if (eqIdx === -1) continue;
|
|
366
|
+
containerEnv.set(line.slice(0, eqIdx), line.slice(eqIdx + 1));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Compare
|
|
370
|
+
const missing: string[] = [];
|
|
371
|
+
const different: string[] = [];
|
|
372
|
+
const extraInContainer: string[] = [];
|
|
373
|
+
|
|
374
|
+
for (const [key, hostValue] of hostEnv) {
|
|
375
|
+
if (!containerEnv.has(key)) {
|
|
376
|
+
missing.push(key);
|
|
377
|
+
} else if (containerEnv.get(key) !== hostValue) {
|
|
378
|
+
different.push(`${key}: host="${hostValue}" container="${containerEnv.get(key)}"`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Skip common system vars for "extra in container" check
|
|
383
|
+
const systemVars = new Set([
|
|
384
|
+
"PATH", "HOME", "HOSTNAME", "TERM", "SHLVL", "PWD", "LANG", "LC_ALL",
|
|
385
|
+
"GPG_KEY", "PHPIZE_DEPS", "PHP_INI_DIR", "PHP_VERSION", "PHP_SHA256",
|
|
386
|
+
"COMPOSER_ALLOW_SUPERUSER", "NODE_VERSION", "YARN_VERSION",
|
|
387
|
+
]);
|
|
388
|
+
for (const [key] of containerEnv) {
|
|
389
|
+
if (!hostEnv.has(key) && !systemVars.has(key) && !key.startsWith("_")) {
|
|
390
|
+
extraInContainer.push(key);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const parts = [`# Environment Check: ${envFile} vs ${params.container}\n`];
|
|
395
|
+
|
|
396
|
+
if (missing.length === 0 && different.length === 0) {
|
|
397
|
+
parts.push("All host .env variables are present in the container with matching values.\n");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (missing.length > 0) {
|
|
401
|
+
parts.push(`## Missing in container (${missing.length})\n`);
|
|
402
|
+
parts.push("These variables are in your .env but not in the container:\n");
|
|
403
|
+
for (const key of missing) parts.push(`- \`${key}\``);
|
|
404
|
+
parts.push("");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (different.length > 0) {
|
|
408
|
+
parts.push(`## Different values (${different.length})\n`);
|
|
409
|
+
for (const diff of different) parts.push(`- ${diff}`);
|
|
410
|
+
parts.push("");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (extraInContainer.length > 0) {
|
|
414
|
+
parts.push(`## Only in container (${extraInContainer.length})\n`);
|
|
415
|
+
parts.push("These are set in the container but not in your .env:\n");
|
|
416
|
+
for (const key of extraInContainer) parts.push(`- \`${key}\``);
|
|
417
|
+
parts.push("");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
422
|
+
details: {
|
|
423
|
+
missing: missing.length,
|
|
424
|
+
different: different.length,
|
|
425
|
+
extra: extraInContainer.length,
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// ── Command: /docker ────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
elyra.registerCommand("docker", {
|
|
434
|
+
description: "Docker container status dashboard",
|
|
435
|
+
handler: async (_args, ctx) => {
|
|
436
|
+
if (!dockerAvailable()) {
|
|
437
|
+
ctx.ui.notify("Docker is not running or not installed.", "error");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const ps = exec(
|
|
442
|
+
'docker ps --format "{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"',
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
if (!ps) {
|
|
446
|
+
ctx.ui.notify("No Docker containers running.", "info");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const lines = ps.split("\n").map((line) => {
|
|
451
|
+
const [name, image, status, ports] = line.split("\t");
|
|
452
|
+
return ` ${name}\t${image}\t${status}\t${ports || ""}`;
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
ctx.ui.notify(
|
|
456
|
+
`Docker Containers:\n\n${lines.join("\n")}`,
|
|
457
|
+
"info",
|
|
458
|
+
);
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elyracode/docker",
|
|
3
|
+
"version": "0.5.12",
|
|
4
|
+
"description": "Docker-aware development for Elyra -- container exec, log tailing, compose operations, environment sync",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"elyra-package",
|
|
8
|
+
"docker",
|
|
9
|
+
"containers",
|
|
10
|
+
"docker-compose",
|
|
11
|
+
"devops"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Knut W. Horne",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/kwhorne/elyra.git",
|
|
18
|
+
"directory": "packages/docker"
|
|
19
|
+
},
|
|
20
|
+
"elyra": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./extensions/index.ts"
|
|
23
|
+
],
|
|
24
|
+
"skills": [
|
|
25
|
+
"./skills"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@elyracode/coding-agent": "*",
|
|
30
|
+
"typebox": "*"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"clean": "echo 'nothing to clean'",
|
|
34
|
+
"build": "echo 'nothing to build'",
|
|
35
|
+
"check": "echo 'nothing to check'"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: elyra-docker
|
|
3
|
+
description: Docker container awareness. Use when the project has docker-compose.yml or Dockerfile, or when the user asks about containers, logs, or Docker operations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Docker Development
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use Docker tools when:
|
|
11
|
+
- The project has a docker-compose.yml or compose.yml
|
|
12
|
+
- The user asks to run commands that should execute inside a container
|
|
13
|
+
- The user needs container logs or status
|
|
14
|
+
- Environment variable mismatches between host and container need debugging
|
|
15
|
+
|
|
16
|
+
## Available Tools
|
|
17
|
+
|
|
18
|
+
| Tool | Use when |
|
|
19
|
+
|------|----------|
|
|
20
|
+
| `docker_exec` | Running commands inside containers (migrations, tests, builds) |
|
|
21
|
+
| `docker_logs` | Reading application or service logs from containers |
|
|
22
|
+
| `docker_status` | Checking what's running, ports, resource usage |
|
|
23
|
+
| `docker_compose` | Starting, stopping, restarting, or rebuilding services |
|
|
24
|
+
| `docker_env_check` | Debugging environment variable mismatches |
|
|
25
|
+
|
|
26
|
+
## Container Routing
|
|
27
|
+
|
|
28
|
+
When a project uses Docker, commands like `php artisan migrate`, `npm test`, or `python manage.py` should typically run inside the appropriate container, not on the host. Use `docker_exec` to route these commands.
|
|
29
|
+
|
|
30
|
+
Common container names to look for:
|
|
31
|
+
- app, web, api -- the main application
|
|
32
|
+
- node, frontend -- JavaScript/Node.js services
|
|
33
|
+
- db, mysql, postgres, redis -- data services
|
|
34
|
+
- nginx, caddy -- web servers
|
|
35
|
+
- worker, queue -- background job processors
|
|
36
|
+
|
|
37
|
+
## Rules
|
|
38
|
+
|
|
39
|
+
- Always check `docker_status` first if unsure which containers are running
|
|
40
|
+
- Use `docker_logs` before debugging -- the answer is often in the logs
|
|
41
|
+
- Run `docker_env_check` when environment-related bugs appear
|
|
42
|
+
- Prefer `docker_compose restart <service>` over `docker compose down && up` for faster iteration
|