hammerkit 1.4.0 → 1.5.0-alpha.1
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/.devcontainer/Dockerfile +5 -0
- package/.devcontainer/devcontainer.json +40 -0
- package/README.md +13 -86
- package/SUMMARY.md +43 -0
- package/build.schema.json +56 -2
- package/dist/cli.js +97 -0
- package/dist/cli.spec.js +5 -0
- package/dist/console/console-context-mock.js +11 -0
- package/dist/console/get-logger.js +23 -0
- package/dist/docker/pull.js +5 -5
- package/dist/docker/remove-container.js +21 -0
- package/dist/docker/stream.js +14 -10
- package/dist/docker/using-container.js +28 -0
- package/dist/environment/get-process-env.js +4 -4
- package/dist/environment/replace-env-variables.js +2 -3
- package/dist/executer/abort.js +13 -0
- package/dist/executer/check-readiness.js +25 -0
- package/dist/executer/create-scheduler-state.js +32 -0
- package/dist/executer/docker-node.js +123 -0
- package/dist/executer/docker-service.js +113 -0
- package/dist/executer/environment-mock.js +17 -0
- package/dist/executer/event-cache.js +165 -0
- package/dist/executer/execute-command.js +42 -0
- package/dist/executer/execute-docker.js +59 -195
- package/dist/executer/execute.spec.js +37 -37
- package/dist/executer/execution-steps.js +57 -0
- package/dist/executer/get-docker-executor.js +6 -100
- package/dist/executer/hierarchy-scheduler.js +94 -0
- package/dist/executer/kubernetes-service.js +64 -0
- package/dist/executer/label-values.js +65 -0
- package/dist/executer/local-node.js +57 -0
- package/dist/executer/print-container-options.js +17 -0
- package/dist/{logging/log-strategy.js → executer/process-item.js} +1 -1
- package/dist/executer/process-listener.js +3 -0
- package/dist/executer/process-manager.js +111 -0
- package/dist/executer/process-manager.spec.js +91 -0
- package/dist/executer/{executor.js → process.js} +1 -1
- package/dist/executer/{execute-result.js → readonly-state.js} +1 -1
- package/dist/executer/schedule.js +139 -0
- package/dist/executer/scheduler/check-for-loop.js +21 -0
- package/dist/executer/scheduler/enqueue-next.js +27 -0
- package/dist/{testing/test-context.js → executer/scheduler/node-state.js} +1 -1
- package/dist/executer/scheduler/scheduler-result.js +3 -0
- package/dist/executer/scheduler/scheduler-state.js +3 -0
- package/dist/executer/scheduler/service-state.js +3 -0
- package/dist/executer/service-dns.js +6 -0
- package/dist/executer/set-user-permission.js +16 -0
- package/dist/executer/start-node.js +59 -0
- package/dist/executer/state-listener.js +3 -0
- package/dist/executer/state.js +49 -0
- package/dist/executer/states.js +4 -159
- package/dist/executer/watch-node.js +57 -0
- package/dist/executer/watch-service.js +54 -0
- package/dist/executer/work-scope.js +10 -0
- package/dist/file/get-file-context-mock.spec.js +5 -5
- package/dist/file/get-file-context.js +41 -18
- package/dist/file/move-files.js +1 -1
- package/dist/get-work-context.js +29 -0
- package/dist/index.js +8 -4
- package/dist/log.js +197 -50
- package/dist/logging/grouped-logger.js +31 -19
- package/dist/logging/interactive-logger.js +20 -23
- package/dist/logging/live-logger.js +7 -17
- package/dist/optimizer/calculate-checksum.js +1 -1
- package/dist/optimizer/get-cache-directory.js +8 -13
- package/dist/optimizer/get-work-node-cache-stats.js +68 -21
- package/dist/optimizer/read-work-node-cache.js +2 -2
- package/dist/optimizer/work-node-cache-description.js +12 -11
- package/dist/optimizer/write-work-node-cache.js +9 -8
- package/dist/{executer/execution-context.js → parser/build-file-service.js} +1 -1
- package/dist/{optimizer → parser}/cache-method.js +0 -0
- package/dist/parser/get-build-file.js +1 -1
- package/dist/parser/parse-boolean.js +29 -0
- package/dist/parser/parse-boolean.spec.js +48 -0
- package/dist/parser/parse-build-file-references.js +2 -2
- package/dist/parser/parse-build-file-services.js +58 -0
- package/dist/parser/parse-build-file-task-command.js +6 -5
- package/dist/parser/parse-build-file-task-source.js +5 -5
- package/dist/parser/parse-build-file.js +39 -15
- package/dist/parser/parse-context.js +13 -0
- package/dist/parser/parse-envs.js +4 -3
- package/dist/parser/parse-healthcheck.js +28 -0
- package/dist/parser/parse-label-arguments.js +23 -0
- package/dist/parser/parse-service-selector.js +39 -0
- package/dist/parser/parse-string-array.js +6 -5
- package/dist/parser/parse-string-map.js +30 -0
- package/dist/parser/parse-string.js +20 -0
- package/dist/parser/read-build-file.js +16 -5
- package/dist/parser/read-env-file.js +3 -3
- package/dist/planner/utils/get-container-mounts.js +24 -0
- package/dist/planner/utils/normalize-path.js +17 -0
- package/dist/planner/utils/parse-work-node-mount.js +3 -14
- package/dist/planner/utils/parse-work-node-mount.spec.js +10 -10
- package/dist/planner/utils/parse-work-service-volume.js +21 -0
- package/dist/planner/utils/plan-work-command.js +1 -1
- package/dist/planner/utils/plan-work-dependency.js +14 -8
- package/dist/planner/utils/plan-work-node.js +203 -128
- package/dist/planner/utils/plan-work-nodes.js +59 -14
- package/dist/planner/utils/plan-work-tree.js +8 -4
- package/dist/planner/utils/plan-work-volume.js +29 -0
- package/dist/planner/validate.js +56 -8
- package/dist/planner/work-context.js +20 -0
- package/dist/planner/work-mount.js +3 -0
- package/dist/planner/work-node-id.js +4 -4
- package/dist/planner/work-node-id.spec.js +11 -8
- package/dist/planner/work-node-path.js +0 -1
- package/dist/planner/work-node-status.js +121 -18
- package/dist/planner/work-node.js +3 -3
- package/dist/planner/work-service-id.js +13 -0
- package/dist/planner/work-service-volume.js +3 -0
- package/dist/planner/work-service.js +6 -0
- package/dist/planner/work-services.js +3 -0
- package/dist/planner/work-volume.js +3 -0
- package/dist/program.js +155 -90
- package/dist/program.spec.js +20 -0
- package/dist/start-watch-processes.js +28 -0
- package/dist/testing/cli.js +73 -0
- package/dist/testing/create-build-file.js +1 -1
- package/dist/testing/example-test-suite.js +54 -0
- package/dist/testing/expect.js +35 -18
- package/dist/testing/get-logger.js +23 -0
- package/dist/testing/get-node.js +2 -2
- package/dist/testing/get-test-context.js +5 -3
- package/dist/testing/get-test-suite.js +2 -50
- package/dist/testing/integration/cache.spec.js +25 -28
- package/dist/testing/integration/cancellation.spec.js +19 -21
- package/dist/testing/integration/clean.spec.js +25 -28
- package/dist/testing/integration/cmd.spec.js +5 -8
- package/dist/testing/integration/concurrency.spec.js +8 -17
- package/dist/testing/integration/docker.spec.js +6 -9
- package/dist/testing/integration/env.spec.js +27 -34
- package/dist/testing/integration/error.spec.js +5 -9
- package/dist/testing/integration/extend.spec.js +32 -0
- package/dist/testing/integration/glob.spec.js +35 -28
- package/dist/testing/integration/include.spec.js +17 -16
- package/dist/testing/integration/invalid.spec.js +3 -2
- package/dist/testing/integration/invalid_loop.spec.js +13 -0
- package/dist/testing/integration/kubernetes.spec.js +15 -0
- package/dist/testing/integration/labels.spec.js +47 -0
- package/dist/testing/integration/local.spec.js +4 -7
- package/dist/testing/integration/monorepo.spec.js +5 -9
- package/dist/testing/integration/multi-line.spec.js +4 -4
- package/dist/testing/integration/program.spec.js +3 -10
- package/dist/testing/integration/reference.spec.js +10 -13
- package/dist/testing/integration/services.spec.js +14 -0
- package/dist/testing/integration/store.spec.js +42 -38
- package/dist/testing/integration/unknown.spec.js +4 -4
- package/dist/testing/integration/validate.spec.js +4 -4
- package/dist/testing/integration/watch.spec.js +34 -33
- package/dist/testing/lazy.js +17 -0
- package/dist/testing/start-watch-processes.js +29 -0
- package/dist/testing/test-suite-setup.js +3 -0
- package/dist/utils/abort-event.js +14 -1
- package/dist/utils/abortable-function.js +71 -0
- package/dist/utils/debouncer.spec.js +8 -8
- package/dist/utils/environment-config.js +13 -0
- package/dist/utils/fail-never.js +1 -1
- package/dist/utils/sleep.spec.js +3 -3
- package/package.json +24 -21
- package/dist/console/get-console-context-mock.js +0 -33
- package/dist/executer/clean.js +0 -17
- package/dist/executer/execute-local.js +0 -59
- package/dist/executer/execute.js +0 -139
- package/dist/executer/execution-context-event.js +0 -3
- package/dist/executer/execution-context-mock.js +0 -3
- package/dist/executer/executor-mock.js +0 -61
- package/dist/executer/get-environment-mock.js +0 -16
- package/dist/executer/get-execution-context-mock.js +0 -18
- package/dist/executer/get-local-executor.js +0 -56
- package/dist/executer/restore.js +0 -19
- package/dist/executer/store.js +0 -19
- package/dist/logging/get-logger.js +0 -23
- package/dist/logging/message-to-console.js +0 -16
- package/dist/optimizer/optimize.js +0 -32
- package/dist/optimizer/work-node-description.js +0 -12
- package/dist/planner/utils/find-build-task.js +0 -18
- package/dist/testing/create-work-tree.js +0 -11
|
@@ -3,87 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.execCommand = exports.convertToPosixPath = exports.startContainer = exports.getContainerCli = void 0;
|
|
7
7
|
const stream_1 = require("../docker/stream");
|
|
8
8
|
const dockerode_1 = __importDefault(require("dockerode"));
|
|
9
|
-
const pull_1 = require("../docker/pull");
|
|
10
9
|
const path_1 = require("path");
|
|
11
10
|
const os_1 = require("os");
|
|
12
|
-
const template_value_1 = require("../planner/utils/template-value");
|
|
13
|
-
const crypto_1 = require("crypto");
|
|
14
|
-
const get_docker_executor_1 = require("./get-docker-executor");
|
|
15
11
|
const abort_event_1 = require("../utils/abort-event");
|
|
16
|
-
function generateId(generate) {
|
|
17
|
-
return crypto_1.createHash('sha1').update(generate).digest('hex');
|
|
18
|
-
}
|
|
19
|
-
exports.generateId = generateId;
|
|
20
|
-
function getVolumeName(generate) {
|
|
21
|
-
return `hammerkit-${generateId(generate)}`;
|
|
22
|
-
}
|
|
23
|
-
exports.getVolumeName = getVolumeName;
|
|
24
|
-
async function getContainerVolumes(node) {
|
|
25
|
-
const volumes = [];
|
|
26
|
-
for (const generate of node.generates) {
|
|
27
|
-
const name = getVolumeName(generate.path);
|
|
28
|
-
volumes.push({
|
|
29
|
-
name,
|
|
30
|
-
containerPath: generate.path,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
return volumes;
|
|
34
|
-
}
|
|
35
|
-
exports.getContainerVolumes = getContainerVolumes;
|
|
36
|
-
async function getContainerMounts(node, context) {
|
|
37
|
-
const result = [];
|
|
38
|
-
for (const source of node.src) {
|
|
39
|
-
const exists = await context.file.exists(source.absolutePath);
|
|
40
|
-
if (exists) {
|
|
41
|
-
result.push({
|
|
42
|
-
localPath: source.absolutePath,
|
|
43
|
-
containerPath: source.absolutePath,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
if (path_1.extname(source.absolutePath)) {
|
|
48
|
-
await context.file.writeFile(source.absolutePath, '');
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
await context.file.createDirectory(source.absolutePath);
|
|
52
|
-
result.push({
|
|
53
|
-
localPath: source.absolutePath,
|
|
54
|
-
containerPath: source.absolutePath,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
for (const mount of node.mounts) {
|
|
60
|
-
result.push(mount);
|
|
61
|
-
}
|
|
62
|
-
for (const volume of result) {
|
|
63
|
-
const otherVolumes = result.filter((v) => v.containerPath === volume.containerPath && v.localPath !== volume.localPath);
|
|
64
|
-
if (otherVolumes.length > 0) {
|
|
65
|
-
throw new Error(`duplicate container mount with different sources ${[
|
|
66
|
-
volume.localPath,
|
|
67
|
-
...otherVolumes.map((ov) => ov.localPath),
|
|
68
|
-
].join(', ')}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return result.filter((v, i) => result.findIndex((iv) => iv.containerPath == v.containerPath) === i);
|
|
72
|
-
}
|
|
73
|
-
exports.getContainerMounts = getContainerMounts;
|
|
74
12
|
let dockerInstance = null;
|
|
75
|
-
function
|
|
13
|
+
function getContainerCli() {
|
|
76
14
|
if (!dockerInstance) {
|
|
77
15
|
dockerInstance = new dockerode_1.default();
|
|
78
16
|
}
|
|
79
17
|
return dockerInstance;
|
|
80
18
|
}
|
|
81
|
-
exports.
|
|
82
|
-
async function startContainer(
|
|
19
|
+
exports.getContainerCli = getContainerCli;
|
|
20
|
+
async function startContainer(status, container) {
|
|
83
21
|
return new Promise((resolve, reject) => {
|
|
84
22
|
const handle = setTimeout(() => {
|
|
85
|
-
|
|
86
|
-
},
|
|
23
|
+
status.write('warn', 'start of container is potentially stuck on start');
|
|
24
|
+
}, 3000);
|
|
87
25
|
container
|
|
88
26
|
.start()
|
|
89
27
|
.then(() => {
|
|
@@ -92,13 +30,28 @@ async function startContainer(node, container) {
|
|
|
92
30
|
})
|
|
93
31
|
.catch((e) => {
|
|
94
32
|
clearTimeout(handle);
|
|
95
|
-
|
|
33
|
+
if ('json' in e && 'message' in e.json) {
|
|
34
|
+
const errorMessage = e.json.message;
|
|
35
|
+
const portBlocked = /Bind for .*:\d+ failed: port is already allocated/.exec(errorMessage);
|
|
36
|
+
if (portBlocked) {
|
|
37
|
+
reject(new Error(portBlocked[0]));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
reject(new Error(errorMessage));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if ('json' in e && e.json instanceof Buffer) {
|
|
44
|
+
reject(new Error(e.json.toString()));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
reject(e);
|
|
48
|
+
}
|
|
96
49
|
});
|
|
97
50
|
});
|
|
98
51
|
}
|
|
99
52
|
exports.startContainer = startContainer;
|
|
100
53
|
function convertToPosixPath(path) {
|
|
101
|
-
if (os_1.platform() === 'win32') {
|
|
54
|
+
if ((0, os_1.platform)() === 'win32') {
|
|
102
55
|
return path
|
|
103
56
|
.split(path_1.sep)
|
|
104
57
|
.map((value, index) => (index === 0 && value.endsWith(':') ? '/' + value.substr(0, value.length - 1) : value))
|
|
@@ -106,111 +59,8 @@ function convertToPosixPath(path) {
|
|
|
106
59
|
}
|
|
107
60
|
return path;
|
|
108
61
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
throw new Error('canceled');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
exports.checkIfAbort = checkIfAbort;
|
|
115
|
-
async function executeDocker(node, context, cancelDefer) {
|
|
116
|
-
node.status.console.write('internal', 'debug', `execute ${node.name} as docker task`);
|
|
117
|
-
const docker = getDocker();
|
|
118
|
-
const volumes = await getContainerVolumes(node);
|
|
119
|
-
const mounts = await getContainerMounts(node, context.environment);
|
|
120
|
-
checkIfAbort(cancelDefer);
|
|
121
|
-
await pull_1.pull(node, docker, node.image);
|
|
122
|
-
checkIfAbort(cancelDefer);
|
|
123
|
-
for (const volume of volumes) {
|
|
124
|
-
await get_docker_executor_1.ensureVolumeExists(docker, volume.name);
|
|
125
|
-
}
|
|
126
|
-
checkIfAbort(cancelDefer);
|
|
127
|
-
node.status.console.write('internal', 'debug', `create container with image ${node.image} with ${node.shell}`);
|
|
128
|
-
const container = await docker.createContainer({
|
|
129
|
-
Image: node.image,
|
|
130
|
-
Tty: true,
|
|
131
|
-
Entrypoint: node.shell,
|
|
132
|
-
Env: Object.keys(node.envs).map((k) => `${k}=${node.envs[k]}`),
|
|
133
|
-
WorkingDir: convertToPosixPath(node.cwd),
|
|
134
|
-
Labels: { app: 'hammerkit' },
|
|
135
|
-
HostConfig: {
|
|
136
|
-
Binds: [
|
|
137
|
-
...mounts.map((v) => `${v.localPath}:${convertToPosixPath(v.containerPath)}`),
|
|
138
|
-
...volumes.map((v) => `${v.name}:${convertToPosixPath(v.containerPath)}`),
|
|
139
|
-
],
|
|
140
|
-
PortBindings: node.ports.reduce((map, port) => {
|
|
141
|
-
map[`${port.containerPort}/tcp`] = [{ HostPort: `${port.hostPort}` }];
|
|
142
|
-
return map;
|
|
143
|
-
}, {}),
|
|
144
|
-
AutoRemove: true,
|
|
145
|
-
},
|
|
146
|
-
ExposedPorts: node.ports.reduce((map, port) => {
|
|
147
|
-
map[`${port.containerPort}/tcp`] = {};
|
|
148
|
-
return map;
|
|
149
|
-
}, {}),
|
|
150
|
-
});
|
|
151
|
-
abort_event_1.listenOnAbort(cancelDefer.signal, () => {
|
|
152
|
-
container.remove({ force: true }).catch((e) => {
|
|
153
|
-
if (e.statusCode !== 404) {
|
|
154
|
-
node.status.console.write('internal', 'debug', `remove of container failed ${e.message}`);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
const user = os_1.platform() === 'linux' || os_1.platform() === 'freebsd' || os_1.platform() === 'openbsd' || os_1.platform() === 'sunos'
|
|
159
|
-
? `${process.getuid()}:${process.getgid()}`
|
|
160
|
-
: undefined;
|
|
161
|
-
try {
|
|
162
|
-
for (const mount of mounts) {
|
|
163
|
-
node.status.console.write('internal', 'debug', `bind mount ${mount.localPath}:${mount.containerPath}`);
|
|
164
|
-
}
|
|
165
|
-
for (const volume of volumes) {
|
|
166
|
-
node.status.console.write('internal', 'debug', `volume mount ${volume.name}:${volume.containerPath}`);
|
|
167
|
-
}
|
|
168
|
-
node.status.console.write('internal', 'info', `starting container with image ${node.image}`);
|
|
169
|
-
await startContainer(node, container);
|
|
170
|
-
if (user) {
|
|
171
|
-
const setUserPermission = async (directory) => {
|
|
172
|
-
node.status.console.write('internal', 'debug', 'set permission on ' + directory);
|
|
173
|
-
const result = await execCommand(node, docker, container, '/', ['chown', user, directory], undefined);
|
|
174
|
-
if (!result || result.ExitCode !== 0) {
|
|
175
|
-
node.status.console.write('internal', 'warn', `unable to set permissions for ${directory}`);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
await setUserPermission(node.cwd);
|
|
179
|
-
for (const volume of volumes) {
|
|
180
|
-
await setUserPermission(volume.containerPath);
|
|
181
|
-
}
|
|
182
|
-
for (const mount of mounts) {
|
|
183
|
-
await setUserPermission(mount.containerPath);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
for (const cmd of node.cmds) {
|
|
187
|
-
checkIfAbort(cancelDefer);
|
|
188
|
-
const command = template_value_1.templateValue(cmd.cmd, node.envs);
|
|
189
|
-
node.status.console.write('internal', 'info', `execute cmd ${command} in container`);
|
|
190
|
-
const result = await execCommand(node, docker, container, convertToPosixPath(cmd.path), [node.shell, '-c', command], user);
|
|
191
|
-
if (!result) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (result.ExitCode !== 0) {
|
|
195
|
-
node.status.console.write('internal', 'error', `command ${command} failed with ${result.ExitCode}`);
|
|
196
|
-
throw new Error(`command ${command} failed with ${result.ExitCode}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
finally {
|
|
201
|
-
try {
|
|
202
|
-
node.status.console.write('internal', 'debug', `remove container`);
|
|
203
|
-
await container.remove({ force: true });
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
if (e.statusCode !== 404) {
|
|
207
|
-
node.status.console.write('internal', 'debug', `remove of container failed ${e.message}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
exports.executeDocker = executeDocker;
|
|
213
|
-
async function execCommand(node, docker, container, cwd, cmd, user) {
|
|
62
|
+
exports.convertToPosixPath = convertToPosixPath;
|
|
63
|
+
async function execCommand(status, environment, container, cwd, cmd, user, timeout, abort) {
|
|
214
64
|
const exec = await container.exec({
|
|
215
65
|
Cmd: cmd,
|
|
216
66
|
WorkingDir: cwd,
|
|
@@ -218,31 +68,45 @@ async function execCommand(node, docker, container, cwd, cmd, user) {
|
|
|
218
68
|
AttachStdout: true,
|
|
219
69
|
AttachStdin: true,
|
|
220
70
|
AttachStderr: true,
|
|
221
|
-
User: user,
|
|
71
|
+
User: user ?? undefined,
|
|
222
72
|
});
|
|
223
|
-
|
|
73
|
+
status.write('debug', `received exec id ${exec.id}`);
|
|
224
74
|
const stream = await exec.start({ stdin: true, hijack: true, Detach: false, Tty: false });
|
|
225
75
|
return new Promise((resolve, reject) => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
.
|
|
76
|
+
let resolved = false;
|
|
77
|
+
if (abort) {
|
|
78
|
+
(0, abort_event_1.listenOnAbort)(abort, () => {
|
|
79
|
+
if (resolved) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
resolved = true;
|
|
83
|
+
resolve({ type: 'canceled' });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
(0, stream_1.awaitStream)(status, stream)
|
|
87
|
+
.then(() => {
|
|
88
|
+
if (resolved) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
return exec.inspect().then((result) => {
|
|
92
|
+
if (resolved) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
resolve({ type: 'result', result });
|
|
96
|
+
resolved = true;
|
|
97
|
+
});
|
|
98
|
+
})
|
|
229
99
|
.catch(reject);
|
|
230
|
-
|
|
100
|
+
if (timeout) {
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
if (resolved) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolve({ type: 'timeout' });
|
|
106
|
+
resolved = true;
|
|
107
|
+
}, timeout);
|
|
108
|
+
}
|
|
231
109
|
});
|
|
232
110
|
}
|
|
233
111
|
exports.execCommand = execCommand;
|
|
234
|
-
function pollStatus(exec, resolve, reject) {
|
|
235
|
-
exec
|
|
236
|
-
.inspect()
|
|
237
|
-
.then((result) => {
|
|
238
|
-
if (!result.Running) {
|
|
239
|
-
resolve(result);
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
setTimeout(() => pollStatus(exec, resolve, reject), 50);
|
|
243
|
-
}
|
|
244
|
-
})
|
|
245
|
-
.catch(reject);
|
|
246
|
-
}
|
|
247
|
-
exports.pollStatus = pollStatus;
|
|
248
112
|
//# sourceMappingURL=execute-docker.js.map
|
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const get_execution_context_mock_1 = require("./get-execution-context-mock");
|
|
3
|
+
const get_test_suite_1 = require("../testing/get-test-suite");
|
|
4
|
+
const expect_1 = require("../testing/expect");
|
|
6
5
|
describe('execute', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let nodeId;
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
ctx = get_execution_context_mock_1.getExecutionContextMock();
|
|
12
|
-
ctx.watch = true;
|
|
13
|
-
workTree = await create_work_tree_1.createWorkTree(ctx.environment, {
|
|
14
|
-
tasks: {
|
|
15
|
-
api: {
|
|
16
|
-
cmds: ['node index.js'],
|
|
17
|
-
src: ['index.js', 'package.json'],
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
}, 'api');
|
|
21
|
-
nodeId = workTree.rootNode.id;
|
|
22
|
-
await ctx.environment.file.createDirectory(ctx.environment.cwd);
|
|
23
|
-
await ctx.environment.file.writeFile(`${ctx.environment.cwd}/index.js`, "console.log('hello')");
|
|
24
|
-
await ctx.environment.file.writeFile(`${ctx.environment.cwd}/package.json`, '{}');
|
|
25
|
-
});
|
|
6
|
+
const suite = (0, get_test_suite_1.getTestSuite)('hello-world-node', ['build.yaml', 'package.json', 'index.js']);
|
|
7
|
+
afterAll(() => suite.close());
|
|
26
8
|
it('should restart watching task if once completed', async () => {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
9
|
+
const { cli, environment } = await suite.setup({ taskName: 'api' });
|
|
10
|
+
const node = cli.node('api');
|
|
11
|
+
const exec = cli.execWatch({ watch: true });
|
|
12
|
+
let count = 0;
|
|
13
|
+
exec.state.on((state) => {
|
|
14
|
+
if (state.node[node.id].type === 'completed') {
|
|
15
|
+
count++;
|
|
16
|
+
if (count === 1) {
|
|
17
|
+
environment.file.appendFile(`${environment.cwd}/index.js`, '\n');
|
|
18
|
+
}
|
|
19
|
+
else if (count === 2) {
|
|
20
|
+
environment.abortCtrl.abort();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const result = await exec.start();
|
|
25
|
+
await (0, expect_1.expectSuccessfulResult)(result, environment);
|
|
35
26
|
});
|
|
36
27
|
it('should restart watching task if once failed', async () => {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
const { cli, environment } = await suite.setup({ taskName: 'api_crashing' });
|
|
29
|
+
const node = cli.node('api_crashing');
|
|
30
|
+
const exec = cli.execWatch({ watch: true });
|
|
31
|
+
let count = 0;
|
|
32
|
+
exec.state.on((state) => {
|
|
33
|
+
if (state.node[node.id].type === 'crash') {
|
|
34
|
+
count++;
|
|
35
|
+
if (count === 1) {
|
|
36
|
+
environment.file.appendFile(`${environment.cwd}/index.js`, '\n');
|
|
37
|
+
}
|
|
38
|
+
else if (count === 2) {
|
|
39
|
+
environment.abortCtrl.abort();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const result = await exec.start();
|
|
44
44
|
expect(result.success).toBeFalsy();
|
|
45
45
|
});
|
|
46
46
|
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setUserPermissions = exports.prepareMounts = exports.prepareVolume = exports.pullImage = void 0;
|
|
4
|
+
const work_node_1 = require("../planner/work-node");
|
|
5
|
+
const pull_1 = require("../docker/pull");
|
|
6
|
+
const get_docker_executor_1 = require("./get-docker-executor");
|
|
7
|
+
const work_service_1 = require("../planner/work-service");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const set_user_permission_1 = require("./set-user-permission");
|
|
10
|
+
async function pullImage(node, environment) {
|
|
11
|
+
if ((0, work_node_1.isContainerWorkNode)(node)) {
|
|
12
|
+
await (0, pull_1.pull)(environment.status.task(node), environment, node.image);
|
|
13
|
+
}
|
|
14
|
+
else if ((0, work_service_1.isContainerWorkService)(node)) {
|
|
15
|
+
await (0, pull_1.pull)(environment.status.service(node), environment, node.image);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.pullImage = pullImage;
|
|
19
|
+
async function prepareVolume(node, environment) {
|
|
20
|
+
if ((0, work_node_1.isContainerWorkNode)(node) || (0, work_service_1.isContainerWorkService)(node)) {
|
|
21
|
+
for (const volume of node.volumes) {
|
|
22
|
+
await (0, get_docker_executor_1.ensureVolumeExists)(environment, volume.name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.prepareVolume = prepareVolume;
|
|
27
|
+
async function prepareMounts(node, environment) {
|
|
28
|
+
for (const mount of node.mounts) {
|
|
29
|
+
const exists = await environment.file.exists(mount.localPath);
|
|
30
|
+
if (exists) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if ((0, path_1.extname)(mount.localPath)) {
|
|
34
|
+
await environment.file.writeFile(mount.localPath, '');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await environment.file.createDirectory(mount.localPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.prepareMounts = prepareMounts;
|
|
42
|
+
async function setUserPermissions(node, container, environment) {
|
|
43
|
+
const status = environment.status.from(node);
|
|
44
|
+
if (node.user) {
|
|
45
|
+
if ((0, work_node_1.isContainerWorkNode)(node)) {
|
|
46
|
+
await (0, set_user_permission_1.setUserPermission)(node.cwd, status, environment, container, node.user);
|
|
47
|
+
}
|
|
48
|
+
for (const volume of node.volumes) {
|
|
49
|
+
await (0, set_user_permission_1.setUserPermission)(volume.containerPath, status, environment, container, node.user);
|
|
50
|
+
}
|
|
51
|
+
for (const mount of node.mounts) {
|
|
52
|
+
await (0, set_user_permission_1.setUserPermission)(mount.containerPath, status, environment, container, node.user);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.setUserPermissions = setUserPermissions;
|
|
57
|
+
//# sourceMappingURL=execution-steps.js.map
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
const execute_docker_1 = require("./execute-docker");
|
|
6
|
-
const get_local_executor_1 = require("./get-local-executor");
|
|
7
|
-
const replace_env_variables_1 = require("../environment/replace-env-variables");
|
|
8
|
-
const path_1 = require("path");
|
|
9
|
-
async function existsVolume(docker, volumeName) {
|
|
3
|
+
exports.ensureVolumeExists = exports.existsVolume = void 0;
|
|
4
|
+
async function existsVolume(environment, volumeName) {
|
|
10
5
|
try {
|
|
11
|
-
const volume = await docker.getVolume(volumeName);
|
|
6
|
+
const volume = await environment.docker.getVolume(volumeName);
|
|
12
7
|
return await volume.inspect();
|
|
13
8
|
}
|
|
14
9
|
catch (e) {
|
|
@@ -16,10 +11,10 @@ async function existsVolume(docker, volumeName) {
|
|
|
16
11
|
}
|
|
17
12
|
}
|
|
18
13
|
exports.existsVolume = existsVolume;
|
|
19
|
-
async function ensureVolumeExists(
|
|
20
|
-
const volumeExists = await existsVolume(
|
|
14
|
+
async function ensureVolumeExists(environment, volumeName) {
|
|
15
|
+
const volumeExists = await existsVolume(environment, volumeName);
|
|
21
16
|
if (!volumeExists) {
|
|
22
|
-
await docker.createVolume({
|
|
17
|
+
await environment.docker.createVolume({
|
|
23
18
|
Name: volumeName,
|
|
24
19
|
Driver: 'local',
|
|
25
20
|
Labels: { app: 'hammerkit' },
|
|
@@ -27,93 +22,4 @@ async function ensureVolumeExists(docker, volumeName) {
|
|
|
27
22
|
}
|
|
28
23
|
}
|
|
29
24
|
exports.ensureVolumeExists = ensureVolumeExists;
|
|
30
|
-
function getDockerExecutor() {
|
|
31
|
-
const localExec = get_local_executor_1.getLocalExecutor();
|
|
32
|
-
return {
|
|
33
|
-
async restore(node, environment, path) {
|
|
34
|
-
const docker = execute_docker_1.getDocker();
|
|
35
|
-
for (const generate of node.generates) {
|
|
36
|
-
if (generate.inherited) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const id = execute_docker_1.generateId(generate.path);
|
|
40
|
-
const volumeName = execute_docker_1.getVolumeName(generate.path);
|
|
41
|
-
const sourcePath = path_1.join(path, 'generates', `${id}.tar`);
|
|
42
|
-
if (!(await environment.file.exists(sourcePath))) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
await ensureVolumeExists(docker, volumeName);
|
|
46
|
-
node.status.console.write('internal', 'info', `import data into volume ${volumeName}`);
|
|
47
|
-
const res = await docker.run('ubuntu', ['bash', '-c', `cd /data && tar xvf /import/${id}.tar --strip 1`], [], {
|
|
48
|
-
HostConfig: {
|
|
49
|
-
Binds: [`${volumeName}:/data`, `${sourcePath}:/import/${id}.tar`],
|
|
50
|
-
AutoRemove: true,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
if (res[0].StatusCode !== 0) {
|
|
54
|
-
throw new Error(res[0].Error);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
async store(node, environment, path) {
|
|
59
|
-
const docker = execute_docker_1.getDocker();
|
|
60
|
-
for (const generate of node.generates) {
|
|
61
|
-
if (generate.inherited) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const id = execute_docker_1.generateId(generate.path);
|
|
65
|
-
const volumeName = execute_docker_1.getVolumeName(generate.path);
|
|
66
|
-
const volumeExists = await existsVolume(docker, volumeName);
|
|
67
|
-
if (!volumeExists) {
|
|
68
|
-
node.status.console.write('internal', 'info', `generate ${generate} has no volume ${volumeName}`);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const targetPath = path_1.join(path, 'generates');
|
|
72
|
-
await environment.file.createDirectory(targetPath);
|
|
73
|
-
node.status.console.write('internal', 'info', `export data from volume ${volumeName}`);
|
|
74
|
-
const res = await docker.run('ubuntu', ['tar', 'cvf', `/export/${id}.tar`, '/data'], [], {
|
|
75
|
-
HostConfig: {
|
|
76
|
-
Binds: [`${volumeName}:/data`, `${targetPath}:/export`],
|
|
77
|
-
AutoRemove: true,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
if (res[0].StatusCode !== 0) {
|
|
81
|
-
throw new Error(res[0].Error);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
async clean(node, environment) {
|
|
86
|
-
if (!work_node_1.isContainerWorkNode(node)) {
|
|
87
|
-
return localExec.clean(node, environment);
|
|
88
|
-
}
|
|
89
|
-
const docker = execute_docker_1.getDocker();
|
|
90
|
-
for (const generate of node.generates) {
|
|
91
|
-
if (generate.inherited) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const volumeName = execute_docker_1.getVolumeName(generate.path);
|
|
95
|
-
const volumeExists = await existsVolume(docker, volumeName);
|
|
96
|
-
if (volumeExists) {
|
|
97
|
-
node.status.console.write('internal', 'info', `remove volume ${volumeName}`);
|
|
98
|
-
const volume = await docker.getVolume(volumeName);
|
|
99
|
-
await volume.remove();
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
node.status.console.write('internal', 'info', `generate ${generate} has no volume ${volumeName}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
async exec(node, context, cancelDefer) {
|
|
107
|
-
if (!work_node_1.isContainerWorkNode(node)) {
|
|
108
|
-
return localExec.exec(node, context, cancelDefer);
|
|
109
|
-
}
|
|
110
|
-
const envs = replace_env_variables_1.replaceEnvVariables(node, context.environment.processEnvs);
|
|
111
|
-
await execute_docker_1.executeDocker({
|
|
112
|
-
...node,
|
|
113
|
-
envs,
|
|
114
|
-
}, context, cancelDefer);
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
exports.getDockerExecutor = getDockerExecutor;
|
|
119
25
|
//# sourceMappingURL=get-docker-executor.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.schedule = void 0;
|
|
4
|
+
const work_service_1 = require("../planner/work-service");
|
|
5
|
+
const docker_service_1 = require("./docker-service");
|
|
6
|
+
const kubernetes_service_1 = require("./kubernetes-service");
|
|
7
|
+
const work_node_1 = require("../planner/work-node");
|
|
8
|
+
const docker_node_1 = require("./docker-node");
|
|
9
|
+
const local_node_1 = require("./local-node");
|
|
10
|
+
const work_node_status_1 = require("../planner/work-node-status");
|
|
11
|
+
async function schedule(processManager, state, environment) {
|
|
12
|
+
state.on((currentState) => {
|
|
13
|
+
for (const [, nodeState] of Object.entries(currentState.node)) {
|
|
14
|
+
if (nodeState.type === 'pending') {
|
|
15
|
+
const endedNeeds = nodeState.node.needs
|
|
16
|
+
.map((need) => currentState.service[need.id])
|
|
17
|
+
.filter((service) => service.type === 'end');
|
|
18
|
+
if (endedNeeds.length > 0) {
|
|
19
|
+
state.patchNode({
|
|
20
|
+
type: 'canceled',
|
|
21
|
+
node: nodeState.node,
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const pendingNeeds = nodeState.node.needs
|
|
26
|
+
.map((need) => currentState.service[need.id])
|
|
27
|
+
.filter((service) => service.type === 'pending');
|
|
28
|
+
const hasOpenDeps = nodeState.node.deps.some((dep) => currentState.node[dep.id].type !== 'completed');
|
|
29
|
+
if (hasOpenDeps) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (pendingNeeds.length > 0) {
|
|
33
|
+
// TODO do not start, if needs are cached
|
|
34
|
+
for (const pendingNeed of pendingNeeds) {
|
|
35
|
+
const ctx = (0, work_node_status_1.logContext)('service', pendingNeed.service);
|
|
36
|
+
state.patchService({
|
|
37
|
+
type: 'running',
|
|
38
|
+
service: pendingNeed.service,
|
|
39
|
+
abortController: processManager.background(ctx, (0, work_service_1.isContainerWorkService)(pendingNeed.service)
|
|
40
|
+
? (0, docker_service_1.dockerService)(pendingNeed.service, state, environment)
|
|
41
|
+
: (0, kubernetes_service_1.kubernetesService)(pendingNeed.service, state, environment)),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const hasNotReadyNeeds = nodeState.node.needs.some((need) => currentState.service[need.id].type !== 'ready');
|
|
47
|
+
if (hasNotReadyNeeds) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const serviceContainers = {};
|
|
51
|
+
for (const need of nodeState.node.needs) {
|
|
52
|
+
const serviceState = currentState.service[need.id];
|
|
53
|
+
if (serviceState.type === 'ready') {
|
|
54
|
+
serviceContainers[need.id] = serviceState.dns;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const ctx = (0, work_node_status_1.logContext)('task', nodeState.node);
|
|
58
|
+
state.patchNode({
|
|
59
|
+
type: 'starting',
|
|
60
|
+
node: nodeState.node,
|
|
61
|
+
started: new Date(),
|
|
62
|
+
abortController: (0, work_node_1.isContainerWorkNode)(nodeState.node)
|
|
63
|
+
? processManager.task(ctx, (0, docker_node_1.dockerNode)(nodeState.node, serviceContainers, state, environment))
|
|
64
|
+
: processManager.task(ctx, (0, local_node_1.localNode)(nodeState.node, state, environment)),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const [serviceId, serviceState] of Object.entries(currentState.service)) {
|
|
69
|
+
if (serviceState.type !== 'ready' && serviceState.type !== 'running') {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
let hasNeed = false;
|
|
73
|
+
for (const nodeState of Object.values(currentState.node)) {
|
|
74
|
+
if (nodeState.type === 'running' || nodeState.type === 'starting' || nodeState.type === 'pending') {
|
|
75
|
+
if (nodeState.node.needs.some((n) => n.id === serviceId)) {
|
|
76
|
+
hasNeed = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!hasNeed) {
|
|
81
|
+
environment.status.service(serviceState.service).write('info', 'stop unused service');
|
|
82
|
+
serviceState.abortController.abort();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
await processManager.onComplete();
|
|
87
|
+
const success = !Object.values(state.current.node).some((n) => n.type !== 'completed');
|
|
88
|
+
return {
|
|
89
|
+
state: state.current,
|
|
90
|
+
success,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
exports.schedule = schedule;
|
|
94
|
+
//# sourceMappingURL=hierarchy-scheduler.js.map
|