@olivervea/pipelines 2.0.0-beta.18 → 2.0.0-beta.19

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 (51) hide show
  1. package/package.json +4 -2
  2. package/src/assets.d.ts +2 -2
  3. package/src/build/build.ts +111 -43
  4. package/src/build/cli.ts +28 -21
  5. package/src/build/elf.ts +8 -7
  6. package/src/build/hashes.ts +61 -28
  7. package/src/build/project.ts +69 -30
  8. package/src/builder/pipeline.ts +274 -102
  9. package/src/builder/refs.ts +12 -6
  10. package/src/core/brands.ts +6 -6
  11. package/src/core/config-refs.ts +46 -20
  12. package/src/core/config.ts +110 -33
  13. package/src/core/external.ts +30 -12
  14. package/src/core/handlers.ts +54 -14
  15. package/src/core/models.ts +29 -14
  16. package/src/core/origin.ts +7 -4
  17. package/src/core/secrets.ts +2 -2
  18. package/src/core/steps.ts +39 -23
  19. package/src/core/types.ts +5 -1
  20. package/src/core/webhooks.ts +21 -7
  21. package/src/ctx/files.ts +111 -40
  22. package/src/ctx/index.ts +75 -34
  23. package/src/ctx/sh.ts +104 -44
  24. package/src/ctx/types.ts +4 -4
  25. package/src/describe/describe.ts +550 -196
  26. package/src/describe/manifest-schema.ts +210 -80
  27. package/src/describe/mapper-source.ts +249 -104
  28. package/src/describe/record.ts +67 -37
  29. package/src/dev/cli.ts +64 -34
  30. package/src/harness/env.ts +33 -27
  31. package/src/harness/execute.ts +55 -29
  32. package/src/harness/index.ts +21 -8
  33. package/src/harness/main.ts +197 -74
  34. package/src/harness/result.ts +38 -22
  35. package/src/harness/validate.ts +62 -19
  36. package/src/index.ts +123 -14
  37. package/src/library/checks.ts +11 -6
  38. package/src/library/compose-deploy.ts +40 -23
  39. package/src/library/github-source.ts +69 -39
  40. package/src/library/helm-deploy-ssh.ts +213 -76
  41. package/src/library/helm-deploy.ts +263 -85
  42. package/src/library/http-health.ts +7 -5
  43. package/src/library/image-build.ts +293 -117
  44. package/src/library/index.ts +47 -15
  45. package/src/library/models.ts +17 -9
  46. package/src/library/ssh.ts +31 -14
  47. package/src/testing/index.ts +63 -24
  48. package/src/util/canonical.ts +7 -4
  49. package/src/util/handler-hash.ts +15 -5
  50. package/src/util/hash.ts +5 -5
  51. package/src/util/tar.ts +74 -37
@@ -3,42 +3,52 @@
3
3
  //
4
4
  // Ported from v1's olve-lib.sh (olve_ssh_host, olve_image_import, olve_helm_deploy) and
5
5
  // deploy-beta.sh (rollout wait + health loop); the footguns are kept as comments below.
6
- import { existsSync, readFileSync, statSync } from "node:fs";
7
- import { join } from "node:path";
8
- import type { Ctx } from "../ctx/types.ts";
9
- import type { FileRef } from "../core/models.ts";
10
- import type { Secret } from "../core/secrets.ts";
11
- import type { Configurable } from "../core/config.ts";
12
- import { resolveConfigRefs } from "../core/config-refs.ts";
13
- import { implement, type Implementation } from "../core/handlers.ts";
14
- import { step, type LiteralName, type Step } from "../core/steps.ts";
15
- import type { Simplify } from "../core/types.ts";
16
- import { httpHealthCheck, type HttpHealthCheckOptions } from "./http-health.ts";
17
- import { BuiltImage, HelmRelease, PushedImage, Source } from "./models.ts";
18
- import { shq, sshSession } from "./ssh.ts";
19
- import { helmDeployOverSshTarget, type HelmDeploySshTargetDef, type HelmTargetDeployInput } from "./helm-deploy.ts";
6
+ import { existsSync, readFileSync, statSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Ctx } from '../ctx/types.ts';
9
+ import type { FileRef } from '../core/models.ts';
10
+ import type { Secret } from '../core/secrets.ts';
11
+ import type { Configurable } from '../core/config.ts';
12
+ import { resolveConfigRefs } from '../core/config-refs.ts';
13
+ import { implement, type Implementation } from '../core/handlers.ts';
14
+ import { step, type LiteralName, type Step } from '../core/steps.ts';
15
+ import type { Simplify } from '../core/types.ts';
16
+ import { httpHealthCheck, type HttpHealthCheckOptions } from './http-health.ts';
17
+ import { BuiltImage, HelmRelease, PushedImage, Source } from './models.ts';
18
+ import { shq, sshSession } from './ssh.ts';
19
+ import {
20
+ helmDeployOverSshTarget,
21
+ type HelmDeploySshTargetDef,
22
+ type HelmTargetDeployInput,
23
+ } from './helm-deploy.ts';
20
24
 
21
25
  /** Alpine: `apk add openssh-client` at run time gives ssh/scp (the health check runs in the harness). */
22
- export const HELM_DEPLOY_IMAGE = "alpine:3.22";
26
+ export const HELM_DEPLOY_IMAGE = 'alpine:3.22';
23
27
 
24
28
  /**
25
29
  * Footgun: import via the k3s containerd socket, NOT the default one — kubelet only sees images in
26
30
  * the k3s instance's `k8s.io` namespace (otherwise pods fail with ErrImageNeverPull).
27
31
  */
28
- export const K3S_IMAGE_IMPORT = "sudo nerdctl --address /run/k3s/containerd/containerd.sock --namespace k8s.io load";
32
+ export const K3S_IMAGE_IMPORT =
33
+ 'sudo nerdctl --address /run/k3s/containerd/containerd.sock --namespace k8s.io load';
29
34
  /** Registry images are pulled the same way before the rollout, so the rollout doesn't wait on the pull. */
30
- export const K3S_IMAGE_PULL = "sudo nerdctl --address /run/k3s/containerd/containerd.sock --namespace k8s.io pull -q";
35
+ export const K3S_IMAGE_PULL =
36
+ 'sudo nerdctl --address /run/k3s/containerd/containerd.sock --namespace k8s.io pull -q';
31
37
 
32
38
  export const DEFAULT_ROLLOUT_TIMEOUT_SECONDS = 120;
33
39
 
34
- type DefaultImages = { readonly image: "image" };
35
- type ImageModel<R extends boolean> = R extends true ? typeof PushedImage : typeof BuiltImage;
40
+ type DefaultImages = { readonly image: 'image' };
41
+ type ImageModel<R extends boolean> = R extends true
42
+ ? typeof PushedImage
43
+ : typeof BuiltImage;
36
44
  export type HelmDeployOverSshInput<
37
45
  Im extends Record<string, string> = DefaultImages,
38
46
  R extends boolean = false,
39
47
  Rf extends Record<string, string> = {},
40
48
  > = Simplify<
41
- { readonly source: typeof Source } & { readonly [K in keyof Im]: ImageModel<R> } & { readonly [K in keyof Rf]: typeof PushedImage }
49
+ { readonly source: typeof Source } & { readonly [K in keyof Im]: ImageModel<R> } & {
50
+ readonly [K in keyof Rf]: typeof PushedImage;
51
+ }
42
52
  >;
43
53
 
44
54
  export interface HelmDeployOverSshDef<
@@ -78,7 +88,10 @@ export interface HelmDeployOverSshDef<
78
88
  * first, then `pullPolicy=IfNotPresent`. Default: `BuiltImage` tarballs, imported on the host, then
79
89
  * `pullPolicy=Never`.
80
90
  */
81
- fromRegistry?: R & (boolean extends R ? "❌ fromRegistry must be a literal true or false (use `as const` on a shared config object)" : unknown);
91
+ fromRegistry?: R &
92
+ (boolean extends R
93
+ ? '❌ fromRegistry must be a literal true or false (use `as const` on a shared config object)'
94
+ : unknown);
82
95
  /**
83
96
  * Registry images the chart only references (not run by the release's own pods, e.g. an image the
84
97
  * app starts Jobs with): step input key → values prefix. Each input is a `PushedImage`; the step sets
@@ -101,7 +114,12 @@ export interface HelmDeployOverSshDef<
101
114
  /** `helm upgrade --atomic`: wait like `helmWait`, and roll back when the upgrade fails. Default false. */
102
115
  atomic?: boolean;
103
116
  /** After the rollouts, GET this URL until it returns 2xx (`httpHealthCheck`: 5 × 5 s unless given), or fail the step. */
104
- healthUrl?: Configurable<string> | ({ url: Configurable<string> } & Pick<HttpHealthCheckOptions, "attempts" | "delaySeconds">);
117
+ healthUrl?:
118
+ | Configurable<string>
119
+ | ({ url: Configurable<string> } & Pick<
120
+ HttpHealthCheckOptions,
121
+ 'attempts' | 'delaySeconds'
122
+ >);
105
123
  timeoutSeconds?: number;
106
124
  }
107
125
 
@@ -109,7 +127,7 @@ export interface HelmImageFlags {
109
127
  prefix: string;
110
128
  repository: string;
111
129
  tag: string;
112
- pullPolicy: "Never" | "IfNotPresent";
130
+ pullPolicy: 'Never' | 'IfNotPresent';
113
131
  /** Pins the image: `<prefix>.digest` (the chart renders `repository:tag@digest`). */
114
132
  digest?: string;
115
133
  }
@@ -135,32 +153,49 @@ export function helmFlags(o: HelmFlagOptions): string[] {
135
153
  // copied chart dir. Extra flags come before the image flags, so the image overrides win.
136
154
  // --set-string: a tag like `2026.10` or `1e5` would otherwise be parsed as a number.
137
155
  const flags = [
138
- ...o.values.flatMap((v) => ["-f", v]),
139
- ...Object.entries(o.set).flatMap(([k, v]) => ["--set", `${k}=${v}`]),
156
+ ...o.values.flatMap((v) => ['-f', v]),
157
+ ...Object.entries(o.set).flatMap(([k, v]) => ['--set', `${k}=${v}`]),
140
158
  ...o.images.flatMap((i) => [
141
- "--set", `${i.prefix}.repository=${i.repository}`,
142
- "--set", `${i.prefix}.tag=${i.tag}`,
143
- "--set", `${i.prefix}.pullPolicy=${i.pullPolicy}`,
144
- ...(i.digest ? ["--set-string", `${i.prefix}.digest=${i.digest}`] : []),
159
+ '--set',
160
+ `${i.prefix}.repository=${i.repository}`,
161
+ '--set',
162
+ `${i.prefix}.tag=${i.tag}`,
163
+ '--set',
164
+ `${i.prefix}.pullPolicy=${i.pullPolicy}`,
165
+ ...(i.digest ? ['--set-string', `${i.prefix}.digest=${i.digest}`] : []),
145
166
  ]),
146
167
  ...(o.imageRefs ?? []).flatMap((i) => [
147
- "--set-string", `${i.prefix}.repository=${i.repository}`,
148
- "--set-string", `${i.prefix}.tag=${i.tag}`,
149
- "--set-string", `${i.prefix}.digest=${i.digest}`,
168
+ '--set-string',
169
+ `${i.prefix}.repository=${i.repository}`,
170
+ '--set-string',
171
+ `${i.prefix}.tag=${i.tag}`,
172
+ '--set-string',
173
+ `${i.prefix}.digest=${i.digest}`,
150
174
  ]),
151
175
  ];
152
176
  return flags;
153
177
  }
154
178
 
155
179
  /** Remote `helm upgrade --install`, run from inside the copied chart directory. */
156
- export function helmUpgradeCommand(o: HelmFlagOptions & { remoteChart: string; release: string; namespace: string; wait?: HelmWait }): string {
180
+ export function helmUpgradeCommand(
181
+ o: HelmFlagOptions & {
182
+ remoteChart: string;
183
+ release: string;
184
+ namespace: string;
185
+ wait?: HelmWait;
186
+ },
187
+ ): string {
157
188
  const flags = helmFlags(o);
158
- const wait = o.wait ? [o.wait.atomic ? "--atomic" : "--wait", `--timeout=${o.wait.timeoutSeconds}s`] : [];
189
+ const wait = o.wait
190
+ ? [o.wait.atomic ? '--atomic' : '--wait', `--timeout=${o.wait.timeoutSeconds}s`]
191
+ : [];
159
192
  return [
160
193
  `cd ${shq(o.remoteChart)}`,
161
- ["helm", "upgrade", "--install", o.release, ".", "-n", o.namespace, ...flags, ...wait].map(shq).join(" "),
194
+ ['helm', 'upgrade', '--install', o.release, '.', '-n', o.namespace, ...flags, ...wait]
195
+ .map(shq)
196
+ .join(' '),
162
197
  `cd / && rm -rf ${shq(o.remoteChart)}`,
163
- ].join(" && ");
198
+ ].join(' && ');
164
199
  }
165
200
 
166
201
  /** helm's own wait (`--wait`, or `--atomic` to roll back on failure). */
@@ -178,18 +213,39 @@ export interface ChartDependency {
178
213
 
179
214
  /** The dependencies `<chartDir>/Chart.yaml` lists, checked to be pullable (an http(s) or oci repository). */
180
215
  export function chartDependencies(chartDir: string): ChartDependency[] {
181
- const chart = Bun.YAML.parse(readFileSync(join(chartDir, "Chart.yaml"), "utf8")) as { dependencies?: Partial<ChartDependency>[] } | null;
216
+ const chart = Bun.YAML.parse(readFileSync(join(chartDir, 'Chart.yaml'), 'utf8')) as {
217
+ dependencies?: Partial<ChartDependency>[];
218
+ } | null;
182
219
  return (chart?.dependencies ?? []).map((d) => {
183
- if (!d.name || !d.version || !d.repository || !/^(https?|oci):\/\//.test(d.repository))
184
- throw new Error(`chart dependency ${JSON.stringify(d)}: needs a name, a version and an http(s) or oci repository`);
220
+ if (
221
+ !d.name ||
222
+ !d.version ||
223
+ !d.repository ||
224
+ !/^(https?|oci):\/\//.test(d.repository)
225
+ )
226
+ throw new Error(
227
+ `chart dependency ${JSON.stringify(d)}: needs a name, a version and an http(s) or oci repository`,
228
+ );
185
229
  return { name: d.name, version: d.version, repository: d.repository };
186
230
  });
187
231
  }
188
232
 
189
233
  /** The remote `helm pull` of a dependency into `<remoteChart>/charts`. */
190
234
  export function helmPullCommand(dep: ChartDependency, remoteChart: string): string {
191
- const ref = dep.repository.startsWith("oci://") ? [`${dep.repository.replace(/\/+$/, "")}/${dep.name}`] : [dep.name, "--repo", dep.repository];
192
- return ["helm", "pull", ...ref, "--version", dep.version, "--destination", `${remoteChart}/charts`].map(shq).join(" ");
235
+ const ref = dep.repository.startsWith('oci://')
236
+ ? [`${dep.repository.replace(/\/+$/, '')}/${dep.name}`]
237
+ : [dep.name, '--repo', dep.repository];
238
+ return [
239
+ 'helm',
240
+ 'pull',
241
+ ...ref,
242
+ '--version',
243
+ dep.version,
244
+ '--destination',
245
+ `${remoteChart}/charts`,
246
+ ]
247
+ .map(shq)
248
+ .join(' ');
193
249
  }
194
250
 
195
251
  /** One image to get onto the host before the upgrade. */
@@ -224,36 +280,55 @@ export async function deployOverSsh(
224
280
  const ssh = await sshSession(ctx, { host: o.host, key: o.key });
225
281
  for (const image of o.hostImages) {
226
282
  if (image.tar) {
227
- ctx.log("importing image", { image: `${image.repository}:${image.tag}`, host: o.host });
283
+ ctx.log('importing image', {
284
+ image: `${image.repository}:${image.tag}`,
285
+ host: o.host,
286
+ });
228
287
  // Streamed over ssh's stdin (ctx.exec has none, hence the shell redirect).
229
288
  await ctx.sh(`${ssh.shell(K3S_IMAGE_IMPORT)} < ${shq(image.tar)}`);
230
289
  } else {
231
- const ref = image.digest ? `${image.repository}@${image.digest}` : `${image.repository}:${image.tag}`;
232
- ctx.log("pulling image", { image: ref, host: o.host });
290
+ const ref = image.digest
291
+ ? `${image.repository}@${image.digest}`
292
+ : `${image.repository}:${image.tag}`;
293
+ ctx.log('pulling image', { image: ref, host: o.host });
233
294
  await ssh.run(`${K3S_IMAGE_PULL} ${shq(ref)}`);
234
295
  }
235
296
  }
236
297
  const remoteChart = `/tmp/${o.release}-helm-${o.namespace}`;
237
- ctx.log("helm upgrade", { release: o.release, namespace: o.namespace, host: o.host });
298
+ ctx.log('helm upgrade', { release: o.release, namespace: o.namespace, host: o.host });
238
299
  await ssh.run(`rm -rf ${shq(remoteChart)}`);
239
300
  await ssh.copy(o.chartDir, remoteChart);
240
301
  if (o.dependencies?.length) await ssh.run(`mkdir -p ${shq(`${remoteChart}/charts`)}`);
241
302
  for (const dep of o.dependencies ?? []) {
242
- ctx.log("pulling chart dependency", { chart: `${dep.name}@${dep.version}`, repository: dep.repository });
303
+ ctx.log('pulling chart dependency', {
304
+ chart: `${dep.name}@${dep.version}`,
305
+ repository: dep.repository,
306
+ });
243
307
  await ssh.run(helmPullCommand(dep, remoteChart));
244
308
  }
245
- await ssh.run(helmUpgradeCommand({ ...o.flags, remoteChart, release: o.release, namespace: o.namespace, ...(o.wait ? { wait: o.wait } : {}) }));
309
+ await ssh.run(
310
+ helmUpgradeCommand({
311
+ ...o.flags,
312
+ remoteChart,
313
+ release: o.release,
314
+ namespace: o.namespace,
315
+ ...(o.wait ? { wait: o.wait } : {}),
316
+ }),
317
+ );
246
318
  for (const d of o.deployments) {
247
- const target = d.includes("/") ? d : `deploy/${d}`;
248
- ctx.log("waiting for rollout", { deployment: target });
249
- await ssh.run(`kubectl -n ${shq(o.namespace)} rollout status ${shq(target)} --timeout=${o.rolloutTimeoutSeconds}s`);
319
+ const target = d.includes('/') ? d : `deploy/${d}`;
320
+ ctx.log('waiting for rollout', { deployment: target });
321
+ await ssh.run(
322
+ `kubectl -n ${shq(o.namespace)} rollout status ${shq(target)} --timeout=${o.rolloutTimeoutSeconds}s`,
323
+ );
250
324
  }
251
325
  }
252
326
 
253
327
  /** The chart directory inside a fetched source tree, or an error naming it. */
254
328
  export function chartDirectory(tree: string, chart: string): string {
255
329
  const dir = join(tree, chart);
256
- if (!existsSync(dir) || !statSync(dir).isDirectory()) throw new Error(`chart directory '${chart}' not found in the source tree`);
330
+ if (!existsSync(dir) || !statSync(dir).isDirectory())
331
+ throw new Error(`chart directory '${chart}' not found in the source tree`);
257
332
  return dir;
258
333
  }
259
334
 
@@ -267,45 +342,68 @@ export function helmDeployOverSsh<
267
342
  const K extends string,
268
343
  const Im extends Record<string, string> = DefaultImages,
269
344
  const Rf extends Record<string, string> = {},
270
- >(def: HelmDeploySshTargetDef<N, K, Im, Rf>): Implementation<Step<N, HelmTargetDeployInput<Im, Rf>, typeof HelmRelease, K>>;
345
+ >(
346
+ def: HelmDeploySshTargetDef<N, K, Im, Rf>,
347
+ ): Implementation<Step<N, HelmTargetDeployInput<Im, Rf>, typeof HelmRelease, K>>;
271
348
  export function helmDeployOverSsh<
272
349
  const N extends string,
273
350
  const K extends string,
274
351
  const Im extends Record<string, string> = DefaultImages,
275
352
  const R extends boolean = false,
276
353
  const Rf extends Record<string, string> = {},
277
- >(def: HelmDeployOverSshDef<N, K, Im, R, Rf>): Implementation<Step<N, HelmDeployOverSshInput<Im, R, Rf>, typeof HelmRelease, K>>;
354
+ >(
355
+ def: HelmDeployOverSshDef<N, K, Im, R, Rf>,
356
+ ): Implementation<Step<N, HelmDeployOverSshInput<Im, R, Rf>, typeof HelmRelease, K>>;
278
357
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
279
358
  export function helmDeployOverSsh(def: any): any {
280
- return "target" in def ? helmDeployOverSshTarget(def) : helmDeployOverSshLiteral(def);
359
+ return 'target' in def ? helmDeployOverSshTarget(def) : helmDeployOverSshLiteral(def);
281
360
  }
282
361
 
283
362
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
284
- function helmDeployOverSshLiteral(def: HelmDeployOverSshDef<string, string, any, boolean, any>): Implementation {
285
- const images: Record<string, string> = def.images ?? { image: "image" };
363
+ function helmDeployOverSshLiteral(
364
+ def: HelmDeployOverSshDef<string, string, any, boolean, any>,
365
+ ): Implementation {
366
+ const images: Record<string, string> = def.images ?? { image: 'image' };
286
367
  const imageEntries = Object.entries(images);
287
368
  const fromRegistry = def.fromRegistry === true;
288
369
  const rolloutTimeout = def.rolloutTimeoutSeconds ?? DEFAULT_ROLLOUT_TIMEOUT_SECONDS;
289
- const wait: HelmWait | undefined = def.atomic || def.helmWait ? { atomic: def.atomic === true, timeoutSeconds: rolloutTimeout } : undefined;
290
- if ("source" in images) throw new Error(`helmDeployOverSsh '${def.name}': 'source' can't be an image input`);
291
- if (def.deployments?.length === 0) throw new Error(`helmDeployOverSsh '${def.name}': deployments must not be empty`);
370
+ const wait: HelmWait | undefined =
371
+ def.atomic || def.helmWait
372
+ ? { atomic: def.atomic === true, timeoutSeconds: rolloutTimeout }
373
+ : undefined;
374
+ if ('source' in images)
375
+ throw new Error(`helmDeployOverSsh '${def.name}': 'source' can't be an image input`);
376
+ if (def.deployments?.length === 0)
377
+ throw new Error(`helmDeployOverSsh '${def.name}': deployments must not be empty`);
292
378
  const imageRefs: Record<string, string> = def.imageRefs ?? {};
293
379
  const refEntries = Object.entries(imageRefs);
294
- if ("source" in imageRefs) throw new Error(`helmDeployOverSsh '${def.name}': 'source' can't be an image input`);
295
- for (const [key] of refEntries) if (key in images) throw new Error(`helmDeployOverSsh '${def.name}': '${key}' is both in images and imageRefs`);
380
+ if ('source' in imageRefs)
381
+ throw new Error(`helmDeployOverSsh '${def.name}': 'source' can't be an image input`);
382
+ for (const [key] of refEntries)
383
+ if (key in images)
384
+ throw new Error(
385
+ `helmDeployOverSsh '${def.name}': '${key}' is both in images and imageRefs`,
386
+ );
296
387
  const imageModel = fromRegistry ? PushedImage : BuiltImage;
297
388
  const input = {
298
389
  source: Source,
299
390
  ...Object.fromEntries(imageEntries.map(([key]) => [key, imageModel])),
300
391
  ...Object.fromEntries(refEntries.map(([key]) => [key, PushedImage])),
301
392
  };
302
- const isDefaultImages = imageEntries.length === 1 && images.image === "image";
303
- const configurable = { host: def.host, release: def.release, namespace: def.namespace, values: def.values ?? [], set: def.set ?? {}, healthUrl: def.healthUrl };
393
+ const isDefaultImages = imageEntries.length === 1 && images.image === 'image';
394
+ const configurable = {
395
+ host: def.host,
396
+ release: def.release,
397
+ namespace: def.namespace,
398
+ values: def.values ?? [],
399
+ set: def.set ?? {},
400
+ healthUrl: def.healthUrl,
401
+ };
304
402
 
305
403
  const contract = step({
306
404
  name: def.name,
307
405
  description: def.description,
308
- kind: "helmDeployOverSsh",
406
+ kind: 'helmDeployOverSsh',
309
407
  image: HELM_DEPLOY_IMAGE,
310
408
  input,
311
409
  output: HelmRelease,
@@ -323,36 +421,75 @@ function helmDeployOverSshLiteral(def: HelmDeployOverSshDef<string, string, any,
323
421
  ...(fromRegistry ? { fromRegistry: true } : {}),
324
422
  ...(refEntries.length > 0 ? { imageRefs: { ...imageRefs } } : {}),
325
423
  ...(def.deployments ? { deployments: [...def.deployments] } : {}),
326
- ...(def.rolloutTimeoutSeconds !== undefined ? { rolloutTimeoutSeconds: def.rolloutTimeoutSeconds } : {}),
424
+ ...(def.rolloutTimeoutSeconds !== undefined
425
+ ? { rolloutTimeoutSeconds: def.rolloutTimeoutSeconds }
426
+ : {}),
327
427
  ...(def.dependencies ? { dependencies: true } : {}),
328
428
  ...(def.atomic ? { atomic: true } : def.helmWait ? { helmWait: true } : {}),
329
429
  ...(def.healthUrl !== undefined ? { healthUrl: def.healthUrl } : {}),
330
430
  },
331
431
  });
332
432
  return implement(contract, async (stepInput, ctx: Ctx) => {
333
- const input = stepInput as unknown as Record<string, { repository: string; tag: string; digest: string; tar?: FileRef }> & { source: { tree: FileRef } };
433
+ const input = stepInput as unknown as Record<
434
+ string,
435
+ { repository: string; tag: string; digest: string; tar?: FileRef }
436
+ > & { source: { tree: FileRef } };
334
437
  const d = resolveConfigRefs(configurable, ctx.config ?? {}) as {
335
- host: string; release: string; namespace: string; values: string[]; set: Record<string, string>;
336
- healthUrl?: string | ({ url: string } & Pick<HttpHealthCheckOptions, "attempts" | "delaySeconds">);
438
+ host: string;
439
+ release: string;
440
+ namespace: string;
441
+ values: string[];
442
+ set: Record<string, string>;
443
+ healthUrl?:
444
+ | string
445
+ | ({ url: string } & Pick<HttpHealthCheckOptions, 'attempts' | 'delaySeconds'>);
337
446
  };
338
447
  const chartDir = chartDirectory(await ctx.files.fetch(input.source.tree), def.chart);
339
448
  const hostImages: HostImage[] = [];
340
449
  const flags: HelmImageFlags[] = [];
341
450
  for (const [key, prefix] of imageEntries) {
342
451
  const image = input[key]!;
343
- hostImages.push({ repository: image.repository, tag: image.tag, ...(fromRegistry ? {} : { tar: await ctx.files.fetch(image.tar!) }) });
344
- flags.push({ prefix, repository: image.repository, tag: image.tag, pullPolicy: fromRegistry ? "IfNotPresent" : "Never" });
452
+ hostImages.push({
453
+ repository: image.repository,
454
+ tag: image.tag,
455
+ ...(fromRegistry ? {} : { tar: await ctx.files.fetch(image.tar!) }),
456
+ });
457
+ flags.push({
458
+ prefix,
459
+ repository: image.repository,
460
+ tag: image.tag,
461
+ pullPolicy: fromRegistry ? 'IfNotPresent' : 'Never',
462
+ });
345
463
  }
346
- const refs: HelmImageRefFlags[] = refEntries.map(([key, prefix]) => ({ prefix, repository: input[key]!.repository, tag: input[key]!.tag, digest: input[key]!.digest }));
464
+ const refs: HelmImageRefFlags[] = refEntries.map(([key, prefix]) => ({
465
+ prefix,
466
+ repository: input[key]!.repository,
467
+ tag: input[key]!.tag,
468
+ digest: input[key]!.digest,
469
+ }));
347
470
  await deployOverSsh(ctx, {
348
- host: d.host, key: ctx.secrets[def.sshKey.name]!, chartDir, release: d.release, namespace: d.namespace, hostImages,
471
+ host: d.host,
472
+ key: ctx.secrets[def.sshKey.name]!,
473
+ chartDir,
474
+ release: d.release,
475
+ namespace: d.namespace,
476
+ hostImages,
349
477
  flags: { values: d.values, set: d.set, images: flags, imageRefs: refs },
350
- deployments: def.deployments ?? (imageEntries.length && !wait ? [d.release] : []), rolloutTimeoutSeconds: rolloutTimeout,
478
+ deployments: def.deployments ?? (imageEntries.length && !wait ? [d.release] : []),
479
+ rolloutTimeoutSeconds: rolloutTimeout,
351
480
  ...(def.dependencies ? { dependencies: chartDependencies(chartDir) } : {}),
352
481
  ...(wait ? { wait } : {}),
353
482
  });
354
- const health = typeof d.healthUrl === "string" ? { url: d.healthUrl } : d.healthUrl;
355
- if (health) await httpHealthCheck(ctx, health.url, { attempts: health.attempts, delaySeconds: health.delaySeconds });
356
- return { release: d.release, namespace: d.namespace, ...(flags[0] ? { image: `${flags[0].repository}:${flags[0].tag}` } : {}) };
483
+ const health = typeof d.healthUrl === 'string' ? { url: d.healthUrl } : d.healthUrl;
484
+ if (health)
485
+ await httpHealthCheck(ctx, health.url, {
486
+ attempts: health.attempts,
487
+ delaySeconds: health.delaySeconds,
488
+ });
489
+ return {
490
+ release: d.release,
491
+ namespace: d.namespace,
492
+ ...(flags[0] ? { image: `${flags[0].repository}:${flags[0].tag}` } : {}),
493
+ };
357
494
  }) as never;
358
495
  }