@docker-harpoon/core 0.2.0 → 0.3.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/dist/api/promise.d.ts.map +1 -1
- package/dist/api/promise.js +26 -104
- package/dist/docker-resolver.d.ts +36 -0
- package/dist/docker-resolver.d.ts.map +1 -0
- package/dist/docker-resolver.js +214 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/services/DockerClient.d.ts.map +1 -1
- package/dist/services/DockerClient.js +15 -29
- package/dist/services/HarpoonConfig.d.ts +2 -2
- package/dist/services/HarpoonConfig.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"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"}
|
package/dist/api/promise.js
CHANGED
|
@@ -7,44 +7,15 @@
|
|
|
7
7
|
* Inspired by Harpoon and Pulumi's IAC patterns.
|
|
8
8
|
*/
|
|
9
9
|
import { Effect, Scope, Exit } from 'effect';
|
|
10
|
-
import { homedir, platform } from 'os';
|
|
11
|
-
import { existsSync } from 'fs';
|
|
12
|
-
import { spawn } from 'child_process';
|
|
13
|
-
import Docker from 'dockerode';
|
|
14
10
|
// Import Effect-based resources (internal only)
|
|
15
11
|
import { Network as EffectNetwork, Container as EffectContainer, Image as EffectImage, } from '../resources';
|
|
16
12
|
import { database as effectDatabase, } from '../helpers';
|
|
13
|
+
import { createDockerClient, DockerResolutionError, formatDockerConnectionFailure, resolveDockerConnection, } from '../docker-resolver';
|
|
17
14
|
// Binding type is used as-is from internal types
|
|
18
15
|
// Pre-built bindings like PrismaBinding handle Effect internally
|
|
19
16
|
// Users creating simple bindings can use createEnvBinding()
|
|
20
17
|
// ============ Docker Client Management ============
|
|
21
18
|
let dockerClient = null;
|
|
22
|
-
/**
|
|
23
|
-
* Detect Docker socket path for the current platform.
|
|
24
|
-
*/
|
|
25
|
-
function detectDockerSocket() {
|
|
26
|
-
const candidates = ['/var/run/docker.sock', `${homedir()}/.docker/run/docker.sock`];
|
|
27
|
-
for (const path of candidates) {
|
|
28
|
-
if (existsSync(path)) {
|
|
29
|
-
return path;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// Windows uses named pipes, let dockerode handle it
|
|
33
|
-
return '/var/run/docker.sock';
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Get the Docker client, auto-detecting the socket if needed.
|
|
37
|
-
*/
|
|
38
|
-
function getDocker() {
|
|
39
|
-
if (!dockerClient) {
|
|
40
|
-
const socketPath = detectDockerSocket();
|
|
41
|
-
dockerClient = new Docker({ socketPath });
|
|
42
|
-
}
|
|
43
|
-
return dockerClient;
|
|
44
|
-
}
|
|
45
|
-
// ============ Docker Auto-Start (Harpoon Style) ============
|
|
46
|
-
const DOCKER_STARTUP_TIMEOUT_MS = 60_000;
|
|
47
|
-
let dockerEnsured = false;
|
|
48
19
|
/**
|
|
49
20
|
* Simple logging helper for promise-based API.
|
|
50
21
|
*/
|
|
@@ -59,87 +30,38 @@ const harpoonLog = {
|
|
|
59
30
|
error: (message, err) => console.error(`[Harpoon] ${message}`, err ?? ''),
|
|
60
31
|
};
|
|
61
32
|
/**
|
|
62
|
-
*
|
|
33
|
+
* Get Docker client with shared endpoint discovery.
|
|
63
34
|
*/
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
harpoonLog.info('Starting Docker Desktop...');
|
|
68
|
-
const child = spawn('open', ['-a', 'Docker', '--background'], {
|
|
69
|
-
detached: true,
|
|
70
|
-
stdio: 'ignore',
|
|
71
|
-
});
|
|
72
|
-
child.unref();
|
|
35
|
+
async function getDockerAsync() {
|
|
36
|
+
if (dockerClient) {
|
|
37
|
+
return dockerClient;
|
|
73
38
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
detached: true,
|
|
78
|
-
stdio: 'ignore',
|
|
79
|
-
});
|
|
80
|
-
child.unref();
|
|
39
|
+
let resolution;
|
|
40
|
+
try {
|
|
41
|
+
resolution = resolveDockerConnection();
|
|
81
42
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
if (existsSync(socketPath)) {
|
|
93
|
-
const docker = new Docker({ socketPath });
|
|
94
|
-
await docker.ping();
|
|
95
|
-
harpoonLog.info('Docker is ready');
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof DockerResolutionError) {
|
|
45
|
+
const lines = [
|
|
46
|
+
'Failed to resolve Docker endpoint.',
|
|
47
|
+
error.message,
|
|
48
|
+
'Harpoon will not launch Docker (desktop, engine, or other runtimes) automatically. Start your configured Docker runtime, or set DOCKER_SOCKET, DOCKER_HOST, or DOCKER_CONTEXT to a local Docker endpoint.',
|
|
49
|
+
'You can also inject a Docker client with setDocker().',
|
|
50
|
+
];
|
|
51
|
+
throw new Error(lines.join('\n'));
|
|
98
52
|
}
|
|
99
|
-
|
|
100
|
-
lastError = e;
|
|
101
|
-
}
|
|
102
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
53
|
+
throw error;
|
|
103
54
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
* Ensure Docker is running, auto-starting if needed.
|
|
108
|
-
* This is the Harpoon-style declarative approach.
|
|
109
|
-
*/
|
|
110
|
-
async function ensureDockerRunning() {
|
|
111
|
-
if (dockerEnsured)
|
|
112
|
-
return;
|
|
113
|
-
const socketPath = detectDockerSocket();
|
|
114
|
-
// Check if already running
|
|
115
|
-
if (existsSync(socketPath)) {
|
|
116
|
-
try {
|
|
117
|
-
const docker = new Docker({ socketPath });
|
|
118
|
-
await docker.ping();
|
|
119
|
-
dockerEnsured = true;
|
|
120
|
-
return; // Docker is ready
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// Socket exists but daemon not responding, wait for it
|
|
124
|
-
harpoonLog.debug('Docker socket exists but daemon not responding, waiting...');
|
|
125
|
-
}
|
|
55
|
+
const docker = createDockerClient(resolution);
|
|
56
|
+
try {
|
|
57
|
+
await docker.ping();
|
|
126
58
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
harpoonLog.info('Docker not running, attempting auto-start...');
|
|
130
|
-
startDockerDesktop();
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new Error(formatDockerConnectionFailure(resolution, error));
|
|
131
61
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Get Docker client with auto-start support.
|
|
138
|
-
* Ensures Docker is running before returning client.
|
|
139
|
-
*/
|
|
140
|
-
async function getDockerAsync() {
|
|
141
|
-
await ensureDockerRunning();
|
|
142
|
-
return getDocker();
|
|
62
|
+
harpoonLog.debug(`Connected to Docker daemon via ${resolution.sourceDescription}`);
|
|
63
|
+
dockerClient = docker;
|
|
64
|
+
return dockerClient;
|
|
143
65
|
}
|
|
144
66
|
/**
|
|
145
67
|
* Set a custom Docker client.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Docker from 'dockerode';
|
|
2
|
+
interface DockerResolutionFileSystem {
|
|
3
|
+
existsSync: (filePath: string) => boolean;
|
|
4
|
+
readFileSync: (filePath: string) => string;
|
|
5
|
+
readdirSync: (directoryPath: string) => string[];
|
|
6
|
+
}
|
|
7
|
+
export interface ResolveDockerConnectionOptions {
|
|
8
|
+
readonly explicitSocketPath?: string;
|
|
9
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
10
|
+
readonly homeDir?: string;
|
|
11
|
+
readonly platform?: NodeJS.Platform;
|
|
12
|
+
readonly fs?: DockerResolutionFileSystem;
|
|
13
|
+
}
|
|
14
|
+
export interface DockerConnectionResolution {
|
|
15
|
+
readonly dockerOptions: Docker.DockerOptions;
|
|
16
|
+
readonly endpoint: string;
|
|
17
|
+
readonly socketPath: string;
|
|
18
|
+
readonly source: string;
|
|
19
|
+
readonly sourceDescription: string;
|
|
20
|
+
readonly triedEndpoints: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
export declare class DockerResolutionError extends Error {
|
|
23
|
+
readonly source: string;
|
|
24
|
+
readonly triedEndpoints: readonly string[];
|
|
25
|
+
readonly cause: Error | undefined;
|
|
26
|
+
constructor(message: string, options: {
|
|
27
|
+
source: string;
|
|
28
|
+
triedEndpoints?: readonly string[];
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export declare function resolveDockerConnection(options?: ResolveDockerConnectionOptions): DockerConnectionResolution;
|
|
33
|
+
export declare function createDockerClient(options?: ResolveDockerConnectionOptions | DockerConnectionResolution): Docker;
|
|
34
|
+
export declare function formatDockerConnectionFailure(resolution: DockerConnectionResolution, cause: unknown, injectedClientHint?: string): string;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=docker-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-resolver.d.ts","sourceRoot":"","sources":["../src/docker-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,MAAM,MAAM,WAAW,CAAC;AAS/B,UAAU,0BAA0B;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,WAAW,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CAClD;AAQD,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,EAAE,0BAA0B,CAAC;CAC1C;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAE3C,SAAkB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAE3C,YACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,EAWF;CACF;AAyND,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,8BAAmC,GAC3C,0BAA0B,CAqE5B;AAED,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,8BAA8B,GAAG,0BAA+B,GACxE,MAAM,CAIR;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,0BAA0B,EACtC,KAAK,EAAE,OAAO,EACd,kBAAkB,SAAgB,GACjC,MAAM,CAiBR"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { homedir, platform as osPlatform } from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import Docker from 'dockerode';
|
|
5
|
+
const WINDOWS_DOCKER_PIPE = '//./pipe/docker_engine';
|
|
6
|
+
// Docker's built-in "default" context has no on-disk metadata in
|
|
7
|
+
// ~/.docker/contexts/meta/ and must be treated the same as having no context
|
|
8
|
+
// configured, falling through to the local socket candidate search.
|
|
9
|
+
const DOCKER_DEFAULT_CONTEXT = 'default';
|
|
10
|
+
const defaultFileSystem = {
|
|
11
|
+
existsSync,
|
|
12
|
+
readFileSync: (filePath) => readFileSync(filePath, 'utf-8'),
|
|
13
|
+
readdirSync: (directoryPath) => readdirSync(directoryPath),
|
|
14
|
+
};
|
|
15
|
+
export class DockerResolutionError extends Error {
|
|
16
|
+
source;
|
|
17
|
+
triedEndpoints;
|
|
18
|
+
cause;
|
|
19
|
+
constructor(message, options) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'DockerResolutionError';
|
|
22
|
+
this.source = options.source;
|
|
23
|
+
this.triedEndpoints = options.triedEndpoints ?? [];
|
|
24
|
+
this.cause = asError(options.cause);
|
|
25
|
+
if (Error.captureStackTrace) {
|
|
26
|
+
Error.captureStackTrace(this, DockerResolutionError);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const asError = (value) => {
|
|
31
|
+
if (value === undefined) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
35
|
+
};
|
|
36
|
+
const buildResolutionError = (message, source, triedEndpoints = [], cause) => new DockerResolutionError(message, {
|
|
37
|
+
source,
|
|
38
|
+
triedEndpoints,
|
|
39
|
+
cause,
|
|
40
|
+
});
|
|
41
|
+
const getLocalDockerSocketCandidates = (homeDirectory, platform) => {
|
|
42
|
+
if (platform === 'win32') {
|
|
43
|
+
return [WINDOWS_DOCKER_PIPE];
|
|
44
|
+
}
|
|
45
|
+
const candidates = ['/var/run/docker.sock'];
|
|
46
|
+
if (homeDirectory) {
|
|
47
|
+
candidates.push(`${homeDirectory}/.docker/run/docker.sock`, `${homeDirectory}/.docker/desktop/docker.sock`, `${homeDirectory}/.rd/docker.sock`, `${homeDirectory}/.colima/default/docker.sock`, `${homeDirectory}/.orbstack/run/docker.sock`);
|
|
48
|
+
}
|
|
49
|
+
return candidates;
|
|
50
|
+
};
|
|
51
|
+
const normalizePipePath = (value) => {
|
|
52
|
+
const withForwardSlashes = value.replace(/\\/g, '/');
|
|
53
|
+
if (withForwardSlashes.startsWith('//./pipe/')) {
|
|
54
|
+
return withForwardSlashes;
|
|
55
|
+
}
|
|
56
|
+
const trimmed = withForwardSlashes.replace(/^\/+/, '');
|
|
57
|
+
if (trimmed.startsWith('./pipe/')) {
|
|
58
|
+
return `//${trimmed}`;
|
|
59
|
+
}
|
|
60
|
+
if (trimmed.startsWith('pipe/')) {
|
|
61
|
+
return `//./${trimmed}`;
|
|
62
|
+
}
|
|
63
|
+
return withForwardSlashes;
|
|
64
|
+
};
|
|
65
|
+
const normalizeDockerHost = (value, sourceDescription) => {
|
|
66
|
+
if (value.startsWith('unix://')) {
|
|
67
|
+
const encoded = value.slice('unix://'.length);
|
|
68
|
+
try {
|
|
69
|
+
return decodeURIComponent(encoded);
|
|
70
|
+
}
|
|
71
|
+
catch (cause) {
|
|
72
|
+
throw buildResolutionError(`Malformed percent-encoding in ${sourceDescription} value "${value}". The path portion "${encoded}" is not valid URI-encoded text.`, sourceDescription, [value], cause);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (value.startsWith('npipe://')) {
|
|
76
|
+
return normalizePipePath(value.slice('npipe://'.length));
|
|
77
|
+
}
|
|
78
|
+
const hasUriScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
|
|
79
|
+
if (hasUriScheme) {
|
|
80
|
+
throw buildResolutionError(`Unsupported ${sourceDescription} value "${value}". Harpoon currently supports only local Docker endpoints via raw socket paths, unix://, or npipe://.`, sourceDescription, [value]);
|
|
81
|
+
}
|
|
82
|
+
if (value.startsWith('\\\\.\\pipe\\') || value.startsWith('//./pipe/')) {
|
|
83
|
+
return normalizePipePath(value);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
};
|
|
87
|
+
const makeResolution = (socketPath, endpoint, source, sourceDescription, triedEndpoints) => ({
|
|
88
|
+
dockerOptions: { socketPath },
|
|
89
|
+
endpoint,
|
|
90
|
+
socketPath,
|
|
91
|
+
source,
|
|
92
|
+
sourceDescription,
|
|
93
|
+
triedEndpoints,
|
|
94
|
+
});
|
|
95
|
+
const resolveDirectSocket = (value, source, sourceDescription) => {
|
|
96
|
+
const socketPath = normalizeDockerHost(value, sourceDescription);
|
|
97
|
+
return makeResolution(socketPath, value, source, sourceDescription, [socketPath]);
|
|
98
|
+
};
|
|
99
|
+
const readJson = (filePath, fileSystem) => {
|
|
100
|
+
if (!fileSystem.existsSync(filePath)) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(fileSystem.readFileSync(filePath));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const readCurrentDockerContext = (homeDirectory, fileSystem) => {
|
|
111
|
+
if (!homeDirectory) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const configPath = path.join(homeDirectory, '.docker', 'config.json');
|
|
115
|
+
const config = readJson(configPath, fileSystem);
|
|
116
|
+
if (!config) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const currentContext = config['currentContext'];
|
|
120
|
+
return typeof currentContext === 'string' &&
|
|
121
|
+
currentContext.length > 0 &&
|
|
122
|
+
currentContext !== DOCKER_DEFAULT_CONTEXT
|
|
123
|
+
? currentContext
|
|
124
|
+
: undefined;
|
|
125
|
+
};
|
|
126
|
+
const resolveDockerContext = (contextName, source, sourceDescription, homeDirectory, fileSystem) => {
|
|
127
|
+
if (!homeDirectory) {
|
|
128
|
+
throw buildResolutionError(`Unable to resolve ${sourceDescription}. No home directory is available for Docker context lookup.`, source);
|
|
129
|
+
}
|
|
130
|
+
const contextsDirectory = path.join(homeDirectory, '.docker', 'contexts', 'meta');
|
|
131
|
+
let entries;
|
|
132
|
+
try {
|
|
133
|
+
entries = fileSystem.readdirSync(contextsDirectory);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw buildResolutionError(`Unable to resolve ${sourceDescription}. Docker context metadata was not found at ${contextsDirectory}.`, source, [contextsDirectory], error);
|
|
137
|
+
}
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const metaPath = path.join(contextsDirectory, entry, 'meta.json');
|
|
140
|
+
const meta = readJson(metaPath, fileSystem);
|
|
141
|
+
if (!meta || meta['Name'] !== contextName) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const endpoints = meta['Endpoints'];
|
|
145
|
+
const dockerEndpoint = endpoints && typeof endpoints === 'object'
|
|
146
|
+
? endpoints['docker']
|
|
147
|
+
: undefined;
|
|
148
|
+
const host = dockerEndpoint && typeof dockerEndpoint === 'object'
|
|
149
|
+
? dockerEndpoint['Host']
|
|
150
|
+
: undefined;
|
|
151
|
+
if (typeof host !== 'string' || host.length === 0) {
|
|
152
|
+
throw buildResolutionError(`Docker context "${contextName}" does not define a Docker host endpoint.`, source, [metaPath]);
|
|
153
|
+
}
|
|
154
|
+
const socketPath = normalizeDockerHost(host, sourceDescription);
|
|
155
|
+
return makeResolution(socketPath, host, source, sourceDescription, [socketPath]);
|
|
156
|
+
}
|
|
157
|
+
throw buildResolutionError(`Docker context "${contextName}" was not found in ${contextsDirectory}.`, source, [contextsDirectory]);
|
|
158
|
+
};
|
|
159
|
+
export function resolveDockerConnection(options = {}) {
|
|
160
|
+
const fileSystem = options.fs ?? defaultFileSystem;
|
|
161
|
+
const env = options.env ?? process.env;
|
|
162
|
+
const homeDirectory = options.homeDir ?? homedir();
|
|
163
|
+
const platform = options.platform ?? osPlatform();
|
|
164
|
+
if (options.explicitSocketPath) {
|
|
165
|
+
return resolveDirectSocket(options.explicitSocketPath, 'explicitSocketPath', 'explicit socket override');
|
|
166
|
+
}
|
|
167
|
+
const dockerSocket = env.DOCKER_SOCKET;
|
|
168
|
+
if (dockerSocket) {
|
|
169
|
+
return resolveDirectSocket(dockerSocket, 'DOCKER_SOCKET', 'DOCKER_SOCKET');
|
|
170
|
+
}
|
|
171
|
+
const dockerHost = env.DOCKER_HOST;
|
|
172
|
+
if (dockerHost) {
|
|
173
|
+
return resolveDirectSocket(dockerHost, 'DOCKER_HOST', 'DOCKER_HOST');
|
|
174
|
+
}
|
|
175
|
+
const dockerContext = env.DOCKER_CONTEXT;
|
|
176
|
+
if (dockerContext && dockerContext !== DOCKER_DEFAULT_CONTEXT) {
|
|
177
|
+
return resolveDockerContext(dockerContext, 'DOCKER_CONTEXT', `DOCKER_CONTEXT=${dockerContext}`, homeDirectory, fileSystem);
|
|
178
|
+
}
|
|
179
|
+
const currentContext = readCurrentDockerContext(homeDirectory, fileSystem);
|
|
180
|
+
if (currentContext) {
|
|
181
|
+
return resolveDockerContext(currentContext, 'currentContext', `Docker currentContext=${currentContext}`, homeDirectory, fileSystem);
|
|
182
|
+
}
|
|
183
|
+
const candidates = getLocalDockerSocketCandidates(homeDirectory, platform);
|
|
184
|
+
let resolvedSocketPath;
|
|
185
|
+
const triedEndpoints = [];
|
|
186
|
+
for (const candidatePath of candidates) {
|
|
187
|
+
triedEndpoints.push(candidatePath);
|
|
188
|
+
if (fileSystem.existsSync(candidatePath)) {
|
|
189
|
+
resolvedSocketPath = candidatePath;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (resolvedSocketPath === undefined) {
|
|
194
|
+
resolvedSocketPath = candidates[0];
|
|
195
|
+
}
|
|
196
|
+
return makeResolution(resolvedSocketPath, resolvedSocketPath, 'fallback', 'fallback socket search', triedEndpoints);
|
|
197
|
+
}
|
|
198
|
+
export function createDockerClient(options = {}) {
|
|
199
|
+
const resolution = 'dockerOptions' in options ? options : resolveDockerConnection(options);
|
|
200
|
+
return new Docker(resolution.dockerOptions);
|
|
201
|
+
}
|
|
202
|
+
export function formatDockerConnectionFailure(resolution, cause, injectedClientHint = 'setDocker()') {
|
|
203
|
+
const lines = ['Failed to connect to Docker daemon.'];
|
|
204
|
+
lines.push(`Source: ${resolution.sourceDescription}`);
|
|
205
|
+
lines.push(`Endpoint: ${resolution.endpoint}`);
|
|
206
|
+
lines.push(`Tried: ${resolution.triedEndpoints.join(', ')}`);
|
|
207
|
+
const error = asError(cause);
|
|
208
|
+
if (error?.message) {
|
|
209
|
+
lines.push(`Cause: ${error.message}`);
|
|
210
|
+
}
|
|
211
|
+
lines.push('Harpoon will not launch Docker (desktop, engine, or other runtimes) automatically. Start your configured Docker runtime, or set DOCKER_SOCKET, DOCKER_HOST, or DOCKER_CONTEXT to a local Docker endpoint.');
|
|
212
|
+
lines.push(`You can also inject a Docker client with ${injectedClientHint}.`);
|
|
213
|
+
return lines.join('\n');
|
|
214
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* - BuildBinding interface for Dockerfile transformations
|
|
29
29
|
*/
|
|
30
30
|
export { Network, Container, database, Image, setDocker, resetDocker, destroyAll, type NetworkConfig, type NetworkResource, type ContainerConfig, type ContainerResource, type PortMapping, type ShutdownMetadata, type DatabaseConfig, type DatabaseResource, type ImageConfig, type ImageResource, type ContainerStats, type ExecOptions, type ExecResult, type LogOptions, type LogLine, type VolumeMapping, } from './api';
|
|
31
|
+
export { createDockerClient, resolveDockerConnection, formatDockerConnectionFailure, DockerResolutionError, type ResolveDockerConnectionOptions, type DockerConnectionResolution, } from './docker-resolver';
|
|
31
32
|
export type { Binding, BuildBinding, BindingsEnv, FlatBindingsEnv } from './bindings';
|
|
32
33
|
export { isBuildBinding, mergeBindingsEnv, createEnvBinding } from './bindings';
|
|
33
34
|
export { HarpoonError, NetworkError, ImageError, ContainerError, ScopeError, BindingError, toHarpoonError, asCause, isHarpoonError, isNetworkError, isImageError, isContainerError, isScopeError, isBindingError, } from './errors';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAEL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,KAAK,EAGL,SAAS,EACT,WAAW,EACX,UAAU,EAGV,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EAGlB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,aAAa,GACnB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAEL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,KAAK,EAGL,SAAS,EACT,WAAW,EACX,UAAU,EAGV,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EAGlB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,aAAa,GACnB,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,6BAA6B,EAC7B,qBAAqB,EACrB,KAAK,8BAA8B,EACnC,KAAK,0BAA0B,GAChC,MAAM,mBAAmB,CAAC;AAI3B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIhF,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,OAAO,EACP,cAAc,EACd,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,8BAA8B,EAC9B,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAChB,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ export {
|
|
|
33
33
|
Network, Container, database, Image,
|
|
34
34
|
// Docker client management
|
|
35
35
|
setDocker, resetDocker, destroyAll, } from './api';
|
|
36
|
+
export { createDockerClient, resolveDockerConnection, formatDockerConnectionFailure, DockerResolutionError, } from './docker-resolver';
|
|
36
37
|
export { isBuildBinding, mergeBindingsEnv, createEnvBinding } from './bindings';
|
|
37
38
|
// ============ Errors ============
|
|
38
39
|
export { HarpoonError, NetworkError, ImageError, ContainerError, ScopeError, BindingError, toHarpoonError, asCause, isHarpoonError, isNetworkError, isImageError, isContainerError, isScopeError, isBindingError, } from './errors';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DockerClient.d.ts","sourceRoot":"","sources":["../../src/services/DockerClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAS,MAAM,QAAQ,CAAC;AACvD,OAAO,MAAM,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"DockerClient.d.ts","sourceRoot":"","sources":["../../src/services/DockerClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Context, Effect, Layer } from 'effect';
|
|
8
8
|
import Docker from 'dockerode';
|
|
9
|
-
import {
|
|
10
|
-
import { homedir, platform } from 'os';
|
|
9
|
+
import { formatDockerConnectionFailure, resolveDockerConnection } from '../docker-resolver';
|
|
11
10
|
import { HarpoonConfig } from './HarpoonConfig';
|
|
12
11
|
import { DockerConnectionError, wrapAsDockerError, isTransientError, } from './DockerErrors';
|
|
13
12
|
import { DockerRateLimiter, DockerRateLimiterLive, dockerRetrySchedule } from './DockerRateLimiter';
|
|
@@ -16,28 +15,6 @@ import { DockerRateLimiter, DockerRateLimiterLive, dockerRetrySchedule } from '.
|
|
|
16
15
|
*/
|
|
17
16
|
export class DockerClient extends Context.Tag('@harpoon/DockerClient')() {
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Detect Docker socket path for the current platform.
|
|
21
|
-
*/
|
|
22
|
-
function detectDockerSocket() {
|
|
23
|
-
const candidates = [
|
|
24
|
-
'/var/run/docker.sock',
|
|
25
|
-
`${homedir()}/.docker/run/docker.sock`,
|
|
26
|
-
// Colima on macOS
|
|
27
|
-
`${homedir()}/.colima/default/docker.sock`,
|
|
28
|
-
];
|
|
29
|
-
for (const path of candidates) {
|
|
30
|
-
if (existsSync(path)) {
|
|
31
|
-
return path;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Windows uses named pipes, let dockerode handle it
|
|
35
|
-
if (platform() === 'win32') {
|
|
36
|
-
return '//./pipe/docker_engine';
|
|
37
|
-
}
|
|
38
|
-
// Default fallback
|
|
39
|
-
return '/var/run/docker.sock';
|
|
40
|
-
}
|
|
41
18
|
/**
|
|
42
19
|
* Helper to add retry logic for transient errors.
|
|
43
20
|
*/
|
|
@@ -58,14 +35,23 @@ const withRetryOnTransient = (effect) => effect.pipe(Effect.retry({
|
|
|
58
35
|
export const DockerClientLive = Layer.scoped(DockerClient, Effect.gen(function* () {
|
|
59
36
|
const config = yield* HarpoonConfig;
|
|
60
37
|
const rateLimiter = yield* DockerRateLimiter;
|
|
61
|
-
|
|
62
|
-
|
|
38
|
+
const resolution = yield* Effect.try({
|
|
39
|
+
try: () => resolveDockerConnection({
|
|
40
|
+
...(config.dockerSocket !== undefined && {
|
|
41
|
+
explicitSocketPath: config.dockerSocket,
|
|
42
|
+
}),
|
|
43
|
+
}),
|
|
44
|
+
catch: (e) => new DockerConnectionError(e instanceof Error ? e.message : String(e), {
|
|
45
|
+
cause: e,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
const socketPath = resolution.socketPath;
|
|
63
49
|
// Create Docker client
|
|
64
|
-
const docker = new Docker(
|
|
50
|
+
const docker = new Docker(resolution.dockerOptions);
|
|
65
51
|
// Verify connection on startup (with retry for transient errors)
|
|
66
52
|
yield* Effect.tryPromise({
|
|
67
53
|
try: () => docker.ping(),
|
|
68
|
-
catch: (e) => new DockerConnectionError(
|
|
54
|
+
catch: (e) => new DockerConnectionError(formatDockerConnectionFailure(resolution, e, 'a custom DockerClient layer'), {
|
|
69
55
|
socketPath,
|
|
70
56
|
cause: e,
|
|
71
57
|
}),
|
|
@@ -79,7 +65,7 @@ export const DockerClientLive = Layer.scoped(DockerClient, Effect.gen(function*
|
|
|
79
65
|
client: docker,
|
|
80
66
|
ping: () => rateLimiter.withPermit(Effect.tryPromise({
|
|
81
67
|
try: () => docker.ping(),
|
|
82
|
-
catch: (e) => new DockerConnectionError(
|
|
68
|
+
catch: (e) => new DockerConnectionError(formatDockerConnectionFailure(resolution, e, 'a custom DockerClient layer'), {
|
|
83
69
|
socketPath,
|
|
84
70
|
cause: e,
|
|
85
71
|
}),
|
|
@@ -24,7 +24,7 @@ export interface HarpoonConfigService {
|
|
|
24
24
|
readonly retryAttempts: number;
|
|
25
25
|
/** Delay between retry attempts */
|
|
26
26
|
readonly retryDelay: Duration.Duration;
|
|
27
|
-
/** Docker startup timeout
|
|
27
|
+
/** Legacy Docker startup timeout setting. Currently unused. */
|
|
28
28
|
readonly dockerStartupTimeout: Duration.Duration;
|
|
29
29
|
}
|
|
30
30
|
declare const HarpoonConfig_base: Context.TagClass<HarpoonConfig, "@harpoon/HarpoonConfig", HarpoonConfigService>;
|
|
@@ -44,7 +44,7 @@ export declare class HarpoonConfig extends HarpoonConfig_base {
|
|
|
44
44
|
* - HARPOON_PARALLEL_LIMIT: Max parallel operations (default: 4)
|
|
45
45
|
* - HARPOON_RETRY_ATTEMPTS: Retry attempts (default: 3)
|
|
46
46
|
* - HARPOON_RETRY_DELAY: Retry delay in milliseconds (default: 1000)
|
|
47
|
-
* - HARPOON_DOCKER_STARTUP_TIMEOUT: Docker startup timeout in milliseconds (default: 60000)
|
|
47
|
+
* - HARPOON_DOCKER_STARTUP_TIMEOUT: Legacy Docker startup timeout in milliseconds (default: 60000)
|
|
48
48
|
*/
|
|
49
49
|
export declare const HarpoonConfigLive: Layer.Layer<HarpoonConfig, import("effect/ConfigError").ConfigError, never>;
|
|
50
50
|
/**
|
|
@@ -21,7 +21,7 @@ export class HarpoonConfig extends Context.Tag('@harpoon/HarpoonConfig')() {
|
|
|
21
21
|
* - HARPOON_PARALLEL_LIMIT: Max parallel operations (default: 4)
|
|
22
22
|
* - HARPOON_RETRY_ATTEMPTS: Retry attempts (default: 3)
|
|
23
23
|
* - HARPOON_RETRY_DELAY: Retry delay in milliseconds (default: 1000)
|
|
24
|
-
* - HARPOON_DOCKER_STARTUP_TIMEOUT: Docker startup timeout in milliseconds (default: 60000)
|
|
24
|
+
* - HARPOON_DOCKER_STARTUP_TIMEOUT: Legacy Docker startup timeout in milliseconds (default: 60000)
|
|
25
25
|
*/
|
|
26
26
|
export const HarpoonConfigLive = Layer.effect(HarpoonConfig, Effect.gen(function* () {
|
|
27
27
|
// Load all configuration with defaults
|