hammerkit 1.6.0 → 1.8.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/README.md +7 -7
- package/build.schema.json +59 -15
- package/dist/cache/backends/local-cache-backend.js +58 -1
- package/dist/cache/backends/registry/registry-client.js +264 -0
- package/dist/cache/backends/registry/registry-credentials.js +81 -0
- package/dist/cache/backends/registry/registry-reference.js +36 -0
- package/dist/cache/backends/registry-cache-backend.js +145 -0
- package/dist/cache/backends/s3-cache-backend.js +99 -46
- package/dist/cache/cache-backend-registry.js +2 -0
- package/dist/cache/cache-inventory.js +84 -0
- package/dist/cache/cache-sync.js +77 -0
- package/dist/cache/explain.js +214 -0
- package/dist/cache/last-resolved.js +48 -0
- package/dist/cache/read-only.js +15 -0
- package/dist/cache/retention.js +61 -0
- package/dist/cli.js +43 -0
- package/dist/docker/using-container.js +17 -1
- package/dist/executer/archive-task-entry.js +21 -0
- package/dist/executer/await-completed-dependencies.js +32 -14
- package/dist/executer/cache-metadata.js +9 -2
- package/dist/executer/docker-service.js +2 -1
- package/dist/executer/docker-task.js +18 -3
- package/dist/executer/dry-run.js +73 -0
- package/dist/executer/event-cache.js +6 -5
- package/dist/executer/execute-command.js +21 -3
- package/dist/executer/execute-docker.js +18 -2
- package/dist/executer/execute-work-service.js +10 -1
- package/dist/executer/execute-work-task.js +111 -26
- package/dist/executer/execute-work-tree.js +26 -1
- package/dist/executer/execution-steps.js +3 -8
- package/dist/executer/get-container-binds.js +13 -1
- package/dist/executer/get-scheduler-execute-result.js +4 -1
- package/dist/executer/lazy-resolver.js +15 -3
- package/dist/executer/reset-work-tree.js +2 -0
- package/dist/executer/run-summary.js +102 -0
- package/dist/executer/scheduler/enqueue-next.js +15 -60
- package/dist/executer/scheduler/state-key.js +78 -0
- package/dist/index.js +14 -1
- package/dist/kubernetes/apply.js +6 -7
- package/dist/kubernetes/await-running-state.js +100 -60
- package/dist/kubernetes/ensure-http-route.js +7 -10
- package/dist/kubernetes/ensure-namespace.js +9 -10
- package/dist/kubernetes/ensure-persistent-data.js +6 -5
- package/dist/kubernetes/get-service-ip.js +2 -2
- package/dist/kubernetes/kubernetes-instance.js +0 -4
- package/dist/kubernetes/remove-persistent-data.js +16 -6
- package/dist/kubernetes/resolve-pod-name.js +7 -7
- package/dist/log.js +16 -6
- package/dist/logging/live-logger.js +2 -1
- package/dist/optimizer/calculate-checksum.js +8 -2
- package/dist/optimizer/get-work-cache-stats.js +66 -16
- package/dist/optimizer/work-task-cache-description.js +34 -5
- package/dist/planner/project-root.js +26 -0
- package/dist/planner/utils/append-work-environment.js +1 -1
- package/dist/planner/utils/append-work-service.js +2 -1
- package/dist/planner/utils/append-work-task.js +7 -1
- package/dist/planner/utils/get-outputs-to-reset.js +20 -0
- package/dist/planner/utils/parse-work-generate.js +2 -2
- package/dist/planner/utils/parse-work-source.js +35 -30
- package/dist/planner/utils/portable-path.js +30 -0
- package/dist/planner/work-cache-id.js +2 -2
- package/dist/planner/work-graph-serializer.js +103 -0
- package/dist/planner/work-instance-id.js +15 -0
- package/dist/planner/work-runtime-docker.js +58 -15
- package/dist/planner/work-runtime-kubernetes.js +95 -45
- package/dist/planner/work-runtime-local.js +31 -8
- package/dist/planner/work-service-id.js +16 -4
- package/dist/program.js +235 -1
- package/dist/schema/build-file-base-task-schema.js +4 -0
- package/dist/schema/build-file-environment-schema-http-route.js +15 -0
- package/dist/schema/build-file-environment-schema.js +8 -2
- package/dist/schema/build-file-schema.js +1 -1
- package/dist/schema/cache-schema.js +21 -2
- package/dist/schema/duration-schema.js +18 -0
- package/dist/schema/envs-schema.js +1 -1
- package/dist/schema/reference-parser.js +3 -0
- package/dist/schema/size-schema.js +18 -0
- package/dist/schema/work-scope-parser.js +15 -2
- package/dist/utils/units.js +87 -0
- package/package.json +20 -9
- package/.agents/FEEDBACK.md +0 -74
- package/.agents/skills/before-commit/SKILL.md +0 -101
- package/.agents/skills/do-the-work/SKILL.md +0 -64
- package/.agents/skills/fix-ci/SKILL.md +0 -98
- package/.agents/skills/speckit-git-commit/SKILL.md +0 -53
- package/.agents/skills/speckit-git-feature/SKILL.md +0 -72
- package/.agents/skills/speckit-git-initialize/SKILL.md +0 -54
- package/.agents/skills/speckit-git-remote/SKILL.md +0 -50
- package/.agents/skills/speckit-git-validate/SKILL.md +0 -54
- package/.devcontainer/Dockerfile +0 -5
- package/.devcontainer/devcontainer.json +0 -40
- package/.gitbook.yaml +0 -5
- package/.specify/extensions/.registry +0 -37
- package/.specify/extensions/git/README.md +0 -100
- package/.specify/extensions/git/commands/speckit.git.commit.md +0 -48
- package/.specify/extensions/git/commands/speckit.git.feature.md +0 -67
- package/.specify/extensions/git/commands/speckit.git.initialize.md +0 -49
- package/.specify/extensions/git/commands/speckit.git.remote.md +0 -45
- package/.specify/extensions/git/commands/speckit.git.validate.md +0 -49
- package/.specify/extensions/git/config-template.yml +0 -62
- package/.specify/extensions/git/extension.yml +0 -140
- package/.specify/extensions/git/git-config.yml +0 -62
- package/.specify/extensions.yml +0 -149
- package/.specify/feature.json +0 -3
- package/.specify/init-options.json +0 -10
- package/.specify/integration.json +0 -15
- package/.specify/integrations/claude.manifest.json +0 -16
- package/.specify/integrations/speckit.manifest.json +0 -17
- package/.specify/memory/constitution.md +0 -162
- package/.specify/templates/checklist-template.md +0 -40
- package/.specify/templates/constitution-template.md +0 -50
- package/.specify/templates/plan-template.md +0 -113
- package/.specify/templates/spec-template.md +0 -131
- package/.specify/templates/tasks-template.md +0 -252
- package/.specify/workflows/speckit/workflow.yml +0 -77
- package/.specify/workflows/workflow-registry.json +0 -13
- package/AGENTS.md +0 -94
- package/Dockerfile +0 -32
- package/FOLLOWUPS.md +0 -34
- package/SUMMARY.md +0 -68
- package/VALUES.md +0 -53
- package/dist/cache/auto-pull.spec.js +0 -123
- package/dist/cache/backends/local-cache-backend.spec.js +0 -72
- package/dist/cache/backends/s3-cache-backend.spec.js +0 -118
- package/dist/cache/cache-backend-registry.spec.js +0 -22
- package/dist/cache/resolve-cache.spec.js +0 -52
- package/dist/cli.spec.js +0 -23
- package/dist/console/get-logger.spec.js +0 -23
- package/dist/docker/get-default-platform.spec.js +0 -29
- package/dist/docker/longest-common-prefix.spec.js +0 -26
- package/dist/docker/package.spec.js +0 -145
- package/dist/docker/remove-container.spec.js +0 -32
- package/dist/docker/using-container.spec.js +0 -90
- package/dist/environment/merge-environment-variables.spec.js +0 -24
- package/dist/environment/replace-env-variables.spec.js +0 -47
- package/dist/executer/abort.spec.js +0 -18
- package/dist/executer/environment-mock.spec.js +0 -22
- package/dist/executer/execute.spec.js +0 -51
- package/dist/executer/get-service-containers.spec.js +0 -59
- package/dist/executer/get-service-env-hints.spec.js +0 -65
- package/dist/executer/label-values.spec.js +0 -61
- package/dist/executer/lazy-resolver.spec.js +0 -20
- package/dist/executer/process-manager.spec.js +0 -54
- package/dist/executer/scheduler/enqueue-next.spec.js +0 -136
- package/dist/executer/state-resolver.spec.js +0 -37
- package/dist/executer/state.spec.js +0 -66
- package/dist/executer/states.spec.js +0 -14
- package/dist/file/get-file-context.spec.js +0 -178
- package/dist/file/move-files.spec.js +0 -48
- package/dist/kubernetes/ensure-http-route.spec.js +0 -70
- package/dist/kubernetes/ensure-ingress.js +0 -54
- package/dist/kubernetes/ensure-namespace.spec.js +0 -39
- package/dist/kubernetes/resolve-pod-name.spec.js +0 -81
- package/dist/log.spec.js +0 -131
- package/dist/logging/grouped-logger.spec.js +0 -84
- package/dist/logging/interactive-logger.spec.js +0 -57
- package/dist/logging/live-logger.spec.js +0 -37
- package/dist/logging/loggers.spec.js +0 -154
- package/dist/optimizer/calculate-checksum.spec.js +0 -34
- package/dist/optimizer/get-cache-directory.spec.js +0 -59
- package/dist/optimizer/get-work-cache-stats.spec.js +0 -79
- package/dist/optimizer/work-task-cache-description.spec.js +0 -45
- package/dist/parser/default-build-file.spec.js +0 -48
- package/dist/parser/parse-label-arguments.spec.js +0 -27
- package/dist/parser/read-build-file.spec.js +0 -89
- package/dist/parser/read-env-file.spec.js +0 -42
- package/dist/planner/utils/append-work-service.spec.js +0 -71
- package/dist/planner/utils/get-container-user.spec.js +0 -30
- package/dist/planner/utils/get-default-kube-config.spec.js +0 -11
- package/dist/planner/utils/normalize-path.spec.js +0 -24
- package/dist/planner/utils/parse-work-command.spec.js +0 -21
- package/dist/planner/utils/parse-work-generate.spec.js +0 -45
- package/dist/planner/utils/parse-work-mount.spec.js +0 -62
- package/dist/planner/utils/parse-work-mounts.spec.js +0 -19
- package/dist/planner/utils/parse-work-port.spec.js +0 -24
- package/dist/planner/utils/parse-work-ports.spec.js +0 -24
- package/dist/planner/utils/parse-work-source.spec.js +0 -58
- package/dist/planner/utils/parse-work-volume.spec.js +0 -18
- package/dist/planner/utils/plan-work-tasks.spec.js +0 -30
- package/dist/planner/utils/plan-work-volume.spec.js +0 -33
- package/dist/planner/utils/split-name.spec.js +0 -16
- package/dist/planner/utils/template-value.spec.js +0 -14
- package/dist/planner/validate.spec.js +0 -251
- package/dist/planner/work-cache-id.spec.js +0 -173
- package/dist/planner/work-runtime-local.spec.js +0 -193
- package/dist/program-commands.spec.js +0 -84
- package/dist/program.spec.js +0 -43
- package/dist/schema/build-file-environment-schema-ingress.js +0 -14
- package/dist/schema/build-file-schema.spec.js +0 -61
- package/dist/schema/doc-build-files.spec.js +0 -223
- package/dist/schema/parse-error.spec.js +0 -18
- package/dist/schema/reference-parser.spec.js +0 -83
- package/dist/schema/schema-parser.spec.js +0 -80
- package/dist/schema/type-guards.spec.js +0 -23
- package/dist/schema/work-scope-parser.spec.js +0 -66
- package/dist/service/update-service-status.spec.js +0 -27
- package/dist/testing/ensure-local-registry.js +0 -134
- package/dist/testing/example-test-suite.js +0 -63
- package/dist/testing/expect.js +0 -82
- package/dist/testing/get-test-suite.js +0 -9
- package/dist/testing/http-get.js +0 -21
- package/dist/testing/integration/cache.spec.js +0 -49
- package/dist/testing/integration/cancellation.spec.js +0 -45
- package/dist/testing/integration/clean.spec.js +0 -43
- package/dist/testing/integration/cmd.spec.js +0 -15
- package/dist/testing/integration/concurrency.spec.js +0 -19
- package/dist/testing/integration/docker-package-registry.spec.js +0 -44
- package/dist/testing/integration/docker-service-crash.spec.js +0 -36
- package/dist/testing/integration/docker.spec.js +0 -18
- package/dist/testing/integration/env.spec.js +0 -48
- package/dist/testing/integration/environment.spec.js +0 -97
- package/dist/testing/integration/error.spec.js +0 -19
- package/dist/testing/integration/export.spec.js +0 -22
- package/dist/testing/integration/extend.spec.js +0 -29
- package/dist/testing/integration/glob.spec.js +0 -40
- package/dist/testing/integration/include.spec.js +0 -31
- package/dist/testing/integration/invalid.spec.js +0 -17
- package/dist/testing/integration/invalid_loop.spec.js +0 -56
- package/dist/testing/integration/k8s-state-cache.spec.js +0 -41
- package/dist/testing/integration/kubernetes-service.spec.js +0 -148
- package/dist/testing/integration/kubernetes.spec.js +0 -99
- package/dist/testing/integration/labels.spec.js +0 -42
- package/dist/testing/integration/local.spec.js +0 -14
- package/dist/testing/integration/monorepo.spec.js +0 -15
- package/dist/testing/integration/multi-line.spec.js +0 -17
- package/dist/testing/integration/reference.spec.js +0 -21
- package/dist/testing/integration/services.spec.js +0 -38
- package/dist/testing/integration/store.spec.js +0 -52
- package/dist/testing/integration/unknown.spec.js +0 -28
- package/dist/testing/integration/validate.spec.js +0 -160
- package/dist/testing/integration/watch-dependency.spec.js +0 -33
- package/dist/testing/integration/watch.spec.js +0 -45
- package/dist/testing/requires-kubernetes.js +0 -18
- package/dist/testing/requires-linux-containers.js +0 -18
- package/dist/testing/requires-windows-containers.js +0 -18
- package/dist/testing/test-case.js +0 -75
- package/dist/testing/test-streams.js +0 -28
- package/dist/testing/test-suite-setup.js +0 -3
- package/dist/testing/test-suite.js +0 -3
- package/dist/testing/testing-timeout.js +0 -32
- package/dist/utils/abort-event.spec.js +0 -46
- package/dist/utils/buffer-context.spec.js +0 -38
- package/dist/utils/ci.spec.js +0 -53
- package/dist/utils/debouncer.spec.js +0 -58
- package/dist/utils/emitter.spec.js +0 -34
- package/dist/utils/empty-writable.spec.js +0 -18
- package/dist/utils/environment-config.spec.js +0 -26
- package/dist/utils/fail-never.spec.js +0 -9
- package/dist/utils/sleep.spec.js +0 -22
- package/dist/version.spec.js +0 -12
- package/specs/build-file/caches/spec.md +0 -114
- package/specs/build-file/environment-variables/spec.md +0 -82
- package/specs/build-file/includes/spec.md +0 -60
- package/specs/build-file/references/spec.md +0 -56
- package/specs/build-file/spec.md +0 -62
- package/specs/cli/clean/spec.md +0 -85
- package/specs/cli/down/spec.md +0 -85
- package/specs/cli/execute/spec.md +0 -101
- package/specs/cli/init/spec.md +0 -64
- package/specs/cli/ls/spec.md +0 -66
- package/specs/cli/package/spec.md +0 -110
- package/specs/cli/spec.md +0 -73
- package/specs/cli/store-restore/spec.md +0 -74
- package/specs/cli/up/spec.md +0 -80
- package/specs/cli/validate/spec.md +0 -70
- package/specs/labels/spec.md +0 -91
- package/specs/service/container/spec.md +0 -120
- package/specs/service/kubernetes/spec.md +0 -97
- package/specs/service/spec.md +0 -105
- package/specs/task/caching/spec.md +0 -107
- package/specs/task/container/spec.md +0 -118
- package/specs/task/dependencies/spec.md +0 -70
- package/specs/task/extending/spec.md +0 -74
- package/specs/task/kubernetes/spec.md +0 -125
- package/specs/task/needs/spec.md +0 -59
- package/specs/task/spec.md +0 -87
- package/specs/task/watching/spec.md +0 -69
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializeWorkGraph = void 0;
|
|
4
|
+
const plan_work_tasks_1 = require("./utils/plan-work-tasks");
|
|
5
|
+
const validate_1 = require("./validate");
|
|
6
|
+
function buildGraph(workTree) {
|
|
7
|
+
const taskItems = Array.from((0, plan_work_tasks_1.iterateWorkTasks)(workTree)).sort((a, b) => a.name.localeCompare(b.name));
|
|
8
|
+
const serviceItems = Array.from((0, plan_work_tasks_1.iterateWorkServices)(workTree)).sort((a, b) => a.name.localeCompare(b.name));
|
|
9
|
+
const nodes = [];
|
|
10
|
+
const taskNodeId = new Map();
|
|
11
|
+
const serviceNodeId = new Map();
|
|
12
|
+
taskItems.forEach((task, index) => {
|
|
13
|
+
const id = `t${index}`;
|
|
14
|
+
taskNodeId.set(task.name, id);
|
|
15
|
+
nodes.push({ id, name: task.name, kind: 'task' });
|
|
16
|
+
});
|
|
17
|
+
serviceItems.forEach((service, index) => {
|
|
18
|
+
const id = `s${index}`;
|
|
19
|
+
serviceNodeId.set(service.name, id);
|
|
20
|
+
nodes.push({ id, name: service.name, kind: 'service' });
|
|
21
|
+
});
|
|
22
|
+
const edges = [];
|
|
23
|
+
for (const task of taskItems) {
|
|
24
|
+
const from = taskNodeId.get(task.name);
|
|
25
|
+
if (!from) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const dep of task.deps) {
|
|
29
|
+
const to = taskNodeId.get(dep.name);
|
|
30
|
+
if (to) {
|
|
31
|
+
edges.push({ from, to, kind: 'deps' });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const need of task.needs) {
|
|
35
|
+
const to = serviceNodeId.get(need.service.name);
|
|
36
|
+
if (to) {
|
|
37
|
+
edges.push({ from, to, kind: 'needs' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.kind.localeCompare(b.kind));
|
|
42
|
+
let cycle = null;
|
|
43
|
+
for (const task of taskItems) {
|
|
44
|
+
const path = (0, validate_1.hasDependencyCycle)(task, []);
|
|
45
|
+
if (path && path.length > 0) {
|
|
46
|
+
cycle = path.map((n) => n.name);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { nodes, edges, cycle };
|
|
51
|
+
}
|
|
52
|
+
function mermaidLabel(name) {
|
|
53
|
+
return name.replace(/"/g, "'");
|
|
54
|
+
}
|
|
55
|
+
function renderMermaid(nodes, edges, cycle) {
|
|
56
|
+
const lines = ['graph TD'];
|
|
57
|
+
for (const node of nodes) {
|
|
58
|
+
// tasks are rectangles, services are stadium-shaped, so the two are visually distinct
|
|
59
|
+
lines.push(node.kind === 'task'
|
|
60
|
+
? ` ${node.id}["${mermaidLabel(node.name)}"]`
|
|
61
|
+
: ` ${node.id}(["${mermaidLabel(node.name)}"])`);
|
|
62
|
+
}
|
|
63
|
+
for (const edge of edges) {
|
|
64
|
+
// deps are solid arrows, needs are dotted — distinct edge types
|
|
65
|
+
lines.push(edge.kind === 'deps' ? ` ${edge.from} --> ${edge.to}` : ` ${edge.from} -.-> ${edge.to}`);
|
|
66
|
+
}
|
|
67
|
+
if (cycle) {
|
|
68
|
+
lines.push(` %% cycle detected: ${cycle.join(' -> ')}`);
|
|
69
|
+
}
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
function dotId(id) {
|
|
73
|
+
return `"${id}"`;
|
|
74
|
+
}
|
|
75
|
+
function dotLabel(name) {
|
|
76
|
+
return name.replace(/"/g, '\\"');
|
|
77
|
+
}
|
|
78
|
+
function renderDot(nodes, edges, cycle) {
|
|
79
|
+
const lines = ['digraph hammerkit {'];
|
|
80
|
+
if (cycle) {
|
|
81
|
+
lines.push(` // cycle detected: ${cycle.join(' -> ')}`);
|
|
82
|
+
}
|
|
83
|
+
for (const node of nodes) {
|
|
84
|
+
const shape = node.kind === 'task' ? 'box' : 'ellipse';
|
|
85
|
+
lines.push(` ${dotId(node.id)} [label="${dotLabel(node.name)}" shape=${shape}];`);
|
|
86
|
+
}
|
|
87
|
+
for (const edge of edges) {
|
|
88
|
+
const style = edge.kind === 'needs' ? ' [style=dotted]' : '';
|
|
89
|
+
lines.push(` ${dotId(edge.from)} -> ${dotId(edge.to)}${style};`);
|
|
90
|
+
}
|
|
91
|
+
lines.push('}');
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
// Serialize the planner's already-built work graph (tasks, services, deps and
|
|
95
|
+
// needs edges) into a renderable format. A cyclic graph still renders and marks
|
|
96
|
+
// the cycle rather than crashing.
|
|
97
|
+
function serializeWorkGraph(workTree, format) {
|
|
98
|
+
const { nodes, edges, cycle } = buildGraph(workTree);
|
|
99
|
+
const output = format === 'dot' ? renderDot(nodes, edges, cycle) : renderMermaid(nodes, edges, cycle);
|
|
100
|
+
return { output, cycle };
|
|
101
|
+
}
|
|
102
|
+
exports.serializeWorkGraph = serializeWorkGraph;
|
|
103
|
+
//# sourceMappingURL=work-graph-serializer.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getWorkInstanceId = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
// `id()` is the portable cache identity: identical for the same task in every
|
|
6
|
+
// checkout, so a shared cache hits across machines. Machine-local runtime state
|
|
7
|
+
// (docker container labels, the cache staging directory) must instead stay
|
|
8
|
+
// scoped to one checkout — two worktrees of the same repo on one host would
|
|
9
|
+
// otherwise see each other's paused containers and staging files. The instance
|
|
10
|
+
// id folds the absolute project root into the portable id for that purpose.
|
|
11
|
+
function getWorkInstanceId(item) {
|
|
12
|
+
return (0, crypto_1.createHash)('sha1').update(`${item.id()}\0${item.data.projectRoot}`).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
exports.getWorkInstanceId = getWorkInstanceId;
|
|
15
|
+
//# sourceMappingURL=work-instance-id.js.map
|
|
@@ -9,14 +9,17 @@ const using_container_1 = require("../docker/using-container");
|
|
|
9
9
|
const event_cache_1 = require("../executer/event-cache");
|
|
10
10
|
const get_docker_executor_1 = require("../executer/get-docker-executor");
|
|
11
11
|
const path_1 = require("path");
|
|
12
|
+
const tar_1 = require("tar");
|
|
12
13
|
const plan_work_volume_1 = require("./utils/plan-work-volume");
|
|
14
|
+
const work_instance_id_1 = require("./work-instance-id");
|
|
15
|
+
const stream_1 = require("stream");
|
|
13
16
|
function dockerTaskRuntime(task, workEnvironment) {
|
|
14
17
|
const docker = (0, execute_docker_1.getContainerCli)(workEnvironment);
|
|
15
18
|
return {
|
|
16
19
|
async initialize(state) {
|
|
17
20
|
const currentTasks = await docker.listContainers({
|
|
18
21
|
filters: {
|
|
19
|
-
label: [`hammerkit-id=${
|
|
22
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(task)}`],
|
|
20
23
|
},
|
|
21
24
|
});
|
|
22
25
|
const currentTask = currentTasks[0];
|
|
@@ -40,7 +43,7 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
40
43
|
const containers = await docker.listContainers({
|
|
41
44
|
all: true,
|
|
42
45
|
filters: {
|
|
43
|
-
label: [`hammerkit-id=${
|
|
46
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(task)}`],
|
|
44
47
|
},
|
|
45
48
|
});
|
|
46
49
|
for (const container of containers) {
|
|
@@ -66,11 +69,11 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
},
|
|
69
|
-
async currentStateKey() {
|
|
72
|
+
async currentStateKey(environment) {
|
|
70
73
|
const containers = await docker.listContainers({
|
|
71
74
|
all: true,
|
|
72
75
|
filters: {
|
|
73
|
-
label: [`hammerkit-id=${
|
|
76
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(task)}`],
|
|
74
77
|
},
|
|
75
78
|
});
|
|
76
79
|
const container = containers[0];
|
|
@@ -80,11 +83,26 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
80
83
|
if (!container.Labels['hammerkit-state']) {
|
|
81
84
|
return null;
|
|
82
85
|
}
|
|
86
|
+
// outputs removed since the run (a deleted export, a pruned volume)
|
|
87
|
+
// leave nothing to reuse
|
|
88
|
+
for (const generate of task.data.generates.filter((g) => !g.inherited)) {
|
|
89
|
+
const present = isOnHost(generate)
|
|
90
|
+
? await environment.file.exists(generate.path)
|
|
91
|
+
: !!(await (0, get_docker_executor_1.existsVolume)(docker, generate.volumeName));
|
|
92
|
+
if (!present) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
83
96
|
return container.Labels['hammerkit-state'];
|
|
84
97
|
},
|
|
85
98
|
};
|
|
86
99
|
}
|
|
87
100
|
exports.dockerTaskRuntime = dockerTaskRuntime;
|
|
101
|
+
// File outputs are bind mounts of host files and exported directories are
|
|
102
|
+
// copied to the host, so both must be present on the host, not only in a volume.
|
|
103
|
+
function isOnHost(generate) {
|
|
104
|
+
return generate.isFile || generate.export;
|
|
105
|
+
}
|
|
88
106
|
async function restoreContainer(docker, environment, item, path) {
|
|
89
107
|
await (0, using_container_1.usingContainer)(docker, item, {
|
|
90
108
|
abortSignal: environment.abortCtrl.signal,
|
|
@@ -95,25 +113,44 @@ async function restoreContainer(docker, environment, item, path) {
|
|
|
95
113
|
WorkingDir: (0, execute_docker_1.convertToPosixPath)(item.data.cwd),
|
|
96
114
|
Labels: {
|
|
97
115
|
app: 'hammerkit',
|
|
98
|
-
'hammerkit-id':
|
|
116
|
+
'hammerkit-id': (0, work_instance_id_1.getWorkInstanceId)(item),
|
|
99
117
|
'hammerkit-pid': process.pid.toString(),
|
|
100
118
|
'hammerkit-type': 'task',
|
|
101
119
|
},
|
|
102
120
|
HostConfig: {
|
|
103
121
|
AutoRemove: true,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
// mount the same volumes archiveContainer reads from, so the restored
|
|
123
|
+
// archives land in the task's output volumes rather than in this
|
|
124
|
+
// throwaway container's filesystem
|
|
125
|
+
Binds: item.data.type === 'container-service'
|
|
126
|
+
? item.data.volumes
|
|
127
|
+
.filter((v) => !v.inherited)
|
|
128
|
+
.map((v) => `${v.name}:${(0, execute_docker_1.convertToPosixPath)(v.containerPath)}`)
|
|
129
|
+
: item.data.generates
|
|
130
|
+
.filter((v) => !v.inherited && !v.isFile)
|
|
131
|
+
.map((v) => `${v.volumeName}:${(0, execute_docker_1.convertToPosixPath)(v.path)}`),
|
|
107
132
|
},
|
|
108
133
|
}, null, async (container) => {
|
|
109
134
|
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
110
135
|
if (await environment.file.exists(generate.filename)) {
|
|
136
|
+
// a cache hit means exactly the stored outputs: whatever a failed or
|
|
137
|
+
// older run left in the volume goes first
|
|
138
|
+
await (0, execute_docker_1.clearContainerDirectory)(item.status, environment, container, generate.path);
|
|
111
139
|
await container.putArchive(environment.file.readStream(generate.filename), {
|
|
112
140
|
path: (0, path_1.dirname)(generate.path),
|
|
113
141
|
});
|
|
114
142
|
}
|
|
115
143
|
}
|
|
116
144
|
});
|
|
145
|
+
if (item.data.type === 'container-task') {
|
|
146
|
+
const onHost = new Set(item.data.generates.filter((g) => !g.inherited && isOnHost(g)).map((g) => g.path));
|
|
147
|
+
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
148
|
+
if (onHost.has(generate.path) && (await environment.file.exists(generate.filename))) {
|
|
149
|
+
await environment.file.remove(generate.path);
|
|
150
|
+
await (0, tar_1.extract)({ file: generate.filename, cwd: (0, path_1.dirname)(generate.path) });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
117
154
|
}
|
|
118
155
|
function dockerServiceRuntime(service, workEnvironment) {
|
|
119
156
|
const docker = (0, execute_docker_1.getContainerCli)(workEnvironment);
|
|
@@ -121,7 +158,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
121
158
|
async initialize(state) {
|
|
122
159
|
const currentServices = await docker.listContainers({
|
|
123
160
|
filters: {
|
|
124
|
-
label: [`hammerkit-id=${
|
|
161
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(service)}`],
|
|
125
162
|
},
|
|
126
163
|
});
|
|
127
164
|
const currentService = currentServices[0];
|
|
@@ -143,7 +180,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
143
180
|
const containers = await docker.listContainers({
|
|
144
181
|
all: true,
|
|
145
182
|
filters: {
|
|
146
|
-
label: [`hammerkit-id=${
|
|
183
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(service)}`],
|
|
147
184
|
},
|
|
148
185
|
});
|
|
149
186
|
for (const container of containers) {
|
|
@@ -173,7 +210,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
173
210
|
const containers = await docker.listContainers({
|
|
174
211
|
all: true,
|
|
175
212
|
filters: {
|
|
176
|
-
label: [`hammerkit-id=${
|
|
213
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(service)}`],
|
|
177
214
|
},
|
|
178
215
|
});
|
|
179
216
|
const container = containers[0];
|
|
@@ -198,7 +235,7 @@ async function archiveContainer(docker, environment, item, path) {
|
|
|
198
235
|
WorkingDir: (0, execute_docker_1.convertToPosixPath)(item.data.cwd),
|
|
199
236
|
Labels: {
|
|
200
237
|
app: 'hammerkit',
|
|
201
|
-
'hammerkit-id':
|
|
238
|
+
'hammerkit-id': (0, work_instance_id_1.getWorkInstanceId)(item),
|
|
202
239
|
'hammerkit-pid': process.pid.toString(),
|
|
203
240
|
'hammerkit-type': 'task',
|
|
204
241
|
},
|
|
@@ -213,10 +250,16 @@ async function archiveContainer(docker, environment, item, path) {
|
|
|
213
250
|
.map((v) => `${v.volumeName}:${(0, execute_docker_1.convertToPosixPath)(v.path)}`),
|
|
214
251
|
},
|
|
215
252
|
}, null, async (container) => {
|
|
253
|
+
const hostFiles = new Set(item.data.type === 'container-task'
|
|
254
|
+
? item.data.generates.filter((g) => !g.inherited && g.isFile).map((g) => g.path)
|
|
255
|
+
: []);
|
|
216
256
|
for (const generatedArchive of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
257
|
+
// a file output is a bind mount of a host file, so archive it from the host
|
|
258
|
+
const readable = hostFiles.has(generatedArchive.path)
|
|
259
|
+
? stream_1.Readable.from((0, tar_1.create)({ cwd: (0, path_1.dirname)(generatedArchive.path) }, [(0, path_1.basename)(generatedArchive.path)]))
|
|
260
|
+
: await container.getArchive({
|
|
261
|
+
path: generatedArchive.path,
|
|
262
|
+
});
|
|
220
263
|
await environment.file.writeStream(generatedArchive.filename, readable);
|
|
221
264
|
}
|
|
222
265
|
});
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.kubernetesForwardRuntime = exports.kubernetesServiceRuntime = exports.kubernetesTaskRuntime = void 0;
|
|
7
|
+
const abort_1 = require("../executer/abort");
|
|
7
8
|
const kubernetes_service_1 = require("../executer/kubernetes-service");
|
|
8
9
|
const kubernetes_instance_1 = require("../kubernetes/kubernetes-instance");
|
|
9
10
|
const replace_env_variables_1 = require("../environment/replace-env-variables");
|
|
@@ -15,7 +16,6 @@ const ensure_persistent_data_1 = require("../kubernetes/ensure-persistent-data")
|
|
|
15
16
|
const await_running_state_1 = require("../kubernetes/await-running-state");
|
|
16
17
|
const volumes_1 = require("../kubernetes/volumes");
|
|
17
18
|
const resources_1 = require("../kubernetes/resources");
|
|
18
|
-
const ensure_ingress_1 = require("../kubernetes/ensure-ingress");
|
|
19
19
|
const ensure_http_route_1 = require("../kubernetes/ensure-http-route");
|
|
20
20
|
const find_process_1 = __importDefault(require("find-process"));
|
|
21
21
|
const log_1 = require("../log");
|
|
@@ -23,9 +23,12 @@ const version_1 = require("../version");
|
|
|
23
23
|
const restore_kubernetes_data_1 = require("../kubernetes/restore-kubernetes-data");
|
|
24
24
|
const store_kubernetes_data_1 = require("../kubernetes/store-kubernetes-data");
|
|
25
25
|
const remove_persistent_data_1 = require("../kubernetes/remove-persistent-data");
|
|
26
|
-
function
|
|
27
|
-
const
|
|
28
|
-
|
|
26
|
+
async function listJobNames(instance, kubernetes, task) {
|
|
27
|
+
const jobs = await instance.batchApi.listNamespacedJob({
|
|
28
|
+
namespace: kubernetes.namespace,
|
|
29
|
+
labelSelector: `hammerkit.dev/id=${task.id()}`,
|
|
30
|
+
});
|
|
31
|
+
return jobs.items.flatMap((job) => (job.metadata?.name ? [job.metadata.name] : []));
|
|
29
32
|
}
|
|
30
33
|
function kubernetesTaskRuntime(task, kubernetes) {
|
|
31
34
|
const instance = (0, kubernetes_instance_1.createKubernetesInstances)(kubernetes);
|
|
@@ -44,6 +47,11 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
44
47
|
const persistence = await (0, volumes_1.getKubernetesPersistence)(task);
|
|
45
48
|
await (0, ensure_namespace_1.ensureNamespace)(instance, kubernetes.namespace);
|
|
46
49
|
await (0, ensure_persistent_data_1.ensurePersistentData)(instance, kubernetes, environment, task, persistence);
|
|
50
|
+
// The last job of a run stays: its state label is what currentStateKey
|
|
51
|
+
// reads. Jobs from an earlier run of this task go before this one starts.
|
|
52
|
+
for (const name of await listJobNames(instance, kubernetes, task)) {
|
|
53
|
+
await (0, await_running_state_1.deleteJobAndWait)(instance, kubernetes, name, options.abort);
|
|
54
|
+
}
|
|
47
55
|
const podName = `${task.name}-${options.stateKey}`;
|
|
48
56
|
let i = 0;
|
|
49
57
|
for (const cmd of task.data.cmds) {
|
|
@@ -95,12 +103,19 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
95
103
|
try {
|
|
96
104
|
const pod = await (0, apply_1.apply)(instance, spec);
|
|
97
105
|
if (!pod.status?.succeeded) {
|
|
98
|
-
await (0, await_running_state_1.
|
|
106
|
+
await (0, await_running_state_1.awaitJobCompletion)(instance, kubernetes, spec.metadata.name, options.abort);
|
|
99
107
|
}
|
|
100
108
|
task.status.write('debug', 'pod completed');
|
|
101
|
-
|
|
109
|
+
if (i < task.data.cmds.length) {
|
|
110
|
+
await (0, await_running_state_1.deleteJobAndWait)(instance, kubernetes, podName, options.abort);
|
|
111
|
+
}
|
|
102
112
|
}
|
|
103
113
|
catch (e) {
|
|
114
|
+
if (e instanceof abort_1.AbortError) {
|
|
115
|
+
options.state.set({ stateKey: options.stateKey, type: 'canceled' });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await (0, await_running_state_1.deleteJob)(instance, kubernetes, podName).catch(() => undefined);
|
|
104
119
|
options.state.set({
|
|
105
120
|
stateKey: options.stateKey,
|
|
106
121
|
type: 'error',
|
|
@@ -111,22 +126,34 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
111
126
|
}
|
|
112
127
|
},
|
|
113
128
|
async stop() {
|
|
114
|
-
const jobs = await instance.batchApi.listNamespacedJob(
|
|
115
|
-
|
|
129
|
+
const jobs = await instance.batchApi.listNamespacedJob({
|
|
130
|
+
namespace: kubernetes.namespace,
|
|
131
|
+
labelSelector: `hammerkit.dev/id=${task.id()}`,
|
|
132
|
+
});
|
|
133
|
+
for (const pod of jobs.items) {
|
|
116
134
|
if (pod.metadata?.name) {
|
|
117
|
-
await instance.batchApi.deleteNamespacedJob(pod.metadata.name, kubernetes.namespace);
|
|
135
|
+
await instance.batchApi.deleteNamespacedJob({ name: pod.metadata.name, namespace: kubernetes.namespace });
|
|
118
136
|
}
|
|
119
137
|
}
|
|
120
|
-
const deployments = await instance.appsApi.listNamespacedDeployment(
|
|
121
|
-
|
|
138
|
+
const deployments = await instance.appsApi.listNamespacedDeployment({
|
|
139
|
+
namespace: kubernetes.namespace,
|
|
140
|
+
labelSelector: `hammerkit.dev/id=${task.id()}`,
|
|
141
|
+
});
|
|
142
|
+
for (const deploy of deployments.items) {
|
|
122
143
|
if (deploy.metadata?.name) {
|
|
123
|
-
await instance.appsApi.deleteNamespacedDeployment(
|
|
144
|
+
await instance.appsApi.deleteNamespacedDeployment({
|
|
145
|
+
name: deploy.metadata.name,
|
|
146
|
+
namespace: kubernetes.namespace,
|
|
147
|
+
});
|
|
124
148
|
}
|
|
125
149
|
}
|
|
126
|
-
const pods = await instance.coreApi.listNamespacedPod(
|
|
127
|
-
|
|
150
|
+
const pods = await instance.coreApi.listNamespacedPod({
|
|
151
|
+
namespace: kubernetes.namespace,
|
|
152
|
+
labelSelector: `hammerkit.dev/id=${task.id()}`,
|
|
153
|
+
});
|
|
154
|
+
for (const pod of pods.items) {
|
|
128
155
|
if (pod.metadata?.name) {
|
|
129
|
-
await instance.coreApi.deleteNamespacedPod(pod.metadata.name, kubernetes.namespace);
|
|
156
|
+
await instance.coreApi.deleteNamespacedPod({ name: pod.metadata.name, namespace: kubernetes.namespace });
|
|
130
157
|
}
|
|
131
158
|
}
|
|
132
159
|
},
|
|
@@ -135,11 +162,32 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
135
162
|
await (0, remove_persistent_data_1.removePersistentData)(instance, kubernetes, task);
|
|
136
163
|
},
|
|
137
164
|
async currentStateKey() {
|
|
138
|
-
const jobs = await instance.batchApi.listNamespacedJob(
|
|
139
|
-
|
|
165
|
+
const jobs = await instance.batchApi.listNamespacedJob({
|
|
166
|
+
namespace: kubernetes.namespace,
|
|
167
|
+
labelSelector: `hammerkit.dev/id=${task.id()}`,
|
|
168
|
+
});
|
|
169
|
+
const completedStates = jobs.items
|
|
140
170
|
.filter((j) => j.status?.succeeded && j.metadata?.labels?.['hammerkit.dev/state'])
|
|
141
171
|
.map((j) => j.metadata.labels['hammerkit.dev/state']);
|
|
142
|
-
|
|
172
|
+
const stateKey = completedStates[completedStates.length - 1] ?? null;
|
|
173
|
+
if (stateKey === null || !task.data.generates.some((g) => !g.inherited)) {
|
|
174
|
+
return stateKey;
|
|
175
|
+
}
|
|
176
|
+
// outputs live in the task's claim; without it the finished job left
|
|
177
|
+
// nothing to reuse
|
|
178
|
+
try {
|
|
179
|
+
const claim = await instance.coreApi.readNamespacedPersistentVolumeClaim({
|
|
180
|
+
name: (0, volumes_1.getVolumeName)(task),
|
|
181
|
+
namespace: kubernetes.namespace,
|
|
182
|
+
});
|
|
183
|
+
return claim.metadata?.deletionTimestamp ? null : stateKey;
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
if ((0, apply_1.statusCodeOf)(e) === 404) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
143
191
|
},
|
|
144
192
|
};
|
|
145
193
|
}
|
|
@@ -157,10 +205,16 @@ function kubernetesServiceRuntime(service, kubernetes) {
|
|
|
157
205
|
await (0, store_kubernetes_data_1.storeKubernetesData)(service, kubernetes, instance, environment, path);
|
|
158
206
|
},
|
|
159
207
|
async stop() {
|
|
160
|
-
const deployments = await instance.appsApi.listNamespacedDeployment(
|
|
161
|
-
|
|
208
|
+
const deployments = await instance.appsApi.listNamespacedDeployment({
|
|
209
|
+
namespace: kubernetes.namespace,
|
|
210
|
+
labelSelector: `hammerkit.dev/id=${service.id()}`,
|
|
211
|
+
});
|
|
212
|
+
for (const deployment of deployments.items) {
|
|
162
213
|
if (deployment.metadata?.name) {
|
|
163
|
-
await instance.appsApi.deleteNamespacedDeployment(
|
|
214
|
+
await instance.appsApi.deleteNamespacedDeployment({
|
|
215
|
+
name: deployment.metadata.name,
|
|
216
|
+
namespace: kubernetes.namespace,
|
|
217
|
+
});
|
|
164
218
|
}
|
|
165
219
|
}
|
|
166
220
|
},
|
|
@@ -177,29 +231,16 @@ function kubernetesServiceRuntime(service, kubernetes) {
|
|
|
177
231
|
stateKey: options.stateKey,
|
|
178
232
|
remote: null,
|
|
179
233
|
});
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
if (ingress.kind === 'httproute') {
|
|
183
|
-
await (0, ensure_http_route_1.ensureHttpRoute)(instance, kubernetes, ingress, service);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
await (0, ensure_ingress_1.ensureIngress)(instance, kubernetes, ingress, service);
|
|
187
|
-
}
|
|
234
|
+
for (const route of kubernetes.httpRoutes.filter((r) => r.service === service.name)) {
|
|
235
|
+
await (0, ensure_http_route_1.ensureHttpRoute)(instance, kubernetes, route, service);
|
|
188
236
|
}
|
|
189
237
|
},
|
|
190
238
|
async remove() {
|
|
191
|
-
const ingresses = await instance.networkingApi.listNamespacedIngress(kubernetes.namespace, undefined, undefined, undefined, undefined, `hammerkit.dev/id=${service.id()}`);
|
|
192
|
-
for (const ingress of ingresses.body.items) {
|
|
193
|
-
if (ingress.metadata?.name) {
|
|
194
|
-
await instance.networkingApi.deleteNamespacedIngress(ingress.metadata.name, kubernetes.namespace);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
239
|
// HTTPRoutes are a CRD, so they are deleted by name through the generic
|
|
198
240
|
// objectApi rather than listed (a list would error on clusters without the
|
|
199
|
-
// Gateway API CRDs even when none are declared). Only
|
|
200
|
-
//
|
|
201
|
-
const
|
|
202
|
-
for (const route of httpRoutes) {
|
|
241
|
+
// Gateway API CRDs even when none are declared). Only the routes this
|
|
242
|
+
// service declares are removed; a missing route (404) is fine.
|
|
243
|
+
for (const route of kubernetes.httpRoutes.filter((r) => r.service === service.name)) {
|
|
203
244
|
try {
|
|
204
245
|
await instance.objectApi.delete({
|
|
205
246
|
apiVersion: ensure_http_route_1.HTTP_ROUTE_API_VERSION,
|
|
@@ -208,23 +249,32 @@ function kubernetesServiceRuntime(service, kubernetes) {
|
|
|
208
249
|
});
|
|
209
250
|
}
|
|
210
251
|
catch (e) {
|
|
211
|
-
if (statusCodeOf(e) !== 404) {
|
|
252
|
+
if ((0, apply_1.statusCodeOf)(e) !== 404) {
|
|
212
253
|
throw e;
|
|
213
254
|
}
|
|
214
255
|
}
|
|
215
256
|
}
|
|
216
|
-
const services = await instance.coreApi.listNamespacedService(
|
|
217
|
-
|
|
257
|
+
const services = await instance.coreApi.listNamespacedService({
|
|
258
|
+
namespace: kubernetes.namespace,
|
|
259
|
+
labelSelector: `hammerkit.dev/id=${service.id()}`,
|
|
260
|
+
});
|
|
261
|
+
for (const service of services.items) {
|
|
218
262
|
if (service.metadata?.name) {
|
|
219
|
-
await instance.coreApi.deleteNamespacedService(
|
|
263
|
+
await instance.coreApi.deleteNamespacedService({
|
|
264
|
+
name: service.metadata.name,
|
|
265
|
+
namespace: kubernetes.namespace,
|
|
266
|
+
});
|
|
220
267
|
}
|
|
221
268
|
}
|
|
222
269
|
await this.stop();
|
|
223
270
|
await (0, remove_persistent_data_1.removePersistentData)(instance, kubernetes, service);
|
|
224
271
|
},
|
|
225
272
|
async currentStateKey() {
|
|
226
|
-
const deployments = await instance.appsApi.listNamespacedDeployment(
|
|
227
|
-
|
|
273
|
+
const deployments = await instance.appsApi.listNamespacedDeployment({
|
|
274
|
+
namespace: kubernetes.namespace,
|
|
275
|
+
labelSelector: `hammerkit.dev/id=${service.id()}`,
|
|
276
|
+
});
|
|
277
|
+
const deployment = deployments.items[0];
|
|
228
278
|
return deployment?.metadata?.labels?.['hammerkit.dev/state'] ?? null;
|
|
229
279
|
},
|
|
230
280
|
};
|
|
@@ -8,7 +8,9 @@ const tar_1 = require("tar");
|
|
|
8
8
|
const path_1 = require("path");
|
|
9
9
|
const local_task_1 = require("../executer/local-task");
|
|
10
10
|
const event_cache_1 = require("../executer/event-cache");
|
|
11
|
+
const get_outputs_to_reset_1 = require("./utils/get-outputs-to-reset");
|
|
11
12
|
const find_process_1 = __importDefault(require("find-process"));
|
|
13
|
+
const stream_1 = require("stream");
|
|
12
14
|
function getStateFilename(task) {
|
|
13
15
|
return (0, path_1.join)(task.data.cwd, '.hammerkit', `${task.id()}`);
|
|
14
16
|
}
|
|
@@ -56,8 +58,21 @@ function getLocalWorkRuntime(task) {
|
|
|
56
58
|
const pidFile = getPidFilename(task);
|
|
57
59
|
await environment.file.writeFile(pidFile, `${process.pid}`);
|
|
58
60
|
try {
|
|
61
|
+
// The record says "the outputs belong to this state key". A run that
|
|
62
|
+
// fails overwrites outputs, so the previous record goes first, and only
|
|
63
|
+
// a run that succeeded writes a new one.
|
|
64
|
+
const stateFile = getStateFilename(task);
|
|
65
|
+
if (await environment.file.exists(stateFile)) {
|
|
66
|
+
await environment.file.remove(stateFile);
|
|
67
|
+
}
|
|
68
|
+
for (const generate of (0, get_outputs_to_reset_1.getOutputsToReset)(task.data)) {
|
|
69
|
+
await environment.file.remove(generate.path);
|
|
70
|
+
}
|
|
59
71
|
await (0, local_task_1.localTask)(task, environment, options);
|
|
60
|
-
|
|
72
|
+
const failed = ['crash', 'error', 'canceled'].includes(options.state.current.type);
|
|
73
|
+
if (!failed) {
|
|
74
|
+
await environment.file.writeFile(stateFile, options.stateKey);
|
|
75
|
+
}
|
|
61
76
|
}
|
|
62
77
|
finally {
|
|
63
78
|
if (await environment.file.exists(pidFile)) {
|
|
@@ -71,10 +86,16 @@ function getLocalWorkRuntime(task) {
|
|
|
71
86
|
},
|
|
72
87
|
async currentStateKey(environment) {
|
|
73
88
|
const stateFileName = getStateFilename(task);
|
|
74
|
-
if (await environment.file.exists(stateFileName)) {
|
|
75
|
-
return
|
|
89
|
+
if (!(await environment.file.exists(stateFileName))) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
// outputs deleted since the run (rm -rf dist) leave nothing to reuse
|
|
93
|
+
for (const generate of task.data.generates) {
|
|
94
|
+
if (!generate.inherited && !(await environment.file.exists(generate.path))) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
76
97
|
}
|
|
77
|
-
return
|
|
98
|
+
return await environment.file.read(stateFileName);
|
|
78
99
|
},
|
|
79
100
|
async remove(environment) {
|
|
80
101
|
for (const generate of task.data.generates) {
|
|
@@ -90,15 +111,17 @@ function getLocalWorkRuntime(task) {
|
|
|
90
111
|
exports.getLocalWorkRuntime = getLocalWorkRuntime;
|
|
91
112
|
async function archiveLocal(environment, task, path) {
|
|
92
113
|
for (const generatedArchive of (0, event_cache_1.getArchivePaths)(task.data, path)) {
|
|
93
|
-
await environment.file.writeStream(generatedArchive.filename,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}, [(0, path_1.relative)(task.data.cwd, generatedArchive.path)]));
|
|
114
|
+
await environment.file.writeStream(generatedArchive.filename,
|
|
115
|
+
// tar's stream is a Minipass stream; Readable.from adapts it to a Node one
|
|
116
|
+
stream_1.Readable.from((0, tar_1.create)({ cwd: task.data.cwd, gzip: true }, [(0, path_1.relative)(task.data.cwd, generatedArchive.path)])));
|
|
97
117
|
}
|
|
98
118
|
}
|
|
99
119
|
async function restoreLocal(environment, task, path) {
|
|
100
120
|
for (const generate of (0, event_cache_1.getArchivePaths)(task, path)) {
|
|
101
121
|
if (await environment.file.exists(generate.filename)) {
|
|
122
|
+
// a cache hit means exactly the stored outputs: whatever a failed or
|
|
123
|
+
// older run left in the folder goes first
|
|
124
|
+
await environment.file.remove(generate.path);
|
|
102
125
|
await (0, tar_1.extract)({
|
|
103
126
|
file: generate.filename,
|
|
104
127
|
cwd: task.cwd,
|
|
@@ -3,19 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getWorkServiceId = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const work_service_1 = require("./work-service");
|
|
6
|
+
const portable_path_1 = require("./utils/portable-path");
|
|
7
|
+
const plan_work_volume_1 = require("./utils/plan-work-volume");
|
|
8
|
+
// Paths are made project-relative (portablePath) so a service id is identical
|
|
9
|
+
// wherever the project is checked out, like a task id.
|
|
6
10
|
function getWorkServiceId(service) {
|
|
11
|
+
const portable = (path) => (0, portable_path_1.portablePath)(service.projectRoot, path);
|
|
7
12
|
const jsonData = JSON.stringify((0, work_service_1.isContainerWorkService)(service)
|
|
8
13
|
? {
|
|
9
|
-
cwd: service.cwd
|
|
14
|
+
cwd: portable(service.cwd),
|
|
10
15
|
image: service.image,
|
|
11
|
-
volumes: service.volumes
|
|
12
|
-
|
|
16
|
+
volumes: service.volumes
|
|
17
|
+
.map((v) =>
|
|
18
|
+
// a derived volume name hashes the absolute path, so only an
|
|
19
|
+
// explicitly declared name participates
|
|
20
|
+
v.name === (0, plan_work_volume_1.getVolumeName)(v.containerPath)
|
|
21
|
+
? portable(v.containerPath)
|
|
22
|
+
: `${v.name}:${portable(v.containerPath)}`)
|
|
23
|
+
.sort(),
|
|
24
|
+
src: service.src.map((s) => portable(s.absolutePath)).sort(),
|
|
13
25
|
mounts: service.mounts.map((m) => m.mount).sort(),
|
|
14
26
|
}
|
|
15
27
|
: {
|
|
16
28
|
context: service.context,
|
|
17
29
|
selector: service.selector,
|
|
18
|
-
kubeconfig: service.kubeconfig,
|
|
30
|
+
kubeconfig: portable(service.kubeconfig),
|
|
19
31
|
});
|
|
20
32
|
return (0, crypto_1.createHash)('sha1').update(jsonData).digest('hex');
|
|
21
33
|
}
|