@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.
@@ -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 },