@olivervea/pipelines 2.0.0-beta.27 → 2.0.0-beta.29
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/README.md +2 -2
- package/package.json +1 -1
- package/src/describe/config-schema.ts +98 -0
- package/src/describe/context.ts +182 -0
- package/src/describe/describe.ts +37 -851
- package/src/describe/failure-handlers.ts +34 -0
- package/src/describe/schemas.ts +71 -0
- package/src/describe/stages.ts +165 -0
- package/src/describe/step.ts +395 -0
- package/src/describe/triggers.ts +72 -0
- package/src/library/image-build.ts +31 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { STEP_NAME } from '../core/steps.ts';
|
|
2
|
+
import type { TriggerNode } from '../builder/pipeline.ts';
|
|
3
|
+
import type { TriggerT } from './manifest-schema.ts';
|
|
4
|
+
import type { DescribeContext } from './context.ts';
|
|
5
|
+
|
|
6
|
+
export function describeTriggers(
|
|
7
|
+
ctx: DescribeContext,
|
|
8
|
+
triggers: readonly TriggerNode[],
|
|
9
|
+
): TriggerT[] {
|
|
10
|
+
const names = new Set<string>();
|
|
11
|
+
return triggers.map((t) => {
|
|
12
|
+
if (names.has(t.name))
|
|
13
|
+
ctx.problems.push(
|
|
14
|
+
`duplicate trigger '${t.name}'${t.kind === 'push' ? ' (onPush may be used once)' : ' (give each onPoll and onWebhook a distinct name)'}`,
|
|
15
|
+
);
|
|
16
|
+
names.add(t.name);
|
|
17
|
+
if (t.kind === 'push') return { kind: 'push', name: 'push' };
|
|
18
|
+
if (t.kind === 'webhook') return describeWebhook(ctx, t);
|
|
19
|
+
return describePoll(ctx, t);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function describeWebhook(
|
|
24
|
+
ctx: DescribeContext,
|
|
25
|
+
t: Extract<TriggerNode, { kind: 'webhook' }>,
|
|
26
|
+
): TriggerT {
|
|
27
|
+
if (!STEP_NAME.test(t.name))
|
|
28
|
+
ctx.problems.push(`webhook trigger name '${t.name}' must be lower-case kebab`);
|
|
29
|
+
const secret = ctx.useSecret(t.verify.secret, `webhook '${t.name}'`);
|
|
30
|
+
if (t.verify.scheme === 'bearer')
|
|
31
|
+
return { kind: 'webhook', name: t.name, verify: { scheme: 'bearer', secret } };
|
|
32
|
+
if (!t.verify.header)
|
|
33
|
+
ctx.problems.push(`webhook '${t.name}': the signature header must not be empty`);
|
|
34
|
+
return {
|
|
35
|
+
kind: 'webhook',
|
|
36
|
+
name: t.name,
|
|
37
|
+
verify: {
|
|
38
|
+
scheme: 'hmac-sha256',
|
|
39
|
+
secret,
|
|
40
|
+
header: t.verify.header,
|
|
41
|
+
prefix: t.verify.prefix,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function describePoll(
|
|
47
|
+
ctx: DescribeContext,
|
|
48
|
+
t: Extract<TriggerNode, { kind: 'poll' }>,
|
|
49
|
+
): TriggerT {
|
|
50
|
+
const problems = ctx.problems;
|
|
51
|
+
if (!STEP_NAME.test(t.name))
|
|
52
|
+
problems.push(`poll trigger name '${t.name}' must be lower-case kebab`);
|
|
53
|
+
if (!(Number.isInteger(t.intervalSeconds) && t.intervalSeconds > 0))
|
|
54
|
+
problems.push(`poll '${t.name}': intervalSeconds must be a positive integer`);
|
|
55
|
+
const declared = new Set(t.secrets.map((s) => ctx.useSecret(s, `poll '${t.name}'`)));
|
|
56
|
+
for (const [h, v] of Object.entries(t.headers)) {
|
|
57
|
+
for (const m of v.matchAll(/\$SECRET:([A-Za-z0-9_]+)/g))
|
|
58
|
+
if (!declared.has(m[1]!))
|
|
59
|
+
problems.push(
|
|
60
|
+
`poll '${t.name}': header '${h}' references secret '${m[1]}', which is not in the poll's secrets`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
kind: 'poll',
|
|
65
|
+
name: t.name,
|
|
66
|
+
url: t.url,
|
|
67
|
+
headers: { ...t.headers },
|
|
68
|
+
secrets: [...declared].sort(),
|
|
69
|
+
valuePath: t.valuePath,
|
|
70
|
+
intervalSeconds: t.intervalSeconds,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -101,6 +101,12 @@ export interface ImageBuildDef<N extends string, K extends string = never> {
|
|
|
101
101
|
* Used for kaniko's push, base image and cache pulls from that registry, and the `contentTag` lookup.
|
|
102
102
|
*/
|
|
103
103
|
registryAuth?: RegistryAuth<K> | ConfigRef<RegistryAuthConfig | undefined>;
|
|
104
|
+
/**
|
|
105
|
+
* kaniko's layer cache: a repository it reuses unchanged layers from and pushes new ones to. Default
|
|
106
|
+
* `<repository>/cache` when pushing, none for a tarball build; `false` turns it off. Login and plain
|
|
107
|
+
* HTTP apply when it is on `repository`'s registry. An unreachable cache is skipped, never a failure.
|
|
108
|
+
*/
|
|
109
|
+
cacheRepo?: Configurable<string | false | undefined>;
|
|
104
110
|
/** The registry of `repository` speaks plain HTTP (a local `registry:2`). */
|
|
105
111
|
insecureRegistry?: Configurable<boolean | undefined>;
|
|
106
112
|
/** Secrets a config-referenced `registryAuth` may name (the step declares them). */
|
|
@@ -197,6 +203,22 @@ export async function reachableMirrors(
|
|
|
197
203
|
return reachable;
|
|
198
204
|
}
|
|
199
205
|
|
|
206
|
+
/** The layer cache repository when its registry answers, else nothing (logged). */
|
|
207
|
+
export async function reachableCache(
|
|
208
|
+
ctx: Pick<Ctx, 'http' | 'log' | 'signal'>,
|
|
209
|
+
cacheRepo: string | false,
|
|
210
|
+
insecureHosts: readonly string[] = [],
|
|
211
|
+
): Promise<string | undefined> {
|
|
212
|
+
if (!cacheRepo) return undefined;
|
|
213
|
+
const host = registryHost(cacheRepo);
|
|
214
|
+
if (
|
|
215
|
+
await registryUp(ctx.http, host, ctx.signal, undefined, insecureHosts.includes(host))
|
|
216
|
+
)
|
|
217
|
+
return cacheRepo;
|
|
218
|
+
ctx.log(`${host} unreachable; building without the layer cache`);
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
200
222
|
/** Default `retries`: a stalled or flaky build gets two fresh attempts before the run fails. */
|
|
201
223
|
export const DEFAULT_RETRIES = 2;
|
|
202
224
|
|
|
@@ -446,6 +468,7 @@ export function imageBuild<const N extends string, const K extends string = neve
|
|
|
446
468
|
...(push ? { push: true } : {}),
|
|
447
469
|
...(contentTag ? { contentTag: true } : {}),
|
|
448
470
|
...(mapped ? { registryMap } : {}),
|
|
471
|
+
...(def.cacheRepo !== undefined ? { cacheRepo: def.cacheRepo } : {}),
|
|
449
472
|
...(authSecret
|
|
450
473
|
? {
|
|
451
474
|
registryAuth: {
|
|
@@ -468,6 +491,7 @@ export function imageBuild<const N extends string, const K extends string = neve
|
|
|
468
491
|
{
|
|
469
492
|
repository: def.repository,
|
|
470
493
|
registryMap: def.registryMap,
|
|
494
|
+
cacheRepo: def.cacheRepo,
|
|
471
495
|
insecureRegistry: def.insecureRegistry,
|
|
472
496
|
registryAuth: def.registryAuth,
|
|
473
497
|
enabled: def.enabled,
|
|
@@ -520,6 +544,12 @@ export function imageBuild<const N extends string, const K extends string = neve
|
|
|
520
544
|
(d.registryMap as Record<string, string> | undefined) ?? DEFAULT_REGISTRY_MAP,
|
|
521
545
|
insecure ? [host] : [],
|
|
522
546
|
);
|
|
547
|
+
const cache = await reachableCache(
|
|
548
|
+
ctx,
|
|
549
|
+
(d.cacheRepo as string | false | undefined) ??
|
|
550
|
+
(push ? `${repository}/cache` : false),
|
|
551
|
+
insecure ? [host] : [],
|
|
552
|
+
);
|
|
523
553
|
await ctx.exec(['mkdir', '-p', KANIKO_CONTEXT]);
|
|
524
554
|
await ctx.exec(['cp', '-r', `${tree}/.`, KANIKO_CONTEXT]);
|
|
525
555
|
const kanikoContext = context ? `${KANIKO_CONTEXT}/${context}` : KANIKO_CONTEXT;
|
|
@@ -543,6 +573,7 @@ export function imageBuild<const N extends string, const K extends string = neve
|
|
|
543
573
|
`--destination=${repository}:${tag}`,
|
|
544
574
|
...(insecure ? ['--insecure', `--insecure-registry=${host}`] : []),
|
|
545
575
|
...KANIKO_BUILD_FLAGS,
|
|
576
|
+
...(cache ? ['--cache=true', `--cache-repo=${cache}`] : []),
|
|
546
577
|
...Object.entries(mirrors).map(([from, to]) => `--registry-map=${from}=${to}`),
|
|
547
578
|
],
|
|
548
579
|
{ idleTimeoutSeconds: def.stallTimeoutSeconds ?? DEFAULT_STALL_TIMEOUT_SECONDS },
|