hammerkit 1.27.0-next.3 → 1.27.0-next.31
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 +188 -0
- package/dist/cache/backends/local-cache-backend.js +6 -0
- package/dist/cache/backends/registry/registry-client.js +1 -0
- package/dist/cache/backends/registry-cache-backend.js +2 -2
- package/dist/cache/explain.js +17 -0
- package/dist/cli.js +2 -0
- package/dist/dashboard/assets/index-CjnKm5-3.css +2 -0
- package/dist/dashboard/assets/index-zPXa4klZ.js +13 -0
- package/dist/dashboard/favicon.svg +1 -0
- package/dist/dashboard/index.html +15 -0
- package/dist/docker/item-network.js +13 -5
- package/dist/executer/deadline.js +17 -3
- package/dist/executer/deferred-restore.js +36 -0
- package/dist/executer/docker-service.js +30 -8
- package/dist/executer/docker-task.js +21 -1
- package/dist/executer/event-cache.js +20 -2
- package/dist/executer/execute-work-service.js +2 -0
- package/dist/executer/execute-work-task.js +10 -3
- package/dist/executer/execution-steps.js +25 -4
- package/dist/executer/reset-work-tree.js +4 -0
- package/dist/executer/scheduler/enqueue-next.js +57 -6
- package/dist/executer/scheduler/state-key.js +42 -5
- package/dist/executer/watch-loop.js +4 -1
- package/dist/harness/connect.js +20 -6
- package/dist/harness/mcp-command.js +22 -6
- package/dist/harness/mcp.js +63 -114
- package/dist/harness/opencode.js +158 -50
- package/dist/harness/pi.js +29 -0
- package/dist/harness/sandbox-client.js +168 -17
- package/dist/harness/service-tools.js +141 -0
- package/dist/harness/task-tools.js +221 -0
- package/dist/kubernetes/apply.js +27 -16
- package/dist/kubernetes/await-running-state.js +59 -13
- package/dist/kubernetes/claim-layout.js +71 -0
- package/dist/kubernetes/cluster-state.js +245 -0
- package/dist/kubernetes/ensure-http-route.js +1 -1
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +35 -12
- package/dist/kubernetes/ensure-kubernetes-persistent-volume-claim-exists.js +1 -1
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +6 -9
- package/dist/kubernetes/ensure-persistent-data.js +30 -20
- package/dist/kubernetes/follow-pod-logs.js +182 -0
- package/dist/kubernetes/get-service-ip.js +2 -2
- package/dist/kubernetes/job-placement.js +36 -12
- package/dist/kubernetes/job-secrets.js +33 -19
- package/dist/kubernetes/kept-claims.js +22 -10
- package/dist/kubernetes/kubernetes-instance.js +1 -0
- package/dist/kubernetes/network-policy.js +85 -27
- package/dist/kubernetes/remove-persistent-data.js +3 -1
- package/dist/kubernetes/resources.js +25 -3
- package/dist/kubernetes/restore-kubernetes-data.js +9 -3
- package/dist/kubernetes/sandbox-runner.js +628 -0
- package/dist/kubernetes/secrets.js +7 -11
- package/dist/kubernetes/volumes.js +6 -3
- package/dist/main.js +2 -0
- package/dist/network/egress-proxy.js +45 -6
- package/dist/optimizer/calculate-checksum.js +3 -2
- package/dist/optimizer/check-key.js +94 -0
- package/dist/optimizer/check-keys.js +64 -0
- package/dist/optimizer/get-work-cache-stats.js +17 -3
- package/dist/optimizer/work-task-cache-description.js +51 -1
- package/dist/planner/utils/append-work-dependencies.js +10 -5
- package/dist/planner/utils/append-work-service.js +19 -1
- package/dist/planner/utils/append-work-task.js +18 -3
- package/dist/planner/utils/get-work-runtime.js +6 -1
- package/dist/planner/utils/normalize-path.js +19 -1
- package/dist/planner/utils/parse-work-mount.js +7 -1
- package/dist/planner/utils/parse-work-port.js +4 -1
- package/dist/planner/utils/task-arguments.js +3 -0
- package/dist/planner/validate.js +22 -0
- package/dist/planner/work-network.js +3 -1
- package/dist/planner/work-runtime-aggregate.js +32 -0
- package/dist/planner/work-runtime-docker.js +2 -1
- package/dist/planner/work-runtime-kubernetes.js +124 -48
- package/dist/planner/work-runtime-local.js +2 -1
- package/dist/planner/work-task.js +6 -1
- package/dist/program.js +31 -10
- package/dist/remote/device-login.js +7 -2
- package/dist/remote/host-config.js +69 -3
- package/dist/remote/remote-commands.js +33 -7
- package/dist/remote/remote-status.js +112 -0
- package/dist/remote/remote-work-commands.js +545 -45
- package/dist/remote/server-client.js +15 -2
- package/dist/remote/token-source.js +64 -26
- package/dist/run-program.js +9 -0
- package/dist/schema/build-file-base-task-schema.js +8 -1
- package/dist/schema/build-file-container-service-schema.js +12 -0
- package/dist/schema/build-file-container-task-schema.js +16 -1
- package/dist/schema/build-file-include-schema.js +18 -4
- package/dist/schema/build-file-kubernetes-service-schema.js +2 -0
- package/dist/schema/build-file-task-schema.js +5 -1
- package/dist/schema/capabilities-schema.js +56 -0
- package/dist/schema/reference-parser.js +120 -11
- package/dist/schema/schema-parser.js +92 -41
- package/dist/schema/work-scope-parser.js +33 -10
- package/dist/secrets/command-secret-provider.js +3 -1
- package/dist/secrets/github-app-token-command.js +3 -1
- package/dist/secrets/secret-catalog.js +43 -19
- package/dist/server/accounts-api.js +4 -1
- package/dist/server/approvals-api.js +1 -1
- package/dist/server/capacity-format.js +36 -0
- package/dist/server/capacity.js +300 -0
- package/dist/server/checks-api.js +83 -0
- package/dist/server/checks.js +116 -0
- package/dist/server/dashboard.js +196 -0
- package/dist/server/definitions-api.js +66 -3
- package/dist/server/endpoints.js +793 -0
- package/dist/server/executor.js +57 -5
- package/dist/server/kubernetes-backend.js +392 -65
- package/dist/server/kubernetes-sandbox-storage.js +48 -3
- package/dist/server/oauth.js +12 -1
- package/dist/server/overview.js +271 -0
- package/dist/server/plan.js +188 -103
- package/dist/server/policy.js +34 -2
- package/dist/server/proposals-api.js +17 -8
- package/dist/server/repo-access.js +5 -0
- package/dist/server/run-logs.js +4 -0
- package/dist/server/run-plan.js +50 -19
- package/dist/server/run-request.js +58 -3
- package/dist/server/run-worker.js +245 -12
- package/dist/server/runs.js +532 -35
- package/dist/server/sandbox-pod.js +28 -0
- package/dist/server/sandbox-storage.js +23 -0
- package/dist/server/sandboxes-api.js +10 -4
- package/dist/server/sandboxes.js +8 -0
- package/dist/server/server-command.js +39 -20
- package/dist/server/server-config.js +277 -14
- package/dist/server/server.js +402 -33
- package/dist/server/service-runs.js +82 -0
- package/dist/server/timings.js +132 -0
- package/dist/server/wait-reason.js +21 -0
- package/dist/utils/abort-on-signals.js +46 -6
- package/dist/utils/tar.js +36 -0
- package/dist/utils/terminal-prompt.js +53 -0
- package/package.json +8 -2
|
@@ -17,12 +17,15 @@ const sleep_1 = require("../utils/sleep");
|
|
|
17
17
|
const network_gate_1 = require("./network-gate");
|
|
18
18
|
const job_placement_1 = require("./job-placement");
|
|
19
19
|
Object.defineProperty(exports, "RUN_LABEL", { enumerable: true, get: function () { return job_placement_1.RUN_LABEL; } });
|
|
20
|
-
const
|
|
20
|
+
const resources_1 = require("./resources");
|
|
21
21
|
const PROXY_LABEL = 'hammerkit.dev/proxy-for';
|
|
22
22
|
const NETWORK_OF_LABEL = 'hammerkit.dev/network-of';
|
|
23
23
|
// on the proxy of a task on a sandbox: the sandbox, whose pod of files it stays with
|
|
24
24
|
const PROXY_OF_SANDBOX_LABEL = 'hammerkit.dev/proxy-of-sandbox';
|
|
25
25
|
const PROXY_READY_TIMEOUT_MS = 60000;
|
|
26
|
+
// the token of the endpoints of secret providers, in a Secret named like the proxy
|
|
27
|
+
const SECRET_TOKEN_KEY = 'token';
|
|
28
|
+
const SECRET_TOKEN_VARIABLE = 'HAMMERKIT_PROXY_TOKEN';
|
|
26
29
|
// how long the pods of an item may take to end before their policy goes
|
|
27
30
|
const POD_END_TIMEOUT_MS = 120000;
|
|
28
31
|
const POD_END_POLL_MS = 500;
|
|
@@ -83,7 +86,7 @@ function getNetworkPolicyName(item, scope) {
|
|
|
83
86
|
exports.getNetworkPolicyName = getNetworkPolicyName;
|
|
84
87
|
// the pods of the item, of the run when the scope has one
|
|
85
88
|
function getItemPodLabels(item, scope) {
|
|
86
|
-
return { [ID_LABEL]: item.id(), ...(scope.run ? { [job_placement_1.RUN_LABEL]: scope.run } : {}) };
|
|
89
|
+
return { [resources_1.ID_LABEL]: item.id(), ...(scope.run ? { [job_placement_1.RUN_LABEL]: scope.run } : {}) };
|
|
87
90
|
}
|
|
88
91
|
function toSelector(labels) {
|
|
89
92
|
return Object.entries(labels)
|
|
@@ -104,15 +107,20 @@ function getIngress(env, item) {
|
|
|
104
107
|
if (ids.length === 0) {
|
|
105
108
|
return [];
|
|
106
109
|
}
|
|
107
|
-
|
|
110
|
+
// on a server, of this run: the runs of a caller share their namespace
|
|
111
|
+
const run = (0, job_placement_1.getRunLabels)(env);
|
|
112
|
+
const from = {
|
|
113
|
+
matchExpressions: [{ key: resources_1.ID_LABEL, operator: 'In', values: ids }],
|
|
114
|
+
...(Object.keys(run).length > 0 ? { matchLabels: run } : {}),
|
|
115
|
+
};
|
|
108
116
|
// the client's name for `from`, a reserved word
|
|
109
117
|
return [{ _from: [{ podSelector: from }] }];
|
|
110
118
|
}
|
|
111
119
|
// The NetworkPolicy of a limited item: egress to the pods of the services it
|
|
112
|
-
// needs and, under `allow
|
|
113
|
-
// and the proxy are host aliases). Ingress
|
|
114
|
-
// item has none.
|
|
115
|
-
function buildNetworkPolicy(env, item, network, scope, proxy = getRunProxy(item, scope)) {
|
|
120
|
+
// needs and, under `allow` or with secrets read through it, to its proxy;
|
|
121
|
+
// nothing else, DNS included (needs and the proxy are host aliases). Ingress
|
|
122
|
+
// only from what needs it. An open item has none.
|
|
123
|
+
function buildNetworkPolicy(env, item, network, scope, proxy = getRunProxy(item, scope), proxied = network.type === 'allow') {
|
|
116
124
|
const needs = item.needs.filter((need) => (0, work_item_1.isContainerWorkServiceItem)(need.service));
|
|
117
125
|
return {
|
|
118
126
|
apiVersion: 'networking.k8s.io/v1',
|
|
@@ -121,7 +129,8 @@ function buildNetworkPolicy(env, item, network, scope, proxy = getRunProxy(item,
|
|
|
121
129
|
namespace: env.namespace,
|
|
122
130
|
name: getNetworkPolicyName(item, scope),
|
|
123
131
|
annotations: { 'hammerkit.dev/version': (0, version_1.getVersion)() },
|
|
124
|
-
labels: { [NETWORK_OF_LABEL]: item.id(), ...(0, job_placement_1.getRunLabels)(env) },
|
|
132
|
+
labels: { [NETWORK_OF_LABEL]: item.id(), ...(scope.owners ? {} : (0, job_placement_1.getRunLabels)(env)) },
|
|
133
|
+
...(scope.owners ? { ownerReferences: scope.owners } : {}),
|
|
125
134
|
},
|
|
126
135
|
spec: {
|
|
127
136
|
podSelector: { matchLabels: getItemPodLabels(item, scope) },
|
|
@@ -129,9 +138,9 @@ function buildNetworkPolicy(env, item, network, scope, proxy = getRunProxy(item,
|
|
|
129
138
|
ingress: getIngress(env, item),
|
|
130
139
|
egress: [
|
|
131
140
|
...(needs.length > 0
|
|
132
|
-
? [{ to: needs.map((need) => ({ podSelector: { matchLabels:
|
|
141
|
+
? [{ to: needs.map((need) => ({ podSelector: { matchLabels: (0, resources_1.getItemLabels)(need.service, env) } })) }]
|
|
133
142
|
: []),
|
|
134
|
-
...(
|
|
143
|
+
...(proxied
|
|
135
144
|
? [
|
|
136
145
|
{
|
|
137
146
|
to: [{ podSelector: { matchLabels: { [PROXY_LABEL]: proxy.key } } }],
|
|
@@ -179,6 +188,9 @@ exports.buildProxyPolicy = buildProxyPolicy;
|
|
|
179
188
|
// left goes.
|
|
180
189
|
// A task follows the proxy's log while its jobs run. A service outlives the
|
|
181
190
|
// run on a cluster, so its refused hosts are in the proxy's own log.
|
|
191
|
+
// `secretHosts` are the endpoints of the providers whose secrets the init
|
|
192
|
+
// containers of a task on a server read: a limited task gets a proxy for them
|
|
193
|
+
// (under `none` too), which passes them to those containers only.
|
|
182
194
|
async function ensureKubernetesItemNetwork(instance, env, item, options) {
|
|
183
195
|
const network = (0, work_network_1.getEffectiveNetwork)(item.data);
|
|
184
196
|
if (network.type !== 'open' && !env.networkPolicyEnforced) {
|
|
@@ -191,22 +203,30 @@ async function ensureKubernetesItemNetwork(instance, env, item, options) {
|
|
|
191
203
|
// without the rights to manage NetworkPolicies, hammerkit never made one
|
|
192
204
|
await removeKubernetesItemNetwork(instance, env, item, { run: null }, { ignoreForbidden: true });
|
|
193
205
|
}
|
|
194
|
-
return { env: [], hostAliases, initContainers: [], close() { } };
|
|
206
|
+
return { env: [], secretEnv: [], hostAliases, initContainers: [], close() { } };
|
|
207
|
+
}
|
|
208
|
+
const secretHosts = options.secretHosts ?? [];
|
|
209
|
+
if (secretHosts.length > 0 && options.sandbox) {
|
|
210
|
+
// a run on a sandbox uses no account, so it reads no secret of a provider
|
|
211
|
+
throw new Error(`${item.name}: a task on a sandbox reads no secret of a provider`);
|
|
195
212
|
}
|
|
196
213
|
const proxy = network.type === 'allow' && options.sandbox
|
|
197
214
|
? getSandboxProxy(env, item, network, hostAliases, options.sandbox)
|
|
198
215
|
: getRunProxy(item, options);
|
|
199
|
-
|
|
216
|
+
const proxied = network.type === 'allow' || secretHosts.length > 0;
|
|
217
|
+
await (0, apply_1.apply)(instance, buildNetworkPolicy(env, item, network, options, proxy, proxied));
|
|
200
218
|
const initContainers = [(0, network_gate_1.getNetworkGateContainer)()];
|
|
201
|
-
if (
|
|
219
|
+
if (!proxied) {
|
|
202
220
|
if (options.sandbox) {
|
|
203
221
|
await removeSandboxProxies(instance, env.namespace, { sandbox: options.sandbox.label, item });
|
|
204
222
|
}
|
|
205
223
|
else {
|
|
206
224
|
await removeProxy(instance, env.namespace, proxy.name, [404]);
|
|
207
225
|
}
|
|
208
|
-
return { env: [], hostAliases, initContainers, close() { } };
|
|
226
|
+
return { env: [], secretEnv: [], hostAliases, initContainers, close() { } };
|
|
209
227
|
}
|
|
228
|
+
// under `none`, a proxy for the secrets only: it allows the item no host
|
|
229
|
+
const proxyNetwork = network.type === 'allow' ? network : { type: 'allow', hosts: [] };
|
|
210
230
|
let started;
|
|
211
231
|
try {
|
|
212
232
|
if (proxy.sandbox) {
|
|
@@ -214,7 +234,7 @@ async function ensureKubernetesItemNetwork(instance, env, item, options) {
|
|
|
214
234
|
await removeSandboxProxies(instance, env.namespace, { sandbox: proxy.sandbox.label, item }, (deployment) => deployment.metadata?.labels?.[PROXY_LABEL] === proxy.key);
|
|
215
235
|
proxy.owner = await getFilesPodOwner(instance, env, proxy.sandbox);
|
|
216
236
|
}
|
|
217
|
-
started = await ensureProxy(instance, env, item,
|
|
237
|
+
started = await ensureProxy(instance, env, item, proxyNetwork, hostAliases, options, proxy, secretHosts);
|
|
218
238
|
}
|
|
219
239
|
catch (e) {
|
|
220
240
|
await removeKubernetesItemNetwork(instance, env, item, options).catch(() => undefined);
|
|
@@ -223,11 +243,17 @@ async function ensureKubernetesItemNetwork(instance, env, item, options) {
|
|
|
223
243
|
const follow = options.followLog
|
|
224
244
|
? await followProxyLog(instance, env, item, proxy, { tailLines: started.reused ? 0 : undefined })
|
|
225
245
|
: null;
|
|
246
|
+
const needs = item.needs.map((need) => need.name);
|
|
247
|
+
const toEnv = (variables) => Object.entries(variables).map(([name, value]) => ({ name, value }));
|
|
226
248
|
return {
|
|
227
|
-
env:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
env: network.type === 'allow' ? toEnv((0, egress_proxy_1.getProxyClientEnvironment)(needs)) : [],
|
|
250
|
+
// the token from the proxy's Secret, which the item's own container never mounts
|
|
251
|
+
secretEnv: secretHosts.length > 0
|
|
252
|
+
? [
|
|
253
|
+
{ name: SECRET_TOKEN_VARIABLE, valueFrom: { secretKeyRef: { name: proxy.name, key: SECRET_TOKEN_KEY } } },
|
|
254
|
+
...toEnv((0, egress_proxy_1.getProxyClientEnvironment)(needs, `$(${SECRET_TOKEN_VARIABLE})`)),
|
|
255
|
+
]
|
|
256
|
+
: [],
|
|
231
257
|
// the item reaches each TCP forward under its host's name, at the proxy
|
|
232
258
|
hostAliases: [
|
|
233
259
|
...hostAliases,
|
|
@@ -286,12 +312,14 @@ async function releaseKubernetesItemNetwork(instance, env, item, scope) {
|
|
|
286
312
|
}
|
|
287
313
|
exports.releaseKubernetesItemNetwork = releaseKubernetesItemNetwork;
|
|
288
314
|
// Every policy and proxy of the item, of any run, once none of its pods runs:
|
|
289
|
-
// what `down` and `clean` remove.
|
|
315
|
+
// what `down` and `clean` remove. On a server, those of the server's run only: the
|
|
316
|
+
// runs of a caller share their namespace, and the pods of another run of the same item
|
|
317
|
+
// would run unlimited without their policy.
|
|
290
318
|
async function removeAllKubernetesItemNetworks(instance, env, item) {
|
|
291
|
-
if (!(await awaitPodsEnded(instance, env,
|
|
319
|
+
if (!(await awaitPodsEnded(instance, env, (0, resources_1.getItemLabels)(item, env)))) {
|
|
292
320
|
throw new Error(`pods of ${item.name} are still running, so its network policy stays`);
|
|
293
321
|
}
|
|
294
|
-
const labelSelector =
|
|
322
|
+
const labelSelector = toSelector({ [NETWORK_OF_LABEL]: item.id(), ...(0, job_placement_1.getRunLabels)(env) });
|
|
295
323
|
let policies;
|
|
296
324
|
try {
|
|
297
325
|
const list = await instance.objectApi.list('networking.k8s.io/v1', 'NetworkPolicy', env.namespace, undefined, undefined, undefined, undefined, labelSelector);
|
|
@@ -318,6 +346,7 @@ async function removeAllKubernetesItemNetworks(instance, env, item) {
|
|
|
318
346
|
if (name) {
|
|
319
347
|
await ignoreStatus(instance.appsApi.deleteNamespacedDeployment({ name, namespace: env.namespace }), [404]);
|
|
320
348
|
await ignoreStatus(instance.coreApi.deleteNamespacedService({ name, namespace: env.namespace }), [404]);
|
|
349
|
+
await ignoreStatus(instance.coreApi.deleteNamespacedSecret({ name, namespace: env.namespace }), [404]);
|
|
321
350
|
}
|
|
322
351
|
}
|
|
323
352
|
}
|
|
@@ -349,7 +378,7 @@ function getProxyReadinessScript(ports) {
|
|
|
349
378
|
`.on('error', () => process.exit(1))`);
|
|
350
379
|
}
|
|
351
380
|
exports.getProxyReadinessScript = getProxyReadinessScript;
|
|
352
|
-
async function ensureProxy(instance, env, item, network, hostAliases, scope, proxy) {
|
|
381
|
+
async function ensureProxy(instance, env, item, network, hostAliases, scope, proxy, secretHosts = []) {
|
|
353
382
|
const name = proxy.name;
|
|
354
383
|
const labels = { [PROXY_LABEL]: proxy.key };
|
|
355
384
|
const metadata = {
|
|
@@ -388,7 +417,17 @@ async function ensureProxy(instance, env, item, network, hostAliases, scope, pro
|
|
|
388
417
|
name: 'proxy',
|
|
389
418
|
image: egress_proxy_1.EGRESS_PROXY_IMAGE,
|
|
390
419
|
command: (0, egress_proxy_1.getEgressProxyCommand)(),
|
|
391
|
-
env:
|
|
420
|
+
env: [
|
|
421
|
+
...Object.entries({ ...(0, egress_proxy_1.getItemProxyEnvironment)(item), ...(0, egress_proxy_1.getSecretProxyEnvironment)(secretHosts) }).map(([key, value]) => ({ name: key, value })),
|
|
422
|
+
...(secretHosts.length > 0
|
|
423
|
+
? [
|
|
424
|
+
{
|
|
425
|
+
name: 'HAMMERKIT_SECRET_TOKEN',
|
|
426
|
+
valueFrom: { secretKeyRef: { name, key: SECRET_TOKEN_KEY } },
|
|
427
|
+
},
|
|
428
|
+
]
|
|
429
|
+
: []),
|
|
430
|
+
],
|
|
392
431
|
ports: (0, egress_proxy_1.getEgressProxyPorts)(network).map((port) => ({ containerPort: port })),
|
|
393
432
|
// in the pod, since its ingress policy may keep the kubelet out;
|
|
394
433
|
// ready once it listens on every port, its TCP forwards' too
|
|
@@ -422,6 +461,21 @@ async function ensureProxy(instance, env, item, network, hostAliases, scope, pro
|
|
|
422
461
|
? await instance.objectApi.read(deployment).then((current) => !current.metadata?.deletionTimestamp, (e) => ((0, apply_1.statusCodeOf)(e) === 404 ? false : Promise.reject(e)))
|
|
423
462
|
: false;
|
|
424
463
|
await (0, apply_1.apply)(instance, buildProxyPolicy(env, item, network, scope, proxy));
|
|
464
|
+
if (secretHosts.length > 0) {
|
|
465
|
+
// new with each run's proxy, and kept while it is there: its pod read it when it started.
|
|
466
|
+
// The init containers that read secrets get it, the task not.
|
|
467
|
+
const secret = {
|
|
468
|
+
apiVersion: 'v1',
|
|
469
|
+
kind: 'Secret',
|
|
470
|
+
metadata: { ...metadata, annotations: { 'hammerkit.dev/version': (0, version_1.getVersion)() } },
|
|
471
|
+
type: 'Opaque',
|
|
472
|
+
data: { [SECRET_TOKEN_KEY]: Buffer.from((0, crypto_1.randomBytes)(32).toString('hex')).toString('base64') },
|
|
473
|
+
};
|
|
474
|
+
const exists = await instance.objectApi.read(secret).then(() => true, (e) => ((0, apply_1.statusCodeOf)(e) === 404 ? false : Promise.reject(e)));
|
|
475
|
+
if (!exists) {
|
|
476
|
+
await instance.objectApi.create(secret);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
425
479
|
await (0, apply_1.apply)(instance, deployment);
|
|
426
480
|
const created = await (0, apply_1.apply)(instance, service);
|
|
427
481
|
await (0, await_running_state_1.awaitDeployRunningState)(instance, env, name, {
|
|
@@ -433,7 +487,7 @@ async function ensureProxy(instance, env, item, network, hostAliases, scope, pro
|
|
|
433
487
|
if (!ip) {
|
|
434
488
|
throw new Error(`the egress proxy of ${item.name} has no cluster ip`);
|
|
435
489
|
}
|
|
436
|
-
item.status.write('debug', `${reused ? 'egress proxy of the sandbox' : 'egress proxy'} allows ${network.hosts.join(', ') || 'no host'}`);
|
|
490
|
+
item.status.write('debug', `${reused ? 'egress proxy of the sandbox' : 'egress proxy'} allows ${network.hosts.join(', ') || 'no host'}${secretHosts.length > 0 ? `, and ${secretHosts.join(', ')} to the reads of secrets` : ''}`);
|
|
437
491
|
return { ip, reused };
|
|
438
492
|
}
|
|
439
493
|
// Refused hosts show up in the item's log, as on Docker; of a proxy from an
|
|
@@ -468,9 +522,13 @@ async function followProxyLog(instance, env, item, proxy, options) {
|
|
|
468
522
|
return null;
|
|
469
523
|
}
|
|
470
524
|
}
|
|
471
|
-
|
|
525
|
+
// The token of a proxy for secrets goes with it; a proxy of a sandbox has none.
|
|
526
|
+
async function removeProxy(instance, namespace, name, ignored, options = { token: true }) {
|
|
472
527
|
await ignoreStatus(instance.appsApi.deleteNamespacedDeployment({ name, namespace }), ignored);
|
|
473
528
|
await ignoreStatus(instance.coreApi.deleteNamespacedService({ name, namespace }), ignored);
|
|
529
|
+
if (options.token) {
|
|
530
|
+
await ignoreStatus(instance.coreApi.deleteNamespacedSecret({ name, namespace }), ignored);
|
|
531
|
+
}
|
|
474
532
|
await ignoreStatus(instance.objectApi.delete({
|
|
475
533
|
apiVersion: 'networking.k8s.io/v1',
|
|
476
534
|
kind: 'NetworkPolicy',
|
|
@@ -492,7 +550,7 @@ async function removeSandboxProxies(instance, namespace, of, keep = () => false)
|
|
|
492
550
|
for (const deployment of deployments.items) {
|
|
493
551
|
const name = deployment.metadata?.name;
|
|
494
552
|
if (name && !keep(deployment)) {
|
|
495
|
-
await removeProxy(instance, namespace, name, [404]);
|
|
553
|
+
await removeProxy(instance, namespace, name, [404], { token: false });
|
|
496
554
|
}
|
|
497
555
|
}
|
|
498
556
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.removePersistentData = void 0;
|
|
4
|
+
const resources_1 = require("./resources");
|
|
4
5
|
async function removePersistentData(instance, kubernetes, task) {
|
|
5
|
-
|
|
6
|
+
// on a server, the run's: a claim it kept has no run label and goes once it expired
|
|
7
|
+
const labelSelector = (0, resources_1.getItemSelector)(task, kubernetes);
|
|
6
8
|
const configmaps = await instance.coreApi.listNamespacedConfigMap({ namespace: kubernetes.namespace, labelSelector });
|
|
7
9
|
for (const configMap of configmaps.items) {
|
|
8
10
|
if (configMap.metadata?.name) {
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.removeInvalidCharacters = exports.getResourceName = void 0;
|
|
3
|
+
exports.removeInvalidCharacters = exports.getResourceName = exports.getItemSelector = exports.getItemLabels = exports.getItemKey = exports.ID_LABEL = void 0;
|
|
4
|
+
const job_placement_1 = require("./job-placement");
|
|
4
5
|
// An object name is a DNS label: lowercase letters, digits and `-`, at most 63
|
|
5
6
|
// characters, starting and ending with a letter or digit. The id makes a name
|
|
6
7
|
// unique; the item's name is there for people and is cut to fit.
|
|
7
8
|
const MAX_NAME = 63;
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
exports.ID_LABEL = 'hammerkit.dev/id';
|
|
10
|
+
// What tells the objects of an item apart in its namespace: its id, and on a server the
|
|
11
|
+
// run's label too. The runs of a caller execute side by side in their namespace, and two
|
|
12
|
+
// of them often run the same task (the same command on two sandboxes of a repository),
|
|
13
|
+
// so each has objects of its own, which nothing of the other run finds or removes. The
|
|
14
|
+
// first 8 digits of the run label tell runs apart, as they do for a run's proxies.
|
|
15
|
+
function getItemKey(item, env) {
|
|
16
|
+
return env.server ? `${item.id().substring(0, 32)}-${(0, job_placement_1.getRunLabelValue)(env.server.run).substring(0, 8)}` : item.id();
|
|
17
|
+
}
|
|
18
|
+
exports.getItemKey = getItemKey;
|
|
19
|
+
// The labels the objects of an item carry, by which they are found again
|
|
20
|
+
function getItemLabels(item, env) {
|
|
21
|
+
return { [exports.ID_LABEL]: item.id(), ...(0, job_placement_1.getRunLabels)(env) };
|
|
22
|
+
}
|
|
23
|
+
exports.getItemLabels = getItemLabels;
|
|
24
|
+
function getItemSelector(item, env) {
|
|
25
|
+
return Object.entries(getItemLabels(item, env))
|
|
26
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
27
|
+
.join(',');
|
|
28
|
+
}
|
|
29
|
+
exports.getItemSelector = getItemSelector;
|
|
30
|
+
function getResourceName(item, env, suffix) {
|
|
31
|
+
const tail = `-${getItemKey(item, env)}${suffix ?? ''}`;
|
|
10
32
|
const name = removeInvalidCharacters(item.name)
|
|
11
33
|
.slice(0, Math.max(0, MAX_NAME - tail.length))
|
|
12
34
|
.replace(/-+$/, '');
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.restoreKubernetesData = void 0;
|
|
3
|
+
exports.restoreKubernetesData = exports.getRestoreTarget = void 0;
|
|
4
4
|
const event_cache_1 = require("../executer/event-cache");
|
|
5
5
|
const log_1 = require("../log");
|
|
6
|
-
const path_1 = require("path");
|
|
7
6
|
const volumes_1 = require("./volumes");
|
|
8
7
|
const ensure_persistent_data_1 = require("./ensure-persistent-data");
|
|
8
|
+
const claim_layout_1 = require("./claim-layout");
|
|
9
|
+
// Where the upload pod (which mounts the work's claim at /dev/hammerkit/<id>) unpacks the
|
|
10
|
+
// archive of an output: the output's place in the claim, which its mount reads
|
|
11
|
+
function getRestoreTarget(work, path) {
|
|
12
|
+
return `/dev/hammerkit/${work.id()}/${(0, claim_layout_1.getClaimSubPath)(work, path)}`;
|
|
13
|
+
}
|
|
14
|
+
exports.getRestoreTarget = getRestoreTarget;
|
|
9
15
|
async function restoreKubernetesData(work, kubernetes, instance, environment, path,
|
|
10
16
|
// the kept claim of the work holds what an earlier run left, which goes first
|
|
11
17
|
options = {}) {
|
|
@@ -18,7 +24,7 @@ options = {}) {
|
|
|
18
24
|
if (!(await environment.file.exists(generatedArchive.filename))) {
|
|
19
25
|
continue;
|
|
20
26
|
}
|
|
21
|
-
const targetPath =
|
|
27
|
+
const targetPath = getRestoreTarget(work, generatedArchive.path);
|
|
22
28
|
// a claim made for this run (a server removes the claims of a run when it
|
|
23
29
|
// ends) does not have the directory of the output yet
|
|
24
30
|
let failure = null;
|