hammerkit 1.4.0-beta.6 → 1.5.0-alpha.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/.devcontainer/Dockerfile +5 -0
- package/.devcontainer/devcontainer.json +40 -0
- package/Dockerfile +1 -1
- 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 +19 -21
- 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 +73 -223
- package/dist/executer/execute.spec.js +37 -37
- package/dist/executer/execution-steps.js +57 -0
- package/dist/executer/get-docker-executor.js +21 -20
- 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 -129
- 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.js +1 -0
- package/dist/file/get-file-context-mock.spec.js +28 -27
- package/dist/file/get-file-context.js +73 -63
- package/dist/file/move-files.js +1 -1
- package/dist/get-work-context.js +29 -0
- package/dist/index.js +11 -10
- package/dist/log.js +209 -52
- 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 +29 -17
- 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 +7 -7
- 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 +29 -24
- package/dist/planner/utils/plan-work-node.js +203 -120
- package/dist/planner/utils/plan-work-nodes.js +60 -12
- package/dist/planner/utils/plan-work-tree.js +8 -4
- package/dist/planner/utils/plan-work-volume.js +25 -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 -91
- 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 +30 -18
- package/dist/testing/get-logger.js +23 -0
- package/dist/testing/get-node.js +2 -2
- package/dist/testing/get-test-context.js +6 -5
- package/dist/testing/get-test-suite.js +2 -51
- 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 +29 -13
- 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 +6 -30
- 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 -35
- 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 +25 -23
- 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 +24 -0
- 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 -22
- package/dist/executer/execute-local.js +0 -58
- package/dist/executer/execute.js +0 -95
- 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 -39
- package/dist/executer/get-environment-mock.js +0 -17
- package/dist/executer/get-execution-context-mock.js +0 -19
- package/dist/executer/get-local-executor.js +0 -22
- package/dist/executer/get-ready-work-nodes.js +0 -15
- package/dist/executer/restore.js +0 -22
- package/dist/executer/store.js +0 -23
- 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
- package/dist/utils/defer.js +0 -57
- package/dist/utils/defer.spec.js +0 -76
|
@@ -3,125 +3,55 @@ 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
|
|
13
|
-
const defer_1 = require("../utils/defer");
|
|
14
|
-
const crypto_1 = require("crypto");
|
|
15
|
-
async function getContainerVolumes(node, context) {
|
|
16
|
-
const volumes = [];
|
|
17
|
-
for (const generate of node.generates) {
|
|
18
|
-
const name = `hammerkit-${crypto_1.createHash('sha1').update(generate).digest('hex')}`;
|
|
19
|
-
volumes.push({
|
|
20
|
-
name,
|
|
21
|
-
containerPath: generate,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return volumes;
|
|
25
|
-
}
|
|
26
|
-
exports.getContainerVolumes = getContainerVolumes;
|
|
27
|
-
async function getContainerMounts(node, context) {
|
|
28
|
-
const result = [];
|
|
29
|
-
for (const source of node.src) {
|
|
30
|
-
const exists = await context.file.exists(source.absolutePath);
|
|
31
|
-
if (exists) {
|
|
32
|
-
result.push({
|
|
33
|
-
localPath: source.absolutePath,
|
|
34
|
-
containerPath: source.absolutePath,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
if (path_1.extname(source.absolutePath)) {
|
|
39
|
-
await context.file.writeFile(source.absolutePath, '');
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
await context.file.createDirectory(source.absolutePath);
|
|
43
|
-
result.push({
|
|
44
|
-
localPath: source.absolutePath,
|
|
45
|
-
containerPath: source.absolutePath,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
for (const mount of node.mounts) {
|
|
51
|
-
result.push(mount);
|
|
52
|
-
}
|
|
53
|
-
for (const volume of result) {
|
|
54
|
-
const otherVolumes = result.filter((v) => v.containerPath === volume.containerPath && v.localPath !== volume.localPath);
|
|
55
|
-
if (otherVolumes.length > 0) {
|
|
56
|
-
throw new Error(`duplicate container mount with different sources ${[
|
|
57
|
-
volume.localPath,
|
|
58
|
-
...otherVolumes.map((ov) => ov.localPath),
|
|
59
|
-
].join(', ')}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return result.filter((v, i) => result.findIndex((iv) => iv.containerPath == v.containerPath) === i);
|
|
63
|
-
}
|
|
64
|
-
exports.getContainerMounts = getContainerMounts;
|
|
65
|
-
class Lock {
|
|
66
|
-
constructor(total) {
|
|
67
|
-
this.total = total;
|
|
68
|
-
this.count = 0;
|
|
69
|
-
this.queue = [];
|
|
70
|
-
}
|
|
71
|
-
enqueueQueue() {
|
|
72
|
-
const item = this.queue.pop();
|
|
73
|
-
if (item) {
|
|
74
|
-
item.resolve(this.lease());
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
lease() {
|
|
78
|
-
return {
|
|
79
|
-
close: () => {
|
|
80
|
-
this.count--;
|
|
81
|
-
this.enqueueQueue();
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
async acquire() {
|
|
86
|
-
if (this.count < this.total) {
|
|
87
|
-
this.count++;
|
|
88
|
-
return this.lease();
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
const defer = new defer_1.Defer();
|
|
92
|
-
this.queue.push(defer);
|
|
93
|
-
return defer.promise;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
11
|
+
const abort_event_1 = require("../utils/abort-event");
|
|
97
12
|
let dockerInstance = null;
|
|
98
|
-
|
|
99
|
-
function getDocker() {
|
|
13
|
+
function getContainerCli() {
|
|
100
14
|
if (!dockerInstance) {
|
|
101
15
|
dockerInstance = new dockerode_1.default();
|
|
102
16
|
}
|
|
103
17
|
return dockerInstance;
|
|
104
18
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
19
|
+
exports.getContainerCli = getContainerCli;
|
|
20
|
+
async function startContainer(status, container) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const handle = setTimeout(() => {
|
|
23
|
+
status.write('warn', 'start of container is potentially stuck on start');
|
|
24
|
+
}, 1000);
|
|
25
|
+
container
|
|
26
|
+
.start()
|
|
27
|
+
.then(() => {
|
|
28
|
+
clearTimeout(handle);
|
|
29
|
+
resolve();
|
|
30
|
+
})
|
|
31
|
+
.catch((e) => {
|
|
32
|
+
clearTimeout(handle);
|
|
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
|
+
}
|
|
49
|
+
});
|
|
120
50
|
});
|
|
121
|
-
return defer.promise;
|
|
122
51
|
}
|
|
52
|
+
exports.startContainer = startContainer;
|
|
123
53
|
function convertToPosixPath(path) {
|
|
124
|
-
if (os_1.platform() === 'win32') {
|
|
54
|
+
if ((0, os_1.platform)() === 'win32') {
|
|
125
55
|
return path
|
|
126
56
|
.split(path_1.sep)
|
|
127
57
|
.map((value, index) => (index === 0 && value.endsWith(':') ? '/' + value.substr(0, value.length - 1) : value))
|
|
@@ -129,101 +59,8 @@ function convertToPosixPath(path) {
|
|
|
129
59
|
}
|
|
130
60
|
return path;
|
|
131
61
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
await useDocker(async (docker) => {
|
|
135
|
-
const volumes = await getContainerVolumes(node, context.environment);
|
|
136
|
-
const mounts = await getContainerMounts(node, context.environment);
|
|
137
|
-
await pull_1.pull(node, docker, node.image);
|
|
138
|
-
for (const volume of volumes) {
|
|
139
|
-
try {
|
|
140
|
-
const existing = await docker.getVolume(volume.name);
|
|
141
|
-
await existing.inspect();
|
|
142
|
-
}
|
|
143
|
-
catch (e) {
|
|
144
|
-
await docker.createVolume({
|
|
145
|
-
Name: volume.name,
|
|
146
|
-
Driver: 'local',
|
|
147
|
-
Labels: { app: 'hammerkit' },
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
node.status.console.write('internal', 'debug', `create container with image ${node.image} with ${node.shell}`);
|
|
152
|
-
const container = await docker.createContainer({
|
|
153
|
-
Image: node.image,
|
|
154
|
-
Tty: true,
|
|
155
|
-
Entrypoint: node.shell,
|
|
156
|
-
Env: Object.keys(node.envs).map((k) => `${k}=${node.envs[k]}`),
|
|
157
|
-
WorkingDir: convertToPosixPath(node.cwd),
|
|
158
|
-
Labels: { app: 'hammerkit' },
|
|
159
|
-
HostConfig: {
|
|
160
|
-
Binds: [
|
|
161
|
-
...mounts.map((v) => `${v.localPath}:${convertToPosixPath(v.containerPath)}`),
|
|
162
|
-
...volumes.map((v) => `${v.name}:${convertToPosixPath(v.containerPath)}`),
|
|
163
|
-
],
|
|
164
|
-
PortBindings: node.ports.reduce((map, port) => {
|
|
165
|
-
map[`${port.containerPort}/tcp`] = [{ HostPort: `${port.hostPort}` }];
|
|
166
|
-
return map;
|
|
167
|
-
}, {}),
|
|
168
|
-
},
|
|
169
|
-
ExposedPorts: node.ports.reduce((map, port) => {
|
|
170
|
-
map[`${port.containerPort}/tcp`] = {};
|
|
171
|
-
return map;
|
|
172
|
-
}, {}),
|
|
173
|
-
});
|
|
174
|
-
for (const mount of mounts) {
|
|
175
|
-
node.status.console.write('internal', 'debug', `bind mount ${mount.localPath}:${mount.containerPath}`);
|
|
176
|
-
}
|
|
177
|
-
for (const volume of volumes) {
|
|
178
|
-
node.status.console.write('internal', 'debug', `volume mount ${volume.name}:${volume.containerPath}`);
|
|
179
|
-
}
|
|
180
|
-
cancelDefer.promise.then(() => {
|
|
181
|
-
container.remove({ force: true }).catch(() => {
|
|
182
|
-
node.status.console.write('internal', 'debug', `remove of container failed`);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
const user = os_1.platform() === 'linux' || os_1.platform() === 'freebsd' || os_1.platform() === 'openbsd' || os_1.platform() === 'sunos'
|
|
186
|
-
? `${process.getuid()}:${process.getgid()}`
|
|
187
|
-
: undefined;
|
|
188
|
-
try {
|
|
189
|
-
node.status.console.write('internal', 'info', `starting container with image ${node.image}`);
|
|
190
|
-
await startContainer(container);
|
|
191
|
-
if (user) {
|
|
192
|
-
const setUserPermission = async (directory) => {
|
|
193
|
-
node.status.console.write('internal', 'debug', 'set permission on ' + directory);
|
|
194
|
-
const result = await execCommand(node, docker, container, '/', ['chown', user, directory], undefined);
|
|
195
|
-
if (result.ExitCode !== 0) {
|
|
196
|
-
node.status.console.write('internal', 'warn', `unable to set permissions for ${directory}`);
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
await setUserPermission(node.cwd);
|
|
200
|
-
for (const mount of mounts) {
|
|
201
|
-
await setUserPermission(mount.containerPath);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
for (const cmd of node.cmds) {
|
|
205
|
-
if (cancelDefer.isResolved) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
const command = template_value_1.templateValue(cmd.cmd, node.envs);
|
|
209
|
-
node.status.console.write('internal', 'info', `execute cmd ${command} in container`);
|
|
210
|
-
const result = await execCommand(node, docker, container, convertToPosixPath(cmd.path), [node.shell, '-c', command], user);
|
|
211
|
-
if (result.ExitCode !== 0) {
|
|
212
|
-
node.status.console.write('internal', 'error', `command ${command} failed with ${result.ExitCode}`);
|
|
213
|
-
throw new Error(`command ${command} failed with ${result.ExitCode}`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
finally {
|
|
218
|
-
node.status.console.write('internal', 'debug', `remove container`);
|
|
219
|
-
await container.remove({ force: true }).catch(() => {
|
|
220
|
-
node.status.console.write('internal', 'debug', `remove of container failed`);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
exports.executeDocker = executeDocker;
|
|
226
|
-
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) {
|
|
227
64
|
const exec = await container.exec({
|
|
228
65
|
Cmd: cmd,
|
|
229
66
|
WorkingDir: cwd,
|
|
@@ -231,32 +68,45 @@ async function execCommand(node, docker, container, cwd, cmd, user) {
|
|
|
231
68
|
AttachStdout: true,
|
|
232
69
|
AttachStdin: true,
|
|
233
70
|
AttachStderr: true,
|
|
234
|
-
User: user,
|
|
71
|
+
User: user ?? undefined,
|
|
235
72
|
});
|
|
236
|
-
|
|
237
|
-
node.status.console.write('internal', 'debug', `received exec id ${exec.id}`);
|
|
73
|
+
status.write('debug', `received exec id ${exec.id}`);
|
|
238
74
|
const stream = await exec.start({ stdin: true, hijack: true, Detach: false, Tty: false });
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
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;
|
|
244
90
|
}
|
|
91
|
+
return exec.inspect().then((result) => {
|
|
92
|
+
if (resolved) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
resolve({ type: 'result', result });
|
|
96
|
+
resolved = true;
|
|
97
|
+
});
|
|
98
|
+
})
|
|
99
|
+
.catch(reject);
|
|
100
|
+
if (timeout) {
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
if (resolved) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolve({ type: 'timeout' });
|
|
106
|
+
resolved = true;
|
|
107
|
+
}, timeout);
|
|
245
108
|
}
|
|
246
109
|
});
|
|
247
|
-
pollStatus(exec, defer);
|
|
248
|
-
return defer.promise;
|
|
249
|
-
}
|
|
250
|
-
function pollStatus(exec, defer) {
|
|
251
|
-
async function inspect() {
|
|
252
|
-
const result = await exec.inspect();
|
|
253
|
-
if (!result.Running && !defer.isResolved) {
|
|
254
|
-
defer.resolve(result);
|
|
255
|
-
}
|
|
256
|
-
if (!defer.isResolved) {
|
|
257
|
-
setTimeout(() => inspect(), 50);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
inspect();
|
|
261
110
|
}
|
|
111
|
+
exports.execCommand = execCommand;
|
|
262
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,24 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!work_node_1.isContainerWorkNode(node)) {
|
|
13
|
-
return localExec.exec(node, context, cancelDefer);
|
|
14
|
-
}
|
|
15
|
-
const envs = replace_env_variables_1.replaceEnvVariables(node, context.environment.processEnvs);
|
|
16
|
-
await execute_docker_1.executeDocker({
|
|
17
|
-
...node,
|
|
18
|
-
envs,
|
|
19
|
-
}, context, cancelDefer);
|
|
20
|
-
},
|
|
21
|
-
};
|
|
3
|
+
exports.ensureVolumeExists = exports.existsVolume = void 0;
|
|
4
|
+
async function existsVolume(environment, volumeName) {
|
|
5
|
+
try {
|
|
6
|
+
const volume = await environment.docker.getVolume(volumeName);
|
|
7
|
+
return await volume.inspect();
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
22
12
|
}
|
|
23
|
-
exports.
|
|
13
|
+
exports.existsVolume = existsVolume;
|
|
14
|
+
async function ensureVolumeExists(environment, volumeName) {
|
|
15
|
+
const volumeExists = await existsVolume(environment, volumeName);
|
|
16
|
+
if (!volumeExists) {
|
|
17
|
+
await environment.docker.createVolume({
|
|
18
|
+
Name: volumeName,
|
|
19
|
+
Driver: 'local',
|
|
20
|
+
Labels: { app: 'hammerkit' },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ensureVolumeExists = ensureVolumeExists;
|
|
24
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
|