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.
Files changed (182) hide show
  1. package/.devcontainer/Dockerfile +5 -0
  2. package/.devcontainer/devcontainer.json +40 -0
  3. package/Dockerfile +1 -1
  4. package/README.md +13 -86
  5. package/SUMMARY.md +43 -0
  6. package/build.schema.json +56 -2
  7. package/dist/cli.js +97 -0
  8. package/dist/cli.spec.js +5 -0
  9. package/dist/console/console-context-mock.js +11 -0
  10. package/dist/console/get-logger.js +23 -0
  11. package/dist/docker/pull.js +5 -5
  12. package/dist/docker/remove-container.js +21 -0
  13. package/dist/docker/stream.js +19 -21
  14. package/dist/docker/using-container.js +28 -0
  15. package/dist/environment/get-process-env.js +4 -4
  16. package/dist/environment/replace-env-variables.js +2 -3
  17. package/dist/executer/abort.js +13 -0
  18. package/dist/executer/check-readiness.js +25 -0
  19. package/dist/executer/create-scheduler-state.js +32 -0
  20. package/dist/executer/docker-node.js +123 -0
  21. package/dist/executer/docker-service.js +113 -0
  22. package/dist/executer/environment-mock.js +17 -0
  23. package/dist/executer/event-cache.js +165 -0
  24. package/dist/executer/execute-command.js +42 -0
  25. package/dist/executer/execute-docker.js +73 -223
  26. package/dist/executer/execute.spec.js +37 -37
  27. package/dist/executer/execution-steps.js +57 -0
  28. package/dist/executer/get-docker-executor.js +21 -20
  29. package/dist/executer/hierarchy-scheduler.js +94 -0
  30. package/dist/executer/kubernetes-service.js +64 -0
  31. package/dist/executer/label-values.js +65 -0
  32. package/dist/executer/local-node.js +57 -0
  33. package/dist/executer/print-container-options.js +17 -0
  34. package/dist/{logging/log-strategy.js → executer/process-item.js} +1 -1
  35. package/dist/executer/process-listener.js +3 -0
  36. package/dist/executer/process-manager.js +111 -0
  37. package/dist/executer/process-manager.spec.js +91 -0
  38. package/dist/executer/{executor.js → process.js} +1 -1
  39. package/dist/executer/{execute-result.js → readonly-state.js} +1 -1
  40. package/dist/executer/schedule.js +139 -0
  41. package/dist/executer/scheduler/check-for-loop.js +21 -0
  42. package/dist/executer/scheduler/enqueue-next.js +27 -0
  43. package/dist/{testing/test-context.js → executer/scheduler/node-state.js} +1 -1
  44. package/dist/executer/scheduler/scheduler-result.js +3 -0
  45. package/dist/executer/scheduler/scheduler-state.js +3 -0
  46. package/dist/executer/scheduler/service-state.js +3 -0
  47. package/dist/executer/service-dns.js +6 -0
  48. package/dist/executer/set-user-permission.js +16 -0
  49. package/dist/executer/start-node.js +59 -0
  50. package/dist/executer/state-listener.js +3 -0
  51. package/dist/executer/state.js +49 -0
  52. package/dist/executer/states.js +4 -129
  53. package/dist/executer/watch-node.js +57 -0
  54. package/dist/executer/watch-service.js +54 -0
  55. package/dist/executer/work-scope.js +10 -0
  56. package/dist/file/get-file-context-mock.js +1 -0
  57. package/dist/file/get-file-context-mock.spec.js +28 -27
  58. package/dist/file/get-file-context.js +73 -63
  59. package/dist/file/move-files.js +1 -1
  60. package/dist/get-work-context.js +29 -0
  61. package/dist/index.js +11 -10
  62. package/dist/log.js +209 -52
  63. package/dist/logging/grouped-logger.js +31 -19
  64. package/dist/logging/interactive-logger.js +20 -23
  65. package/dist/logging/live-logger.js +7 -17
  66. package/dist/optimizer/calculate-checksum.js +1 -1
  67. package/dist/optimizer/get-cache-directory.js +8 -13
  68. package/dist/optimizer/get-work-node-cache-stats.js +68 -21
  69. package/dist/optimizer/read-work-node-cache.js +2 -2
  70. package/dist/optimizer/work-node-cache-description.js +12 -11
  71. package/dist/optimizer/write-work-node-cache.js +9 -8
  72. package/dist/{executer/execution-context.js → parser/build-file-service.js} +1 -1
  73. package/dist/{optimizer → parser}/cache-method.js +0 -0
  74. package/dist/parser/get-build-file.js +1 -1
  75. package/dist/parser/parse-boolean.js +29 -0
  76. package/dist/parser/parse-boolean.spec.js +48 -0
  77. package/dist/parser/parse-build-file-references.js +2 -2
  78. package/dist/parser/parse-build-file-services.js +58 -0
  79. package/dist/parser/parse-build-file-task-command.js +6 -5
  80. package/dist/parser/parse-build-file-task-source.js +5 -5
  81. package/dist/parser/parse-build-file.js +29 -17
  82. package/dist/parser/parse-context.js +13 -0
  83. package/dist/parser/parse-envs.js +4 -3
  84. package/dist/parser/parse-healthcheck.js +28 -0
  85. package/dist/parser/parse-label-arguments.js +23 -0
  86. package/dist/parser/parse-service-selector.js +39 -0
  87. package/dist/parser/parse-string-array.js +6 -5
  88. package/dist/parser/parse-string-map.js +30 -0
  89. package/dist/parser/parse-string.js +20 -0
  90. package/dist/parser/read-build-file.js +16 -5
  91. package/dist/parser/read-env-file.js +7 -7
  92. package/dist/planner/utils/get-container-mounts.js +24 -0
  93. package/dist/planner/utils/normalize-path.js +17 -0
  94. package/dist/planner/utils/parse-work-node-mount.js +3 -14
  95. package/dist/planner/utils/parse-work-node-mount.spec.js +10 -10
  96. package/dist/planner/utils/parse-work-service-volume.js +21 -0
  97. package/dist/planner/utils/plan-work-command.js +1 -1
  98. package/dist/planner/utils/plan-work-dependency.js +29 -24
  99. package/dist/planner/utils/plan-work-node.js +203 -120
  100. package/dist/planner/utils/plan-work-nodes.js +60 -12
  101. package/dist/planner/utils/plan-work-tree.js +8 -4
  102. package/dist/planner/utils/plan-work-volume.js +25 -0
  103. package/dist/planner/validate.js +56 -8
  104. package/dist/planner/work-context.js +20 -0
  105. package/dist/planner/work-mount.js +3 -0
  106. package/dist/planner/work-node-id.js +4 -4
  107. package/dist/planner/work-node-id.spec.js +11 -8
  108. package/dist/planner/work-node-path.js +0 -1
  109. package/dist/planner/work-node-status.js +121 -18
  110. package/dist/planner/work-node.js +3 -3
  111. package/dist/planner/work-service-id.js +13 -0
  112. package/dist/planner/work-service-volume.js +3 -0
  113. package/dist/planner/work-service.js +6 -0
  114. package/dist/planner/work-services.js +3 -0
  115. package/dist/planner/work-volume.js +3 -0
  116. package/dist/program.js +155 -91
  117. package/dist/program.spec.js +20 -0
  118. package/dist/start-watch-processes.js +28 -0
  119. package/dist/testing/cli.js +73 -0
  120. package/dist/testing/create-build-file.js +1 -1
  121. package/dist/testing/example-test-suite.js +54 -0
  122. package/dist/testing/expect.js +30 -18
  123. package/dist/testing/get-logger.js +23 -0
  124. package/dist/testing/get-node.js +2 -2
  125. package/dist/testing/get-test-context.js +6 -5
  126. package/dist/testing/get-test-suite.js +2 -51
  127. package/dist/testing/integration/cache.spec.js +25 -28
  128. package/dist/testing/integration/cancellation.spec.js +19 -21
  129. package/dist/testing/integration/clean.spec.js +29 -13
  130. package/dist/testing/integration/cmd.spec.js +5 -8
  131. package/dist/testing/integration/concurrency.spec.js +8 -17
  132. package/dist/testing/integration/docker.spec.js +6 -9
  133. package/dist/testing/integration/env.spec.js +27 -34
  134. package/dist/testing/integration/error.spec.js +5 -9
  135. package/dist/testing/integration/extend.spec.js +32 -0
  136. package/dist/testing/integration/glob.spec.js +35 -28
  137. package/dist/testing/integration/include.spec.js +17 -16
  138. package/dist/testing/integration/invalid.spec.js +3 -2
  139. package/dist/testing/integration/invalid_loop.spec.js +13 -0
  140. package/dist/testing/integration/kubernetes.spec.js +15 -0
  141. package/dist/testing/integration/labels.spec.js +47 -0
  142. package/dist/testing/integration/local.spec.js +4 -7
  143. package/dist/testing/integration/monorepo.spec.js +6 -30
  144. package/dist/testing/integration/multi-line.spec.js +4 -4
  145. package/dist/testing/integration/program.spec.js +3 -10
  146. package/dist/testing/integration/reference.spec.js +10 -13
  147. package/dist/testing/integration/services.spec.js +14 -0
  148. package/dist/testing/integration/store.spec.js +42 -35
  149. package/dist/testing/integration/unknown.spec.js +4 -4
  150. package/dist/testing/integration/validate.spec.js +4 -4
  151. package/dist/testing/integration/watch.spec.js +25 -23
  152. package/dist/testing/lazy.js +17 -0
  153. package/dist/testing/start-watch-processes.js +29 -0
  154. package/dist/testing/test-suite-setup.js +3 -0
  155. package/dist/utils/abort-event.js +24 -0
  156. package/dist/utils/abortable-function.js +71 -0
  157. package/dist/utils/debouncer.spec.js +8 -8
  158. package/dist/utils/environment-config.js +13 -0
  159. package/dist/utils/fail-never.js +1 -1
  160. package/dist/utils/sleep.spec.js +3 -3
  161. package/package.json +24 -21
  162. package/dist/console/get-console-context-mock.js +0 -33
  163. package/dist/executer/clean.js +0 -22
  164. package/dist/executer/execute-local.js +0 -58
  165. package/dist/executer/execute.js +0 -95
  166. package/dist/executer/execution-context-event.js +0 -3
  167. package/dist/executer/execution-context-mock.js +0 -3
  168. package/dist/executer/executor-mock.js +0 -39
  169. package/dist/executer/get-environment-mock.js +0 -17
  170. package/dist/executer/get-execution-context-mock.js +0 -19
  171. package/dist/executer/get-local-executor.js +0 -22
  172. package/dist/executer/get-ready-work-nodes.js +0 -15
  173. package/dist/executer/restore.js +0 -22
  174. package/dist/executer/store.js +0 -23
  175. package/dist/logging/get-logger.js +0 -23
  176. package/dist/logging/message-to-console.js +0 -16
  177. package/dist/optimizer/optimize.js +0 -32
  178. package/dist/optimizer/work-node-description.js +0 -12
  179. package/dist/planner/utils/find-build-task.js +0 -18
  180. package/dist/testing/create-work-tree.js +0 -11
  181. package/dist/utils/defer.js +0 -57
  182. 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.executeDocker = exports.getContainerMounts = exports.getContainerVolumes = void 0;
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 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
- const instanceLock = new Lock(2);
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
- async function useDocker(fn) {
106
- const lease = await instanceLock.acquire();
107
- try {
108
- const docker = getDocker();
109
- await fn(docker);
110
- }
111
- finally {
112
- lease.close();
113
- }
114
- }
115
- async function startContainer(container) {
116
- const defer = new defer_1.Defer();
117
- // TODO watch if start takes too long to finish, report warning
118
- container.start().then(() => {
119
- defer.resolve();
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
- async function executeDocker(node, context, cancelDefer) {
133
- node.status.console.write('internal', 'debug', `execute ${node.name} as docker task`);
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
- const defer = new defer_1.Defer();
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
- stream_1.awaitStream(node, docker, stream).then(async () => {
240
- if (!defer.isResolved) {
241
- const result = await exec.inspect();
242
- if (!defer.isResolved) {
243
- defer.resolve(result);
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 create_work_tree_1 = require("../testing/create-work-tree");
4
- const execute_1 = require("./execute");
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
- let ctx;
8
- let workTree;
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 resultPromise = execute_1.execute(workTree, ctx);
28
- const runningApi = await ctx.executor.waitFor(nodeId);
29
- runningApi.resolve();
30
- await ctx.environment.file.appendFile(`${ctx.environment.cwd}/index.js`, '\n');
31
- await ctx.executor.waitFor(nodeId);
32
- await ctx.environment.cancelDefer.resolve();
33
- const result = await resultPromise;
34
- expect(result.success).toBeFalsy();
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 resultPromise = execute_1.execute(workTree, ctx);
38
- const runningApi = await ctx.executor.waitFor(nodeId);
39
- runningApi.reject(new Error('runtime error'));
40
- await ctx.environment.file.appendFile(`${ctx.environment.cwd}/index.js`, '\n');
41
- await ctx.executor.waitFor(nodeId);
42
- await ctx.environment.cancelDefer.resolve();
43
- const result = await resultPromise;
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.getDockerExecutor = void 0;
4
- const work_node_1 = require("../planner/work-node");
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
- function getDockerExecutor() {
9
- const localExec = get_local_executor_1.getLocalExecutor();
10
- return {
11
- async exec(node, context, cancelDefer) {
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.getDockerExecutor = getDockerExecutor;
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