hammerkit 1.26.0 → 1.27.0-next.10
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/build.schema.json +62 -1
- package/dist/cache/backends/local-cache-backend.js +10 -2
- package/dist/cache/backends/registry-cache-backend.js +2 -2
- package/dist/cache/explain.js +13 -11
- package/dist/cli.js +3 -0
- package/dist/docker/item-network.js +471 -0
- package/dist/docker/pull.js +4 -2
- package/dist/docker/remove-orphaned-containers.js +13 -6
- package/dist/environment/replace-env-variables.js +8 -6
- package/dist/executer/await-completed-dependencies.js +2 -1
- package/dist/executer/check-readiness.js +2 -2
- package/dist/executer/docker-service.js +24 -13
- package/dist/executer/docker-task.js +18 -29
- package/dist/executer/event-cache.js +24 -2
- package/dist/executer/execute-command.js +18 -5
- package/dist/executer/execute-docker.js +4 -1
- package/dist/executer/execution-steps.js +33 -7
- package/dist/executer/get-docker-executor.js +14 -7
- package/dist/executer/scheduler/enqueue-next.js +17 -3
- package/dist/executer/watch-loop.js +2 -2
- package/dist/executer/watch-state-key.js +1 -1
- package/dist/file/within-root.js +42 -0
- package/dist/harness/connect.js +99 -0
- package/dist/harness/format.js +73 -0
- package/dist/harness/mcp-command.js +59 -0
- package/dist/harness/mcp.js +432 -0
- package/dist/harness/opencode.js +492 -0
- package/dist/harness/pi.js +217 -0
- package/dist/harness/sandbox-client.js +411 -0
- package/dist/index.js +33 -36
- package/dist/kubernetes/apply.js +42 -1
- package/dist/kubernetes/await-running-state.js +33 -1
- package/dist/kubernetes/claim-layout.js +71 -0
- package/dist/kubernetes/ensure-http-route.js +1 -1
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +46 -28
- package/dist/kubernetes/ensure-kubernetes-persistent-volume-claim-exists.js +132 -5
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +27 -11
- package/dist/kubernetes/ensure-persistent-data.js +75 -24
- package/dist/kubernetes/get-service-ip.js +16 -2
- package/dist/kubernetes/job-placement.js +99 -0
- package/dist/kubernetes/job-secrets.js +320 -0
- package/dist/kubernetes/kept-claims.js +203 -0
- package/dist/kubernetes/kubernetes-instance.js +10 -2
- package/dist/kubernetes/network-gate.js +71 -0
- package/dist/kubernetes/network-policy.js +583 -0
- package/dist/kubernetes/remove-persistent-data.js +3 -1
- package/dist/kubernetes/resources.js +37 -4
- package/dist/kubernetes/restore-kubernetes-data.js +28 -7
- package/dist/kubernetes/secrets.js +7 -9
- package/dist/kubernetes/store-kubernetes-data.js +1 -1
- package/dist/kubernetes/volumes.js +104 -12
- package/dist/log.js +1 -1
- package/dist/main.js +41 -0
- package/dist/network/egress-proxy.js +407 -0
- package/dist/network/port-relay.js +35 -0
- package/dist/optimizer/get-work-cache-stats.js +16 -0
- package/dist/optimizer/work-task-cache-description.js +5 -2
- package/dist/planner/service-definition.js +5 -0
- package/dist/planner/utils/append-work-dependencies.js +5 -2
- package/dist/planner/utils/append-work-environment.js +2 -0
- package/dist/planner/utils/append-work-service.js +5 -0
- package/dist/planner/utils/append-work-task.js +20 -1
- package/dist/planner/utils/assert-item-paths.js +50 -0
- package/dist/planner/utils/hosted-volumes.js +35 -0
- package/dist/planner/utils/task-arguments.js +3 -0
- package/dist/planner/work-network.js +193 -0
- package/dist/planner/work-runtime-docker.js +43 -1
- package/dist/planner/work-runtime-kubernetes.js +370 -111
- package/dist/planner/work-runtime-local.js +2 -1
- package/dist/program.js +33 -5
- package/dist/remote/annotations.js +115 -0
- package/dist/remote/bundle-text.js +24 -0
- package/dist/remote/bundle.js +9 -1
- package/dist/remote/remote-commands.js +15 -3
- package/dist/remote/remote-work-commands.js +272 -71
- package/dist/remote/server-client.js +18 -5
- package/dist/remote/source-manifest.js +15 -2
- package/dist/remote/token-source.js +67 -0
- package/dist/schema/build-file-base-task-schema.js +7 -0
- package/dist/schema/build-file-container-service-schema.js +8 -0
- package/dist/schema/build-file-container-task-schema.js +3 -0
- package/dist/schema/build-file-environment-schema.js +9 -0
- package/dist/schema/network-schema.js +78 -0
- package/dist/schema/reference-parser.js +2 -0
- package/dist/schema/schema-parser.js +16 -3
- package/dist/schema/work-scope-parser.js +5 -0
- package/dist/secrets/command-secret-provider.js +73 -10
- package/dist/secrets/github-app-token-command.js +26 -0
- package/dist/secrets/github-app-token.js +191 -0
- package/dist/server/accounts-api.js +9 -2
- package/dist/server/definitions-api.js +69 -47
- package/dist/server/definitions.js +76 -51
- package/dist/server/executor.js +156 -41
- package/dist/server/git-checkout.js +72 -8
- package/dist/server/kubernetes-backend.js +603 -0
- package/dist/server/kubernetes-sandbox-storage.js +997 -0
- package/dist/server/oauth.js +12 -0
- package/dist/server/plan.js +88 -2
- package/dist/server/policy.js +104 -15
- package/dist/server/proposals-api.js +24 -40
- package/dist/server/proposals.js +12 -25
- package/dist/server/repo-access.js +37 -6
- package/dist/server/run-logs.js +217 -0
- package/dist/server/run-plan.js +48 -2
- package/dist/server/run-request.js +29 -10
- package/dist/server/run-store.js +5 -0
- package/dist/server/run-worker-command.js +13 -1
- package/dist/server/run-worker.js +121 -24
- package/dist/server/runs.js +191 -10
- package/dist/server/sandbox-docker.js +62 -0
- package/dist/server/sandbox-files-api.js +269 -0
- package/dist/server/sandbox-files.js +1054 -0
- package/dist/server/sandbox-patch.js +481 -0
- package/dist/server/sandbox-pod-command.js +15 -0
- package/dist/server/sandbox-pod.js +146 -0
- package/dist/server/sandbox-storage.js +462 -0
- package/dist/server/sandboxes-api.js +216 -0
- package/dist/server/sandboxes.js +519 -0
- package/dist/server/server-command.js +39 -20
- package/dist/server/server-config.js +443 -22
- package/dist/server/server.js +219 -40
- package/dist/utils/abort-on-signals.js +46 -6
- package/dist/utils/run-id.js +12 -0
- package/dist/utils/tar.js +25 -0
- package/harness/opencode.ts +11 -0
- package/harness/pi.ts +7 -0
- package/package.json +8 -2
- package/dist/remote/text-diff.js +0 -89
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertWorkTreePathsWithinRoot = exports.assertItemPathsWithinRoot = void 0;
|
|
4
|
+
const within_root_1 = require("../../file/within-root");
|
|
5
|
+
const work_service_1 = require("../work-service");
|
|
6
|
+
const work_task_1 = require("../work-task");
|
|
7
|
+
const hosted_volumes_1 = require("./hosted-volumes");
|
|
8
|
+
/**
|
|
9
|
+
* Refuses an item whose sources, outputs or mounts leave the root of a run, or go
|
|
10
|
+
* through a symbolic link in it. The server runs build files it did not write, in a
|
|
11
|
+
* checkout it did not write either: a `src: ../..` or a link in the checkout would
|
|
12
|
+
* have the server mount or upload its own files (its credentials, other callers'
|
|
13
|
+
* caches) into a task. Checked when the run is planned and again before each item
|
|
14
|
+
* runs, since an earlier task may have left a link behind.
|
|
15
|
+
*/
|
|
16
|
+
async function assertItemPathsWithinRoot(item, root) {
|
|
17
|
+
const data = item.data;
|
|
18
|
+
for (const source of 'src' in data ? data.src : []) {
|
|
19
|
+
if (!source.inherited) {
|
|
20
|
+
await (0, within_root_1.assertWithinRoot)(root, source.absolutePath, `${item.name}: src ${source.source}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if ((0, work_task_1.isContainerWorkTask)(data) || (0, work_service_1.isContainerWorkService)(data)) {
|
|
24
|
+
for (const mount of data.mounts) {
|
|
25
|
+
await (0, within_root_1.assertWithinRoot)(root, mount.localPath, `${item.name}: mount ${mount.localPath}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if ((0, work_service_1.isContainerWorkService)(data)) {
|
|
29
|
+
for (const volume of data.volumes) {
|
|
30
|
+
if (!(0, hosted_volumes_1.isVolumeName)(volume.name)) {
|
|
31
|
+
throw new Error(`${item.name}: volume ${volume.name} is not a volume name, a path of the server can't be mounted`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if ((0, work_task_1.isWorkTask)(data)) {
|
|
36
|
+
for (const generate of data.generates) {
|
|
37
|
+
if (!generate.inherited) {
|
|
38
|
+
await (0, within_root_1.assertWithinRoot)(root, generate.path, `${item.name}: generates ${generate.path}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.assertItemPathsWithinRoot = assertItemPathsWithinRoot;
|
|
44
|
+
async function assertWorkTreePathsWithinRoot(workTree, root) {
|
|
45
|
+
for (const item of [...Object.values(workTree.tasks), ...Object.values(workTree.services)]) {
|
|
46
|
+
await assertItemPathsWithinRoot(item, root);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.assertWorkTreePathsWithinRoot = assertWorkTreePathsWithinRoot;
|
|
50
|
+
//# sourceMappingURL=assert-item-paths.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scopeServiceVolumes = exports.isVolumeName = void 0;
|
|
4
|
+
const work_service_1 = require("../work-service");
|
|
5
|
+
// What Docker takes as the name of a volume. Anything else in a service's `volumes` is a
|
|
6
|
+
// path of the host to Docker (`/var/run/docker.sock:/run/docker.sock` binds the socket).
|
|
7
|
+
const VOLUME_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/;
|
|
8
|
+
function isVolumeName(name) {
|
|
9
|
+
return VOLUME_NAME.test(name);
|
|
10
|
+
}
|
|
11
|
+
exports.isVolumeName = isVolumeName;
|
|
12
|
+
/**
|
|
13
|
+
* On a server, the docker volumes of a run's services are its caller's: each name gets the
|
|
14
|
+
* run's scope (its caller and source trust) in front, so a build file can neither open nor,
|
|
15
|
+
* with `resetOnChange`, remove a volume of another caller, of a sandbox or of the host.
|
|
16
|
+
* The outputs a service inherits from its deps keep their names: those are made from paths
|
|
17
|
+
* in the run's own files, and the task that writes them uses the same name. A Kubernetes
|
|
18
|
+
* backend keeps claims in the caller's namespace and is left as it is.
|
|
19
|
+
*/
|
|
20
|
+
function scopeServiceVolumes(workTree, scope) {
|
|
21
|
+
const scoped = new Set();
|
|
22
|
+
for (const service of Object.values(workTree.services)) {
|
|
23
|
+
if (!(0, work_service_1.isContainerWorkService)(service.data)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
for (const volume of service.data.volumes) {
|
|
27
|
+
if (!volume.inherited && !scoped.has(volume)) {
|
|
28
|
+
scoped.add(volume);
|
|
29
|
+
volume.name = `hammerkit-${scope}-${volume.name}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.scopeServiceVolumes = scopeServiceVolumes;
|
|
35
|
+
//# sourceMappingURL=hosted-volumes.js.map
|
|
@@ -101,6 +101,9 @@ function applyArguments(selection, args, keepCache = false) {
|
|
|
101
101
|
if (item.type === 'kubernetes-service') {
|
|
102
102
|
throw new Error(`service ${item.name} is a kubernetes service and has no cmd for the arguments after --`);
|
|
103
103
|
}
|
|
104
|
+
if (item.args === false) {
|
|
105
|
+
throw new Error(`${item.type === 'container-service' ? 'service' : 'task'} ${item.name} takes no arguments after -- (args: false)`);
|
|
106
|
+
}
|
|
104
107
|
const target = getArgumentsTarget(item);
|
|
105
108
|
if (!target) {
|
|
106
109
|
throw new Error(item.type === 'container-service'
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyNetworkCeiling = exports.formatNetwork = exports.getTcpForwards = exports.matchesHost = exports.isLimitedNetwork = exports.toNetworkSchema = exports.exceedsNetwork = exports.getEffectiveNetwork = exports.limitNetwork = exports.parseNetworkOption = exports.parseWorkNetwork = exports.OPEN_NETWORK = void 0;
|
|
4
|
+
const network_schema_1 = require("../schema/network-schema");
|
|
5
|
+
exports.OPEN_NETWORK = { type: 'open' };
|
|
6
|
+
function parseWorkNetwork(schema) {
|
|
7
|
+
if (!schema || schema === 'open') {
|
|
8
|
+
return exports.OPEN_NETWORK;
|
|
9
|
+
}
|
|
10
|
+
if (schema === 'none') {
|
|
11
|
+
return { type: 'none' };
|
|
12
|
+
}
|
|
13
|
+
return { type: 'allow', hosts: normalizeHosts(schema.allow) };
|
|
14
|
+
}
|
|
15
|
+
exports.parseWorkNetwork = parseWorkNetwork;
|
|
16
|
+
// `--network none`, `--network open` or `--network allow:<host>,<host:port>`
|
|
17
|
+
function parseNetworkOption(value) {
|
|
18
|
+
if (value === 'open' || value === 'none') {
|
|
19
|
+
return { type: value };
|
|
20
|
+
}
|
|
21
|
+
if (value.startsWith('allow:')) {
|
|
22
|
+
const hosts = value
|
|
23
|
+
.substring('allow:'.length)
|
|
24
|
+
.split(',')
|
|
25
|
+
.map((host) => host.trim())
|
|
26
|
+
.filter((host) => host.length > 0);
|
|
27
|
+
const invalid = hosts.find((host) => !(0, network_schema_1.isAllowEntry)(host));
|
|
28
|
+
if (invalid) {
|
|
29
|
+
throw new Error(`invalid host "${invalid}", expected a name such as registry.npmjs.org or *.example.com, ` +
|
|
30
|
+
'or a name and port such as db.example.com:5432');
|
|
31
|
+
}
|
|
32
|
+
const error = (0, network_schema_1.getAllowListError)(hosts);
|
|
33
|
+
if (error) {
|
|
34
|
+
throw new Error(error);
|
|
35
|
+
}
|
|
36
|
+
return { type: 'allow', hosts: normalizeHosts(hosts) };
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`invalid network "${value}", expected none, open or allow:<host>,<host>`);
|
|
39
|
+
}
|
|
40
|
+
exports.parseNetworkOption = parseNetworkOption;
|
|
41
|
+
// The setting under a ceiling: a more open setting is clamped down to it, and
|
|
42
|
+
// an allow list under an allow ceiling keeps the hosts both lists allow. A
|
|
43
|
+
// name one list allows only through a pattern reaches no private address.
|
|
44
|
+
function limitNetwork(network, ceiling) {
|
|
45
|
+
if (!ceiling || ceiling.type === 'open' || network.type === 'none') {
|
|
46
|
+
return network;
|
|
47
|
+
}
|
|
48
|
+
if (ceiling.type === 'none') {
|
|
49
|
+
return ceiling;
|
|
50
|
+
}
|
|
51
|
+
if (network.type === 'open') {
|
|
52
|
+
return ceiling;
|
|
53
|
+
}
|
|
54
|
+
const hosts = [
|
|
55
|
+
...network.hosts.filter((host) => ceiling.hosts.some((limit) => coversHost(limit, host))),
|
|
56
|
+
...ceiling.hosts.filter((limit) => network.hosts.some((host) => coversHost(host, limit))),
|
|
57
|
+
];
|
|
58
|
+
const normalized = normalizeHosts(hosts);
|
|
59
|
+
const publicOnly = normalized.filter((host) => !host.startsWith('*.') && !(coversByName(network, host) && coversByName(ceiling, host)));
|
|
60
|
+
return publicOnly.length > 0 ? { type: 'allow', hosts: normalized, publicOnly } : { type: 'allow', hosts: normalized };
|
|
61
|
+
}
|
|
62
|
+
exports.limitNetwork = limitNetwork;
|
|
63
|
+
// Whether a name (not a pattern) of the list covers `host` and may reach
|
|
64
|
+
// private addresses.
|
|
65
|
+
function coversByName(network, host) {
|
|
66
|
+
return network.hosts.some((entry) => !entry.startsWith('*.') && !network.publicOnly?.includes(entry) && coversHost(entry, host));
|
|
67
|
+
}
|
|
68
|
+
// The network of a task or service in this run: its own setting under the
|
|
69
|
+
// run's ceiling (`--network`).
|
|
70
|
+
function getEffectiveNetwork(item) {
|
|
71
|
+
return limitNetwork(item.network, item.networkCeiling);
|
|
72
|
+
}
|
|
73
|
+
exports.getEffectiveNetwork = getEffectiveNetwork;
|
|
74
|
+
// Whether `network` reaches more than `ceiling` allows: a host it allows that no
|
|
75
|
+
// host of the ceiling covers. Redundant hosts of the ceiling don't count.
|
|
76
|
+
function exceedsNetwork(network, ceiling) {
|
|
77
|
+
if (ceiling.type === 'open' || network.type === 'none') {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (network.type === 'open') {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (ceiling.type === 'none') {
|
|
84
|
+
return network.hosts.length > 0;
|
|
85
|
+
}
|
|
86
|
+
return network.hosts.some((host) => !ceiling.hosts.some((limit) => coversHost(limit, host)));
|
|
87
|
+
}
|
|
88
|
+
exports.exceedsNetwork = exceedsNetwork;
|
|
89
|
+
// The setting as a build file writes it.
|
|
90
|
+
function toNetworkSchema(network) {
|
|
91
|
+
return network.type === 'allow' ? { allow: [...network.hosts] } : network.type;
|
|
92
|
+
}
|
|
93
|
+
exports.toNetworkSchema = toNetworkSchema;
|
|
94
|
+
function isLimitedNetwork(network) {
|
|
95
|
+
return network.type !== 'open';
|
|
96
|
+
}
|
|
97
|
+
exports.isLimitedNetwork = isLimitedNetwork;
|
|
98
|
+
// Whether a request to `host` passes `pattern`: the same name, or a name under
|
|
99
|
+
// the domain of a `*.` pattern (the domain itself is not included).
|
|
100
|
+
function matchesHost(pattern, host) {
|
|
101
|
+
const name = host.toLowerCase().replace(/\.$/, '');
|
|
102
|
+
const rule = pattern.toLowerCase();
|
|
103
|
+
return rule.startsWith('*.') ? name.endsWith(rule.substring(1)) : name === rule;
|
|
104
|
+
}
|
|
105
|
+
exports.matchesHost = matchesHost;
|
|
106
|
+
// Whether everything `host` (a name, a `*.` pattern or a name and port)
|
|
107
|
+
// allows, `limit` allows too. A host without a port reaches every port of it
|
|
108
|
+
// through the proxy, so it covers a forward to one of them; a forward covers
|
|
109
|
+
// only itself.
|
|
110
|
+
function coversHost(limit, host) {
|
|
111
|
+
const limitEntry = (0, network_schema_1.parseAllowEntry)(limit);
|
|
112
|
+
const hostEntry = (0, network_schema_1.parseAllowEntry)(host);
|
|
113
|
+
if (!limitEntry || !hostEntry) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (limitEntry.port !== null) {
|
|
117
|
+
return hostEntry.port === limitEntry.port && matchesHost(limitEntry.host, hostEntry.host);
|
|
118
|
+
}
|
|
119
|
+
if (hostEntry.host.startsWith('*.')) {
|
|
120
|
+
// `*.b.example.com` is within `*.example.com`
|
|
121
|
+
return (limitEntry.host.startsWith('*.') &&
|
|
122
|
+
hostEntry.host.toLowerCase().endsWith(limitEntry.host.toLowerCase().substring(1)));
|
|
123
|
+
}
|
|
124
|
+
return matchesHost(limitEntry.host, hostEntry.host);
|
|
125
|
+
}
|
|
126
|
+
// The raw TCP forwards of a network: the entries of `allow` with a port.
|
|
127
|
+
function getTcpForwards(network) {
|
|
128
|
+
if (network.type !== 'allow') {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
return network.hosts
|
|
132
|
+
.map(network_schema_1.parseAllowEntry)
|
|
133
|
+
.filter((entry) => entry !== null && entry.port !== null);
|
|
134
|
+
}
|
|
135
|
+
exports.getTcpForwards = getTcpForwards;
|
|
136
|
+
function normalizeHosts(hosts) {
|
|
137
|
+
return [...new Set(hosts.map((host) => host.toLowerCase()))].sort();
|
|
138
|
+
}
|
|
139
|
+
function formatNetwork(network) {
|
|
140
|
+
if (network.type === 'allow') {
|
|
141
|
+
return network.hosts.length > 0 ? `allow ${network.hosts.join(', ')}` : 'allow (no hosts)';
|
|
142
|
+
}
|
|
143
|
+
return network.type;
|
|
144
|
+
}
|
|
145
|
+
exports.formatNetwork = formatNetwork;
|
|
146
|
+
// Sets the run's ceiling on every task and service of the work tree. A local
|
|
147
|
+
// task can't be limited, so a ceiling below open with one in the run is an
|
|
148
|
+
// error before anything runs. So is a limited item that needs a service under
|
|
149
|
+
// a name that is no host name: the proxy passes the names of the needed
|
|
150
|
+
// services, and `*.com` would pass every .com host. A TCP forward under the
|
|
151
|
+
// name of a needed service would hide that service, so it is an error too, as
|
|
152
|
+
// are two forwards on one port, which the ceiling can leave from two lists.
|
|
153
|
+
function applyNetworkCeiling(workTree, ceiling) {
|
|
154
|
+
const limit = ceiling && isLimitedNetwork(ceiling) ? ceiling : null;
|
|
155
|
+
if (limit) {
|
|
156
|
+
const local = Object.values(workTree.tasks).find((task) => task.data.type === 'local-task');
|
|
157
|
+
if (local) {
|
|
158
|
+
throw new Error(`--network ${formatNetwork(limit)} needs every task in a container, ` +
|
|
159
|
+
`hammerkit can't limit the network of the local task ${local.name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const task of Object.values(workTree.tasks)) {
|
|
163
|
+
task.data.networkCeiling = limit;
|
|
164
|
+
}
|
|
165
|
+
for (const service of Object.values(workTree.services)) {
|
|
166
|
+
if (service.data.type === 'container-service') {
|
|
167
|
+
service.data.networkCeiling = limit;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const item of [...Object.values(workTree.tasks), ...Object.values(workTree.services)]) {
|
|
171
|
+
if (item.data.type === 'local-task' || item.data.type === 'kubernetes-service') {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const network = getEffectiveNetwork(item.data);
|
|
175
|
+
const invalid = item.needs.find((need) => !(0, network_schema_1.isNeedName)(need.name));
|
|
176
|
+
if (isLimitedNetwork(network) && invalid) {
|
|
177
|
+
throw new Error(`${item.name}: network ${formatNetwork(network)} reaches a needed service by a host name, ` +
|
|
178
|
+
`"${invalid.name}" is none (name it like db or api-v2)`);
|
|
179
|
+
}
|
|
180
|
+
const shared = network.type === 'allow' ? (0, network_schema_1.getAllowListError)(network.hosts) : null;
|
|
181
|
+
if (shared) {
|
|
182
|
+
const under = limit ? ` under --network ${formatNetwork(limit)}` : '';
|
|
183
|
+
throw new Error(`${item.name}: network ${formatNetwork(network)}${under}, ${shared}`);
|
|
184
|
+
}
|
|
185
|
+
const hidden = getTcpForwards(network).find((forward) => item.needs.some((need) => need.name.toLowerCase() === forward.host.toLowerCase()));
|
|
186
|
+
if (hidden) {
|
|
187
|
+
throw new Error(`${item.name}: network ${formatNetwork(network)} forwards ${hidden.host}:${hidden.port}, ` +
|
|
188
|
+
`which is the name of a service it needs`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.applyNetworkCeiling = applyNetworkCeiling;
|
|
193
|
+
//# sourceMappingURL=work-network.js.map
|
|
@@ -12,11 +12,16 @@ const event_cache_1 = require("../executer/event-cache");
|
|
|
12
12
|
const get_docker_executor_1 = require("../executer/get-docker-executor");
|
|
13
13
|
const path_1 = require("path");
|
|
14
14
|
const tar_1 = require("tar");
|
|
15
|
+
const tar_2 = require("../utils/tar");
|
|
15
16
|
const plan_work_volume_1 = require("./utils/plan-work-volume");
|
|
16
17
|
const work_instance_id_1 = require("./work-instance-id");
|
|
17
18
|
const stream_1 = require("stream");
|
|
18
19
|
const service_definition_1 = require("./service-definition");
|
|
19
20
|
const container_task_state_1 = require("../executer/container-task-state");
|
|
21
|
+
const item_network_1 = require("../docker/item-network");
|
|
22
|
+
const execution_steps_1 = require("../executer/execution-steps");
|
|
23
|
+
const assert_item_paths_1 = require("./utils/assert-item-paths");
|
|
24
|
+
const log_1 = require("../log");
|
|
20
25
|
function dockerTaskRuntime(task, workEnvironment) {
|
|
21
26
|
const docker = (0, execute_docker_1.getContainerCli)(workEnvironment);
|
|
22
27
|
return {
|
|
@@ -56,8 +61,18 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
56
61
|
for (const container of containers) {
|
|
57
62
|
await (0, remove_container_1.removeContainer)(docker.getContainer(container.Id));
|
|
58
63
|
}
|
|
64
|
+
await (0, item_network_1.removeItemNetworks)(docker, task);
|
|
59
65
|
},
|
|
60
66
|
async execute(environment, options) {
|
|
67
|
+
if (environment.hosted) {
|
|
68
|
+
try {
|
|
69
|
+
await (0, assert_item_paths_1.assertItemPathsWithinRoot)(task, environment.cwd);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
options.state.set({ stateKey: options.stateKey, type: 'error', errorMessage: (0, log_1.getErrorMessage)(e) });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
61
76
|
await (0, docker_task_1.dockerTask)(docker, task, environment, options);
|
|
62
77
|
},
|
|
63
78
|
async remove(environment) {
|
|
@@ -103,6 +118,18 @@ function isOnHost(generate) {
|
|
|
103
118
|
return generate.isFile || generate.export;
|
|
104
119
|
}
|
|
105
120
|
async function restoreContainer(docker, environment, item, path) {
|
|
121
|
+
await (0, execution_steps_1.refuseHostLinks)(item, environment);
|
|
122
|
+
// on a server the volumes the outputs are restored into carry the run's files, so they
|
|
123
|
+
// are removed with a sandbox
|
|
124
|
+
const root = (0, execution_steps_1.hostedRoot)(environment);
|
|
125
|
+
if (root) {
|
|
126
|
+
const names = item.data.type === 'container-service'
|
|
127
|
+
? item.data.volumes.filter((v) => !v.inherited).map((v) => v.name)
|
|
128
|
+
: item.data.generates.filter((v) => !v.inherited && !v.isFile).map((v) => v.volumeName);
|
|
129
|
+
for (const name of names) {
|
|
130
|
+
await (0, get_docker_executor_1.ensureVolumeExists)(docker, item.status, name, (0, get_docker_executor_1.volumeLabels)(root));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
106
133
|
await (0, using_container_1.usingContainer)(docker, item, {
|
|
107
134
|
abortSignal: environment.abortCtrl.signal,
|
|
108
135
|
Image: item.data.image,
|
|
@@ -142,6 +169,7 @@ async function restoreContainer(docker, environment, item, path) {
|
|
|
142
169
|
}
|
|
143
170
|
});
|
|
144
171
|
if (item.data.type === 'container-task') {
|
|
172
|
+
await (0, execution_steps_1.refuseHostLinks)(item, environment);
|
|
145
173
|
const onHost = new Set(item.data.generates.filter((g) => !g.inherited && isOnHost(g)).map((g) => g.path));
|
|
146
174
|
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
147
175
|
if (onHost.has(generate.path) && (await environment.file.exists(generate.filename))) {
|
|
@@ -167,6 +195,8 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
167
195
|
if (currentService.Labels['hammerkit-definition'] !== (0, service_definition_1.getServiceDefinitionHash)(service)) {
|
|
168
196
|
service.status.write('info', `${service.name} changed since it was started, recreating it`);
|
|
169
197
|
await (0, remove_container_1.removeContainer)(docker.getContainer(currentService.Id));
|
|
198
|
+
// its network, proxy and relay go with it, whatever its new setting
|
|
199
|
+
await (0, item_network_1.removeItemNetworks)(docker, service);
|
|
170
200
|
return;
|
|
171
201
|
}
|
|
172
202
|
const servicePid = 'hammerkit-pid' in currentService.Labels ? parseInt(currentService.Labels['hammerkit-pid']) : undefined;
|
|
@@ -190,6 +220,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
190
220
|
for (const container of containers) {
|
|
191
221
|
await (0, remove_container_1.removeContainer)(docker.getContainer(container.Id));
|
|
192
222
|
}
|
|
223
|
+
await (0, item_network_1.removeItemNetworks)(docker, service);
|
|
193
224
|
await (0, container_secrets_1.removeContainerSecrets)((0, work_instance_id_1.getWorkInstanceId)(service));
|
|
194
225
|
},
|
|
195
226
|
async remove() {
|
|
@@ -203,6 +234,16 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
203
234
|
}
|
|
204
235
|
},
|
|
205
236
|
async execute(environment, options) {
|
|
237
|
+
if (environment.hosted) {
|
|
238
|
+
try {
|
|
239
|
+
await (0, assert_item_paths_1.assertItemPathsWithinRoot)(service, environment.cwd);
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
service.status.write('error', (0, log_1.getErrorMessage)(e));
|
|
243
|
+
options.state.set({ type: 'end', reason: 'crash', stateKey: options.stateKey });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
206
247
|
await (0, docker_service_1.dockerService)(docker, service, options, environment);
|
|
207
248
|
},
|
|
208
249
|
async restore(environment, path) {
|
|
@@ -231,6 +272,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
231
272
|
}
|
|
232
273
|
exports.dockerServiceRuntime = dockerServiceRuntime;
|
|
233
274
|
async function archiveContainer(docker, environment, item, path) {
|
|
275
|
+
await (0, execution_steps_1.refuseHostLinks)(item, environment);
|
|
234
276
|
await (0, using_container_1.usingContainer)(docker, item, {
|
|
235
277
|
abortSignal: environment.abortCtrl.signal,
|
|
236
278
|
Image: item.data.image,
|
|
@@ -261,7 +303,7 @@ async function archiveContainer(docker, environment, item, path) {
|
|
|
261
303
|
for (const generatedArchive of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
262
304
|
// a file output is a bind mount of a host file, so archive it from the host
|
|
263
305
|
const readable = hostFiles.has(generatedArchive.path)
|
|
264
|
-
? stream_1.Readable.from((0,
|
|
306
|
+
? stream_1.Readable.from((0, tar_2.packArchive)({ cwd: (0, path_1.dirname)(generatedArchive.path) }, [(0, path_1.basename)(generatedArchive.path)]))
|
|
265
307
|
: await container.getArchive({
|
|
266
308
|
path: generatedArchive.path,
|
|
267
309
|
});
|