hammerkit 2.0.0-next.1 → 2.0.0-next.3

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.evictKeptClaims = exports.sweepKeptClaims = exports.expireKeptClaims = exports.recordClaimState = exports.forgetClaimState = exports.useClaimState = exports.isKeptClaim = exports.getClaimName = exports.EXPIRES_ANNOTATION = exports.STATE_ANNOTATION = exports.KEPT_BY_LABEL = exports.VOLUME_SCOPE_LABEL = void 0;
3
+ exports.evictKeptClaims = exports.sweepKeptClaims = exports.expireKeptClaims = exports.recordClaimState = exports.forgetClaimState = exports.rememberedClaimState = exports.useClaimState = exports.isKeptClaim = exports.getClaimName = exports.EXPIRES_ANNOTATION = exports.STATE_ANNOTATION = exports.KEPT_BY_LABEL = exports.VOLUME_SCOPE_LABEL = void 0;
4
4
  const crypto_1 = require("crypto");
5
5
  const apply_1 = require("./apply");
6
6
  const sleep_1 = require("../utils/sleep");
@@ -98,6 +98,17 @@ async function annotate(instance, namespace, name, annotations) {
98
98
  throw e;
99
99
  }
100
100
  }
101
+ // The state key of the outputs whose folder is whole in the claim of a task of a service run
102
+ // (see getStateFilesDir), null when it names none. The run's namespace is its own, and only
103
+ // the run writes to the claim: it forgets the state before it writes, records it after.
104
+ async function rememberedClaimState(instance, kubernetes, name) {
105
+ const claim = await readClaim(instance, kubernetes.namespace, name);
106
+ if (!claim || claim.metadata?.deletionTimestamp) {
107
+ return null;
108
+ }
109
+ return claim.metadata?.annotations?.[exports.STATE_ANNOTATION] ?? null;
110
+ }
111
+ exports.rememberedClaimState = rememberedClaimState;
101
112
  // Before a task writes to its kept claim: the outputs in it stop being those of a state.
102
113
  // Whether the claim was there, with what an earlier run left in it.
103
114
  function forgetClaimState(instance, kubernetes, name) {
@@ -170,6 +170,9 @@ function kubernetesTaskRuntime(task, kubernetes) {
170
170
  // on a server that keeps them, the task's claim stays after the run with the state of its
171
171
  // outputs, and the next run of the same source mounts them as they are (see kept-claims)
172
172
  const kept = (0, kept_claims_1.isKeptClaim)(task, kubernetes);
173
+ // on a service run, the claim names the last state whose folder is whole (see
174
+ // rememberedClaimState): a wake or a move at that state mounts it without an upload
175
+ const remembers = kept || !!kubernetes.server?.serviceRun;
173
176
  const claim = (0, volumes_1.getVolumeName)(task, kubernetes);
174
177
  return {
175
178
  initialize() {
@@ -182,6 +185,16 @@ function kubernetesTaskRuntime(task, kubernetes) {
182
185
  stateKey &&
183
186
  (await (0, ensure_persistent_data_1.getStateDirsInUse)(instance, kubernetes.namespace, claim)).has((0, volumes_1.getStateFilesDir)(stateKey))) {
184
187
  task.status.write('info', `${task.name}: services of the run use its outputs of this state, they stay`);
188
+ await (0, kept_claims_1.recordClaimState)(instance, kubernetes, claim, stateKey);
189
+ return;
190
+ }
191
+ if (remembers && !kept) {
192
+ // the folder of the state is written now: until it is whole, the claim names no state
193
+ await (0, kept_claims_1.forgetClaimState)(instance, kubernetes, claim);
194
+ await (0, restore_kubernetes_data_1.restoreKubernetesData)(task, kubernetes, instance, environment, path, { stateKey });
195
+ if (stateKey) {
196
+ await (0, kept_claims_1.recordClaimState)(instance, kubernetes, claim, stateKey);
197
+ }
185
198
  return;
186
199
  }
187
200
  if (!kept) {
@@ -231,11 +244,14 @@ function kubernetesTaskRuntime(task, kubernetes) {
231
244
  try {
232
245
  if (files && (await (0, ensure_persistent_data_1.getStateDirsInUse)(instance, kubernetes.namespace, claim)).has(files.dir)) {
233
246
  task.status.write('info', `${task.name}: services of the run use its outputs of this state, they stay`);
247
+ await (0, kept_claims_1.recordClaimState)(instance, kubernetes, claim, options.stateKey);
234
248
  return;
235
249
  }
236
250
  // the task writes to it now: what it holds stops being the outputs of a state, and
237
- // goes, as a new claim would hold nothing
238
- const reset = kept && (await (0, kept_claims_1.forgetClaimState)(instance, kubernetes, claim));
251
+ // goes, as a new claim would hold nothing (on a service run, the folders of other
252
+ // states that no pod mounts go)
253
+ const forgotten = remembers && (await (0, kept_claims_1.forgetClaimState)(instance, kubernetes, claim));
254
+ const reset = kept && forgotten;
239
255
  await (0, ensure_persistent_data_1.ensurePersistentData)(instance, kubernetes, environment, task, persistence, { reset, files });
240
256
  }
241
257
  catch (e) {
@@ -415,7 +431,7 @@ function kubernetesTaskRuntime(task, kubernetes) {
415
431
  }
416
432
  // the finished job stays for its state, the values it read need not
417
433
  await removeTaskSecrets(instance, kubernetes, task);
418
- if (kept) {
434
+ if (remembers) {
419
435
  await (0, kept_claims_1.recordClaimState)(instance, kubernetes, claim, options.stateKey);
420
436
  }
421
437
  }
@@ -471,6 +487,13 @@ function kubernetesTaskRuntime(task, kubernetes) {
471
487
  // the server removes the Jobs of a run at its end, the claim says
472
488
  return (0, kept_claims_1.useClaimState)(instance, kubernetes, claim);
473
489
  }
490
+ if (remembers) {
491
+ // a service run that sleeps loses its Jobs but keeps its claims, the claim says
492
+ const remembered = await (0, kept_claims_1.rememberedClaimState)(instance, kubernetes, claim);
493
+ if (remembered) {
494
+ return remembered;
495
+ }
496
+ }
474
497
  const jobs = await instance.batchApi.listNamespacedJob({
475
498
  namespace: kubernetes.namespace,
476
499
  labelSelector: (0, resources_1.getItemSelector)(task, kubernetes),
@@ -31,7 +31,7 @@ exports.hostServerSchema = zod_1.z
31
31
  .strict();
32
32
  // A command that prints the token for a server instead of a login, see token-source.ts.
33
33
  // It is bound to the url and issuer it was registered for, like a sign-in, and lives
34
- // beside `servers` rather than in it: hammerkit before 1.27 refuses unknown keys of a
34
+ // beside `servers` rather than in it: hammerkit before 2.0 refuses unknown keys of a
35
35
  // server, but keeps unknown top-level keys. It is only ever read from the host config,
36
36
  // a build file can't name one.
37
37
  const tokenCommandSchema = zod_1.z
@@ -221,7 +221,7 @@ function registerWorkCommands(remote, context) {
221
221
  }
222
222
  return { version, proposed };
223
223
  };
224
- // what publishing the bundle would do, undefined from a server that can't say (before 1.27)
224
+ // what publishing the bundle would do, undefined from a server that can't say (before 2.0)
225
225
  const planBundle = async (server, token, bundle) => {
226
226
  try {
227
227
  return (await (0, server_client_1.callServer)(server, token, 'POST', '/v1/definitions/plan', bundle)).body;
@@ -271,7 +271,7 @@ function registerWorkCommands(remote, context) {
271
271
  if (!environment.prompt) {
272
272
  throw new Error(`${notPublished}, publish it first with: hammerkit remote publish ${serverName}, or pass --publish`);
273
273
  }
274
- // a server before 1.27 can't say what publishing would do: it is asked all the same,
274
+ // a server before 2.0 can't say what publishing would do: it is asked all the same,
275
275
  // and refuses then what the caller may not do
276
276
  const planned = await planBundle(server, token, local);
277
277
  if (planned && planned.outcome === null) {
@@ -322,7 +322,7 @@ function registerWorkCommands(remote, context) {
322
322
  const { name: serverName, server, token } = await connect(name);
323
323
  const answer = await (0, server_client_1.callServer)(server, token, 'POST', '/v1/definitions/plan', bundle).catch((e) => {
324
324
  throw e instanceof server_client_1.ServerError && e.status === 404
325
- ? new Error(`${serverName} can't check a build file without publishing it, it needs hammerkit 1.27`)
325
+ ? new Error(`${serverName} can't check a build file without publishing it, it needs hammerkit 2.0`)
326
326
  : e;
327
327
  });
328
328
  const planned = answer.body;
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": "2.0.0-next.1",
4
+ "version": "2.0.0-next.3",
5
5
  "keywords": [
6
6
  "build",
7
7
  "build-tool",