Skip to content

Commit 437b187

Browse files
committed
buildx(imagetools): return attestations digests
Signed-off-by: CrazyMax <[email protected]>
1 parent 2acf540 commit 437b187

File tree

6 files changed

+329
-0
lines changed

6 files changed

+329
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
3+
"digest": "sha256:dccc69dd895968c4f21aa9e43e715f25f0cedfce4b17f1014c88c307928e22fc",
4+
"size": 1599
5+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
{
2+
"schemaVersion": 2,
3+
"mediaType": "application/vnd.oci.image.index.v1+json",
4+
"digest": "sha256:79cc6476ab1a3371c9afd8b44e7c55610057c43e18d9b39b68e2b0c2475cc1b6",
5+
"size": 4654,
6+
"manifests": [
7+
{
8+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
9+
"digest": "sha256:dccc69dd895968c4f21aa9e43e715f25f0cedfce4b17f1014c88c307928e22fc",
10+
"size": 1599,
11+
"platform": {
12+
"architecture": "amd64",
13+
"os": "linux"
14+
}
15+
},
16+
{
17+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
18+
"digest": "sha256:1b6bce668653f08e2d0f9f7c9b646675b2cbce94ce8abdf4eb0eabaef4353045",
19+
"size": 1599,
20+
"platform": {
21+
"architecture": "arm",
22+
"os": "linux",
23+
"variant": "v7"
24+
}
25+
},
26+
{
27+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
28+
"digest": "sha256:8f251fda6057e9dffc54f7874b249920f15f1813e9b1406a0cebeca5e4ab1ad9",
29+
"size": 1599,
30+
"platform": {
31+
"architecture": "arm64",
32+
"os": "linux"
33+
}
34+
},
35+
{
36+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
37+
"digest": "sha256:d306cbc2d506547f136c8e0ea040b929743f298fb2813d9030efdb9d9eee4d51",
38+
"size": 1599,
39+
"platform": {
40+
"architecture": "s390x",
41+
"os": "linux"
42+
}
43+
},
44+
{
45+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
46+
"digest": "sha256:9d195ff2dc9ef347bb52ebb1c2a6e6587d4bd87019d2ea11df3e7046a3d19708",
47+
"size": 1599,
48+
"platform": {
49+
"architecture": "ppc64le",
50+
"os": "linux"
51+
}
52+
},
53+
{
54+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
55+
"digest": "sha256:72410c2c4529fca9339ebbcc8db2a1d5cb4d72d72c669f50b6d45d8a0f79fc22",
56+
"size": 1599,
57+
"platform": {
58+
"architecture": "riscv64",
59+
"os": "linux"
60+
}
61+
},
62+
{
63+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
64+
"digest": "sha256:2ba4ad6eae1efcafee73a971953093c7c32b6938f2f9fd4998c8bf4d0fbe76f2",
65+
"size": 1113,
66+
"annotations": {
67+
"vnd.docker.reference.digest": "sha256:dccc69dd895968c4f21aa9e43e715f25f0cedfce4b17f1014c88c307928e22fc",
68+
"vnd.docker.reference.type": "attestation-manifest"
69+
},
70+
"platform": {
71+
"architecture": "unknown",
72+
"os": "unknown"
73+
}
74+
},
75+
{
76+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
77+
"digest": "sha256:0709528fae1747ce17638ad2978ee7936b38a294136eaadaf692e415f64b1e03",
78+
"size": 1113,
79+
"annotations": {
80+
"vnd.docker.reference.digest": "sha256:1b6bce668653f08e2d0f9f7c9b646675b2cbce94ce8abdf4eb0eabaef4353045",
81+
"vnd.docker.reference.type": "attestation-manifest"
82+
},
83+
"platform": {
84+
"architecture": "unknown",
85+
"os": "unknown"
86+
}
87+
},
88+
{
89+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
90+
"digest": "sha256:241b7159129d53923c89708bcc052b3398086a826519896be2f025545916e43e",
91+
"size": 1113,
92+
"annotations": {
93+
"vnd.docker.reference.digest": "sha256:8f251fda6057e9dffc54f7874b249920f15f1813e9b1406a0cebeca5e4ab1ad9",
94+
"vnd.docker.reference.type": "attestation-manifest"
95+
},
96+
"platform": {
97+
"architecture": "unknown",
98+
"os": "unknown"
99+
}
100+
},
101+
{
102+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
103+
"digest": "sha256:97f4a222a7992dba6dc1a43991d0cca1fcffdc25593033c6a3a7ff14c8651cbf",
104+
"size": 1113,
105+
"annotations": {
106+
"vnd.docker.reference.digest": "sha256:d306cbc2d506547f136c8e0ea040b929743f298fb2813d9030efdb9d9eee4d51",
107+
"vnd.docker.reference.type": "attestation-manifest"
108+
},
109+
"platform": {
110+
"architecture": "unknown",
111+
"os": "unknown"
112+
}
113+
},
114+
{
115+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
116+
"digest": "sha256:aa933713d8094b2708120e889acb6f7153dee4e0f3298ccd3e37a584cd0c260d",
117+
"size": 1113,
118+
"annotations": {
119+
"vnd.docker.reference.digest": "sha256:9d195ff2dc9ef347bb52ebb1c2a6e6587d4bd87019d2ea11df3e7046a3d19708",
120+
"vnd.docker.reference.type": "attestation-manifest"
121+
},
122+
"platform": {
123+
"architecture": "unknown",
124+
"os": "unknown"
125+
}
126+
},
127+
{
128+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
129+
"digest": "sha256:d95ca72d4f2a6bc416d4b2f3003b2af9d5f4dea99acec6ad3ab0c2082000a98c",
130+
"size": 1113,
131+
"annotations": {
132+
"vnd.docker.reference.digest": "sha256:72410c2c4529fca9339ebbcc8db2a1d5cb4d72d72c669f50b6d45d8a0f79fc22",
133+
"vnd.docker.reference.type": "attestation-manifest"
134+
},
135+
"platform": {
136+
"architecture": "unknown",
137+
"os": "unknown"
138+
}
139+
}
140+
]
141+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
[
2+
{
3+
"mediaType":"application/vnd.oci.image.manifest.v1+json",
4+
"digest":"sha256:2ba4ad6eae1efcafee73a971953093c7c32b6938f2f9fd4998c8bf4d0fbe76f2",
5+
"size":1113,
6+
"annotations":{
7+
"vnd.docker.reference.digest":"sha256:dccc69dd895968c4f21aa9e43e715f25f0cedfce4b17f1014c88c307928e22fc",
8+
"vnd.docker.reference.type":"attestation-manifest"
9+
},
10+
"platform":{
11+
"architecture":"unknown",
12+
"os":"unknown"
13+
}
14+
},
15+
{
16+
"mediaType":"application/vnd.oci.image.manifest.v1+json",
17+
"digest":"sha256:0709528fae1747ce17638ad2978ee7936b38a294136eaadaf692e415f64b1e03",
18+
"size":1113,
19+
"annotations":{
20+
"vnd.docker.reference.digest":"sha256:1b6bce668653f08e2d0f9f7c9b646675b2cbce94ce8abdf4eb0eabaef4353045",
21+
"vnd.docker.reference.type":"attestation-manifest"
22+
},
23+
"platform":{
24+
"architecture":"unknown",
25+
"os":"unknown"
26+
}
27+
},
28+
{
29+
"mediaType":"application/vnd.oci.image.manifest.v1+json",
30+
"digest":"sha256:241b7159129d53923c89708bcc052b3398086a826519896be2f025545916e43e",
31+
"size":1113,
32+
"annotations":{
33+
"vnd.docker.reference.digest":"sha256:8f251fda6057e9dffc54f7874b249920f15f1813e9b1406a0cebeca5e4ab1ad9",
34+
"vnd.docker.reference.type":"attestation-manifest"
35+
},
36+
"platform":{
37+
"architecture":"unknown",
38+
"os":"unknown"
39+
}
40+
},
41+
{
42+
"mediaType":"application/vnd.oci.image.manifest.v1+json",
43+
"digest":"sha256:97f4a222a7992dba6dc1a43991d0cca1fcffdc25593033c6a3a7ff14c8651cbf",
44+
"size":1113,
45+
"annotations":{
46+
"vnd.docker.reference.digest":"sha256:d306cbc2d506547f136c8e0ea040b929743f298fb2813d9030efdb9d9eee4d51",
47+
"vnd.docker.reference.type":"attestation-manifest"
48+
},
49+
"platform":{
50+
"architecture":"unknown",
51+
"os":"unknown"
52+
}
53+
},
54+
{
55+
"mediaType":"application/vnd.oci.image.manifest.v1+json",
56+
"digest":"sha256:aa933713d8094b2708120e889acb6f7153dee4e0f3298ccd3e37a584cd0c260d",
57+
"size":1113,
58+
"annotations":{
59+
"vnd.docker.reference.digest":"sha256:9d195ff2dc9ef347bb52ebb1c2a6e6587d4bd87019d2ea11df3e7046a3d19708",
60+
"vnd.docker.reference.type":"attestation-manifest"
61+
},
62+
"platform":{
63+
"architecture":"unknown",
64+
"os":"unknown"
65+
}
66+
},
67+
{
68+
"mediaType":"application/vnd.oci.image.manifest.v1+json",
69+
"digest":"sha256:d95ca72d4f2a6bc416d4b2f3003b2af9d5f4dea99acec6ad3ab0c2082000a98c",
70+
"size":1113,
71+
"annotations":{
72+
"vnd.docker.reference.digest":"sha256:72410c2c4529fca9339ebbcc8db2a1d5cb4d72d72c669f50b6d45d8a0f79fc22",
73+
"vnd.docker.reference.type":"attestation-manifest"
74+
},
75+
"platform":{
76+
"architecture":"unknown",
77+
"os":"unknown"
78+
}
79+
}
80+
]

__tests__/buildx/imagetools.test.itg.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import * as fs from 'fs';
1919
import * as path from 'path';
2020

2121
import {ImageTools} from '../../src/buildx/imagetools';
22+
23+
import {Manifest as ImageToolsManifest} from '../../src/types/buildx/imagetools';
2224
import {Image} from '../../src/types/oci/config';
25+
import {Descriptor} from '../../src/types/oci/descriptor';
2326

2427
const fixturesDir = path.join(__dirname, '..', '.fixtures');
2528

@@ -37,3 +40,39 @@ maybe('inspectImage', () => {
3740
expect(image).toEqual(expectedImage);
3841
});
3942
});
43+
44+
maybe('inspectManifest', () => {
45+
it('inspect descriptor', async () => {
46+
const manifest = await new ImageTools().inspectManifest('moby/buildkit:latest@sha256:dccc69dd895968c4f21aa9e43e715f25f0cedfce4b17f1014c88c307928e22fc');
47+
const expectedManifest = <Descriptor>JSON.parse(fs.readFileSync(path.join(fixturesDir, 'imagetools-03.json'), {encoding: 'utf-8'}).trim());
48+
expect(manifest).toEqual(expectedManifest);
49+
});
50+
it('inspect index', async () => {
51+
const manifest = await new ImageTools().inspectManifest('moby/buildkit:latest@sha256:79cc6476ab1a3371c9afd8b44e7c55610057c43e18d9b39b68e2b0c2475cc1b6');
52+
const expectedManifest = <ImageToolsManifest>JSON.parse(fs.readFileSync(path.join(fixturesDir, 'imagetools-04.json'), {encoding: 'utf-8'}).trim());
53+
expect(manifest).toEqual(expectedManifest);
54+
});
55+
});
56+
57+
maybe('attestationDescriptors', () => {
58+
it('returns buildkit attestations descriptors', async () => {
59+
const attestations = await new ImageTools().attestationDescriptors('moby/buildkit:latest@sha256:79cc6476ab1a3371c9afd8b44e7c55610057c43e18d9b39b68e2b0c2475cc1b6');
60+
const expectedAttestations = <Array<Descriptor>>JSON.parse(fs.readFileSync(path.join(fixturesDir, 'imagetools-05.json'), {encoding: 'utf-8'}).trim());
61+
expect(attestations).toEqual(expectedAttestations);
62+
});
63+
});
64+
65+
maybe('attestationDigests', () => {
66+
it('returns buildkit attestations digests', async () => {
67+
const digests = await new ImageTools().attestationDigests('moby/buildkit:latest@sha256:79cc6476ab1a3371c9afd8b44e7c55610057c43e18d9b39b68e2b0c2475cc1b6');
68+
// prettier-ignore
69+
expect(digests).toEqual([
70+
'sha256:2ba4ad6eae1efcafee73a971953093c7c32b6938f2f9fd4998c8bf4d0fbe76f2',
71+
'sha256:0709528fae1747ce17638ad2978ee7936b38a294136eaadaf692e415f64b1e03',
72+
'sha256:241b7159129d53923c89708bcc052b3398086a826519896be2f025545916e43e',
73+
'sha256:97f4a222a7992dba6dc1a43991d0cca1fcffdc25593033c6a3a7ff14c8651cbf',
74+
'sha256:aa933713d8094b2708120e889acb6f7153dee4e0f3298ccd3e37a584cd0c260d',
75+
'sha256:d95ca72d4f2a6bc416d4b2f3003b2af9d5f4dea99acec6ad3ab0c2082000a98c'
76+
]);
77+
});
78+
});

src/buildx/imagetools.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
import {Buildx} from './buildx';
1818
import {Exec} from '../exec';
1919

20+
import {Manifest as ImageToolsManifest} from '../types/buildx/imagetools';
2021
import {Image} from '../types/oci/config';
22+
import {Descriptor} from '../types/oci/descriptor';
23+
import {Digest} from '../types/oci/digest';
2124

2225
export interface ImageToolsOpts {
2326
buildx?: Buildx;
@@ -58,4 +61,37 @@ export class ImageTools {
5861
throw new Error('Unexpected output format');
5962
});
6063
}
64+
65+
public async inspectManifest(name: string): Promise<ImageToolsManifest | Descriptor> {
66+
const cmd = await this.getInspectCommand([name, '--format', '{{json .Manifest}}']);
67+
return await Exec.getExecOutput(cmd.command, cmd.args, {
68+
ignoreReturnCode: true,
69+
silent: true
70+
}).then(res => {
71+
if (res.stderr.length > 0 && res.exitCode != 0) {
72+
throw new Error(res.stderr.trim());
73+
}
74+
const parsedOutput = JSON.parse(res.stdout);
75+
if (typeof parsedOutput === 'object' && !Array.isArray(parsedOutput) && parsedOutput !== null) {
76+
if (Object.prototype.hasOwnProperty.call(parsedOutput, 'manifests')) {
77+
return <ImageToolsManifest>parsedOutput;
78+
} else {
79+
return <Descriptor>parsedOutput;
80+
}
81+
}
82+
throw new Error('Unexpected output format');
83+
});
84+
}
85+
86+
public async attestationDescriptors(name: string): Promise<Array<Descriptor>> {
87+
const manifest = await this.inspectManifest(name);
88+
if (typeof manifest === 'object' && manifest !== null && 'manifests' in manifest && Array.isArray(manifest.manifests)) {
89+
return manifest.manifests.filter(m => m.annotations && m.annotations['vnd.docker.reference.type'] === 'attestation-manifest');
90+
}
91+
throw new Error(`No attestation descriptors found for ${name}`);
92+
}
93+
94+
public async attestationDigests(name: string): Promise<Array<Digest>> {
95+
return (await this.attestationDescriptors(name)).map(attestation => attestation.digest);
96+
}
6197
}

src/types/buildx/imagetools.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright 2025 actions-toolkit authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {Versioned} from '../oci/versioned';
18+
import {Descriptor} from '../oci/descriptor';
19+
import {Digest} from '../oci/digest';
20+
21+
// https://github.com/docker/buildx/blob/62857022a08552bee5cad0c3044a9a3b185f0b32/util/imagetools/printers.go#L109-L123
22+
export interface Manifest extends Versioned {
23+
mediaType?: string;
24+
digest: Digest;
25+
size: number;
26+
manifests?: Descriptor[];
27+
annotations?: Record<string, string>;
28+
}

0 commit comments

Comments
 (0)