hammerkit 1.7.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 +3 -3
- package/build.schema.json +10 -13
- package/dist/docker/using-container.js +12 -0
- package/dist/executer/docker-task.js +16 -2
- package/dist/executer/execute-docker.js +18 -2
- package/dist/executer/execute-work-task.js +17 -2
- package/dist/executer/execution-steps.js +3 -8
- package/dist/executer/get-container-binds.js +13 -1
- package/dist/index.js +14 -1
- package/dist/kubernetes/apply.js +6 -7
- package/dist/kubernetes/await-running-state.js +32 -46
- 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/planner/utils/append-work-environment.js +1 -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/work-runtime-docker.js +6 -1
- package/dist/planner/work-runtime-kubernetes.js +66 -48
- package/dist/planner/work-runtime-local.js +22 -5
- 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/envs-schema.js +1 -1
- package/package.json +9 -9
- package/dist/kubernetes/ensure-ingress.js +0 -54
- package/dist/schema/build-file-environment-schema-ingress.js +0 -14
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# About
|
|
2
2
|
|
|
3
|
-
<
|
|
3
|
+
<img src="website/public/logo.png" width="144" alt="hammerkit">
|
|
4
4
|
|
|
5
5
|
## What is hammerkit?
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ Hammerkit is a build tool intended to build software projects in **containers**
|
|
|
9
9
|
* the build tools come from the container image, so they are the same across all machines.
|
|
10
10
|
* the build process is isolated since every file in the container has to be declared as a source or an output. Therefore, side effects can be reduced.
|
|
11
11
|
|
|
12
|
-
For a fuller pitch — including how it compares to Make, npm scripts, Taskfile and Earthly — see [Why hammerkit](docs/why-hammerkit
|
|
12
|
+
For a fuller pitch — including how it compares to Make, npm scripts, Taskfile and Earthly — see [Why hammerkit](https://hammerkit.dev/docs/why-hammerkit).
|
|
13
13
|
|
|
14
14
|
There are a lot of containerized CI systems that allow writing containerized builds today, but they have shortcomings that hammerkit tries to address:
|
|
15
15
|
|
|
@@ -23,4 +23,4 @@ Hammerkit, on the other hand, knows the source and output files of each build st
|
|
|
23
23
|
|
|
24
24
|
## Where to go next?
|
|
25
25
|
|
|
26
|
-
Check out the [docs](https://
|
|
26
|
+
Check out the [docs on hammerkit.dev](https://hammerkit.dev) for more info about how hammerkit can be used.
|
package/build.schema.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"number"
|
|
13
13
|
]
|
|
14
14
|
},
|
|
15
|
-
"description": "Environment values for the current build task\nhttps://
|
|
15
|
+
"description": "Environment values for the current build task\nhttps://hammerkit.dev/docs/build-file/environment-variables"
|
|
16
16
|
},
|
|
17
17
|
"tasks": {
|
|
18
18
|
"type": "object",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"envs": {
|
|
63
63
|
"$ref": "#/definitions/hammerkit/properties/envs",
|
|
64
|
-
"description": "Environment values for the current build task\nhttps://
|
|
64
|
+
"description": "Environment values for the current build task\nhttps://hammerkit.dev/docs/build-file/environment-variables"
|
|
65
65
|
},
|
|
66
66
|
"labels": {
|
|
67
67
|
"type": "object",
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
},
|
|
281
281
|
"envs": {
|
|
282
282
|
"$ref": "#/definitions/hammerkit/properties/envs",
|
|
283
|
-
"description": "Environment values for the current build task\nhttps://
|
|
283
|
+
"description": "Environment values for the current build task\nhttps://hammerkit.dev/docs/build-file/environment-variables"
|
|
284
284
|
},
|
|
285
285
|
"mounts": {
|
|
286
286
|
"type": "array",
|
|
@@ -431,18 +431,11 @@
|
|
|
431
431
|
"kubeconfig": {
|
|
432
432
|
"type": "string"
|
|
433
433
|
},
|
|
434
|
-
"
|
|
434
|
+
"httpRoutes": {
|
|
435
435
|
"type": "array",
|
|
436
436
|
"items": {
|
|
437
437
|
"type": "object",
|
|
438
438
|
"properties": {
|
|
439
|
-
"kind": {
|
|
440
|
-
"type": "string",
|
|
441
|
-
"enum": [
|
|
442
|
-
"ingress",
|
|
443
|
-
"httproute"
|
|
444
|
-
]
|
|
445
|
-
},
|
|
446
439
|
"host": {
|
|
447
440
|
"type": "string"
|
|
448
441
|
},
|
|
@@ -464,10 +457,14 @@
|
|
|
464
457
|
},
|
|
465
458
|
"required": [
|
|
466
459
|
"host",
|
|
467
|
-
"service"
|
|
460
|
+
"service",
|
|
461
|
+
"gateway"
|
|
468
462
|
],
|
|
469
463
|
"additionalProperties": false
|
|
470
464
|
}
|
|
465
|
+
},
|
|
466
|
+
"ingresses": {
|
|
467
|
+
"not": {}
|
|
471
468
|
}
|
|
472
469
|
},
|
|
473
470
|
"required": [
|
|
@@ -608,7 +605,7 @@
|
|
|
608
605
|
}
|
|
609
606
|
},
|
|
610
607
|
"additionalProperties": false,
|
|
611
|
-
"description": "Build file with support for containerization\nhttps://
|
|
608
|
+
"description": "Build file with support for containerization\nhttps://hammerkit.dev/docs/build-file"
|
|
612
609
|
}
|
|
613
610
|
},
|
|
614
611
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
@@ -39,6 +39,18 @@ async function usingContainer(docker, item, createOptions, stateKey, callback) {
|
|
|
39
39
|
if (container) {
|
|
40
40
|
await (0, remove_container_1.removeContainer)(container);
|
|
41
41
|
}
|
|
42
|
+
// A task run that failed has written into the outputs an earlier run's
|
|
43
|
+
// container still vouches for (its state label is what currentStateKey
|
|
44
|
+
// reads), so that record goes too.
|
|
45
|
+
if (stateKey) {
|
|
46
|
+
const containers = await docker.listContainers({
|
|
47
|
+
all: true,
|
|
48
|
+
filters: { label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(item)}`] },
|
|
49
|
+
});
|
|
50
|
+
for (const old of containers) {
|
|
51
|
+
await (0, remove_container_1.removeContainer)(docker.getContainer(old.Id));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
56
|
catch (e) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dockerTask = exports.getNeedsNetwork = void 0;
|
|
3
|
+
exports.dockerTask = exports.buildCreateOptions = exports.getNeedsNetwork = void 0;
|
|
4
4
|
const service_dns_1 = require("./service-dns");
|
|
5
5
|
const execute_docker_1 = require("./execute-docker");
|
|
6
6
|
const abort_1 = require("./abort");
|
|
@@ -13,6 +13,7 @@ const replace_env_variables_1 = require("../environment/replace-env-variables");
|
|
|
13
13
|
const get_container_binds_1 = require("./get-container-binds");
|
|
14
14
|
const get_service_containers_1 = require("./get-service-containers");
|
|
15
15
|
const work_instance_id_1 = require("../planner/work-instance-id");
|
|
16
|
+
const get_outputs_to_reset_1 = require("../planner/utils/get-outputs-to-reset");
|
|
16
17
|
function getNeedsNetwork(serviceContainers, needs) {
|
|
17
18
|
const links = [];
|
|
18
19
|
const hosts = [];
|
|
@@ -35,13 +36,17 @@ function buildCreateOptions(item, stateKey, serviceContainers, environment) {
|
|
|
35
36
|
const network = getNeedsNetwork(serviceContainers, item.needs);
|
|
36
37
|
const binds = (0, get_container_binds_1.getContainerBinds)(item);
|
|
37
38
|
const envs = (0, replace_env_variables_1.getEnvironmentVariables)(item.data.envs);
|
|
39
|
+
// Running as the host's uid (Linux), the user has no home in the image and
|
|
40
|
+
// Docker sets HOME=/, which isn't writable: tools then fail to create their
|
|
41
|
+
// caches. Set on the container only, so it's not part of the cache key.
|
|
42
|
+
const home = item.data.user && !('HOME' in envs) ? { HOME: '/tmp' } : {};
|
|
38
43
|
return {
|
|
39
44
|
abortSignal: environment.abortCtrl.signal,
|
|
40
45
|
Image: item.data.image,
|
|
41
46
|
Tty: true,
|
|
42
47
|
Entrypoint: [item.data.shell],
|
|
43
48
|
Cmd: ['-c', 'sleep 3600'],
|
|
44
|
-
Env: Object.entries(envs).map(([key, value]) => `${key}=${value}`),
|
|
49
|
+
Env: Object.entries({ ...envs, ...home }).map(([key, value]) => `${key}=${value}`),
|
|
45
50
|
WorkingDir: (0, execute_docker_1.convertToPosixPath)(item.data.cwd),
|
|
46
51
|
Labels: {
|
|
47
52
|
app: 'hammerkit',
|
|
@@ -58,9 +63,15 @@ function buildCreateOptions(item, stateKey, serviceContainers, environment) {
|
|
|
58
63
|
},
|
|
59
64
|
};
|
|
60
65
|
}
|
|
66
|
+
exports.buildCreateOptions = buildCreateOptions;
|
|
61
67
|
async function dockerTask(docker, item, environment, options) {
|
|
62
68
|
item.status.write('info', `execute ${item.name} in container`);
|
|
63
69
|
try {
|
|
70
|
+
const outputsToReset = (0, get_outputs_to_reset_1.getOutputsToReset)(item.data);
|
|
71
|
+
// outputs on the host: file outputs (bind mounts) and exported directories
|
|
72
|
+
for (const generate of outputsToReset.filter((g) => g.isFile || g.export)) {
|
|
73
|
+
await environment.file.remove(generate.path);
|
|
74
|
+
}
|
|
64
75
|
await (0, execution_steps_1.prepareMounts)(item, environment);
|
|
65
76
|
(0, abort_1.checkForAbort)(options.abort);
|
|
66
77
|
await (0, execution_steps_1.pullImage)(item, docker);
|
|
@@ -72,6 +83,9 @@ async function dockerTask(docker, item, environment, options) {
|
|
|
72
83
|
(0, print_container_options_1.printContainerOptions)(item.status, containerOptions);
|
|
73
84
|
await (0, using_container_1.usingContainer)(docker, item, containerOptions, options.stateKey, async (container) => {
|
|
74
85
|
await (0, execution_steps_1.setUserPermissions)(item, container, environment);
|
|
86
|
+
for (const generate of outputsToReset.filter((g) => !g.isFile)) {
|
|
87
|
+
await (0, execute_docker_1.clearContainerDirectory)(item.status, environment, container, generate.path);
|
|
88
|
+
}
|
|
75
89
|
for (const cmd of item.data.cmds) {
|
|
76
90
|
(0, abort_1.checkForAbort)(options.abort);
|
|
77
91
|
item.status.write('info', `execute cmd "${cmd.cmd}" in container`);
|
|
@@ -3,16 +3,22 @@ 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.execCommand = exports.convertToPosixPath = exports.startContainer = exports.getContainerCli = void 0;
|
|
6
|
+
exports.clearContainerDirectory = exports.execCommand = exports.convertToPosixPath = exports.startContainer = exports.getContainerCli = exports.createDockerClient = void 0;
|
|
7
7
|
const stream_1 = require("../docker/stream");
|
|
8
8
|
const dockerode_1 = __importDefault(require("dockerode"));
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const os_1 = require("os");
|
|
11
11
|
const abort_event_1 = require("../utils/abort-event");
|
|
12
12
|
let dockerInstance = null;
|
|
13
|
+
// Without options dockerode reads DOCKER_HOST and the TLS variables; passing
|
|
14
|
+
// `{ host: undefined }` would overwrite the host it parsed from DOCKER_HOST.
|
|
15
|
+
function createDockerClient(host) {
|
|
16
|
+
return host ? new dockerode_1.default({ host }) : new dockerode_1.default();
|
|
17
|
+
}
|
|
18
|
+
exports.createDockerClient = createDockerClient;
|
|
13
19
|
function getContainerCli(workEnvironment) {
|
|
14
20
|
if (!dockerInstance) {
|
|
15
|
-
dockerInstance =
|
|
21
|
+
dockerInstance = createDockerClient(workEnvironment.host);
|
|
16
22
|
}
|
|
17
23
|
return dockerInstance;
|
|
18
24
|
}
|
|
@@ -127,4 +133,14 @@ async function execCommand(status, environment, container, cwd, cmd, user, timeo
|
|
|
127
133
|
});
|
|
128
134
|
}
|
|
129
135
|
exports.execCommand = execCommand;
|
|
136
|
+
// Empty a directory inside a running container, as root. Volumes are emptied
|
|
137
|
+
// this way rather than recreated: Docker refuses to remove a volume while any
|
|
138
|
+
// container (a dependent's paused one, say) still mounts it.
|
|
139
|
+
async function clearContainerDirectory(status, environment, container, path) {
|
|
140
|
+
const result = await execCommand(status, environment, container, '/', ['sh', '-c', 'rm -rf "$1"/* "$1"/.[!.]* "$1"/..?*', 'sh', convertToPosixPath(path)], null, undefined, environment.abortCtrl.signal);
|
|
141
|
+
if (result.type !== 'result' || result.result.ExitCode !== 0) {
|
|
142
|
+
throw new Error(`unable to empty ${path}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.clearContainerDirectory = clearContainerDirectory;
|
|
130
146
|
//# sourceMappingURL=execute-docker.js.map
|
|
@@ -51,6 +51,15 @@ function withDeadline(parent, timeout) {
|
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
// A task without cmds only groups its dependencies (`ci: { deps: [test, build] }`).
|
|
55
|
+
// Having no src of its own it is never a cache hit, so its dependencies are
|
|
56
|
+
// always checked and their outputs restored. When every one of them was a hit,
|
|
57
|
+
// nothing ran: report the group as cached too. Reporting only, the cache
|
|
58
|
+
// decision is untouched.
|
|
59
|
+
function onlyGroupsCacheHits(work) {
|
|
60
|
+
return (work.data.cmds.length === 0 &&
|
|
61
|
+
work.deps.every((dep) => dep.state.current.type === 'completed' && dep.state.current.cached));
|
|
62
|
+
}
|
|
54
63
|
async function executeWorkTask(work, environment, options,
|
|
55
64
|
// when set, the task was not requested and only runs if one of these tasks
|
|
56
65
|
// depending on it misses the cache (see executeWorkTree)
|
|
@@ -139,12 +148,13 @@ dependents = null) {
|
|
|
139
148
|
if (work.state.current.type === 'running') {
|
|
140
149
|
work.status.write('debug', 'completed for state key ' + cacheState.stateKey);
|
|
141
150
|
await pushToBackend(work, environment, cacheState, options);
|
|
151
|
+
const cached = onlyGroupsCacheHits(work);
|
|
142
152
|
work.state.set({
|
|
143
153
|
stateKey: cacheState.stateKey,
|
|
144
154
|
type: 'completed',
|
|
145
|
-
cached
|
|
155
|
+
cached,
|
|
146
156
|
duration: (0, states_1.getDuration)(started),
|
|
147
|
-
missCauses,
|
|
157
|
+
missCauses: cached ? undefined : missCauses,
|
|
148
158
|
});
|
|
149
159
|
}
|
|
150
160
|
else {
|
|
@@ -170,6 +180,11 @@ dependents = null) {
|
|
|
170
180
|
errorMessage: (0, log_1.getErrorMessage)(e),
|
|
171
181
|
stateKey: null,
|
|
172
182
|
});
|
|
183
|
+
// Stop the run like a failing command does: dependents wait for this task
|
|
184
|
+
// to complete, which it now never will.
|
|
185
|
+
if (!options.watch) {
|
|
186
|
+
environment.abortCtrl.abort();
|
|
187
|
+
}
|
|
173
188
|
}
|
|
174
189
|
}
|
|
175
190
|
}
|
|
@@ -26,18 +26,13 @@ async function prepareVolume(item, docker) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
if ((0, work_task_1.isContainerWorkTask)(item.data)) {
|
|
29
|
+
// a task's outputs are emptied inside its container (see dockerTask)
|
|
29
30
|
for (const generate of item.data.generates) {
|
|
30
31
|
if (generate.isFile) {
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await (0, get_docker_executor_1.recreateVolume)(docker, item.status, generate.volumeName);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
item.status.write('debug', 'ensure volume exists');
|
|
39
|
-
await (0, get_docker_executor_1.ensureVolumeExists)(docker, item.status, generate.volumeName);
|
|
40
|
-
}
|
|
34
|
+
item.status.write('debug', 'ensure volume exists');
|
|
35
|
+
await (0, get_docker_executor_1.ensureVolumeExists)(docker, item.status, generate.volumeName);
|
|
41
36
|
}
|
|
42
37
|
}
|
|
43
38
|
}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getContainerBinds = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
function isInside(path, parent) {
|
|
6
|
+
const rel = (0, path_1.relative)(parent, path);
|
|
7
|
+
return rel !== '' && !rel.startsWith('..') && !(0, path_1.isAbsolute)(rel);
|
|
8
|
+
}
|
|
4
9
|
function getContainerBinds(item) {
|
|
10
|
+
const sources = item.data.src.map((s) => s.absolutePath);
|
|
5
11
|
const items = [
|
|
6
12
|
...item.data.mounts,
|
|
7
|
-
|
|
13
|
+
// A source inside another source (a dependency's `app/app.csproj` under the
|
|
14
|
+
// task's own `app`) is already visible through the outer bind. Binding it
|
|
15
|
+
// again would nest bind mounts, which Docker Desktop then refuses to let the
|
|
16
|
+
// host delete while the paused container exists.
|
|
17
|
+
...sources
|
|
18
|
+
.filter((path) => !sources.some((other) => isInside(path, other)))
|
|
19
|
+
.map((path) => ({ localPath: path, containerPath: path })),
|
|
8
20
|
...item.data.generates.filter((g) => !g.isFile).map((v) => ({ localPath: v.volumeName, containerPath: v.path })),
|
|
9
21
|
...item.data.generates.filter((g) => g.isFile).map((v) => ({ localPath: v.path, containerPath: v.path })),
|
|
10
22
|
];
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,16 @@ const abortCtrl = new AbortController();
|
|
|
10
10
|
process.on('SIGINT', function () {
|
|
11
11
|
abortCtrl.abort();
|
|
12
12
|
});
|
|
13
|
+
let settled = false;
|
|
14
|
+
// A run that never settles (a task waiting on something that can't happen)
|
|
15
|
+
// lets the event loop drain, and Node then exits 0: CI would report success
|
|
16
|
+
// for a build that never finished. Fail loudly instead.
|
|
17
|
+
process.on('beforeExit', () => {
|
|
18
|
+
if (!settled) {
|
|
19
|
+
process.stderr.write('hammerkit stopped before the run finished\n');
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
13
23
|
(0, run_program_1.runProgram)({
|
|
14
24
|
cwd: process.cwd(),
|
|
15
25
|
abortCtrl,
|
|
@@ -20,7 +30,10 @@ process.on('SIGINT', function () {
|
|
|
20
30
|
stdout: process.stdout,
|
|
21
31
|
stderr: process.stderr,
|
|
22
32
|
stdoutColumns: process.stdout.columns,
|
|
23
|
-
}, process.argv, false).
|
|
33
|
+
}, process.argv, false).then(() => {
|
|
34
|
+
settled = true;
|
|
35
|
+
}, () => {
|
|
36
|
+
settled = true;
|
|
24
37
|
process.exit(1);
|
|
25
38
|
});
|
|
26
39
|
//# sourceMappingURL=index.js.map
|
package/dist/kubernetes/apply.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.apply = exports.statusCodeOf = void 0;
|
|
4
|
+
// The client throws an ApiException carrying the HTTP status as `code`; watch
|
|
5
|
+
// errors carry it as `statusCode`.
|
|
4
6
|
function statusCodeOf(e) {
|
|
5
7
|
const err = e;
|
|
6
|
-
return err?.
|
|
8
|
+
return typeof err?.code === 'number' ? err.code : err?.statusCode;
|
|
7
9
|
}
|
|
8
10
|
exports.statusCodeOf = statusCodeOf;
|
|
9
11
|
async function apply(instance, spec) {
|
|
10
12
|
try {
|
|
11
13
|
await instance.objectApi.read(spec);
|
|
12
|
-
|
|
13
|
-
return response.body;
|
|
14
|
+
return await instance.objectApi.patch(spec);
|
|
14
15
|
}
|
|
15
16
|
catch (e) {
|
|
16
17
|
try {
|
|
17
|
-
|
|
18
|
-
return response.body;
|
|
18
|
+
return await instance.objectApi.create(spec);
|
|
19
19
|
}
|
|
20
20
|
catch (createError) {
|
|
21
21
|
// Two work items can apply the same object concurrently (e.g. a volume
|
|
@@ -23,8 +23,7 @@ async function apply(instance, spec) {
|
|
|
23
23
|
// lost the create race, the object now exists — read it back instead of
|
|
24
24
|
// failing the whole item.
|
|
25
25
|
if (statusCodeOf(createError) === 409) {
|
|
26
|
-
|
|
27
|
-
return response.body;
|
|
26
|
+
return await instance.objectApi.read(spec);
|
|
28
27
|
}
|
|
29
28
|
throw createError;
|
|
30
29
|
}
|
|
@@ -4,25 +4,21 @@ exports.deleteJobAndWait = exports.awaitDeployRunningState = exports.deleteJob =
|
|
|
4
4
|
const abort_1 = require("../executer/abort");
|
|
5
5
|
const abort_event_1 = require("../utils/abort-event");
|
|
6
6
|
const apply_1 = require("./apply");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
resolve();
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
});
|
|
7
|
+
const sleep_1 = require("../utils/sleep");
|
|
8
|
+
// The waits below poll instead of using the client's Watch: Watch aborts every
|
|
9
|
+
// request after 30s, which a pod or deployment still pulling its image outlasts.
|
|
10
|
+
async function awaitRunningState(instance, env, name, phase, pollInterval = 1000) {
|
|
11
|
+
for (;;) {
|
|
12
|
+
const pod = await instance.coreApi.readNamespacedPod({ name, namespace: env.namespace });
|
|
13
|
+
const current = pod.status?.phase;
|
|
14
|
+
if (current === phase) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (current === 'Succeeded' || current === 'Failed') {
|
|
18
|
+
throw new Error(`pod ${name} ended in phase ${current} before reaching ${phase}`);
|
|
19
|
+
}
|
|
20
|
+
await (0, sleep_1.sleep)(pollInterval);
|
|
21
|
+
}
|
|
26
22
|
}
|
|
27
23
|
exports.awaitRunningState = awaitRunningState;
|
|
28
24
|
// Wait for a job to finish by polling its status: resolves once it succeeded,
|
|
@@ -35,8 +31,8 @@ async function awaitJobCompletion(instance, env, name, abort, pollInterval = 100
|
|
|
35
31
|
await deleteJob(instance, env, name);
|
|
36
32
|
throw new abort_1.AbortError();
|
|
37
33
|
}
|
|
38
|
-
const job = await instance.batchApi.readNamespacedJobStatus(name, env.namespace);
|
|
39
|
-
const status = job.
|
|
34
|
+
const job = await instance.batchApi.readNamespacedJobStatus({ name, namespace: env.namespace });
|
|
35
|
+
const status = job.status;
|
|
40
36
|
if (status?.succeeded) {
|
|
41
37
|
return;
|
|
42
38
|
}
|
|
@@ -66,7 +62,12 @@ function sleep(ms, abort) {
|
|
|
66
62
|
}
|
|
67
63
|
async function deleteJob(instance, env, name) {
|
|
68
64
|
try {
|
|
69
|
-
await instance.batchApi.deleteNamespacedJob(
|
|
65
|
+
await instance.batchApi.deleteNamespacedJob({
|
|
66
|
+
name,
|
|
67
|
+
namespace: env.namespace,
|
|
68
|
+
gracePeriodSeconds: 0,
|
|
69
|
+
propagationPolicy: 'Background',
|
|
70
|
+
});
|
|
70
71
|
}
|
|
71
72
|
catch (e) {
|
|
72
73
|
if ((0, apply_1.statusCodeOf)(e) !== 404) {
|
|
@@ -75,29 +76,14 @@ async function deleteJob(instance, env, name) {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
exports.deleteJob = deleteJob;
|
|
78
|
-
function awaitDeployRunningState(instance, env, name) {
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
req.then((r) => r.abort());
|
|
87
|
-
resolve();
|
|
88
|
-
}
|
|
89
|
-
}, (err) => {
|
|
90
|
-
if (err && err.message === 'aborted') {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (err) {
|
|
94
|
-
reject(err);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
resolve();
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
});
|
|
79
|
+
async function awaitDeployRunningState(instance, env, name, pollInterval = 1000) {
|
|
80
|
+
for (;;) {
|
|
81
|
+
const deployment = await instance.appsApi.readNamespacedDeployment({ name, namespace: env.namespace });
|
|
82
|
+
if ((deployment.status?.readyReplicas ?? 0) > 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await (0, sleep_1.sleep)(pollInterval);
|
|
86
|
+
}
|
|
101
87
|
}
|
|
102
88
|
exports.awaitDeployRunningState = awaitDeployRunningState;
|
|
103
89
|
// Delete a job and wait until it is gone: its name is reused for the task's next
|
|
@@ -107,7 +93,7 @@ async function deleteJobAndWait(instance, env, name, abort, pollInterval = 500)
|
|
|
107
93
|
await deleteJob(instance, env, name);
|
|
108
94
|
for (;;) {
|
|
109
95
|
try {
|
|
110
|
-
await instance.batchApi.readNamespacedJob(name, env.namespace);
|
|
96
|
+
await instance.batchApi.readNamespacedJob({ name, namespace: env.namespace });
|
|
111
97
|
}
|
|
112
98
|
catch (e) {
|
|
113
99
|
if ((0, apply_1.statusCodeOf)(e) === 404) {
|
|
@@ -5,15 +5,12 @@ const apply_1 = require("./apply");
|
|
|
5
5
|
const resources_1 = require("./resources");
|
|
6
6
|
exports.HTTP_ROUTE_API_VERSION = 'gateway.networking.k8s.io/v1';
|
|
7
7
|
exports.HTTP_ROUTE_KIND = 'HTTPRoute';
|
|
8
|
-
async function ensureHttpRoute(instance, env,
|
|
9
|
-
if (!ingress.gateway) {
|
|
10
|
-
throw new Error(`httproute for ${ingress.host} requires a "gateway" (the parent Gateway name)`);
|
|
11
|
-
}
|
|
8
|
+
async function ensureHttpRoute(instance, env, route, service) {
|
|
12
9
|
const resource = {
|
|
13
10
|
apiVersion: exports.HTTP_ROUTE_API_VERSION,
|
|
14
11
|
kind: exports.HTTP_ROUTE_KIND,
|
|
15
12
|
metadata: {
|
|
16
|
-
name:
|
|
13
|
+
name: route.host,
|
|
17
14
|
namespace: env.namespace,
|
|
18
15
|
labels: {
|
|
19
16
|
'hammerkit.dev/id': service.id(),
|
|
@@ -22,25 +19,25 @@ async function ensureHttpRoute(instance, env, ingress, service) {
|
|
|
22
19
|
spec: {
|
|
23
20
|
parentRefs: [
|
|
24
21
|
{
|
|
25
|
-
name:
|
|
26
|
-
...(
|
|
22
|
+
name: route.gateway,
|
|
23
|
+
...(route.gatewayNamespace ? { namespace: route.gatewayNamespace } : {}),
|
|
27
24
|
},
|
|
28
25
|
],
|
|
29
|
-
hostnames: [
|
|
26
|
+
hostnames: [route.host],
|
|
30
27
|
rules: [
|
|
31
28
|
{
|
|
32
29
|
matches: [
|
|
33
30
|
{
|
|
34
31
|
path: {
|
|
35
32
|
type: 'PathPrefix',
|
|
36
|
-
value:
|
|
33
|
+
value: route.path ?? '/',
|
|
37
34
|
},
|
|
38
35
|
},
|
|
39
36
|
],
|
|
40
37
|
backendRefs: [
|
|
41
38
|
{
|
|
42
39
|
name: (0, resources_1.getResourceName)(service),
|
|
43
|
-
port:
|
|
40
|
+
port: route.servicePort ?? service.data.ports[0].containerPort,
|
|
44
41
|
},
|
|
45
42
|
],
|
|
46
43
|
},
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureNamespace = void 0;
|
|
4
|
-
|
|
5
|
-
const err = e;
|
|
6
|
-
return err?.statusCode ?? err?.response?.statusCode ?? err?.body?.code;
|
|
7
|
-
}
|
|
4
|
+
const apply_1 = require("./apply");
|
|
8
5
|
/**
|
|
9
6
|
* Idempotently make sure `namespace` exists. hammerkit does not otherwise
|
|
10
7
|
* create namespaces — deployments/services/jobs are applied straight into
|
|
@@ -16,25 +13,27 @@ function statusCodeOf(e) {
|
|
|
16
13
|
*/
|
|
17
14
|
async function ensureNamespace(instance, namespace) {
|
|
18
15
|
try {
|
|
19
|
-
await instance.coreApi.readNamespace(namespace);
|
|
16
|
+
await instance.coreApi.readNamespace({ name: namespace });
|
|
20
17
|
return;
|
|
21
18
|
}
|
|
22
19
|
catch (e) {
|
|
23
|
-
if (statusCodeOf(e) !== 404) {
|
|
20
|
+
if ((0, apply_1.statusCodeOf)(e) !== 404) {
|
|
24
21
|
throw e;
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
try {
|
|
28
25
|
await instance.coreApi.createNamespace({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
body: {
|
|
27
|
+
metadata: {
|
|
28
|
+
name: namespace,
|
|
29
|
+
labels: { 'hammerkit.dev/managed': 'true' },
|
|
30
|
+
},
|
|
32
31
|
},
|
|
33
32
|
});
|
|
34
33
|
}
|
|
35
34
|
catch (e) {
|
|
36
35
|
// another worker created it between our read and create
|
|
37
|
-
if (statusCodeOf(e) !== 409) {
|
|
36
|
+
if ((0, apply_1.statusCodeOf)(e) !== 409) {
|
|
38
37
|
throw e;
|
|
39
38
|
}
|
|
40
39
|
}
|
|
@@ -10,6 +10,7 @@ const sleep_1 = require("../utils/sleep");
|
|
|
10
10
|
const version_1 = require("../version");
|
|
11
11
|
const log_1 = require("../log");
|
|
12
12
|
const ensure_kubernetes_persistent_volume_claim_exists_1 = require("./ensure-kubernetes-persistent-volume-claim-exists");
|
|
13
|
+
const stream_1 = require("stream");
|
|
13
14
|
// A previous run that crashed between creating an upload/download pod and its
|
|
14
15
|
// `finally` cleanup leaves a pod behind under the same deterministic name. apply()
|
|
15
16
|
// would then patch that stale pod in place — and if it is stuck in a terminal
|
|
@@ -17,7 +18,7 @@ const ensure_kubernetes_persistent_volume_claim_exists_1 = require("./ensure-kub
|
|
|
17
18
|
// and wait for it to disappear before scheduling a fresh one.
|
|
18
19
|
async function removeStalePod(instance, namespace, name) {
|
|
19
20
|
try {
|
|
20
|
-
await instance.coreApi.deleteNamespacedPod(name, namespace,
|
|
21
|
+
await instance.coreApi.deleteNamespacedPod({ name, namespace, gracePeriodSeconds: 0 });
|
|
21
22
|
}
|
|
22
23
|
catch (e) {
|
|
23
24
|
if ((0, apply_1.statusCodeOf)(e) === 404) {
|
|
@@ -27,7 +28,7 @@ async function removeStalePod(instance, namespace, name) {
|
|
|
27
28
|
}
|
|
28
29
|
for (let attempt = 0; attempt < 30; attempt++) {
|
|
29
30
|
try {
|
|
30
|
-
await instance.coreApi.readNamespacedPod(name, namespace);
|
|
31
|
+
await instance.coreApi.readNamespacedPod({ name, namespace });
|
|
31
32
|
}
|
|
32
33
|
catch (e) {
|
|
33
34
|
if ((0, apply_1.statusCodeOf)(e) === 404) {
|
|
@@ -100,7 +101,7 @@ async function getPodForPersistence(instance, env, service, persistence, type, f
|
|
|
100
101
|
}
|
|
101
102
|
finally {
|
|
102
103
|
service.status.console('stdout', `delete pod ${name}`);
|
|
103
|
-
await instance.coreApi.deleteNamespacedPod(name, env.namespace);
|
|
104
|
+
await instance.coreApi.deleteNamespacedPod({ name, namespace: env.namespace });
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
exports.getPodForPersistence = getPodForPersistence;
|
|
@@ -122,12 +123,12 @@ async function ensurePersistentData(instance, env, environment, service, persist
|
|
|
122
123
|
continue;
|
|
123
124
|
}
|
|
124
125
|
const fileItem = (0, path_1.basename)(source.localPath);
|
|
125
|
-
const data = (0, tar_1.create)({
|
|
126
|
+
const data = stream_1.Readable.from((0, tar_1.create)({
|
|
126
127
|
cwd: (0, path_1.dirname)(source.localPath),
|
|
127
128
|
filter(path) {
|
|
128
129
|
return source.matcher(path, source.localPath);
|
|
129
130
|
},
|
|
130
|
-
}, [fileItem]);
|
|
131
|
+
}, [fileItem]));
|
|
131
132
|
service.status.console('stdout', `upload ${source.localPath}`);
|
|
132
133
|
const targetPath = `/dev/hammerkit/${service.id()}`;
|
|
133
134
|
const res = await instance.exec.exec(env.namespace, name, 'volume', ['tar', 'xf', '-', '-C', targetPath], process.stdout, process.stderr, data, false, (status) => {
|
|
@@ -4,8 +4,8 @@ exports.getServiceIp = void 0;
|
|
|
4
4
|
const resources_1 = require("./resources");
|
|
5
5
|
async function getServiceIp(instance, env, workService) {
|
|
6
6
|
const name = (0, resources_1.getResourceName)(workService);
|
|
7
|
-
const svc = await instance.coreApi.readNamespacedService(name, env.namespace);
|
|
8
|
-
return svc.
|
|
7
|
+
const svc = await instance.coreApi.readNamespacedService({ name, namespace: env.namespace });
|
|
8
|
+
return svc.spec?.clusterIP ?? null;
|
|
9
9
|
}
|
|
10
10
|
exports.getServiceIp = getServiceIp;
|
|
11
11
|
//# sourceMappingURL=get-service-ip.js.map
|
|
@@ -16,14 +16,10 @@ function createKubernetesInstances(kubernetes) {
|
|
|
16
16
|
const coreApi = kc.makeApiClient(client_node_1.CoreV1Api);
|
|
17
17
|
const batchApi = kc.makeApiClient(client_node_1.BatchV1Api);
|
|
18
18
|
const appsApi = kc.makeApiClient(client_node_1.AppsV1Api);
|
|
19
|
-
const networkingApi = kc.makeApiClient(client_node_1.NetworkingV1Api);
|
|
20
19
|
const objectApi = kc.makeApiClient(client_node_1.KubernetesObjectApi);
|
|
21
20
|
const exec = new client_node_1.Exec(kc);
|
|
22
|
-
const watch = new client_node_1.Watch(kc);
|
|
23
21
|
return {
|
|
24
22
|
objectApi,
|
|
25
|
-
networkingApi,
|
|
26
|
-
watch,
|
|
27
23
|
exec,
|
|
28
24
|
appsApi,
|
|
29
25
|
coreApi,
|
|
@@ -2,16 +2,26 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.removePersistentData = void 0;
|
|
4
4
|
async function removePersistentData(instance, kubernetes, task) {
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const labelSelector = `hammerkit.dev/id=${task.id()}`;
|
|
6
|
+
const configmaps = await instance.coreApi.listNamespacedConfigMap({ namespace: kubernetes.namespace, labelSelector });
|
|
7
|
+
for (const configMap of configmaps.items) {
|
|
7
8
|
if (configMap.metadata?.name) {
|
|
8
|
-
await instance.coreApi.deleteNamespacedConfigMap(
|
|
9
|
+
await instance.coreApi.deleteNamespacedConfigMap({
|
|
10
|
+
name: configMap.metadata.name,
|
|
11
|
+
namespace: kubernetes.namespace,
|
|
12
|
+
});
|
|
9
13
|
}
|
|
10
14
|
}
|
|
11
|
-
const pvcs = await instance.coreApi.listNamespacedPersistentVolumeClaim(
|
|
12
|
-
|
|
15
|
+
const pvcs = await instance.coreApi.listNamespacedPersistentVolumeClaim({
|
|
16
|
+
namespace: kubernetes.namespace,
|
|
17
|
+
labelSelector,
|
|
18
|
+
});
|
|
19
|
+
for (const pvc of pvcs.items) {
|
|
13
20
|
if (pvc.metadata?.name) {
|
|
14
|
-
await instance.coreApi.deleteNamespacedPersistentVolumeClaim(
|
|
21
|
+
await instance.coreApi.deleteNamespacedPersistentVolumeClaim({
|
|
22
|
+
name: pvc.metadata.name,
|
|
23
|
+
namespace: kubernetes.namespace,
|
|
24
|
+
});
|
|
15
25
|
}
|
|
16
26
|
}
|
|
17
27
|
}
|
|
@@ -7,9 +7,9 @@ function formatSelector(matchLabels) {
|
|
|
7
7
|
.join(',');
|
|
8
8
|
}
|
|
9
9
|
async function findReadyPod(coreApi, namespace, labelSelector) {
|
|
10
|
-
const pods = await coreApi.listNamespacedPod(namespace,
|
|
11
|
-
const ready = pods.
|
|
12
|
-
const pod = ready ?? pods.
|
|
10
|
+
const pods = await coreApi.listNamespacedPod({ namespace, labelSelector });
|
|
11
|
+
const ready = pods.items.find((p) => p.status?.conditions?.some((c) => c.type === 'Ready' && c.status === 'True'));
|
|
12
|
+
const pod = ready ?? pods.items[0];
|
|
13
13
|
if (!pod?.metadata?.name) {
|
|
14
14
|
throw new Error(`no pod found for selector ${labelSelector} in namespace ${namespace}`);
|
|
15
15
|
}
|
|
@@ -21,16 +21,16 @@ async function resolvePodName(coreApi, appsApi, namespace, selector) {
|
|
|
21
21
|
return selector.name;
|
|
22
22
|
}
|
|
23
23
|
if (type === 'service') {
|
|
24
|
-
const svc = await coreApi.readNamespacedService(selector.name, namespace);
|
|
25
|
-
const matchLabels = svc.
|
|
24
|
+
const svc = await coreApi.readNamespacedService({ name: selector.name, namespace });
|
|
25
|
+
const matchLabels = svc.spec?.selector;
|
|
26
26
|
if (!matchLabels || Object.keys(matchLabels).length === 0) {
|
|
27
27
|
throw new Error(`service ${selector.name} has no selector`);
|
|
28
28
|
}
|
|
29
29
|
return findReadyPod(coreApi, namespace, formatSelector(matchLabels));
|
|
30
30
|
}
|
|
31
31
|
if (type === 'deployment') {
|
|
32
|
-
const deploy = await appsApi.readNamespacedDeployment(selector.name, namespace);
|
|
33
|
-
const matchLabels = deploy.
|
|
32
|
+
const deploy = await appsApi.readNamespacedDeployment({ name: selector.name, namespace });
|
|
33
|
+
const matchLabels = deploy.spec?.selector?.matchLabels;
|
|
34
34
|
if (!matchLabels) {
|
|
35
35
|
throw new Error(`deployment ${selector.name} has no matchLabels`);
|
|
36
36
|
}
|
|
@@ -13,7 +13,7 @@ function appendWorkEnvironment(referenced) {
|
|
|
13
13
|
type: 'kubernetes',
|
|
14
14
|
context: referenced.schema.kubernetes.context,
|
|
15
15
|
kubeConfig: referenced.schema.kubernetes.kubeconfig,
|
|
16
|
-
|
|
16
|
+
httpRoutes: referenced.schema.kubernetes.httpRoutes || [],
|
|
17
17
|
namespace: referenced.schema.kubernetes.namespace ?? 'default',
|
|
18
18
|
};
|
|
19
19
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOutputsToReset = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
function contains(parent, path) {
|
|
6
|
+
const rel = (0, path_1.relative)(parent, path);
|
|
7
|
+
return rel === '' || (!rel.startsWith('..') && !(0, path_1.isAbsolute)(rel));
|
|
8
|
+
}
|
|
9
|
+
// The outputs a run empties before the task's commands: its own outputs with
|
|
10
|
+
// resetOnChange (the default), except one that is a source or contains one
|
|
11
|
+
// (`package-lock.json` declared both ways) or contains the task's directory.
|
|
12
|
+
// Emptying those would delete what the task reads.
|
|
13
|
+
function getOutputsToReset(task) {
|
|
14
|
+
return task.generates.filter((generate) => generate.resetOnChange &&
|
|
15
|
+
!generate.inherited &&
|
|
16
|
+
!contains(generate.path, task.cwd) &&
|
|
17
|
+
!task.src.some((src) => contains(generate.path, src.absolutePath)));
|
|
18
|
+
}
|
|
19
|
+
exports.getOutputsToReset = getOutputsToReset;
|
|
20
|
+
//# sourceMappingURL=get-outputs-to-reset.js.map
|
|
@@ -24,12 +24,12 @@ function parseWorkGenerate(cwd, schema, envs) {
|
|
|
24
24
|
exports.parseWorkGenerate = parseWorkGenerate;
|
|
25
25
|
function mapGenerate(generate) {
|
|
26
26
|
if (typeof generate === 'string') {
|
|
27
|
-
return { path: generate, resetOnChange:
|
|
27
|
+
return { path: generate, resetOnChange: true, export: false };
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
return {
|
|
31
31
|
path: generate.path,
|
|
32
|
-
resetOnChange: generate.resetOnChange ??
|
|
32
|
+
resetOnChange: generate.resetOnChange ?? true,
|
|
33
33
|
export: generate.export ?? false,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
@@ -12,6 +12,7 @@ const path_1 = require("path");
|
|
|
12
12
|
const tar_1 = require("tar");
|
|
13
13
|
const plan_work_volume_1 = require("./utils/plan-work-volume");
|
|
14
14
|
const work_instance_id_1 = require("./work-instance-id");
|
|
15
|
+
const stream_1 = require("stream");
|
|
15
16
|
function dockerTaskRuntime(task, workEnvironment) {
|
|
16
17
|
const docker = (0, execute_docker_1.getContainerCli)(workEnvironment);
|
|
17
18
|
return {
|
|
@@ -132,6 +133,9 @@ async function restoreContainer(docker, environment, item, path) {
|
|
|
132
133
|
}, null, async (container) => {
|
|
133
134
|
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
134
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);
|
|
135
139
|
await container.putArchive(environment.file.readStream(generate.filename), {
|
|
136
140
|
path: (0, path_1.dirname)(generate.path),
|
|
137
141
|
});
|
|
@@ -142,6 +146,7 @@ async function restoreContainer(docker, environment, item, path) {
|
|
|
142
146
|
const onHost = new Set(item.data.generates.filter((g) => !g.inherited && isOnHost(g)).map((g) => g.path));
|
|
143
147
|
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
144
148
|
if (onHost.has(generate.path) && (await environment.file.exists(generate.filename))) {
|
|
149
|
+
await environment.file.remove(generate.path);
|
|
145
150
|
await (0, tar_1.extract)({ file: generate.filename, cwd: (0, path_1.dirname)(generate.path) });
|
|
146
151
|
}
|
|
147
152
|
}
|
|
@@ -251,7 +256,7 @@ async function archiveContainer(docker, environment, item, path) {
|
|
|
251
256
|
for (const generatedArchive of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
252
257
|
// a file output is a bind mount of a host file, so archive it from the host
|
|
253
258
|
const readable = hostFiles.has(generatedArchive.path)
|
|
254
|
-
? (0, tar_1.create)({ cwd: (0, path_1.dirname)(generatedArchive.path) }, [(0, path_1.basename)(generatedArchive.path)])
|
|
259
|
+
? stream_1.Readable.from((0, tar_1.create)({ cwd: (0, path_1.dirname)(generatedArchive.path) }, [(0, path_1.basename)(generatedArchive.path)]))
|
|
255
260
|
: await container.getArchive({
|
|
256
261
|
path: generatedArchive.path,
|
|
257
262
|
});
|
|
@@ -16,7 +16,6 @@ const ensure_persistent_data_1 = require("../kubernetes/ensure-persistent-data")
|
|
|
16
16
|
const await_running_state_1 = require("../kubernetes/await-running-state");
|
|
17
17
|
const volumes_1 = require("../kubernetes/volumes");
|
|
18
18
|
const resources_1 = require("../kubernetes/resources");
|
|
19
|
-
const ensure_ingress_1 = require("../kubernetes/ensure-ingress");
|
|
20
19
|
const ensure_http_route_1 = require("../kubernetes/ensure-http-route");
|
|
21
20
|
const find_process_1 = __importDefault(require("find-process"));
|
|
22
21
|
const log_1 = require("../log");
|
|
@@ -24,13 +23,12 @@ const version_1 = require("../version");
|
|
|
24
23
|
const restore_kubernetes_data_1 = require("../kubernetes/restore-kubernetes-data");
|
|
25
24
|
const store_kubernetes_data_1 = require("../kubernetes/store-kubernetes-data");
|
|
26
25
|
const remove_persistent_data_1 = require("../kubernetes/remove-persistent-data");
|
|
27
|
-
function statusCodeOf(e) {
|
|
28
|
-
const err = e;
|
|
29
|
-
return err?.statusCode ?? err?.response?.statusCode ?? err?.body?.code;
|
|
30
|
-
}
|
|
31
26
|
async function listJobNames(instance, kubernetes, task) {
|
|
32
|
-
const jobs = await instance.batchApi.listNamespacedJob(
|
|
33
|
-
|
|
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] : []));
|
|
34
32
|
}
|
|
35
33
|
function kubernetesTaskRuntime(task, kubernetes) {
|
|
36
34
|
const instance = (0, kubernetes_instance_1.createKubernetesInstances)(kubernetes);
|
|
@@ -128,22 +126,34 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
128
126
|
}
|
|
129
127
|
},
|
|
130
128
|
async stop() {
|
|
131
|
-
const jobs = await instance.batchApi.listNamespacedJob(
|
|
132
|
-
|
|
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) {
|
|
133
134
|
if (pod.metadata?.name) {
|
|
134
|
-
await instance.batchApi.deleteNamespacedJob(pod.metadata.name, kubernetes.namespace);
|
|
135
|
+
await instance.batchApi.deleteNamespacedJob({ name: pod.metadata.name, namespace: kubernetes.namespace });
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
|
-
const deployments = await instance.appsApi.listNamespacedDeployment(
|
|
138
|
-
|
|
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) {
|
|
139
143
|
if (deploy.metadata?.name) {
|
|
140
|
-
await instance.appsApi.deleteNamespacedDeployment(
|
|
144
|
+
await instance.appsApi.deleteNamespacedDeployment({
|
|
145
|
+
name: deploy.metadata.name,
|
|
146
|
+
namespace: kubernetes.namespace,
|
|
147
|
+
});
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
|
-
const pods = await instance.coreApi.listNamespacedPod(
|
|
144
|
-
|
|
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) {
|
|
145
155
|
if (pod.metadata?.name) {
|
|
146
|
-
await instance.coreApi.deleteNamespacedPod(pod.metadata.name, kubernetes.namespace);
|
|
156
|
+
await instance.coreApi.deleteNamespacedPod({ name: pod.metadata.name, namespace: kubernetes.namespace });
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
},
|
|
@@ -152,8 +162,11 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
152
162
|
await (0, remove_persistent_data_1.removePersistentData)(instance, kubernetes, task);
|
|
153
163
|
},
|
|
154
164
|
async currentStateKey() {
|
|
155
|
-
const jobs = await instance.batchApi.listNamespacedJob(
|
|
156
|
-
|
|
165
|
+
const jobs = await instance.batchApi.listNamespacedJob({
|
|
166
|
+
namespace: kubernetes.namespace,
|
|
167
|
+
labelSelector: `hammerkit.dev/id=${task.id()}`,
|
|
168
|
+
});
|
|
169
|
+
const completedStates = jobs.items
|
|
157
170
|
.filter((j) => j.status?.succeeded && j.metadata?.labels?.['hammerkit.dev/state'])
|
|
158
171
|
.map((j) => j.metadata.labels['hammerkit.dev/state']);
|
|
159
172
|
const stateKey = completedStates[completedStates.length - 1] ?? null;
|
|
@@ -163,11 +176,14 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
163
176
|
// outputs live in the task's claim; without it the finished job left
|
|
164
177
|
// nothing to reuse
|
|
165
178
|
try {
|
|
166
|
-
const claim = await instance.coreApi.readNamespacedPersistentVolumeClaim(
|
|
167
|
-
|
|
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;
|
|
168
184
|
}
|
|
169
185
|
catch (e) {
|
|
170
|
-
if (statusCodeOf(e) === 404) {
|
|
186
|
+
if ((0, apply_1.statusCodeOf)(e) === 404) {
|
|
171
187
|
return null;
|
|
172
188
|
}
|
|
173
189
|
throw e;
|
|
@@ -189,10 +205,16 @@ function kubernetesServiceRuntime(service, kubernetes) {
|
|
|
189
205
|
await (0, store_kubernetes_data_1.storeKubernetesData)(service, kubernetes, instance, environment, path);
|
|
190
206
|
},
|
|
191
207
|
async stop() {
|
|
192
|
-
const deployments = await instance.appsApi.listNamespacedDeployment(
|
|
193
|
-
|
|
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) {
|
|
194
213
|
if (deployment.metadata?.name) {
|
|
195
|
-
await instance.appsApi.deleteNamespacedDeployment(
|
|
214
|
+
await instance.appsApi.deleteNamespacedDeployment({
|
|
215
|
+
name: deployment.metadata.name,
|
|
216
|
+
namespace: kubernetes.namespace,
|
|
217
|
+
});
|
|
196
218
|
}
|
|
197
219
|
}
|
|
198
220
|
},
|
|
@@ -209,29 +231,16 @@ function kubernetesServiceRuntime(service, kubernetes) {
|
|
|
209
231
|
stateKey: options.stateKey,
|
|
210
232
|
remote: null,
|
|
211
233
|
});
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
if (ingress.kind === 'httproute') {
|
|
215
|
-
await (0, ensure_http_route_1.ensureHttpRoute)(instance, kubernetes, ingress, service);
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
await (0, ensure_ingress_1.ensureIngress)(instance, kubernetes, ingress, service);
|
|
219
|
-
}
|
|
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);
|
|
220
236
|
}
|
|
221
237
|
},
|
|
222
238
|
async remove() {
|
|
223
|
-
const ingresses = await instance.networkingApi.listNamespacedIngress(kubernetes.namespace, undefined, undefined, undefined, undefined, `hammerkit.dev/id=${service.id()}`);
|
|
224
|
-
for (const ingress of ingresses.body.items) {
|
|
225
|
-
if (ingress.metadata?.name) {
|
|
226
|
-
await instance.networkingApi.deleteNamespacedIngress(ingress.metadata.name, kubernetes.namespace);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
239
|
// HTTPRoutes are a CRD, so they are deleted by name through the generic
|
|
230
240
|
// objectApi rather than listed (a list would error on clusters without the
|
|
231
|
-
// Gateway API CRDs even when none are declared). Only
|
|
232
|
-
//
|
|
233
|
-
const
|
|
234
|
-
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)) {
|
|
235
244
|
try {
|
|
236
245
|
await instance.objectApi.delete({
|
|
237
246
|
apiVersion: ensure_http_route_1.HTTP_ROUTE_API_VERSION,
|
|
@@ -240,23 +249,32 @@ function kubernetesServiceRuntime(service, kubernetes) {
|
|
|
240
249
|
});
|
|
241
250
|
}
|
|
242
251
|
catch (e) {
|
|
243
|
-
if (statusCodeOf(e) !== 404) {
|
|
252
|
+
if ((0, apply_1.statusCodeOf)(e) !== 404) {
|
|
244
253
|
throw e;
|
|
245
254
|
}
|
|
246
255
|
}
|
|
247
256
|
}
|
|
248
|
-
const services = await instance.coreApi.listNamespacedService(
|
|
249
|
-
|
|
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) {
|
|
250
262
|
if (service.metadata?.name) {
|
|
251
|
-
await instance.coreApi.deleteNamespacedService(
|
|
263
|
+
await instance.coreApi.deleteNamespacedService({
|
|
264
|
+
name: service.metadata.name,
|
|
265
|
+
namespace: kubernetes.namespace,
|
|
266
|
+
});
|
|
252
267
|
}
|
|
253
268
|
}
|
|
254
269
|
await this.stop();
|
|
255
270
|
await (0, remove_persistent_data_1.removePersistentData)(instance, kubernetes, service);
|
|
256
271
|
},
|
|
257
272
|
async currentStateKey() {
|
|
258
|
-
const deployments = await instance.appsApi.listNamespacedDeployment(
|
|
259
|
-
|
|
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];
|
|
260
278
|
return deployment?.metadata?.labels?.['hammerkit.dev/state'] ?? null;
|
|
261
279
|
},
|
|
262
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)) {
|
|
@@ -96,15 +111,17 @@ function getLocalWorkRuntime(task) {
|
|
|
96
111
|
exports.getLocalWorkRuntime = getLocalWorkRuntime;
|
|
97
112
|
async function archiveLocal(environment, task, path) {
|
|
98
113
|
for (const generatedArchive of (0, event_cache_1.getArchivePaths)(task.data, path)) {
|
|
99
|
-
await environment.file.writeStream(generatedArchive.filename,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}, [(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)])));
|
|
103
117
|
}
|
|
104
118
|
}
|
|
105
119
|
async function restoreLocal(environment, task, path) {
|
|
106
120
|
for (const generate of (0, event_cache_1.getArchivePaths)(task, path)) {
|
|
107
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);
|
|
108
125
|
await (0, tar_1.extract)({
|
|
109
126
|
file: generate.filename,
|
|
110
127
|
cwd: task.cwd,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildFileEnvironmentSchemaHttpRoute = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
// A Gateway API HTTPRoute exposing a service of the environment under a hostname.
|
|
6
|
+
exports.buildFileEnvironmentSchemaHttpRoute = (0, zod_1.object)({
|
|
7
|
+
host: (0, zod_1.string)(),
|
|
8
|
+
service: (0, zod_1.string)(),
|
|
9
|
+
servicePort: (0, zod_1.number)().optional(),
|
|
10
|
+
path: (0, zod_1.string)().optional(),
|
|
11
|
+
// the parent Gateway the route attaches to
|
|
12
|
+
gateway: (0, zod_1.string)(),
|
|
13
|
+
gatewayNamespace: (0, zod_1.string)().optional(),
|
|
14
|
+
}).strict();
|
|
15
|
+
//# sourceMappingURL=build-file-environment-schema-http-route.js.map
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildFileEnvironmentSchema = exports.buildFileDockerEnvironmentSchema = exports.buildFileKubernetesEnvironmentSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const
|
|
5
|
+
const build_file_environment_schema_http_route_1 = require("./build-file-environment-schema-http-route");
|
|
6
6
|
exports.buildFileKubernetesEnvironmentSchema = (0, zod_1.object)({
|
|
7
7
|
namespace: (0, zod_1.string)().optional(),
|
|
8
8
|
context: (0, zod_1.string)(),
|
|
9
9
|
kubeconfig: (0, zod_1.string)().optional(),
|
|
10
|
-
|
|
10
|
+
httpRoutes: (0, zod_1.array)(build_file_environment_schema_http_route_1.buildFileEnvironmentSchemaHttpRoute).optional(),
|
|
11
|
+
// Ingress support was removed in 1.8.0. Fail instead of dropping the routes.
|
|
12
|
+
ingresses: (0, zod_1.never)({
|
|
13
|
+
errorMap: () => ({
|
|
14
|
+
message: 'ingresses were removed in 1.8.0: declare Gateway API routes under httpRoutes, each with a gateway',
|
|
15
|
+
}),
|
|
16
|
+
}).optional(),
|
|
11
17
|
});
|
|
12
18
|
exports.buildFileDockerEnvironmentSchema = (0, zod_1.object)({
|
|
13
19
|
host: (0, zod_1.string)().optional(),
|
|
@@ -19,5 +19,5 @@ exports.buildFileSchema = (0, zod_1.object)({
|
|
|
19
19
|
labels: labels_schema_1.labelsSchema.optional(),
|
|
20
20
|
})
|
|
21
21
|
.strict()
|
|
22
|
-
.describe('Build file with support for containerization\nhttps://
|
|
22
|
+
.describe('Build file with support for containerization\nhttps://hammerkit.dev/docs/build-file');
|
|
23
23
|
//# sourceMappingURL=build-file-schema.js.map
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.envsSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
exports.envsSchema = (0, zod_1.record)((0, zod_1.union)([(0, zod_1.string)(), (0, zod_1.number)()])).describe('Environment values for the current build task\nhttps://
|
|
5
|
+
exports.envsSchema = (0, zod_1.record)((0, zod_1.union)([(0, zod_1.string)(), (0, zod_1.number)()])).describe('Environment values for the current build task\nhttps://hammerkit.dev/docs/build-file/environment-variables');
|
|
6
6
|
//# sourceMappingURL=envs-schema.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hammerkit",
|
|
3
3
|
"description": "Containerized build tool with incremental caching that runs the same build file locally, in Docker and on Kubernetes.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"build",
|
|
7
7
|
"build-tool",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"monorepo",
|
|
13
13
|
"task-runner"
|
|
14
14
|
],
|
|
15
|
-
"homepage": "https://
|
|
15
|
+
"homepage": "https://hammerkit.dev",
|
|
16
16
|
"bin": "dist/index.js",
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
@@ -47,15 +47,15 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@aws-sdk/client-s3": "^3.470.0",
|
|
50
|
-
"@kubernetes/client-node": "^0.
|
|
51
|
-
"chokidar": "^3.
|
|
50
|
+
"@kubernetes/client-node": "^2.0.0",
|
|
51
|
+
"chokidar": "^3.6.0",
|
|
52
52
|
"colors": "^1.4.0",
|
|
53
53
|
"commander": "^11.1.0",
|
|
54
|
-
"dockerode": "^4.0.
|
|
54
|
+
"dockerode": "^4.0.12",
|
|
55
55
|
"find-process": "^1.4.7",
|
|
56
|
-
"minimatch": "^6.
|
|
57
|
-
"tar": "^
|
|
58
|
-
"yaml": "^2.
|
|
56
|
+
"minimatch": "^6.2.3",
|
|
57
|
+
"tar": "^7.5.22",
|
|
58
|
+
"yaml": "^2.9.1",
|
|
59
59
|
"zod": "^3.22.4"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@types/dockerode": "^3.3.23",
|
|
65
65
|
"@types/minimatch": "^5.1.2",
|
|
66
66
|
"@types/node": "^24.0.0",
|
|
67
|
-
"@types/
|
|
67
|
+
"@types/ws": "^8.18.2",
|
|
68
68
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
|
69
69
|
"@typescript-eslint/parser": "^6.16.0",
|
|
70
70
|
"@vitest/coverage-istanbul": "^2.1.8",
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureIngress = void 0;
|
|
4
|
-
const apply_1 = require("./apply");
|
|
5
|
-
const resources_1 = require("./resources");
|
|
6
|
-
async function ensureIngress(instance, env, ingress, service) {
|
|
7
|
-
const resource = {
|
|
8
|
-
kind: 'Ingress',
|
|
9
|
-
apiVersion: 'networking.k8s.io/v1',
|
|
10
|
-
metadata: {
|
|
11
|
-
name: ingress.host,
|
|
12
|
-
namespace: env.namespace,
|
|
13
|
-
annotations: {
|
|
14
|
-
'cert-manager.io/cluster-issuer': 'hammerkit-issuer',
|
|
15
|
-
},
|
|
16
|
-
labels: {
|
|
17
|
-
'hammerkit.dev/id': service.id(),
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
spec: {
|
|
21
|
-
ingressClassName: 'nginx',
|
|
22
|
-
rules: [
|
|
23
|
-
{
|
|
24
|
-
host: ingress.host,
|
|
25
|
-
http: {
|
|
26
|
-
paths: [
|
|
27
|
-
{
|
|
28
|
-
path: ingress.path ?? '/',
|
|
29
|
-
pathType: 'Prefix',
|
|
30
|
-
backend: {
|
|
31
|
-
service: {
|
|
32
|
-
name: (0, resources_1.getResourceName)(service),
|
|
33
|
-
port: {
|
|
34
|
-
number: ingress.servicePort ?? service.data.ports[0].containerPort,
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
tls: [
|
|
44
|
-
{
|
|
45
|
-
secretName: `${ingress.host}-tls`,
|
|
46
|
-
hosts: [ingress.host],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
return await (0, apply_1.apply)(instance, resource);
|
|
52
|
-
}
|
|
53
|
-
exports.ensureIngress = ensureIngress;
|
|
54
|
-
//# sourceMappingURL=ensure-ingress.js.map
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildFileEnvironmentSchemaIngress = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
exports.buildFileEnvironmentSchemaIngress = (0, zod_1.object)({
|
|
6
|
-
kind: zod_1.z.enum(['ingress', 'httproute']).optional(),
|
|
7
|
-
host: (0, zod_1.string)(),
|
|
8
|
-
service: (0, zod_1.string)(),
|
|
9
|
-
servicePort: (0, zod_1.number)().optional(),
|
|
10
|
-
path: (0, zod_1.string)().optional(),
|
|
11
|
-
gateway: (0, zod_1.string)().optional(),
|
|
12
|
-
gatewayNamespace: (0, zod_1.string)().optional(),
|
|
13
|
-
}).strict();
|
|
14
|
-
//# sourceMappingURL=build-file-environment-schema-ingress.js.map
|