@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.
- package/package.json +4 -2
- package/src/assets.d.ts +2 -2
- package/src/build/build.ts +111 -43
- package/src/build/cli.ts +28 -21
- package/src/build/elf.ts +8 -7
- package/src/build/hashes.ts +61 -28
- package/src/build/project.ts +69 -30
- package/src/builder/pipeline.ts +274 -102
- package/src/builder/refs.ts +12 -6
- package/src/core/brands.ts +6 -6
- package/src/core/config-refs.ts +46 -20
- package/src/core/config.ts +110 -33
- package/src/core/external.ts +30 -12
- package/src/core/handlers.ts +54 -14
- package/src/core/models.ts +29 -14
- package/src/core/origin.ts +7 -4
- package/src/core/secrets.ts +2 -2
- package/src/core/steps.ts +39 -23
- package/src/core/types.ts +5 -1
- package/src/core/webhooks.ts +21 -7
- package/src/ctx/files.ts +111 -40
- package/src/ctx/index.ts +75 -34
- package/src/ctx/sh.ts +104 -44
- package/src/ctx/types.ts +4 -4
- package/src/describe/describe.ts +550 -196
- package/src/describe/manifest-schema.ts +210 -80
- package/src/describe/mapper-source.ts +249 -104
- package/src/describe/record.ts +67 -37
- package/src/dev/cli.ts +64 -34
- package/src/harness/env.ts +33 -27
- package/src/harness/execute.ts +55 -29
- package/src/harness/index.ts +21 -8
- package/src/harness/main.ts +197 -74
- package/src/harness/result.ts +38 -22
- package/src/harness/validate.ts +62 -19
- package/src/index.ts +123 -14
- package/src/library/checks.ts +11 -6
- package/src/library/compose-deploy.ts +40 -23
- package/src/library/github-source.ts +69 -39
- package/src/library/helm-deploy-ssh.ts +213 -76
- package/src/library/helm-deploy.ts +263 -85
- package/src/library/http-health.ts +7 -5
- package/src/library/image-build.ts +293 -117
- package/src/library/index.ts +47 -15
- package/src/library/models.ts +17 -9
- package/src/library/ssh.ts +31 -14
- package/src/testing/index.ts +63 -24
- package/src/util/canonical.ts +7 -4
- package/src/util/handler-hash.ts +15 -5
- package/src/util/hash.ts +5 -5
- 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
|
|
7
|
-
import { join } from
|
|
8
|
-
import type { Ctx } from
|
|
9
|
-
import type { FileRef } from
|
|
10
|
-
import type { Secret } from
|
|
11
|
-
import type { Configurable } from
|
|
12
|
-
import { resolveConfigRefs } from
|
|
13
|
-
import { implement, type Implementation } from
|
|
14
|
-
import { step, type LiteralName, type Step } from
|
|
15
|
-
import type { Simplify } from
|
|
16
|
-
import { httpHealthCheck, type HttpHealthCheckOptions } from
|
|
17
|
-
import { BuiltImage, HelmRelease, PushedImage, Source } from
|
|
18
|
-
import { shq, sshSession } from
|
|
19
|
-
import {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
35
|
-
type ImageModel<R extends boolean> = R extends true
|
|
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> } & {
|
|
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 &
|
|
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?:
|
|
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:
|
|
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) => [
|
|
139
|
-
...Object.entries(o.set).flatMap(([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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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(
|
|
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
|
|
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
|
-
[
|
|
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,
|
|
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 (
|
|
184
|
-
|
|
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(
|
|
192
|
-
|
|
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(
|
|
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
|
|
232
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
248
|
-
ctx.log(
|
|
249
|
-
await ssh.run(
|
|
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())
|
|
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
|
-
>(
|
|
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
|
-
>(
|
|
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
|
|
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(
|
|
285
|
-
|
|
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 =
|
|
290
|
-
|
|
291
|
-
|
|
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 (
|
|
295
|
-
|
|
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 ===
|
|
303
|
-
const configurable = {
|
|
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:
|
|
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
|
|
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<
|
|
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;
|
|
336
|
-
|
|
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({
|
|
344
|
-
|
|
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]) => ({
|
|
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,
|
|
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] : []),
|
|
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 ===
|
|
355
|
-
if (health)
|
|
356
|
-
|
|
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
|
}
|