@izykitten/ce 0.0.3-dev.0 → 0.0.3-dev.1

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 (3) hide show
  1. package/README.md +10 -2
  2. package/dist/ce.js +51 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,7 +38,7 @@ Tags and the `TAG_PREFIX`/`CACHE_PREFIX` values can use placeholders, filled fro
38
38
  | `{image}` | registry host and repository, lowercased, e.g. `code.izy.sh/library/gateway` |
39
39
  | `{ref}` | the branch name, `pr-<number>` for a pull request, or the tag name |
40
40
  | `{branch}`, `{pr}`, `{tag}` | the branch, PR number or tag, set only when building that kind of ref |
41
- | `{default}` | set only when building the default branch, never for a pull request |
41
+ | `{default}` | set only when building the default branch, never for a pull request or tag |
42
42
  | `{revision}`, `{sha}` | the commit, in full and as its first 7 characters |
43
43
  | `{version}`, `{major}`, `{minor}`, `{patch}` | from a SemVer tag such as `v1.2.3` or `1.2.3-rc.1` |
44
44
  | `{stable}` | set only for a SemVer tag without a pre-release |
@@ -48,7 +48,7 @@ Tags and the `TAG_PREFIX`/`CACHE_PREFIX` values can use placeholders, filled fro
48
48
 
49
49
  Values are made safe for a tag: any ASCII character a tag cannot have becomes `-`, so branch `ci/modernize-build` gives `ci-modernize-build`; a non-ASCII character is an error. A value never adds a tag or changes the image name. Prefixes can use `{name}` but not guards.
50
50
 
51
- Guards decide from the run's metadata, so they need it too: outside a Forgejo Actions run, or with its event file missing `repository.default_branch`, `{?default}` fails like `{default}` would. Only `{env.NAME}` works everywhere.
51
+ Guards decide from the run's metadata, so they need it too: outside a Forgejo Actions run, `{?default}` fails like `{default}` would. Only `{env.NAME}` works everywhere. To tell whether a branch is the default one, ce reads `repository.default_branch` from the event file, or `DEFAULT_BRANCH` when the event has none, as on a scheduled run; set `DEFAULT_BRANCH: main` in such a workflow. Pull requests and tags are never the default branch, so they need neither.
52
52
 
53
53
  Put templates in single quotes: `?` is a glob character in zsh.
54
54
 
@@ -64,6 +64,14 @@ A branch, PR and default-branch build:
64
64
  -t '{image}:{ref},{?default}latest,{?default}caddy-{env.CADDY_VERSION}' .
65
65
  ```
66
66
 
67
+ With `CACHE_PREFIX='{ref}'`, each branch and pull request gets a gha cache of its own, so a new one starts cold. To also read the default branch's cache, add it as a `--cache-from`. ce only prefixes a `--cache-from` whose scope matches a `--cache-to`, so this one is passed on unchanged and only read:
68
+
69
+ ```sh
70
+ --cache type=gha,mode=max --cache-from type=gha,scope=main
71
+ ```
72
+
73
+ PR 3 then reads `pr-3` and `main` and writes only `pr-3`; main reads its own cache a second time. With a scope of its own, such as `--cache type=gha,mode=max,scope=js`, the cache to read is `main-js`. This only picks what is read: whether it hits depends on what main last wrote.
74
+
67
75
  A release on a SemVer tag, with the version aliases only for stable releases:
68
76
 
69
77
  ```sh
package/dist/ce.js CHANGED
@@ -360,10 +360,11 @@ class CiMetadata {
360
360
  return ref.kind === "tag" ? SEMVER.exec(ref.name) : null;
361
361
  }
362
362
  #defaultBranch() {
363
- const repository = this.#payload().repository;
364
- const branch = isObject(repository) ? repository.default_branch : undefined;
365
- if (typeof branch !== "string" || !branch) {
366
- throw new CeError("ce: {default} needs repository.default_branch in the event file, which it does not have");
363
+ const repository = this.#ciEnv("EVENT_PATH") ? this.#payload().repository : undefined;
364
+ const fromEvent = isObject(repository) ? repository.default_branch : undefined;
365
+ const branch = typeof fromEvent === "string" && fromEvent || nonEmpty(this.#env.DEFAULT_BRANCH);
366
+ if (!branch) {
367
+ throw new CeError("ce: {default} needs repository.default_branch in the event file or DEFAULT_BRANCH, and neither is set");
367
368
  }
368
369
  return branch;
369
370
  }
@@ -505,6 +506,11 @@ function checkValue(template, vars, what) {
505
506
  }
506
507
  function expandValue(template, vars, encode, what) {
507
508
  checkValue(template, vars, what);
509
+ for (const part of parse(template, vars)) {
510
+ if ("name" in part && vars.get(part.name) === undefined) {
511
+ throw new CeError(`ce: {${part.name}} has no value here, in the ${what} ${JSON.stringify(template)}`);
512
+ }
513
+ }
508
514
  return expand(template, vars, encode);
509
515
  }
510
516
 
@@ -1203,14 +1209,18 @@ function registryGroups() {
1203
1209
  const suffixed = Object.keys(process.env).filter((name) => name.startsWith(prefix) && name.length > prefix.length).sort().map((name) => name.slice(prefix.length));
1204
1210
  return [...process.env.REGISTRY_HOST ? [""] : [], ...suffixed];
1205
1211
  }
1212
+ function unsetOrEmpty(name) {
1213
+ return `${name} is ${process.env[name] === undefined ? "unset" : "empty"}`;
1214
+ }
1206
1215
  function oidcConfig() {
1207
1216
  const audience = process.env.AUD ?? "";
1208
1217
  if (!audience)
1209
1218
  return null;
1210
1219
  const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL ?? "";
1211
1220
  const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ?? "";
1212
- if (!(requestUrl && requestToken)) {
1213
- return "Forgejo OIDC login config is incomplete: AUD requires ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN";
1221
+ const missing = ["ACTIONS_ID_TOKEN_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"].filter((name) => !process.env[name]);
1222
+ if (missing.length) {
1223
+ return `Forgejo OIDC login config is incomplete: AUD is set, but ${missing.map(unsetOrEmpty).join("; ")}`;
1214
1224
  }
1215
1225
  try {
1216
1226
  assertAbsoluteHttpUrl(requestUrl, "must be an absolute HTTP(S) URL");
@@ -1257,36 +1267,46 @@ async function login() {
1257
1267
  let failed = false;
1258
1268
  for (const group of groups) {
1259
1269
  const suffix = group ? `__${group}` : "";
1260
- const host = process.env[`REGISTRY_HOST${suffix}`] ?? "";
1261
- const user = process.env[`REGISTRY_USER${suffix}`] ?? "";
1262
- const password = process.env[`REGISTRY_PASSWORD${suffix}`] ?? "";
1270
+ const hostName = `REGISTRY_HOST${suffix}`;
1271
+ const userName = `REGISTRY_USER${suffix}`;
1272
+ const passwordName = `REGISTRY_PASSWORD${suffix}`;
1273
+ const host = process.env[hostName] ?? "";
1274
+ const user = process.env[userName] ?? "";
1275
+ const password = process.env[passwordName] ?? "";
1263
1276
  const label = group || "default";
1264
- const incomplete = `Login config for '${label}' is incomplete: host/user/password not all set`;
1265
- if (!(host && user)) {
1266
- logError(incomplete);
1267
- failed = true;
1268
- continue;
1269
- }
1270
- for (const rawRegistry of host.split(/\s+/u).filter(Boolean)) {
1271
- let authority;
1272
- let registry;
1277
+ const hosts = host.split(/\s+/u).filter(Boolean);
1278
+ const registries = [];
1279
+ for (const rawRegistry of hosts) {
1273
1280
  try {
1274
- authority = urlAuthority(rawRegistry);
1275
- registry = registryServer(rawRegistry);
1281
+ const authority = urlAuthority(rawRegistry);
1282
+ const registry = registryServer(rawRegistry);
1283
+ registries.push({ registry, useOidc: oidc !== null && sameAuthority(authority, oidc.authority) });
1276
1284
  } catch (exc) {
1277
1285
  if (!(exc instanceof CeError))
1278
1286
  throw exc;
1279
- logError(`Invalid registry host for '${label}' (${rawRegistry}): ${exc.message}`);
1280
- failed = true;
1281
- continue;
1282
- }
1283
- const useOidc = oidc !== null && sameAuthority(authority, oidc.authority);
1284
- if (!useOidc && !password) {
1285
- logError(incomplete);
1287
+ logError(rawRegistry.includes("@") ? `Invalid registry host for '${label}' in ${hostName}, not shown as it has an "@" and may hold a password` : `Invalid registry host for '${label}' (${rawRegistry}): ${exc.message}`);
1286
1288
  failed = true;
1287
- continue;
1288
1289
  }
1289
- targets.push({ registry, label, user, password, useOidc });
1290
+ }
1291
+ const missing = [];
1292
+ if (!host)
1293
+ missing.push(unsetOrEmpty(hostName));
1294
+ else if (!hosts.length)
1295
+ missing.push(`${hostName} has no registry in it`);
1296
+ if (!user)
1297
+ missing.push(unsetOrEmpty(userName));
1298
+ const needPassword = registries.filter((r) => !r.useOidc).map((r) => r.registry);
1299
+ if (!password && needPassword.length)
1300
+ missing.push(`${unsetOrEmpty(passwordName)}, which ${needPassword.join(", ")} needs`);
1301
+ if (missing.length) {
1302
+ logError(`Login config for '${label}' is incomplete: ${missing.join("; ")}`);
1303
+ failed = true;
1304
+ }
1305
+ if (!user)
1306
+ continue;
1307
+ for (const { registry, useOidc } of registries) {
1308
+ if (useOidc || password)
1309
+ targets.push({ registry, label, user, password, useOidc });
1290
1310
  }
1291
1311
  }
1292
1312
  let jwt = null;
@@ -1361,7 +1381,8 @@ Placeholders, in build tags and in the tag and cache prefixes:
1361
1381
  {ref} Branch name, pr-<number>, or tag name
1362
1382
  {branch} {pr} {tag}
1363
1383
  Set only when building that kind of ref
1364
- {default} Set only when building the default branch
1384
+ {default} Set only when building the default branch, named by
1385
+ the event file or else DEFAULT_BRANCH
1365
1386
  {revision} {sha} The commit, in full and as 7 characters
1366
1387
  {version} {major} {minor} {patch}
1367
1388
  From a SemVer tag such as v1.2.3 (v optional)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izykitten/ce",
3
- "version": "0.0.3-dev.0",
3
+ "version": "0.0.3-dev.1",
4
4
  "description": "Container engine wrapper: tag/cache rewriting for docker, podman and buildah, registry login, and a Forgejo OIDC Docker credential helper",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",