Skip to content

Commit 92a0c99

Browse files
author
colinmcneil
committed
Improve error handling for image runs
1 parent f11d056 commit 92a0c99

File tree

5 files changed

+46
-29
lines changed

5 files changed

+46
-29
lines changed

src/extension/ui/src/App.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ClaudeConfigSyncStatus, setNeverShowAgain } from './components/ClaudeCo
1111
import { FolderOpenRounded, } from '@mui/icons-material';
1212
import { ExecResult } from '@docker/extension-api-client-types/dist/v0';
1313
import Gordon from './components/Gordon';
14+
import { tryRunImageSync } from './FileWatcher';
1415

1516
const NEVER_SHOW_AGAIN_KEY = 'registry-sync-never-show-again';
1617

@@ -80,7 +81,7 @@ export function App() {
8081
content: stringify({ registry: newRegistry })
8182
}]
8283
})
83-
await client.docker.cli.exec('run', ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
84+
await tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
8485
client.desktopUI.toast.success('Prompt registered successfully. Restart Claude Desktop to apply.');
8586
await loadRegistry();
8687
setShowReloadModal(!localStorage.getItem(NEVER_SHOW_AGAIN_KEY));
@@ -100,29 +101,28 @@ export function App() {
100101
content: stringify({ registry: currentRegistry })
101102
}]
102103
})
103-
await client.docker.cli.exec('run', ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
104+
await tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
104105
client.desktopUI.toast.success('Prompt unregistered successfully. Restart Claude Desktop to apply.');
105106
await loadRegistry();
106107
setShowReloadModal(!localStorage.getItem(NEVER_SHOW_AGAIN_KEY));
107108
}
108109
catch (error) {
109110
client.desktopUI.toast.error('Failed to unregister prompt: ' + error)
110111
}
111-
112112
}
113113

114114
const startImagesLoading = async () => {
115115
setImagesLoadingResults(null);
116116
try {
117117
const result = await client.docker.cli.exec('pull', ['vonwig/function_write_files:latest'])
118+
await client.docker.cli.exec('pull', ['alpine:latest'])
118119
setImagesLoadingResults(result);
119120
}
120121
catch (error) {
121122
console.error(error)
122123
if (error) {
123124
setImagesLoadingResults(error as ExecResult)
124125
}
125-
126126
}
127127
}
128128

src/extension/ui/src/FileWatcher.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,49 @@
33
* This file is not used due to inability to clean up inotifywait processes
44
*/
55
import { v1 } from "@docker/extension-api-client-types"
6+
import { ExecResult } from "@docker/extension-api-client-types/dist/v0"
67

78
const allWatches: { [key: string]: any } = {}
89

10+
export const tryRunImageSync = async (client: v1.DockerDesktopClient, args: string[]) => {
11+
const showError = client.desktopUI.toast.error
12+
try {
13+
const result = await client.docker.cli.exec('run', args)
14+
if (result.stderr) {
15+
console.error(result.stderr)
16+
showError(result.stderr)
17+
}
18+
return result.stdout
19+
}
20+
catch (e) {
21+
if (e instanceof Error) {
22+
showError(e.message)
23+
}
24+
if ((e as ExecResult).stderr) {
25+
showError(JSON.stringify(e))
26+
}
27+
return ''
28+
}
29+
}
30+
931
const getUser = async (client: v1.DockerDesktopClient) => {
10-
const result = await client.docker.cli.exec('run', ['--rm', '-e', 'USER', 'alpine:latest', 'sh', '-c', `"echo $USER"`])
11-
return result.stdout.trim()
32+
const result = await tryRunImageSync(client, ['--rm', '-e', 'USER', 'alpine:latest', 'sh', '-c', `"echo $USER"`])
33+
return result.trim()
1234
}
1335

1436
export const readFileInPromptsVolume = async (client: v1.DockerDesktopClient, path: string) => {
15-
const result = await client.docker.cli.exec('run', ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'alpine:latest', 'sh', '-c', `"cat ${path}"`])
16-
return result.stdout
37+
return tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'alpine:latest', 'sh', '-c', `"cat ${path}"`])
1738
}
1839

1940
export const writeFileToPromptsVolume = async (client: v1.DockerDesktopClient, content: string) => {
2041
// Workaround for inability to use shell operators w/ DD extension API, use write_files image
21-
const result = await client.docker.cli.exec('run', ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${content}'`])
22-
return result.stdout
42+
return tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${content}'`])
2343
}
2444

2545
export const writeFilesToHost = async (client: v1.DockerDesktopClient, files: { path: string, content: string }[], hostPaths: { source: string, target: string }[], workdir: string) => {
2646
const bindArgs = hostPaths.map(path => `--mount type=bind,source="${path.source}",target="${path.target}"`)
2747
const args = ['--rm', ...bindArgs, '--workdir', workdir, 'vonwig/function_write_files:latest', `'${JSON.stringify({ files })}'`]
28-
const result = await client.docker.cli.exec('run', args)
29-
if (result.stderr) {
30-
console.error(result.stderr)
31-
}
32-
return result.stdout
48+
return tryRunImageSync(client, args)
3349
}
3450

3551
export const watchFile = async (client: v1.DockerDesktopClient, path: string, stream: { onOutput: (data: { stdout?: string; stderr?: string }) => void, onError: (error: string) => void, onClose: (exitCode: number) => void }, host = false) => {

src/extension/ui/src/components/CatalogGrid.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { v1 } from "@docker/extension-api-client-types";
77
import { parse, stringify } from 'yaml';
88
import { getRegistry } from '../Registry';
99
import { FolderOpenRounded } from '@mui/icons-material';
10+
import { tryRunImageSync } from '../FileWatcher';
1011

1112
interface CatalogGridProps {
1213
registryItems: { [key: string]: { ref: string } };
@@ -61,7 +62,7 @@ export const CatalogGrid: React.FC<CatalogGridProps> = ({
6162
content: stringify({ registry: newRegistry })
6263
}]
6364
})
64-
await client.docker.cli.exec('run', ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
65+
await tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
6566
client.desktopUI.toast.success('Prompt registered successfully. Restart Claude Desktop to apply.');
6667
onRegistryChange();
6768

@@ -81,7 +82,7 @@ export const CatalogGrid: React.FC<CatalogGridProps> = ({
8182
content: stringify({ registry: currentRegistry })
8283
}]
8384
})
84-
await client.docker.cli.exec('run', ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
85+
await tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', `'${payload}'`])
8586
client.desktopUI.toast.success('Prompt unregistered successfully. Restart Claude Desktop to apply.');
8687
onRegistryChange();
8788
}

src/extension/ui/src/components/ClaudeConfigSyncStatus.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { v1 } from "@docker/extension-api-client-types";
22
import { Badge, Button, Checkbox, Dialog, DialogContent, DialogContentText, DialogTitle, FormControlLabel, Stack, Typography } from "@mui/material";
33
import { useEffect, useState } from "react";
4-
import { writeFilesToHost } from "../FileWatcher";
4+
import { tryRunImageSync, writeFilesToHost } from "../FileWatcher";
55
import { trackEvent } from "../Usage";
66
import { ExecResult } from "@docker/extension-api-client-types/dist/v1";
77

@@ -47,9 +47,9 @@ const getClaudeConfigPath = (client: v1.DockerDesktopClient) => {
4747

4848
const getClaudeConfig = async (client: v1.DockerDesktopClient) => {
4949
const path = getClaudeConfigPath(client)
50-
const result = await client.docker.cli.exec('run', ['--rm', '--mount', `type=bind,source="${path}",target=/config.json`, 'alpine:latest', 'sh', '-c', `"cat /config.json"`])
51-
localStorage.setItem('claude-config-sync-status-path', result.stdout)
52-
return result.stdout
50+
const result = await tryRunImageSync(client, ['--rm', '--mount', `type=bind,source="${path}",target=/config.json`, 'alpine:latest', 'sh', '-c', `"cat /config.json"`])
51+
localStorage.setItem('claude-config-sync-status-path', result)
52+
return result
5353
}
5454

5555

@@ -180,7 +180,7 @@ export const ClaudeConfigSyncStatus = ({ client, setHasConfig }: { client: v1.Do
180180
else {
181181
trackEvent('claude-config-changed', { action: 'delete' })
182182
try {
183-
await client.docker.cli.exec('run', ['--rm', '--mount', `type=bind,source="${configPath!.split('/').slice(0, -1).join('/')}",target=/claude_desktop_config`, 'alpine:latest', 'sh', '-c', '"rm -f /claude_desktop_config/claude_desktop_config.json"'])
183+
await tryRunImageSync(client, ['--rm', '--mount', `type=bind,source="${configPath!.split('/').slice(0, -1).join('/')}",target=/claude_desktop_config`, 'alpine:latest', 'sh', '-c', '"rm -f /claude_desktop_config/claude_desktop_config.json"'])
184184
refreshConfig()
185185
setDeleteReady(false)
186186
} catch (error) {

src/extension/ui/src/components/Gordon.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Badge, Button, Dialog, DialogContent, DialogTitle, Stack, Typography }
55
import gordon from '../gordon.png';
66
import { v1 } from '@docker/extension-api-client-types';
77
import { parse, stringify } from 'yaml';
8-
import { writeFilesToHost } from '../FileWatcher';
8+
import { tryRunImageSync, writeFilesToHost } from '../FileWatcher';
99

1010
const DOCKER_MCP_CONFIG_YML = {
1111
services: {
@@ -37,18 +37,18 @@ const Gordon: React.FC<{ client: v1.DockerDesktopClient }> = ({ client }) => {
3737
}
3838
try {
3939
const path = dialogResult.filePaths[0]
40-
const result = await client.docker.cli.exec('run', ['--rm', '--mount', `type=bind,source="${path}",target=/project`, 'alpine:latest', 'ls', '-la', '/project'])
41-
const files = result.stdout.split('\n').filter(line => line.trim() !== '').slice(1).map(line => line.split(' ').filter(Boolean).pop())
40+
const result = await tryRunImageSync(client, ['--rm', '--mount', `type=bind,source="${path}",target=/project`, 'alpine:latest', 'ls', '-la', '/project'])
41+
const files = result.split('\n').filter(line => line.trim() !== '').slice(1).map(line => line.split(' ').filter(Boolean).pop())
4242
const has_gordon_mcp_yml = files.some(file => file?.toLowerCase().endsWith('gordon-mcp.yml'))
4343
if (!has_gordon_mcp_yml) {
4444
await writeFilesToHost(client, [{ path: 'gordon-mcp.yml', content: stringify(DOCKER_MCP_CONFIG_YML) }], [{ source: path, target: '/project' }], '/project')
45-
client.desktopUI.toast.success(`Gordon MCP yml saved to ${path}`)
45+
client.desktopUI.toast.success(`Gordon MCP yml saved to ${path}/gordon-mcp.yml`)
4646
}
4747
else {
48-
const current_config = await client.docker.cli.exec('run', ['--rm', '--mount', `type=bind,source="${path}",target=/project`, 'alpine:latest', 'cat', '/project/gordon-mcp.yml'])
49-
const current_config_yaml = parse(current_config.stdout)
48+
const current_config = await tryRunImageSync(client, ['--rm', '--mount', `type=bind,source="${path}",target=/project`, 'alpine:latest', 'cat', '/project/gordon-mcp.yml'])
49+
const current_config_yaml = parse(current_config)
5050
if (current_config_yaml.services.mcp_docker) {
51-
return client.desktopUI.toast.error(`You already have mcp/docker configured in ${path}`)
51+
return client.desktopUI.toast.error(`Found pre-configured mcp/docker at ${path}/gordon-mcp.yml`)
5252
}
5353
const new_config = {
5454
...current_config_yaml,

0 commit comments

Comments
 (0)