@faable/faable 1.15.3 → 1.15.4

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.
@@ -105,11 +105,15 @@ const deploy = {
105
105
  const stop_log_sync = start_log_sync(api, deployment.id);
106
106
  try {
107
107
  await buildpack.build({ workdir, config, app, env_vars }, plan);
108
- // Upload to Faable registry
109
- const { upload_tagname } = await upload_tag({ app, api });
108
+ // Upload to Faable registry, tagged by version and pinned to digest
109
+ const { image_ref } = await upload_tag({
110
+ app,
111
+ api,
112
+ deployment_id: deployment.id
113
+ });
110
114
  // Complete the deployment with the built image — this is the handoff:
111
115
  // the controller claims it and materializes the workload.
112
- await api.completeDeployment(deployment.id, upload_tagname);
116
+ await api.completeDeployment(deployment.id, image_ref);
113
117
  }
114
118
  finally {
115
119
  stop_log_sync();
@@ -1,22 +1,73 @@
1
1
  import { log } from '../../log.js';
2
2
  import { cmd } from '../../lib/cmd.js';
3
3
 
4
+ /**
5
+ * `<hostname>/<image>:<deployment_id>` — one tag per build, never
6
+ * overwritten. Without a tag docker resolves `:latest`, which every deploy
7
+ * rewrites: any rescheduled pod (node drain, eviction) would silently pull
8
+ * whatever build was pushed last, breaking the deployment ↔ code identity.
9
+ */
10
+ const build_upload_tagname = (hostname, image, deployment_id) => `${hostname}/${image}:${deployment_id}`;
11
+ /**
12
+ * Strips the tag from an image ref, keeping registry ports intact
13
+ * (`host:5000/img:tag` → `host:5000/img`). A colon only denotes a tag when
14
+ * it appears after the last path segment separator.
15
+ */
16
+ const strip_image_tag = (ref) => {
17
+ const last_slash = ref.lastIndexOf("/");
18
+ const last_colon = ref.lastIndexOf(":");
19
+ return last_colon > last_slash ? ref.slice(0, last_colon) : ref;
20
+ };
21
+ /**
22
+ * Pins a pushed tag to its content digest: `repo:tag@sha256:…`. Kubernetes
23
+ * pulls by digest (immutable even if the tag were re-pushed); the tag stays
24
+ * as the human-readable label. `RepoDigests` entries come as `repo@sha256:…`
25
+ * (no tag) and may reference other registries — only the entry for this
26
+ * repo counts. Returns null when none matches (caller falls back to the tag).
27
+ */
28
+ const pin_tag_to_digest = (tagname, repo_digests) => {
29
+ const repo = strip_image_tag(tagname);
30
+ const match = repo_digests.find((d) => d.startsWith(`${repo}@sha256:`));
31
+ if (!match)
32
+ return null;
33
+ return `${tagname}@${match.slice(repo.length + 1)}`;
34
+ };
4
35
  const upload_tag = async (args) => {
5
- const { api, app } = args;
36
+ const { api, app, deployment_id } = args;
6
37
  log.info(`🔁 Uploading...`);
7
38
  const registry = await api.getRegistry(app.id);
8
39
  // Registry login
9
40
  const { user, password, hostname, image } = registry;
10
41
  await cmd(`echo "${password}" | docker login --username "${user}" --password-stdin ${hostname}`);
11
- // Tag image for production
12
- const upload_tagname = `${hostname}/${image}`;
42
+ // Tag the local build (tagged `app.id` by the buildpack) for this version
43
+ const upload_tagname = build_upload_tagname(hostname, image, deployment_id);
13
44
  await cmd(`docker tag ${app.id} ${upload_tagname}`);
14
45
  // Upload the image to faable registry
15
46
  await cmd(`docker push ${upload_tagname}`);
47
+ // Pin to the digest the registry just assigned. Best-effort: the tagged
48
+ // ref alone is already unique per build, the digest just makes it
49
+ // tamper-proof.
50
+ let image_ref = upload_tagname;
51
+ try {
52
+ const inspect = await cmd(`docker inspect --format '{{json .RepoDigests}}' ${upload_tagname}`);
53
+ const repo_digests = JSON.parse(String(inspect.stdout).trim());
54
+ const pinned = pin_tag_to_digest(upload_tagname, repo_digests);
55
+ if (pinned) {
56
+ image_ref = pinned;
57
+ }
58
+ else {
59
+ log.warn(`Could not resolve the pushed digest; deploying by tag only.`);
60
+ }
61
+ }
62
+ catch (error) {
63
+ log.warn(`Could not inspect the pushed image (${error.message}); deploying by tag only.`);
64
+ }
16
65
  log.info(`✅ Upload completed.`);
17
66
  return {
18
67
  upload_tagname,
68
+ /** Immutable ref recorded on the deployment: `repo:tag@sha256:…` */
69
+ image_ref,
19
70
  };
20
71
  };
21
72
 
22
- export { upload_tag };
73
+ export { build_upload_tagname, pin_tag_to_digest, strip_image_tag, upload_tag };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/faable",
3
- "version": "1.15.3",
3
+ "version": "1.15.4",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "author": "Marc Pomar <marc@faable.com>",