@docker-harpoon/core 0.1.3 → 0.1.5
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/__tests__/bindings.test.js +9 -5
- package/dist/__tests__/container.test.js +30 -9
- package/dist/__tests__/database.test.js +0 -14
- package/dist/__tests__/docker-infra.template.d.ts +2 -0
- package/dist/__tests__/docker-infra.template.d.ts.map +1 -0
- package/dist/__tests__/docker-infra.template.js +174 -0
- package/dist/__tests__/test-setup.d.ts +9 -0
- package/dist/__tests__/test-setup.d.ts.map +1 -0
- package/dist/__tests__/test-setup.js +27 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/promise.d.ts +13 -3
- package/dist/api/promise.d.ts.map +1 -1
- package/dist/api/promise.js +33 -18
- package/dist/bindings/index.d.ts +2 -2
- package/dist/bindings/index.d.ts.map +1 -1
- package/dist/bindings/index.js +1 -1
- package/dist/bindings/types.d.ts.map +1 -1
- package/dist/bindings/types.js +1 -3
- package/dist/build-strategies/types.d.ts.map +1 -1
- package/dist/config-patchers/index.d.ts.map +1 -1
- package/dist/config-patchers/types.d.ts.map +1 -1
- package/dist/dockerfile-transformers/core.d.ts.map +1 -1
- package/dist/dockerfile-transformers/core.js +2 -5
- package/dist/dockerfile-transformers/index.d.ts.map +1 -1
- package/dist/dockerfile-transformers/types.d.ts.map +1 -1
- package/dist/errors.d.ts +6 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -3
- package/dist/helpers/database.d.ts.map +1 -1
- package/dist/helpers/database.js +1 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/resources/container.d.ts +25 -2
- package/dist/resources/container.d.ts.map +1 -1
- package/dist/resources/container.js +301 -77
- package/dist/resources/image.d.ts.map +1 -1
- package/dist/resources/image.js +14 -35
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/network.d.ts.map +1 -1
- package/dist/resources/network.js +23 -9
- package/dist/resources/schemas.d.ts +178 -18
- package/dist/resources/schemas.d.ts.map +1 -1
- package/dist/resources/schemas.js +2 -1
- package/dist/services/CircuitBreaker.d.ts +83 -0
- package/dist/services/CircuitBreaker.d.ts.map +1 -0
- package/dist/services/CircuitBreaker.js +164 -0
- package/dist/services/ContainerPool.d.ts +82 -0
- package/dist/services/ContainerPool.d.ts.map +1 -0
- package/dist/services/ContainerPool.js +186 -0
- package/dist/services/DockerBatcher.d.ts +74 -0
- package/dist/services/DockerBatcher.d.ts.map +1 -0
- package/dist/services/DockerBatcher.js +107 -0
- package/dist/services/DockerClient.d.ts +125 -0
- package/dist/services/DockerClient.d.ts.map +1 -0
- package/dist/services/DockerClient.js +220 -0
- package/dist/services/DockerErrors.d.ts +145 -0
- package/dist/services/DockerErrors.d.ts.map +1 -0
- package/dist/services/DockerErrors.js +224 -0
- package/dist/services/DockerRateLimiter.d.ts +80 -0
- package/dist/services/DockerRateLimiter.d.ts.map +1 -0
- package/dist/services/DockerRateLimiter.js +93 -0
- package/dist/services/EventBus.d.ts +126 -0
- package/dist/services/EventBus.d.ts.map +1 -0
- package/dist/services/EventBus.js +111 -0
- package/dist/services/Harpoon.d.ts +151 -0
- package/dist/services/Harpoon.d.ts.map +1 -0
- package/dist/services/Harpoon.js +148 -0
- package/dist/services/HarpoonConfig.d.ts +60 -0
- package/dist/services/HarpoonConfig.d.ts.map +1 -0
- package/dist/services/HarpoonConfig.js +67 -0
- package/dist/services/HarpoonLogger.d.ts +36 -0
- package/dist/services/HarpoonLogger.d.ts.map +1 -0
- package/dist/services/HarpoonLogger.js +94 -0
- package/dist/services/ReadinessCoordinator.d.ts +128 -0
- package/dist/services/ReadinessCoordinator.d.ts.map +1 -0
- package/dist/services/ReadinessCoordinator.js +170 -0
- package/dist/services/ResourceTracker.d.ts +74 -0
- package/dist/services/ResourceTracker.d.ts.map +1 -0
- package/dist/services/ResourceTracker.js +145 -0
- package/dist/services/index.d.ts +29 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +47 -0
- package/dist/testing/helpers.d.ts +114 -0
- package/dist/testing/helpers.d.ts.map +1 -0
- package/dist/testing/helpers.js +140 -0
- package/dist/testing/index.d.ts +29 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +47 -0
- package/dist/testing/mocks.d.ts +66 -0
- package/dist/testing/mocks.d.ts.map +1 -0
- package/dist/testing/mocks.js +224 -0
- package/dist/utils/process.d.ts +24 -0
- package/dist/utils/process.d.ts.map +1 -0
- package/dist/utils/process.js +49 -0
- package/package.json +12 -8
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* - Env var injection into containers
|
|
7
7
|
* - Binding composition
|
|
8
8
|
*/
|
|
9
|
-
import { describe, test, expect, afterEach, setDefaultTimeout } from 'bun:test';
|
|
9
|
+
import { describe, test, expect, afterEach, beforeAll, setDefaultTimeout } from 'bun:test';
|
|
10
10
|
import { Container, database, createEnvBinding, destroyAll } from '../index';
|
|
11
|
+
import { buildTestImage, TEST_IMAGE } from './test-setup';
|
|
11
12
|
setDefaultTimeout(30_000);
|
|
12
13
|
const waitUntil = (fn, timeout = 5000) => {
|
|
13
14
|
return new Promise((resolve) => {
|
|
@@ -20,6 +21,9 @@ const waitUntil = (fn, timeout = 5000) => {
|
|
|
20
21
|
});
|
|
21
22
|
};
|
|
22
23
|
describe('Bindings', () => {
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
await buildTestImage();
|
|
26
|
+
});
|
|
23
27
|
afterEach(async () => {
|
|
24
28
|
await destroyAll();
|
|
25
29
|
});
|
|
@@ -40,7 +44,7 @@ describe('Bindings', () => {
|
|
|
40
44
|
APP_VERSION: '1.0.0',
|
|
41
45
|
});
|
|
42
46
|
const container = await Container('test-binding-env', {
|
|
43
|
-
image:
|
|
47
|
+
image: TEST_IMAGE,
|
|
44
48
|
cmd: ['sleep', '30'],
|
|
45
49
|
bindings: {
|
|
46
50
|
config: configBinding,
|
|
@@ -65,7 +69,7 @@ describe('Bindings', () => {
|
|
|
65
69
|
VAR_D: 'd',
|
|
66
70
|
});
|
|
67
71
|
const container = await Container('test-multi-binding', {
|
|
68
|
-
image:
|
|
72
|
+
image: TEST_IMAGE,
|
|
69
73
|
cmd: ['sleep', '30'],
|
|
70
74
|
bindings: {
|
|
71
75
|
first: binding1,
|
|
@@ -86,7 +90,7 @@ describe('Bindings', () => {
|
|
|
86
90
|
MY_VAR: 'from-binding',
|
|
87
91
|
});
|
|
88
92
|
const container = await Container('test-env-precedence', {
|
|
89
|
-
image:
|
|
93
|
+
image: TEST_IMAGE,
|
|
90
94
|
cmd: ['sleep', '30'],
|
|
91
95
|
bindings: {
|
|
92
96
|
config: binding,
|
|
@@ -109,7 +113,7 @@ describe('Bindings', () => {
|
|
|
109
113
|
REDIS_URL: db.connectionString,
|
|
110
114
|
});
|
|
111
115
|
const container = await Container('test-db-consumer', {
|
|
112
|
-
image:
|
|
116
|
+
image: TEST_IMAGE,
|
|
113
117
|
cmd: ['sleep', '30'],
|
|
114
118
|
bindings: {
|
|
115
119
|
db: dbBinding,
|
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
* - stats retrieval
|
|
9
9
|
* - Lifecycle (stop, destroy)
|
|
10
10
|
*/
|
|
11
|
-
import { describe, test, expect, afterEach, setDefaultTimeout } from 'bun:test';
|
|
11
|
+
import { describe, test, expect, afterEach, beforeAll, setDefaultTimeout } from 'bun:test';
|
|
12
12
|
import { Network, Container, destroyAll } from '../index';
|
|
13
|
+
import { buildTestImage, TEST_IMAGE } from './test-setup';
|
|
13
14
|
setDefaultTimeout(30_000);
|
|
14
15
|
describe('Container', () => {
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
await buildTestImage();
|
|
18
|
+
});
|
|
15
19
|
afterEach(async () => {
|
|
16
20
|
await destroyAll();
|
|
17
21
|
});
|
|
18
22
|
test('creates a container with basic config', async () => {
|
|
19
23
|
const container = await Container('test-container-basic', {
|
|
20
|
-
image:
|
|
24
|
+
image: TEST_IMAGE,
|
|
21
25
|
cmd: ['sleep', '30'],
|
|
22
26
|
});
|
|
23
27
|
expect(container.id).toBeDefined();
|
|
@@ -27,7 +31,7 @@ describe('Container', () => {
|
|
|
27
31
|
test('creates a container attached to a network', async () => {
|
|
28
32
|
const network = await Network('test-container-net');
|
|
29
33
|
const container = await Container('test-container-networked', {
|
|
30
|
-
image:
|
|
34
|
+
image: TEST_IMAGE,
|
|
31
35
|
cmd: ['sleep', '30'],
|
|
32
36
|
networks: [network],
|
|
33
37
|
});
|
|
@@ -39,7 +43,7 @@ describe('Container', () => {
|
|
|
39
43
|
});
|
|
40
44
|
test('exec runs commands inside container', async () => {
|
|
41
45
|
const container = await Container('test-container-exec', {
|
|
42
|
-
image:
|
|
46
|
+
image: TEST_IMAGE,
|
|
43
47
|
cmd: ['sleep', '30'],
|
|
44
48
|
});
|
|
45
49
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -51,7 +55,7 @@ describe('Container', () => {
|
|
|
51
55
|
});
|
|
52
56
|
test('stats returns container resource statistics', async () => {
|
|
53
57
|
const container = await Container('test-container-stats', {
|
|
54
|
-
image:
|
|
58
|
+
image: TEST_IMAGE,
|
|
55
59
|
cmd: ['sleep', '30'],
|
|
56
60
|
});
|
|
57
61
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -66,7 +70,7 @@ describe('Container', () => {
|
|
|
66
70
|
});
|
|
67
71
|
test('waitForLog detects log patterns', async () => {
|
|
68
72
|
const container = await Container('test-container-logs', {
|
|
69
|
-
image:
|
|
73
|
+
image: TEST_IMAGE,
|
|
70
74
|
cmd: ['sh', '-c', 'echo "Server started successfully" && sleep 30'],
|
|
71
75
|
});
|
|
72
76
|
await container.waitForLog('Server started', 5000);
|
|
@@ -75,7 +79,7 @@ describe('Container', () => {
|
|
|
75
79
|
});
|
|
76
80
|
test('stop gracefully shuts down container', async () => {
|
|
77
81
|
const container = await Container('test-container-stop', {
|
|
78
|
-
image:
|
|
82
|
+
image: TEST_IMAGE,
|
|
79
83
|
cmd: ['sh', '-c', 'trap "exit 0" TERM; while true; do sleep 1; done'],
|
|
80
84
|
});
|
|
81
85
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -87,7 +91,7 @@ describe('Container', () => {
|
|
|
87
91
|
});
|
|
88
92
|
test('supports environment variables', async () => {
|
|
89
93
|
const container = await Container('test-container-env', {
|
|
90
|
-
image:
|
|
94
|
+
image: TEST_IMAGE,
|
|
91
95
|
cmd: ['sleep', '30'],
|
|
92
96
|
env: {
|
|
93
97
|
MY_VAR: 'hello',
|
|
@@ -105,11 +109,28 @@ describe('Container', () => {
|
|
|
105
109
|
});
|
|
106
110
|
test('supports port mappings', async () => {
|
|
107
111
|
const container = await Container('test-container-ports', {
|
|
108
|
-
image:
|
|
112
|
+
image: TEST_IMAGE,
|
|
109
113
|
cmd: ['sleep', '30'],
|
|
110
114
|
ports: [{ internal: 8080, external: 18080 }],
|
|
111
115
|
});
|
|
112
116
|
expect(container.id).toBeDefined();
|
|
113
117
|
await container.destroy();
|
|
114
118
|
});
|
|
119
|
+
test('streamLogs terminates without follow option', async () => {
|
|
120
|
+
const container = await Container('test-logs-terminate', {
|
|
121
|
+
image: TEST_IMAGE,
|
|
122
|
+
cmd: ['sh', '-c', 'echo "line1" && echo "line2" && echo "line3" && sleep 30'],
|
|
123
|
+
});
|
|
124
|
+
await container.waitForLog('line3', 5000);
|
|
125
|
+
const lines = [];
|
|
126
|
+
const logStream = await container.streamLogs({ tail: 3 });
|
|
127
|
+
for await (const line of logStream) {
|
|
128
|
+
lines.push(line.message);
|
|
129
|
+
}
|
|
130
|
+
// Loop should exit naturally
|
|
131
|
+
expect(lines).toContain('line1');
|
|
132
|
+
expect(lines).toContain('line2');
|
|
133
|
+
expect(lines).toContain('line3');
|
|
134
|
+
await container.destroy();
|
|
135
|
+
});
|
|
115
136
|
});
|
|
@@ -50,20 +50,6 @@ describe('database', () => {
|
|
|
50
50
|
expect(db.connectionString).toBe('redis://:secret@localhost:16380');
|
|
51
51
|
await db.destroy();
|
|
52
52
|
});
|
|
53
|
-
test('creates a mysql database with connection string', async () => {
|
|
54
|
-
const db = await database('test-mysql', {
|
|
55
|
-
image: 'mysql:8',
|
|
56
|
-
env: {
|
|
57
|
-
MYSQL_ROOT_PASSWORD: 'rootpass',
|
|
58
|
-
MYSQL_DATABASE: 'mydb',
|
|
59
|
-
},
|
|
60
|
-
hostPort: 13306,
|
|
61
|
-
});
|
|
62
|
-
expect(db.port).toBe(3306);
|
|
63
|
-
expect(db.hostPort).toBe(13306);
|
|
64
|
-
expect(db.connectionString).toBe('mysql://root:rootpass@localhost:13306/mydb');
|
|
65
|
-
await db.destroy();
|
|
66
|
-
});
|
|
67
53
|
test('attaches database to network', async () => {
|
|
68
54
|
const network = await Network('test-db-net');
|
|
69
55
|
const db = await database('test-db-networked', {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-infra.template.d.ts","sourceRoot":"","sources":["../../src/__tests__/docker-infra.template.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Infrastructure Test Template
|
|
3
|
+
*
|
|
4
|
+
* Copy and customize this file to test your project's Dockerfile and entrypoint.
|
|
5
|
+
* Replace {{PROJECT_NAME}} and {{PROJECT_IMAGE}} with your values.
|
|
6
|
+
*
|
|
7
|
+
* This template demonstrates Harpoon's complete API for testing Docker infrastructure:
|
|
8
|
+
* - Image building with buildArgs
|
|
9
|
+
* - Container creation with volumes, capabilities, and user overrides
|
|
10
|
+
* - Log waiting and streaming
|
|
11
|
+
* - Command execution inside containers
|
|
12
|
+
* - Network isolation testing
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```bash
|
|
16
|
+
* # Copy and customize for your project
|
|
17
|
+
* cp docker-infra.template.ts my-project.test.ts
|
|
18
|
+
* # Edit PROJECT config and tests
|
|
19
|
+
* bun test my-project.test.ts
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { describe, test, expect, beforeAll, afterEach, setDefaultTimeout } from 'bun:test';
|
|
23
|
+
import { Image, Container, Network, destroyAll } from '@docker-harpoon/core';
|
|
24
|
+
// ============ PROJECT CONFIGURATION ============
|
|
25
|
+
// Customize these values for your project
|
|
26
|
+
const PROJECT = {
|
|
27
|
+
name: '{{PROJECT_NAME}}',
|
|
28
|
+
image: '{{PROJECT_IMAGE}}:latest',
|
|
29
|
+
context: './',
|
|
30
|
+
dockerfile: 'Dockerfile',
|
|
31
|
+
port: 3000,
|
|
32
|
+
healthPath: '/api/status',
|
|
33
|
+
};
|
|
34
|
+
// Set a generous timeout for Docker operations
|
|
35
|
+
setDefaultTimeout(120_000);
|
|
36
|
+
describe(`${PROJECT.name} Docker Infrastructure`, () => {
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
// Build the project image
|
|
39
|
+
await Image(PROJECT.image, {
|
|
40
|
+
context: PROJECT.context,
|
|
41
|
+
dockerfile: PROJECT.dockerfile,
|
|
42
|
+
buildArgs: { SEED_DATABASE: 'true' },
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
afterEach(async () => {
|
|
46
|
+
await destroyAll();
|
|
47
|
+
});
|
|
48
|
+
// ============ HEALTH CHECK TEST ============
|
|
49
|
+
test('health check responds 200', async () => {
|
|
50
|
+
const container = await Container('test-health', {
|
|
51
|
+
image: PROJECT.image,
|
|
52
|
+
env: { PORT: String(PROJECT.port) },
|
|
53
|
+
ports: [{ internal: PROJECT.port, external: 13000 }],
|
|
54
|
+
});
|
|
55
|
+
await container.waitForLog(/listening|started|ready/i, 30000);
|
|
56
|
+
const res = await fetch(`http://localhost:13000${PROJECT.healthPath}`);
|
|
57
|
+
expect(res.status).toBe(200);
|
|
58
|
+
});
|
|
59
|
+
// ============ VOLUME MOUNT TEST ============
|
|
60
|
+
test('db snapshot copy-on-boot pattern', async () => {
|
|
61
|
+
const container = await Container('test-snapshot', {
|
|
62
|
+
image: PROJECT.image,
|
|
63
|
+
env: {
|
|
64
|
+
DB_SOURCE: '/data/snapshot/app.db',
|
|
65
|
+
DB_PATH: '/app/data/test.db',
|
|
66
|
+
},
|
|
67
|
+
volumes: [
|
|
68
|
+
{ hostPath: '/tmp/test-snapshot.db', containerPath: '/data/snapshot/app.db', readonly: true },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
await container.waitForLog(/snapshot copied|database ready/i, 30000);
|
|
72
|
+
});
|
|
73
|
+
// ============ FILE SYSTEM TEST ============
|
|
74
|
+
test('file upload directory setup', async () => {
|
|
75
|
+
const container = await Container('test-uploads', {
|
|
76
|
+
image: PROJECT.image,
|
|
77
|
+
env: { FILE_UPLOAD_PATH: '/app/custom-uploads' },
|
|
78
|
+
});
|
|
79
|
+
await container.waitForLog(/upload directory|ensuring/i, 10000);
|
|
80
|
+
const result = await container.exec(['ls', '-la', '/app/custom-uploads']);
|
|
81
|
+
expect(result.exitCode).toBe(0);
|
|
82
|
+
expect(result.stdout).toContain('drwx'); // Directory exists with permissions
|
|
83
|
+
});
|
|
84
|
+
// ============ NETWORK ISOLATION TEST ============
|
|
85
|
+
test('network isolation with iptables', async () => {
|
|
86
|
+
const network = await Network('isolated-net');
|
|
87
|
+
const container = await Container('test-isolation', {
|
|
88
|
+
image: PROJECT.image,
|
|
89
|
+
networks: [network],
|
|
90
|
+
env: { ENABLE_NETWORK_ISOLATION: 'true' },
|
|
91
|
+
capAdd: ['NET_ADMIN'],
|
|
92
|
+
});
|
|
93
|
+
await container.waitForLog(/network isolation configured|iptables/i, 15000);
|
|
94
|
+
});
|
|
95
|
+
// ============ PRIVILEGE DROPPING TEST ============
|
|
96
|
+
test('privilege dropping', async () => {
|
|
97
|
+
const container = await Container('test-privileges', {
|
|
98
|
+
image: PROJECT.image,
|
|
99
|
+
});
|
|
100
|
+
await container.waitForLog(/dropping privileges|started/i, 15000);
|
|
101
|
+
const result = await container.exec(['whoami']);
|
|
102
|
+
expect(result.exitCode).toBe(0);
|
|
103
|
+
expect(result.stdout.trim()).not.toBe('root');
|
|
104
|
+
});
|
|
105
|
+
// ============ ENVIRONMENT VARIABLE TEST ============
|
|
106
|
+
test('environment variable propagation', async () => {
|
|
107
|
+
const container = await Container('test-env', {
|
|
108
|
+
image: PROJECT.image,
|
|
109
|
+
env: {
|
|
110
|
+
PORT: '8080',
|
|
111
|
+
LOG_LEVEL: 'debug',
|
|
112
|
+
CUSTOM_VAR: 'test-value',
|
|
113
|
+
},
|
|
114
|
+
ports: [{ internal: 8080, external: 18080 }],
|
|
115
|
+
});
|
|
116
|
+
await container.waitForLog(/listening|started/i, 30000);
|
|
117
|
+
// Verify env vars are set
|
|
118
|
+
const envResult = await container.exec(['printenv']);
|
|
119
|
+
expect(envResult.stdout).toContain('PORT=8080');
|
|
120
|
+
expect(envResult.stdout).toContain('LOG_LEVEL=debug');
|
|
121
|
+
expect(envResult.stdout).toContain('CUSTOM_VAR=test-value');
|
|
122
|
+
});
|
|
123
|
+
// ============ USER OVERRIDE TEST ============
|
|
124
|
+
test('user override', async () => {
|
|
125
|
+
const container = await Container('test-user', {
|
|
126
|
+
image: PROJECT.image,
|
|
127
|
+
user: '1000:1000',
|
|
128
|
+
});
|
|
129
|
+
await container.waitForLog(/started|ready/i, 15000);
|
|
130
|
+
const result = await container.exec(['id']);
|
|
131
|
+
expect(result.exitCode).toBe(0);
|
|
132
|
+
expect(result.stdout).toContain('uid=1000');
|
|
133
|
+
expect(result.stdout).toContain('gid=1000');
|
|
134
|
+
});
|
|
135
|
+
// ============ CAPABILITIES TEST ============
|
|
136
|
+
test('dropped capabilities', async () => {
|
|
137
|
+
const container = await Container('test-caps', {
|
|
138
|
+
image: PROJECT.image,
|
|
139
|
+
capDrop: ['ALL'],
|
|
140
|
+
capAdd: ['NET_BIND_SERVICE'],
|
|
141
|
+
});
|
|
142
|
+
await container.waitForLog(/started|ready/i, 15000);
|
|
143
|
+
// Container should be running with minimal capabilities
|
|
144
|
+
const result = await container.exec(['cat', '/proc/1/status']);
|
|
145
|
+
expect(result.exitCode).toBe(0);
|
|
146
|
+
// CapEff should show limited capabilities
|
|
147
|
+
expect(result.stdout).toContain('CapEff');
|
|
148
|
+
});
|
|
149
|
+
// ============ GRACEFUL SHUTDOWN TEST ============
|
|
150
|
+
test('graceful shutdown', async () => {
|
|
151
|
+
const container = await Container('test-shutdown', {
|
|
152
|
+
image: PROJECT.image,
|
|
153
|
+
env: { PORT: String(PROJECT.port) },
|
|
154
|
+
});
|
|
155
|
+
await container.waitForLog(/listening|started|ready/i, 30000);
|
|
156
|
+
const metadata = await container.stop('SIGTERM', 10000);
|
|
157
|
+
expect(metadata.graceful).toBe(true);
|
|
158
|
+
expect(metadata.signal).toBe('SIGTERM');
|
|
159
|
+
});
|
|
160
|
+
// ============ RESOURCE STATS TEST ============
|
|
161
|
+
test('container stats', async () => {
|
|
162
|
+
const container = await Container('test-stats', {
|
|
163
|
+
image: PROJECT.image,
|
|
164
|
+
env: { PORT: String(PROJECT.port) },
|
|
165
|
+
});
|
|
166
|
+
await container.waitForLog(/listening|started|ready/i, 30000);
|
|
167
|
+
const stats = await container.stats();
|
|
168
|
+
expect(stats.cpu).toBeDefined();
|
|
169
|
+
expect(stats.cpu.percent).toBeGreaterThanOrEqual(0);
|
|
170
|
+
expect(stats.memory).toBeDefined();
|
|
171
|
+
expect(stats.memory.usage).toBeGreaterThan(0);
|
|
172
|
+
expect(stats.network).toBeDefined();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test setup for integration tests.
|
|
3
|
+
*
|
|
4
|
+
* Builds a minimal test image that can be used across all tests,
|
|
5
|
+
* ensuring tests are self-contained and don't rely on external images.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TEST_IMAGE = "harpoon-test:latest";
|
|
8
|
+
export declare function buildTestImage(): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=test-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/test-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,eAAO,MAAM,UAAU,wBAAwB,CAAC;AAWhD,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAapD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test setup for integration tests.
|
|
3
|
+
*
|
|
4
|
+
* Builds a minimal test image that can be used across all tests,
|
|
5
|
+
* ensuring tests are self-contained and don't rely on external images.
|
|
6
|
+
*/
|
|
7
|
+
import { Image } from '../index';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
export const TEST_IMAGE = 'harpoon-test:latest';
|
|
10
|
+
let imageBuilt = false;
|
|
11
|
+
// Get the source directory path (works in both ESM and CJS)
|
|
12
|
+
const getFixturesDir = () => {
|
|
13
|
+
// Try to resolve from source directory first
|
|
14
|
+
const srcDir = join(process.cwd(), 'src', '__tests__', 'fixtures');
|
|
15
|
+
return srcDir;
|
|
16
|
+
};
|
|
17
|
+
export async function buildTestImage() {
|
|
18
|
+
if (imageBuilt) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const fixturesDir = getFixturesDir();
|
|
22
|
+
await Image(TEST_IMAGE, {
|
|
23
|
+
context: fixturesDir,
|
|
24
|
+
dockerfile: 'Dockerfile',
|
|
25
|
+
});
|
|
26
|
+
imageBuilt = true;
|
|
27
|
+
}
|
package/dist/api/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the Promise-based API for cleaner imports.
|
|
5
5
|
*/
|
|
6
|
-
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, } from './promise';
|
|
6
|
+
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 './promise';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,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,WAAW,CAAC"}
|
package/dist/api/promise.d.ts
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* This module wraps the Effect-based resources with a simple Promise API.
|
|
5
5
|
* Effect is completely hidden from users - they just use async/await.
|
|
6
6
|
*
|
|
7
|
-
* Inspired by
|
|
7
|
+
* Inspired by Harpoon and Pulumi's IAC patterns.
|
|
8
8
|
*/
|
|
9
9
|
import Docker from 'dockerode';
|
|
10
|
-
import { type ContainerStats, type ExecOptions, type ExecResult, type LogOptions, type LogLine } from '../resources';
|
|
11
|
-
export type { ContainerStats, ExecOptions, ExecResult, LogOptions, LogLine, } from '../resources';
|
|
10
|
+
import { type ContainerStats, type ExecOptions, type ExecResult, type LogOptions, type LogLine, type VolumeMapping } from '../resources';
|
|
11
|
+
export type { ContainerStats, ExecOptions, ExecResult, LogOptions, LogLine, VolumeMapping, } from '../resources';
|
|
12
12
|
import type { Binding } from '../bindings/types';
|
|
13
13
|
export interface NetworkConfig {
|
|
14
14
|
/** Network driver (default: 'bridge') */
|
|
@@ -41,6 +41,16 @@ export interface ContainerConfig {
|
|
|
41
41
|
cmd?: string[];
|
|
42
42
|
/** Bindings that provide env vars and lifecycle hooks */
|
|
43
43
|
bindings?: Record<string, Binding>;
|
|
44
|
+
/** Volume mounts (host:container) */
|
|
45
|
+
volumes?: VolumeMapping[];
|
|
46
|
+
/** Capabilities to add (e.g., 'NET_ADMIN', 'SYS_PTRACE') */
|
|
47
|
+
capAdd?: string[];
|
|
48
|
+
/** Capabilities to drop (e.g., 'ALL') */
|
|
49
|
+
capDrop?: string[];
|
|
50
|
+
/** Run in privileged mode */
|
|
51
|
+
privileged?: boolean;
|
|
52
|
+
/** User/group to run as (e.g., '1000:1000') */
|
|
53
|
+
user?: string;
|
|
44
54
|
}
|
|
45
55
|
export interface ContainerResource {
|
|
46
56
|
readonly id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,MAAM,MAAM,WAAW,CAAC;AAG/B,OAAO,EASL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,MAAM,MAAM,WAAW,CAAC;AAG/B,OAAO,EASL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,UAAU,EACV,OAAO,EACP,aAAa,GACd,MAAM,cAAc,CAAC;AAOtB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,eAAe,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,qCAAqC;IACrC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,gCAAgC;IAChC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrE,oCAAoC;IACpC,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,+DAA+D;IAC/D,KAAK,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IACjC,6CAA6C;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7E,iDAAiD;IACjD,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,4BAA4B;IAC5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,KAAK,CAAC,eAAe,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,0CAA0C;IAC1C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAgJD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AA4CD;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CA6BhG;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkEjG;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgE9F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAWpF;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAShD"}
|
package/dist/api/promise.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This module wraps the Effect-based resources with a simple Promise API.
|
|
5
5
|
* Effect is completely hidden from users - they just use async/await.
|
|
6
6
|
*
|
|
7
|
-
* Inspired by
|
|
7
|
+
* Inspired by Harpoon and Pulumi's IAC patterns.
|
|
8
8
|
*/
|
|
9
9
|
import { Effect, Scope, Exit } from 'effect';
|
|
10
10
|
import { homedir, platform } from 'os';
|
|
@@ -23,10 +23,7 @@ let dockerClient = null;
|
|
|
23
23
|
* Detect Docker socket path for the current platform.
|
|
24
24
|
*/
|
|
25
25
|
function detectDockerSocket() {
|
|
26
|
-
const candidates = [
|
|
27
|
-
'/var/run/docker.sock',
|
|
28
|
-
`${homedir()}/.docker/run/docker.sock`,
|
|
29
|
-
];
|
|
26
|
+
const candidates = ['/var/run/docker.sock', `${homedir()}/.docker/run/docker.sock`];
|
|
30
27
|
for (const path of candidates) {
|
|
31
28
|
if (existsSync(path)) {
|
|
32
29
|
return path;
|
|
@@ -45,16 +42,29 @@ function getDocker() {
|
|
|
45
42
|
}
|
|
46
43
|
return dockerClient;
|
|
47
44
|
}
|
|
48
|
-
// ============ Docker Auto-Start (
|
|
45
|
+
// ============ Docker Auto-Start (Harpoon Style) ============
|
|
49
46
|
const DOCKER_STARTUP_TIMEOUT_MS = 60_000;
|
|
50
47
|
let dockerEnsured = false;
|
|
48
|
+
/**
|
|
49
|
+
* Simple logging helper for promise-based API.
|
|
50
|
+
*/
|
|
51
|
+
const harpoonLog = {
|
|
52
|
+
debug: (message) => {
|
|
53
|
+
if (process.env.HARPOON_LOG_LEVEL === 'debug') {
|
|
54
|
+
console.log(`[Harpoon] ${message}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
info: (message) => console.log(`[Harpoon] ${message}`),
|
|
58
|
+
warn: (message) => console.warn(`[Harpoon] ${message}`),
|
|
59
|
+
error: (message, err) => console.error(`[Harpoon] ${message}`, err ?? ''),
|
|
60
|
+
};
|
|
51
61
|
/**
|
|
52
62
|
* Start Docker Desktop on macOS/Windows if not running.
|
|
53
63
|
*/
|
|
54
64
|
function startDockerDesktop() {
|
|
55
65
|
const os = platform();
|
|
56
66
|
if (os === 'darwin') {
|
|
57
|
-
|
|
67
|
+
harpoonLog.info('Starting Docker Desktop...');
|
|
58
68
|
const child = spawn('open', ['-a', 'Docker', '--background'], {
|
|
59
69
|
detached: true,
|
|
60
70
|
stdio: 'ignore',
|
|
@@ -62,7 +72,7 @@ function startDockerDesktop() {
|
|
|
62
72
|
child.unref();
|
|
63
73
|
}
|
|
64
74
|
else if (os === 'win32') {
|
|
65
|
-
|
|
75
|
+
harpoonLog.info('Starting Docker Desktop...');
|
|
66
76
|
const child = spawn('cmd', ['/c', 'start', '', 'Docker Desktop'], {
|
|
67
77
|
detached: true,
|
|
68
78
|
stdio: 'ignore',
|
|
@@ -82,20 +92,20 @@ async function waitForDockerReady(socketPath, timeoutMs) {
|
|
|
82
92
|
if (existsSync(socketPath)) {
|
|
83
93
|
const docker = new Docker({ socketPath });
|
|
84
94
|
await docker.ping();
|
|
85
|
-
|
|
95
|
+
harpoonLog.info('Docker is ready');
|
|
86
96
|
return;
|
|
87
97
|
}
|
|
88
98
|
}
|
|
89
99
|
catch (e) {
|
|
90
100
|
lastError = e;
|
|
91
101
|
}
|
|
92
|
-
await new Promise(r => setTimeout(r, 500));
|
|
102
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
93
103
|
}
|
|
94
|
-
throw new Error(`
|
|
104
|
+
throw new Error(`Docker not ready after ${timeoutMs}ms: ${lastError?.message}`);
|
|
95
105
|
}
|
|
96
106
|
/**
|
|
97
107
|
* Ensure Docker is running, auto-starting if needed.
|
|
98
|
-
* This is the
|
|
108
|
+
* This is the Harpoon-style declarative approach.
|
|
99
109
|
*/
|
|
100
110
|
async function ensureDockerRunning() {
|
|
101
111
|
if (dockerEnsured)
|
|
@@ -111,12 +121,12 @@ async function ensureDockerRunning() {
|
|
|
111
121
|
}
|
|
112
122
|
catch {
|
|
113
123
|
// Socket exists but daemon not responding, wait for it
|
|
114
|
-
|
|
124
|
+
harpoonLog.debug('Docker socket exists but daemon not responding, waiting...');
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
else {
|
|
118
128
|
// Socket doesn't exist, start Docker
|
|
119
|
-
|
|
129
|
+
harpoonLog.info('Docker not running, attempting auto-start...');
|
|
120
130
|
startDockerDesktop();
|
|
121
131
|
}
|
|
122
132
|
// Wait for Docker to be ready
|
|
@@ -159,7 +169,7 @@ function registerCleanup() {
|
|
|
159
169
|
await resource.cleanup();
|
|
160
170
|
}
|
|
161
171
|
catch (e) {
|
|
162
|
-
|
|
172
|
+
harpoonLog.error(`Error cleaning up ${id}`, e);
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
175
|
resources.clear();
|
|
@@ -229,7 +239,7 @@ export async function Container(name, config) {
|
|
|
229
239
|
const docker = await getDockerAsync();
|
|
230
240
|
const scope = Effect.runSync(Scope.make());
|
|
231
241
|
// Convert NetworkResource to { name: string }
|
|
232
|
-
const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
|
|
242
|
+
const networks = config.networks?.map((n) => ('destroy' in n ? { name: n.name } : n));
|
|
233
243
|
const effectConfig = {
|
|
234
244
|
image: config.image,
|
|
235
245
|
...(networks !== undefined && { networks }),
|
|
@@ -237,6 +247,11 @@ export async function Container(name, config) {
|
|
|
237
247
|
...(config.env !== undefined && { env: config.env }),
|
|
238
248
|
...(config.cmd !== undefined && { cmd: config.cmd }),
|
|
239
249
|
...(config.bindings !== undefined && { bindings: config.bindings }),
|
|
250
|
+
...(config.volumes !== undefined && { volumes: config.volumes }),
|
|
251
|
+
...(config.capAdd !== undefined && { capAdd: config.capAdd }),
|
|
252
|
+
...(config.capDrop !== undefined && { capDrop: config.capDrop }),
|
|
253
|
+
...(config.privileged !== undefined && { privileged: config.privileged }),
|
|
254
|
+
...(config.user !== undefined && { user: config.user }),
|
|
240
255
|
};
|
|
241
256
|
const effectResource = await Effect.runPromise(EffectContainer(name, effectConfig, docker).pipe(Scope.extend(scope)));
|
|
242
257
|
const resourceId = `container:${effectResource.id}`;
|
|
@@ -298,7 +313,7 @@ export async function database(name, config) {
|
|
|
298
313
|
const docker = await getDockerAsync();
|
|
299
314
|
const scope = Effect.runSync(Scope.make());
|
|
300
315
|
// Convert NetworkResource to { name: string }
|
|
301
|
-
const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
|
|
316
|
+
const networks = config.networks?.map((n) => ('destroy' in n ? { name: n.name } : n));
|
|
302
317
|
const effectConfig = {
|
|
303
318
|
image: config.image,
|
|
304
319
|
...(config.port !== undefined && { port: config.port }),
|
|
@@ -380,7 +395,7 @@ export async function destroyAll() {
|
|
|
380
395
|
await resource.cleanup();
|
|
381
396
|
}
|
|
382
397
|
catch (e) {
|
|
383
|
-
|
|
398
|
+
harpoonLog.error(`Error cleaning up ${id}`, e);
|
|
384
399
|
}
|
|
385
400
|
}
|
|
386
401
|
resources.clear();
|
package/dist/bindings/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Exports binding types and utilities for creating resource bindings.
|
|
5
5
|
*/
|
|
6
|
-
export type { Binding, BuildBinding, BindingsEnv, FlatBindingsEnv
|
|
7
|
-
export { isBuildBinding, mergeBindingsEnv, createEnvBinding
|
|
6
|
+
export type { Binding, BuildBinding, BindingsEnv, FlatBindingsEnv } from './types';
|
|
7
|
+
export { isBuildBinding, mergeBindingsEnv, createEnvBinding } from './types';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEnF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/bindings/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bindings/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAElF;;;;;GAKG;AACH,MAAM,WAAW,OAAO,CACtB,SAAS,GAAG,OAAO,EACnB,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAE5D,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAE9B;;;OAGG;IACH,MAAM,IAAI,IAAI,CAAC;IAEf;;;OAGG;IACH,QAAQ,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAE7E;;;OAGG;IACH,OAAO,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAExE;;;OAGG;IACH,MAAM,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CACxE;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAC3B,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAC5D,SAAQ,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;IAChC;;;OAGG;IACH,mBAAmB,CAAC,CAClB,YAAY,EAAE,SAAS,qBAAqB,EAAE,EAC9C,GAAG,EAAE,gBAAgB,GACpB,SAAS,qBAAqB,EAAE,CAAC;IAEpC;;OAEG;IACH,WAAW,CAAC,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAEnE;;;OAGG;IACH,mBAAmB,CAAC,CAClB,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,YAAY,GAChB,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI;KAC3F,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;CACnE,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,IAC5F,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3D;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,YAAY,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bindings/types.ts"],"names":[],"mappings":"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"}
|