diff --git a/lib/extractor/oci-archive/layer.ts b/lib/extractor/oci-archive/layer.ts index c899a763..f72ef62a 100644 --- a/lib/extractor/oci-archive/layer.ts +++ b/lib/extractor/oci-archive/layer.ts @@ -51,7 +51,7 @@ export async function extractArchive( const layers: Record = {}; const manifests: Record = {}; - const configs: ImageConfig[] = []; + const configs: Record = {}; let mainIndexFile: OciImageIndex; const indexFiles: Record = {}; @@ -85,7 +85,7 @@ export async function extractArchive( } else if (isImageIndexFile(manifest)) { indexFiles[digest] = manifest as OciImageIndex; } else if (isImageConfigFile(manifest)) { - configs.push(manifest as ImageConfig); + configs[digest] = manifest as ImageConfig; } if (layer !== undefined) { layers[digest] = layer as ExtractedLayers; @@ -131,7 +131,7 @@ function getLayersContentAndArchiveManifest( imageIndex: OciImageIndex | undefined, manifestCollection: Record, indexFiles: Record, - configs: ImageConfig[], + configs: Record, layers: Record, options: Partial, ): { @@ -139,7 +139,8 @@ function getLayersContentAndArchiveManifest( manifest: OciArchiveManifest; imageConfig: ImageConfig; } { - const filteredConfigs = configs.filter((config) => { + const allConfigs = Object.values(configs); + const filteredConfigs = allConfigs.filter((config) => { return config?.os !== "unknown" || config?.architecture !== "unknown"; }); const platform = @@ -154,6 +155,7 @@ function getLayersContentAndArchiveManifest( imageIndex, manifestCollection, indexFiles, + configs, platformInfo, ); @@ -169,7 +171,7 @@ function getLayersContentAndArchiveManifest( throw new Error("We found no layers in the provided image"); } - const imageConfig = getImageConfig(configs, platformInfo); + const imageConfig = getImageConfig(allConfigs, platformInfo); if (imageConfig === undefined) { throw new Error("Could not find the image config in the provided image"); @@ -186,6 +188,7 @@ function getManifest( imageIndex: OciImageIndex | undefined, manifestCollection: Record, indexFiles: Record, + configs: Record, platformInfo: OciPlatformInfo, ): OciArchiveManifest { if (!imageIndex) { @@ -193,7 +196,12 @@ function getManifest( } const allManifests = getAllManifestsIndexItems(imageIndex, indexFiles); - const manifestInfo = getImageManifestInfo(allManifests, platformInfo); + const manifestInfo = getImageManifestInfo( + allManifests, + manifestCollection, + configs, + platformInfo, + ); if (manifestInfo === undefined) { throw new Error( @@ -266,6 +274,8 @@ function getOciPlatformInfoFromOptionString(platform: string): OciPlatformInfo { function getImageManifestInfo( manifests: OciManifestInfo[], + manifestCollection: Record, + configs: Record, platformInfo: OciPlatformInfo, ): OciManifestInfo | undefined { // manifests do not always have a plaform, this is the case for OCI @@ -278,11 +288,27 @@ function getImageManifestInfo( manifests, platformInfo, (target: OciManifestInfo): OciPlatformInfo => { - return { - os: target.platform?.os, - architecture: target.platform?.architecture, - variant: target.platform?.variant, - }; + if (target.platform) { + return { + os: target.platform.os, + architecture: target.platform.architecture, + variant: target.platform.variant, + }; + } + + // try to resolve platform from config + const manifest = manifestCollection[target.digest]; + if (manifest) { + const config = configs[manifest.config.digest]; + if (config) { + return { + os: config.os, + architecture: config.architecture, + }; + } + } + + return {}; }, ); } diff --git a/test/fixtures/docker-oci-archives/busybox.multi-tag.tar b/test/fixtures/docker-oci-archives/busybox.multi-tag.tar new file mode 100644 index 00000000..42dfac90 Binary files /dev/null and b/test/fixtures/docker-oci-archives/busybox.multi-tag.tar differ diff --git a/test/lib/extractor/extractor.spec.ts b/test/lib/extractor/extractor.spec.ts index 1f8fe135..556d1d38 100644 --- a/test/lib/extractor/extractor.spec.ts +++ b/test/lib/extractor/extractor.spec.ts @@ -164,5 +164,25 @@ describe("extractImageContent", () => { ).resolves.not.toThrow(); }); }); + + describe("with multi-tag images missing platform info in index.json", () => { + it("extracts when platform is resolved from config blobs", async () => { + const fixture = getFixture("docker-oci-archives/busybox.multi-tag.tar"); + const result = await extractImageContent(type, fixture, [], { + platform: "linux/arm64", + }); + expect(result).toBeDefined(); + expect(result.platform).toBe("linux/arm64"); + }); + + it("fails when requested platform does not match any config blob", async () => { + const fixture = getFixture("docker-oci-archives/busybox.multi-tag.tar"); + await expect( + extractImageContent(type, fixture, [], { platform: "linux/amd64" }), + ).rejects.toThrow( + "Unsupported archive type. Please use a Docker archive, OCI image layout, or Kaniko-compatible tarball.", + ); + }); + }); }); });