hologit 0.47.3 → 0.47.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.
package/lib/Lens.js CHANGED
@@ -111,45 +111,44 @@ class Lens extends Configurable {
111
111
  }
112
112
 
113
113
  async getImageHash (containerQuery) {
114
- // Try local first (fastest)
114
+ // Get manifest digest from registry without pulling.
115
+ // This is the only identifier that is consistent across all machines
116
+ // and Docker storage backends (graphdriver vs containerd-snapshotter).
117
+ let manifestDigest;
115
118
  try {
116
- const inspectOutput = await Studio.execDocker(['inspect', containerQuery]);
117
- const imageInfo = JSON.parse(inspectOutput)[0];
118
- logger.info(`found local image: ${containerQuery}@${imageInfo.Id}`);
119
- return { hash: imageInfo.Id, isLocal: true };
119
+ logger.info(`querying registry for manifest digest: ${containerQuery}`);
120
+ const output = await Studio.execDocker([
121
+ 'buildx', 'imagetools', 'inspect', '--format', '{{json .}}', containerQuery
122
+ ]);
123
+ const info = JSON.parse(output);
124
+ manifestDigest = info.manifest.digest;
120
125
  } catch (err) {
121
- // Not found locally, try remote manifest
126
+ throw new Error(`failed to get manifest digest for container image ${containerQuery}: ${err.message}`);
122
127
  }
123
128
 
124
- // Try remote manifest (doesn't pull the image)
125
- try {
126
- logger.info(`querying remote manifest for: ${containerQuery}`);
127
- const manifestOutput = await Studio.execDocker(['manifest', 'inspect', containerQuery]);
128
- const manifest = JSON.parse(manifestOutput);
129
-
130
- // Extract digest from manifest
131
- // For multi-arch images, manifest.config.digest contains the config digest
132
- // For single-arch images, we may need to look at the digest field
133
- const digest = manifest.config?.digest || manifest.digest;
134
-
135
- if (!digest) {
136
- throw new Error('No digest found in manifest');
137
- }
129
+ if (!manifestDigest) {
130
+ throw new Error(`no manifest digest found for ${containerQuery}`);
131
+ }
138
132
 
139
- // Ensure the digest is in the format sha256:...
140
- const imageHash = digest.startsWith('sha256:') ? digest : `sha256:${digest}`;
141
- logger.info(`found remote image hash: ${containerQuery}@${imageHash}`);
142
- return { hash: imageHash, isLocal: false };
133
+ // Check if already available locally by digest reference
134
+ const repoName = containerQuery.replace(/:.*$/, '');
135
+ let isLocal = false;
136
+ try {
137
+ await Studio.execDocker(['inspect', `${repoName}@${manifestDigest}`]);
138
+ isLocal = true;
143
139
  } catch (err) {
144
- throw new Error(`failed to get hash for container image ${containerQuery}: ${err.message}`);
140
+ // not local
145
141
  }
142
+
143
+ logger.info(`image ${containerQuery} manifest digest: ${manifestDigest} (local: ${isLocal})`);
144
+ return { hash: manifestDigest, isLocal };
146
145
  }
147
146
 
148
147
  async buildSpecForContainer (inputTree, config) {
149
148
  const { container: containerQuery } = config;
150
149
 
151
- // Get image hash without pulling
152
- const { hash: imageHash, isLocal } = await this.getImageHash(containerQuery);
150
+ // Get manifest digest from registry without pulling
151
+ const { hash: imageHash } = await this.getImageHash(containerQuery);
153
152
 
154
153
  // build spec
155
154
  const data = {
@@ -165,8 +164,7 @@ class Lens extends Configurable {
165
164
  return {
166
165
  ...await SpecObject.write(this.workspace.getRepo(), 'lens', data),
167
166
  data,
168
- type: 'container',
169
- imageIsLocal: isLocal
167
+ type: 'container'
170
168
  };
171
169
  }
172
170
 
@@ -249,11 +247,11 @@ class Lens extends Configurable {
249
247
  };
250
248
  }
251
249
 
252
- async executeSpec (specType, specHash, options) {
253
- return Lens.executeSpec(specType, specHash, {...options, repo: this.workspace.getRepo()});
250
+ async executeSpec (specHash, options) {
251
+ return Lens.executeSpec(specHash, {...options, repo: this.workspace.getRepo()});
254
252
  }
255
253
 
256
- static async executeSpec (specType, specHash, options) {
254
+ static async executeSpec (specHash, options) {
257
255
  const { refresh=false, cacheFrom=null, cacheTo=null, save=true } = options;
258
256
 
259
257
 
@@ -262,6 +260,16 @@ class Lens extends Configurable {
262
260
  const git = await repo.getGit();
263
261
 
264
262
 
263
+ // determine spec type from content
264
+ const specToml = await git.catFile({ p: true }, specHash);
265
+ const { holospec: { lens: spec } } = TOML.parse(specToml);
266
+ const specType = spec.container ? 'container' : spec.package ? 'habitat' : null;
267
+
268
+ if (!specType) {
269
+ throw new Error(`unable to determine spec type: spec has neither container nor package field`);
270
+ }
271
+
272
+
265
273
  // check for existing build
266
274
  const specRef = SpecObject.buildRef('lens', specHash);
267
275
  if (!refresh) {
@@ -324,30 +332,25 @@ class Lens extends Configurable {
324
332
  m: specToml
325
333
  });
326
334
 
327
- // extract repository and hash from container string
328
- const containerMatch = spec.container.match(/^.+@sha256:([a-f0-9]{64})$/);
329
- if (!containerMatch) {
330
- throw new Error(`Invalid container format: ${spec.container}`);
335
+ // use full digest reference (e.g. "ghcr.io/hologit/lenses/shell@sha256:0f8acb01...")
336
+ // for all docker operations — this is the manifest digest, which works
337
+ // consistently across Docker storage backends
338
+ const containerRef = spec.container;
339
+ if (!containerRef.match(/^.+@sha256:[a-f0-9]{64}$/)) {
340
+ throw new Error(`Invalid container format: ${containerRef}`);
331
341
  }
332
- const [, sha256Hash] = containerMatch;
333
342
 
334
343
  // Ensure image is available locally
335
344
  try {
336
- await Studio.execDocker(['inspect', sha256Hash]);
345
+ await Studio.execDocker(['inspect', containerRef]);
337
346
  } catch (err) {
338
- // Image not found locally, need to pull it
339
- // Extract the original container query from spec to pull by name:tag
340
- const containerQueryMatch = spec.container.match(/^(.+)@sha256:[a-f0-9]{64}$/);
341
- if (containerQueryMatch) {
342
- const containerQuery = containerQueryMatch[1];
343
- logger.info(`pulling required image: ${containerQuery}`);
344
- try {
345
- await Studio.execDocker(['pull', containerQuery], { $relayStdout: true });
346
- } catch (pullErr) {
347
- throw new Error(`failed to pull container image ${containerQuery}: ${pullErr.message}`);
348
- }
349
- } else {
350
- throw new Error(`cannot extract container query from spec.container: ${spec.container}`);
347
+ // Pull by digest guarantees we get the exact image version
348
+ // matching the spec that was used for caching
349
+ logger.info(`pulling required image: ${containerRef}`);
350
+ try {
351
+ await Studio.execDocker(['pull', containerRef], { $relayStdout: true });
352
+ } catch (pullErr) {
353
+ throw new Error(`failed to pull container image ${containerRef}: ${pullErr.message}`);
351
354
  }
352
355
  }
353
356
 
@@ -376,7 +379,7 @@ class Lens extends Configurable {
376
379
  '-p', '9000:9000',
377
380
  ...(persistentDebugContainer ? ['--name', persistentDebugContainer] : []),
378
381
  ...(process.env.DEBUG ? ['-e', 'DEBUG=1'] : []),
379
- sha256Hash
382
+ containerRef
380
383
  ]);
381
384
  containerId = containerId.trim();
382
385
 
package/lib/Projection.js CHANGED
@@ -162,11 +162,11 @@ class Projection {
162
162
 
163
163
  // build tree of matching files to input to lens
164
164
  logger.info(`building input tree for lens ${lens.name} from ${inputRoot == '.' ? '' : (path.join(inputRoot, '.')+'/')}{${inputFiles}}`);
165
- const { hash: specHash, type: specType } = await lens.buildSpec(await lens.buildInputTree(this.output.root));
165
+ const { hash: specHash } = await lens.buildSpec(await lens.buildInputTree(this.output.root));
166
166
 
167
167
 
168
168
  // check for existing output tree
169
- const outputTreeHash = await lens.executeSpec(specType, specHash, { cacheFrom, cacheTo });
169
+ const outputTreeHash = await lens.executeSpec(specHash, { cacheFrom, cacheTo });
170
170
 
171
171
 
172
172
  // verify output
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hologit",
3
- "version": "0.47.3",
3
+ "version": "0.47.4",
4
4
  "description": "Hologit automates the projection of layered composite file trees based on flat, declarative plans",
5
5
  "repository": "https://github.com/JarvusInnovations/hologit",
6
6
  "main": "lib/index.js",