@olivervea/pipelines 2.0.0-beta.3 → 2.0.0-beta.31
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 +165 -78
- package/manifest.schema.json +300 -16
- package/package.json +6 -4
- package/src/assets.d.ts +2 -2
- package/src/build/build.ts +98 -64
- package/src/build/cli.ts +33 -23
- package/src/build/elf.ts +16 -7
- package/src/build/hashes.ts +40 -51
- package/src/build/project.ts +55 -52
- package/src/builder/pipeline.ts +348 -89
- package/src/builder/refs.ts +15 -5
- package/src/core/brands.ts +6 -5
- package/src/core/config-refs.ts +103 -0
- package/src/core/config.ts +224 -0
- package/src/core/external.ts +86 -0
- package/src/core/handlers.ts +62 -15
- package/src/core/matrix.ts +150 -0
- package/src/core/models.ts +37 -15
- package/src/core/secrets.ts +2 -2
- package/src/core/steps.ts +113 -38
- package/src/core/types.ts +5 -1
- package/src/core/webhooks.ts +21 -7
- package/src/ctx/files.ts +119 -42
- package/src/ctx/index.ts +110 -39
- package/src/ctx/links.ts +30 -0
- package/src/ctx/sh.ts +106 -44
- package/src/ctx/types.ts +24 -4
- package/src/describe/config-schema.ts +98 -0
- package/src/describe/context.ts +187 -0
- package/src/describe/describe.ts +63 -355
- package/src/describe/failure-handlers.ts +34 -0
- package/src/describe/manifest-schema.ts +304 -61
- package/src/describe/mapper-source.ts +252 -105
- package/src/describe/record.ts +67 -37
- package/src/describe/schemas.ts +71 -0
- package/src/describe/stages.ts +166 -0
- package/src/describe/step.ts +544 -0
- package/src/describe/triggers.ts +72 -0
- package/src/dev/cli.ts +62 -35
- package/src/harness/env.ts +47 -25
- package/src/harness/execute.ts +74 -30
- package/src/harness/exit.ts +1 -1
- package/src/harness/index.ts +19 -8
- package/src/harness/main.ts +245 -99
- package/src/harness/mask.ts +98 -0
- package/src/harness/result.ts +45 -24
- package/src/harness/validate.ts +82 -15
- package/src/index.ts +139 -10
- package/src/library/checks.ts +11 -6
- package/src/library/compose-deploy.ts +115 -0
- package/src/library/github-source.ts +100 -47
- package/src/library/helm-deploy-ssh.ts +426 -112
- package/src/library/helm-deploy.ts +529 -0
- package/src/library/http-health.ts +7 -5
- package/src/library/image-build.ts +510 -162
- package/src/library/index.ts +48 -14
- package/src/library/models.ts +35 -14
- package/src/library/ssh.ts +44 -18
- package/src/testing/index.ts +100 -35
- package/src/util/canonical.ts +7 -4
- package/src/util/code-hash.ts +17 -0
- package/src/util/hash.ts +5 -5
- package/src/util/tar.ts +83 -38
- package/src/library/script.ts +0 -85
- package/src/util/handler-hash.ts +0 -22
|
@@ -2,19 +2,25 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Runs entirely inside the harness (HTTP + tar extraction in Bun): no git, wget or tar is needed
|
|
4
4
|
// in the image, so v1's busybox-wget TLS footgun (`--no-check-certificate`) is gone.
|
|
5
|
-
import { mkdirSync, mkdtempSync, readdirSync, statSync } from
|
|
6
|
-
import { join } from
|
|
7
|
-
import { z } from
|
|
8
|
-
import type { Ctx } from
|
|
9
|
-
import type { Secret } from
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
5
|
+
import { mkdirSync, mkdtempSync, readdirSync, statSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { Ctx } from '../ctx/types.ts';
|
|
9
|
+
import type { Secret } from '../core/secrets.ts';
|
|
10
|
+
import type { Configurable } from '../core/config.ts';
|
|
11
|
+
import { resolveConfigRefs } from '../core/config-refs.ts';
|
|
12
|
+
import { implement, type Implementation } from '../core/handlers.ts';
|
|
13
|
+
import { step, type LiteralName, type Step } from '../core/steps.ts';
|
|
14
|
+
import { extractTar } from '../util/tar.ts';
|
|
15
|
+
import { Source } from './models.ts';
|
|
13
16
|
|
|
14
17
|
/** Image the step runs in. The work happens in the harness, so any image would do. */
|
|
15
|
-
export const GITHUB_SOURCE_IMAGE =
|
|
18
|
+
export const GITHUB_SOURCE_IMAGE = 'alpine:3.22';
|
|
16
19
|
|
|
17
|
-
const input = {
|
|
20
|
+
const input = {
|
|
21
|
+
repo: z.string().regex(/^[\w.-]+\/[\w.-]+$/, 'expected owner/name'),
|
|
22
|
+
sha: z.string().min(1),
|
|
23
|
+
};
|
|
18
24
|
export type GitHubSourceInput = typeof input;
|
|
19
25
|
|
|
20
26
|
export interface GitHubSourceDef<N extends string, T extends string> {
|
|
@@ -23,11 +29,29 @@ export interface GitHubSourceDef<N extends string, T extends string> {
|
|
|
23
29
|
description: string;
|
|
24
30
|
/** Token sent to the GitHub API (needed for private repositories; raises the rate limit). */
|
|
25
31
|
token?: Secret<T>;
|
|
32
|
+
/** GitHub API base (default `https://api.github.com`; GitHub Enterprise: `https://<host>/api/v3`). */
|
|
33
|
+
apiUrl?: Configurable<string | undefined>;
|
|
26
34
|
timeoutSeconds?: number;
|
|
27
35
|
}
|
|
28
36
|
|
|
37
|
+
export const GITHUB_API_URL = 'https://api.github.com';
|
|
38
|
+
|
|
39
|
+
/** A declared secret's value, or "" when it is not set. */
|
|
40
|
+
function optionalSecret(ctx: Pick<Ctx, 'secrets'>, name: string): string {
|
|
41
|
+
try {
|
|
42
|
+
return ((ctx.secrets as Record<string, string>)[name] ?? '').trim();
|
|
43
|
+
} catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
/** Tarball URL of `repo` at `sha` (the API redirects to codeload.github.com). */
|
|
30
|
-
export const gitHubTarballUrl = (
|
|
49
|
+
export const gitHubTarballUrl = (
|
|
50
|
+
repo: string,
|
|
51
|
+
sha: string,
|
|
52
|
+
apiUrl = GITHUB_API_URL,
|
|
53
|
+
): string =>
|
|
54
|
+
`${apiUrl.replace(/\/+$/, '')}/repos/${repo}/tarball/${encodeURIComponent(sha)}`;
|
|
31
55
|
|
|
32
56
|
/**
|
|
33
57
|
* Downloads `repo` at `sha` from GitHub as a tarball and outputs it as a `Source` whose `tree` is the
|
|
@@ -36,42 +60,71 @@ export const gitHubTarballUrl = (repo: string, sha: string): string => `https://
|
|
|
36
60
|
*/
|
|
37
61
|
export function gitHubSource<const N extends string, const T extends string = never>(
|
|
38
62
|
def: GitHubSourceDef<N, T>,
|
|
39
|
-
): Step<N, GitHubSourceInput, typeof Source, T
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
63
|
+
): Implementation<Step<N, GitHubSourceInput, typeof Source, T>> {
|
|
64
|
+
const contract = step({
|
|
65
|
+
name: def.name,
|
|
66
|
+
description: def.description,
|
|
67
|
+
kind: 'gitHubSource',
|
|
68
|
+
image: GITHUB_SOURCE_IMAGE,
|
|
69
|
+
input,
|
|
70
|
+
output: Source,
|
|
71
|
+
secrets: def.token ? [def.token] : [],
|
|
72
|
+
timeoutSeconds: def.timeoutSeconds,
|
|
73
|
+
settings: {
|
|
74
|
+
...(def.token ? { token: def.token.name } : {}),
|
|
75
|
+
...(def.apiUrl !== undefined ? { apiUrl: def.apiUrl } : {}),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return implement(contract, async ({ repo, sha }, ctx: Ctx) => {
|
|
79
|
+
const headers: Record<string, string> = {
|
|
80
|
+
accept: 'application/vnd.github+json',
|
|
81
|
+
'user-agent': 'olve-pipelines',
|
|
82
|
+
};
|
|
83
|
+
const token = def.token ? optionalSecret(ctx, def.token.name) : '';
|
|
84
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
85
|
+
else if (def.token)
|
|
86
|
+
ctx.log(
|
|
87
|
+
`${def.token.name} is not set: fetching anonymously (public repositories only)`,
|
|
88
|
+
);
|
|
89
|
+
const url = gitHubTarballUrl(
|
|
90
|
+
repo,
|
|
91
|
+
sha,
|
|
92
|
+
resolveConfigRefs(def.apiUrl, ctx.config ?? {}) ?? GITHUB_API_URL,
|
|
93
|
+
);
|
|
94
|
+
mkdirSync(ctx.tmpDir, { recursive: true });
|
|
95
|
+
const dir = mkdtempSync(join(ctx.tmpDir, 'github-source-'));
|
|
96
|
+
const archive = join(dir, 'repo.tar.gz');
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
return
|
|
98
|
+
ctx.log('fetching source', { repo, sha });
|
|
99
|
+
// Retries network errors and 5xx; a 4xx (bad sha, no access) fails at once.
|
|
100
|
+
const status = await ctx.retry(
|
|
101
|
+
{ attempts: 4, delaySeconds: 2, backoff: 2 },
|
|
102
|
+
async () => {
|
|
103
|
+
const res = await ctx.http.fetch(url, { headers });
|
|
104
|
+
if (res.status >= 500) throw new Error(`HTTP ${res.status} from ${url}`);
|
|
105
|
+
if (res.ok) await Bun.write(archive, res);
|
|
106
|
+
return res.status;
|
|
74
107
|
},
|
|
75
|
-
|
|
76
|
-
|
|
108
|
+
);
|
|
109
|
+
if (status < 200 || status >= 300) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`cannot fetch ${repo}@${sha}: HTTP ${status} from ${url}${def.token ? '' : ' (no token configured: a private repository needs one)'}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const extracted = join(dir, 'x');
|
|
116
|
+
mkdirSync(extracted);
|
|
117
|
+
await extractTar(archive, extracted);
|
|
118
|
+
// GitHub tarballs hold one top-level directory, plus a pax_global_header entry (the commit id)
|
|
119
|
+
// that some extractors materialise as a file.
|
|
120
|
+
const roots = readdirSync(extracted).filter(
|
|
121
|
+
(e) => e !== 'pax_global_header' && statSync(join(extracted, e)).isDirectory(),
|
|
122
|
+
);
|
|
123
|
+
if (roots.length !== 1)
|
|
124
|
+
throw new Error(
|
|
125
|
+
`unexpected tarball layout for ${repo}@${sha}: top-level entries ${JSON.stringify(readdirSync(extracted))}`,
|
|
126
|
+
);
|
|
127
|
+
const tree = await ctx.output.dir(join(extracted, roots[0]!), { name: 'tree' });
|
|
128
|
+
return { repo, sha, tree };
|
|
129
|
+
}) as never;
|
|
77
130
|
}
|