hammerkit 1.27.0-next.8 → 2.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/build.schema.json +176 -6
  2. package/dist/cache/backends/local-cache-backend.js +6 -0
  3. package/dist/cache/backends/registry/registry-client.js +1 -0
  4. package/dist/cache/cache-sync.js +1 -1
  5. package/dist/cache/explain.js +17 -0
  6. package/dist/cli.js +2 -0
  7. package/dist/dashboard/assets/index-CjnKm5-3.css +2 -0
  8. package/dist/dashboard/assets/index-zPXa4klZ.js +13 -0
  9. package/dist/dashboard/favicon.svg +1 -0
  10. package/dist/dashboard/index.html +15 -0
  11. package/dist/docker/item-network.js +13 -5
  12. package/dist/executer/archive-task-entry.js +4 -2
  13. package/dist/executer/deadline.js +17 -3
  14. package/dist/executer/deferred-restore.js +36 -0
  15. package/dist/executer/docker-service.js +30 -8
  16. package/dist/executer/docker-task.js +21 -1
  17. package/dist/executer/execute-work-service.js +2 -0
  18. package/dist/executer/execute-work-task.js +11 -4
  19. package/dist/executer/execution-steps.js +25 -4
  20. package/dist/executer/reset-work-tree.js +4 -0
  21. package/dist/executer/scheduler/enqueue-next.js +57 -6
  22. package/dist/executer/scheduler/state-key.js +42 -5
  23. package/dist/executer/watch-loop.js +6 -3
  24. package/dist/harness/connect.js +20 -6
  25. package/dist/harness/mcp-command.js +22 -6
  26. package/dist/harness/mcp.js +63 -114
  27. package/dist/harness/opencode.js +158 -50
  28. package/dist/harness/pi.js +29 -0
  29. package/dist/harness/sandbox-client.js +194 -23
  30. package/dist/harness/service-tools.js +141 -0
  31. package/dist/harness/task-tools.js +221 -0
  32. package/dist/kubernetes/apply.js +76 -17
  33. package/dist/kubernetes/await-running-state.js +172 -13
  34. package/dist/kubernetes/cluster-state.js +245 -0
  35. package/dist/kubernetes/ensure-http-route.js +1 -1
  36. package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +138 -18
  37. package/dist/kubernetes/ensure-kubernetes-service-exists.js +6 -9
  38. package/dist/kubernetes/ensure-persistent-data.js +212 -10
  39. package/dist/kubernetes/follow-pod-logs.js +182 -0
  40. package/dist/kubernetes/get-service-ip.js +2 -2
  41. package/dist/kubernetes/job-placement.js +36 -12
  42. package/dist/kubernetes/job-secrets.js +9 -6
  43. package/dist/kubernetes/kept-claims.js +22 -10
  44. package/dist/kubernetes/kubernetes-instance.js +1 -0
  45. package/dist/kubernetes/network-policy.js +16 -8
  46. package/dist/kubernetes/remove-persistent-data.js +3 -1
  47. package/dist/kubernetes/remove-stale-items.js +46 -0
  48. package/dist/kubernetes/resources.js +25 -3
  49. package/dist/kubernetes/restore-kubernetes-data.js +9 -5
  50. package/dist/kubernetes/sandbox-runner.js +628 -0
  51. package/dist/kubernetes/secrets.js +7 -11
  52. package/dist/kubernetes/service-rollouts.js +74 -0
  53. package/dist/kubernetes/store-kubernetes-data.js +5 -2
  54. package/dist/kubernetes/volumes.js +96 -11
  55. package/dist/main.js +2 -0
  56. package/dist/optimizer/calculate-checksum.js +3 -2
  57. package/dist/optimizer/check-key.js +94 -0
  58. package/dist/optimizer/check-keys.js +64 -0
  59. package/dist/optimizer/get-work-cache-stats.js +17 -3
  60. package/dist/optimizer/work-task-cache-description.js +49 -1
  61. package/dist/planner/service-definition.js +9 -4
  62. package/dist/planner/utils/append-work-dependencies.js +5 -3
  63. package/dist/planner/utils/append-work-service.js +18 -1
  64. package/dist/planner/utils/append-work-task.js +13 -3
  65. package/dist/planner/utils/get-work-runtime.js +6 -1
  66. package/dist/planner/utils/normalize-path.js +19 -1
  67. package/dist/planner/utils/parse-work-mount.js +7 -1
  68. package/dist/planner/utils/parse-work-port.js +4 -1
  69. package/dist/planner/validate.js +22 -0
  70. package/dist/planner/work-network.js +3 -1
  71. package/dist/planner/work-runtime-aggregate.js +32 -0
  72. package/dist/planner/work-runtime-kubernetes.js +153 -26
  73. package/dist/planner/work-task.js +6 -1
  74. package/dist/program.js +27 -9
  75. package/dist/remote/device-login.js +7 -2
  76. package/dist/remote/host-config.js +69 -3
  77. package/dist/remote/remote-commands.js +33 -7
  78. package/dist/remote/remote-status.js +112 -0
  79. package/dist/remote/remote-work-commands.js +562 -45
  80. package/dist/remote/server-client.js +15 -2
  81. package/dist/remote/token-source.js +64 -26
  82. package/dist/run-program.js +9 -0
  83. package/dist/schema/build-file-base-task-schema.js +3 -3
  84. package/dist/schema/build-file-container-service-schema.js +10 -0
  85. package/dist/schema/build-file-container-task-schema.js +16 -1
  86. package/dist/schema/build-file-include-schema.js +18 -4
  87. package/dist/schema/build-file-kubernetes-service-schema.js +2 -0
  88. package/dist/schema/build-file-task-schema.js +5 -1
  89. package/dist/schema/capabilities-schema.js +56 -0
  90. package/dist/schema/reference-parser.js +119 -11
  91. package/dist/schema/removed-properties.js +20 -0
  92. package/dist/schema/schema-parser.js +94 -41
  93. package/dist/schema/work-scope-parser.js +33 -10
  94. package/dist/secrets/command-secret-provider.js +3 -1
  95. package/dist/secrets/github-app-token-command.js +3 -1
  96. package/dist/secrets/secret-catalog.js +43 -19
  97. package/dist/server/accounts-api.js +4 -1
  98. package/dist/server/approvals-api.js +1 -1
  99. package/dist/server/capacity-format.js +36 -0
  100. package/dist/server/capacity.js +300 -0
  101. package/dist/server/checks-api.js +83 -0
  102. package/dist/server/checks.js +116 -0
  103. package/dist/server/dashboard.js +196 -0
  104. package/dist/server/definitions-api.js +66 -3
  105. package/dist/server/endpoints.js +794 -0
  106. package/dist/server/executor.js +164 -9
  107. package/dist/server/kubernetes-backend.js +478 -70
  108. package/dist/server/kubernetes-sandbox-storage.js +46 -2
  109. package/dist/server/overview.js +271 -0
  110. package/dist/server/plan.js +170 -121
  111. package/dist/server/policy.js +34 -2
  112. package/dist/server/proposals-api.js +17 -8
  113. package/dist/server/repo-access.js +5 -0
  114. package/dist/server/run-logs.js +4 -0
  115. package/dist/server/run-plan.js +38 -4
  116. package/dist/server/run-request.js +58 -3
  117. package/dist/server/run-worker.js +262 -13
  118. package/dist/server/runs.js +969 -36
  119. package/dist/server/sandbox-pod.js +28 -0
  120. package/dist/server/sandbox-storage.js +23 -0
  121. package/dist/server/sandboxes.js +8 -0
  122. package/dist/server/server-config.js +246 -10
  123. package/dist/server/server.js +429 -32
  124. package/dist/server/service-runs.js +82 -0
  125. package/dist/server/timings.js +132 -0
  126. package/dist/server/wait-reason.js +21 -0
  127. package/dist/utils/tar.js +28 -1
  128. package/dist/utils/terminal-prompt.js +53 -0
  129. package/package.json +8 -2
@@ -24,6 +24,7 @@ function createKubernetesInstances(kubernetes) {
24
24
  return {
25
25
  objectApi,
26
26
  log: new client_node_1.Log(kc),
27
+ metrics: new client_node_1.Metrics(kc),
27
28
  exec,
28
29
  appsApi,
29
30
  coreApi,
@@ -17,7 +17,7 @@ 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 ID_LABEL = 'hammerkit.dev/id';
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
@@ -86,7 +86,7 @@ function getNetworkPolicyName(item, scope) {
86
86
  exports.getNetworkPolicyName = getNetworkPolicyName;
87
87
  // the pods of the item, of the run when the scope has one
88
88
  function getItemPodLabels(item, scope) {
89
- 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 } : {}) };
90
90
  }
91
91
  function toSelector(labels) {
92
92
  return Object.entries(labels)
@@ -107,7 +107,12 @@ function getIngress(env, item) {
107
107
  if (ids.length === 0) {
108
108
  return [];
109
109
  }
110
- const from = { matchExpressions: [{ key: ID_LABEL, operator: 'In', values: ids }] };
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
+ };
111
116
  // the client's name for `from`, a reserved word
112
117
  return [{ _from: [{ podSelector: from }] }];
113
118
  }
@@ -124,7 +129,8 @@ function buildNetworkPolicy(env, item, network, scope, proxy = getRunProxy(item,
124
129
  namespace: env.namespace,
125
130
  name: getNetworkPolicyName(item, scope),
126
131
  annotations: { 'hammerkit.dev/version': (0, version_1.getVersion)() },
127
- 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 } : {}),
128
134
  },
129
135
  spec: {
130
136
  podSelector: { matchLabels: getItemPodLabels(item, scope) },
@@ -132,7 +138,7 @@ function buildNetworkPolicy(env, item, network, scope, proxy = getRunProxy(item,
132
138
  ingress: getIngress(env, item),
133
139
  egress: [
134
140
  ...(needs.length > 0
135
- ? [{ to: needs.map((need) => ({ podSelector: { matchLabels: { [ID_LABEL]: need.service.id() } } })) }]
141
+ ? [{ to: needs.map((need) => ({ podSelector: { matchLabels: (0, resources_1.getItemLabels)(need.service, env) } })) }]
136
142
  : []),
137
143
  ...(proxied
138
144
  ? [
@@ -306,12 +312,14 @@ async function releaseKubernetesItemNetwork(instance, env, item, scope) {
306
312
  }
307
313
  exports.releaseKubernetesItemNetwork = releaseKubernetesItemNetwork;
308
314
  // Every policy and proxy of the item, of any run, once none of its pods runs:
309
- // 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.
310
318
  async function removeAllKubernetesItemNetworks(instance, env, item) {
311
- if (!(await awaitPodsEnded(instance, env, { [ID_LABEL]: item.id() }))) {
319
+ if (!(await awaitPodsEnded(instance, env, (0, resources_1.getItemLabels)(item, env)))) {
312
320
  throw new Error(`pods of ${item.name} are still running, so its network policy stays`);
313
321
  }
314
- const labelSelector = `${NETWORK_OF_LABEL}=${item.id()}`;
322
+ const labelSelector = toSelector({ [NETWORK_OF_LABEL]: item.id(), ...(0, job_placement_1.getRunLabels)(env) });
315
323
  let policies;
316
324
  try {
317
325
  const list = await instance.objectApi.list('networking.k8s.io/v1', 'NetworkPolicy', env.namespace, undefined, undefined, undefined, undefined, labelSelector);
@@ -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
- const labelSelector = `hammerkit.dev/id=${task.id()}`;
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) {
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStaleItemSelector = exports.removeStaleItems = void 0;
4
+ const job_placement_1 = require("./job-placement");
5
+ const resources_1 = require("./resources");
6
+ const apply_1 = require("./apply");
7
+ /**
8
+ * What a service run made for items it has no more, once it moved to another commit: the
9
+ * Deployment, Service, Secret, policies and Jobs of a service or task whose id changed (a new
10
+ * image, other volumes). Their claims stay, with the namespace of the run.
11
+ */
12
+ async function removeStaleItems(instance, env, ids) {
13
+ if (!env.server) {
14
+ return;
15
+ }
16
+ const namespace = env.namespace;
17
+ const labelSelector = getStaleItemSelector(env.server.run, ids);
18
+ const selector = { namespace, labelSelector };
19
+ await instance.appsApi.deleteCollectionNamespacedDeployment({ ...selector, propagationPolicy: 'Background' });
20
+ await instance.batchApi.deleteCollectionNamespacedJob({ ...selector, propagationPolicy: 'Background' });
21
+ await instance.coreApi.deleteCollectionNamespacedService(selector);
22
+ await instance.coreApi.deleteCollectionNamespacedSecret(selector);
23
+ const policies = await instance.objectApi.list('networking.k8s.io/v1', 'NetworkPolicy', namespace, undefined, undefined, undefined, undefined, labelSelector);
24
+ for (const policy of policies.items) {
25
+ const metadata = { name: policy.metadata?.name, namespace };
26
+ await instance.objectApi
27
+ .delete({ apiVersion: 'networking.k8s.io/v1', kind: 'NetworkPolicy', metadata })
28
+ .catch((e) => {
29
+ if ((0, apply_1.statusCodeOf)(e) !== 404) {
30
+ throw e;
31
+ }
32
+ });
33
+ }
34
+ }
35
+ exports.removeStaleItems = removeStaleItems;
36
+ // the objects of the run's items but those of `ids`
37
+ function getStaleItemSelector(run, ids) {
38
+ const current = [...new Set(ids)].sort();
39
+ return [
40
+ `${job_placement_1.RUN_LABEL}=${(0, job_placement_1.getRunLabelValue)(run)}`,
41
+ resources_1.ID_LABEL,
42
+ ...(current.length > 0 ? [`${resources_1.ID_LABEL} notin (${current.join(',')})`] : []),
43
+ ].join(',');
44
+ }
45
+ exports.getStaleItemSelector = getStaleItemSelector;
46
+ //# sourceMappingURL=remove-stale-items.js.map
@@ -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
- function getResourceName(item, suffix) {
9
- const tail = `-${item.id()}${suffix ?? ''}`;
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(/-+$/, '');
@@ -8,14 +8,18 @@ const ensure_persistent_data_1 = require("./ensure-persistent-data");
8
8
  const claim_layout_1 = require("./claim-layout");
9
9
  // Where the upload pod (which mounts the work's claim at /dev/hammerkit/<id>) unpacks the
10
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)}`;
11
+ function getRestoreTarget(work, path,
12
+ // the folder of the state on a service run, see getStateFilesDir
13
+ stateDir) {
14
+ return `/dev/hammerkit/${work.id()}/${stateDir ? `${stateDir}/` : ''}${(0, claim_layout_1.getClaimSubPath)(work, path)}`;
13
15
  }
14
16
  exports.getRestoreTarget = getRestoreTarget;
15
17
  async function restoreKubernetesData(work, kubernetes, instance, environment, path,
16
- // the kept claim of the work holds what an earlier run left, which goes first
18
+ // the kept claim of the work holds what an earlier run left, which goes first; on a
19
+ // service run, the outputs go into the folder of `stateKey` (see getStateFilesDir)
17
20
  options = {}) {
18
- const persistence = await (0, volumes_1.getKubernetesPersistence)(work, kubernetes);
21
+ const persistence = await (0, volumes_1.getKubernetesPersistence)(work, kubernetes, options.stateKey);
22
+ const stateDir = kubernetes.server?.serviceRun ? (0, volumes_1.getItemFilesDir)(work, options.stateKey) : undefined;
19
23
  await (0, ensure_persistent_data_1.getPodForPersistence)(instance, kubernetes, work, persistence, 'write', async (name) => {
20
24
  if (options.reset) {
21
25
  await (0, ensure_persistent_data_1.clearClaim)(instance, kubernetes, work, name);
@@ -24,7 +28,7 @@ options = {}) {
24
28
  if (!(await environment.file.exists(generatedArchive.filename))) {
25
29
  continue;
26
30
  }
27
- const targetPath = getRestoreTarget(work, generatedArchive.path);
31
+ const targetPath = getRestoreTarget(work, generatedArchive.path, stateDir);
28
32
  // a claim made for this run (a server removes the claims of a run when it
29
33
  // ends) does not have the directory of the output yet
30
34
  let failure = null;