@@ -21,9 +21,9 @@ import {Buildx} from './buildx.js';
2121import { Context } from '../context.js' ;
2222import { Exec } from '../exec.js' ;
2323
24- import { CreateOpts , CreateResponse , CreateResult , Manifest as ImageToolsManifest } from '../types/buildx/imagetools.js' ;
24+ import { AttestationInspectOpts , CreateOpts , CreateResponse , CreateResult , InspectOpts , Manifest as ImageToolsManifest } from '../types/buildx/imagetools.js' ;
2525import { Image } from '../types/oci/config.js' ;
26- import { Descriptor , Platform } from '../types/oci/descriptor.js' ;
26+ import { Descriptor } from '../types/oci/descriptor.js' ;
2727import { Digest } from '../types/oci/digest.js' ;
2828
2929export interface ImageToolsOpts {
@@ -49,16 +49,8 @@ export class ImageTools {
4949 return await this . getCommand ( [ 'create' , ...args ] ) ;
5050 }
5151
52- public async inspectImage ( name : string ) : Promise < Record < string , Image > | Image > {
53- const cmd = await this . getInspectCommand ( [ name , '--format' , '{{json .Image}}' ] ) ;
54- return await Exec . getExecOutput ( cmd . command , cmd . args , {
55- ignoreReturnCode : true ,
56- silent : true
57- } ) . then ( res => {
58- if ( res . stderr . length > 0 && res . exitCode != 0 ) {
59- throw new Error ( res . stderr . trim ( ) ) ;
60- }
61- const parsedOutput = JSON . parse ( res . stdout ) ;
52+ public async inspectImage ( opts : InspectOpts ) : Promise < Record < string , Image > | Image > {
53+ return await this . inspect ( opts , '{{json .Image}}' , parsedOutput => {
6254 if ( typeof parsedOutput === 'object' && ! Array . isArray ( parsedOutput ) && parsedOutput !== null ) {
6355 if ( Object . prototype . hasOwnProperty . call ( parsedOutput , 'config' ) ) {
6456 return < Image > parsedOutput ;
@@ -70,16 +62,8 @@ export class ImageTools {
7062 } ) ;
7163 }
7264
73- public async inspectManifest ( name : string ) : Promise < ImageToolsManifest | Descriptor > {
74- const cmd = await this . getInspectCommand ( [ name , '--format' , '{{json .Manifest}}' ] ) ;
75- return await Exec . getExecOutput ( cmd . command , cmd . args , {
76- ignoreReturnCode : true ,
77- silent : true
78- } ) . then ( res => {
79- if ( res . stderr . length > 0 && res . exitCode != 0 ) {
80- throw new Error ( res . stderr . trim ( ) ) ;
81- }
82- const parsedOutput = JSON . parse ( res . stdout ) ;
65+ public async inspectManifest ( opts : InspectOpts ) : Promise < ImageToolsManifest | Descriptor > {
66+ return await this . inspect ( opts , '{{json .Manifest}}' , parsedOutput => {
8367 if ( typeof parsedOutput === 'object' && ! Array . isArray ( parsedOutput ) && parsedOutput !== null ) {
8468 if ( Object . prototype . hasOwnProperty . call ( parsedOutput , 'manifests' ) ) {
8569 return < ImageToolsManifest > parsedOutput ;
@@ -91,17 +75,18 @@ export class ImageTools {
9175 } ) ;
9276 }
9377
94- public async attestationDescriptors ( name : string , platform ?: Platform ) : Promise < Array < Descriptor > > {
95- const manifest = await this . inspectManifest ( name ) ;
78+ public async attestationDescriptors ( opts : AttestationInspectOpts ) : Promise < Array < Descriptor > > {
79+ const manifest = await this . inspectManifest ( opts ) ;
9680
9781 if ( typeof manifest !== 'object' || manifest === null || ! ( 'manifests' in manifest ) || ! Array . isArray ( manifest . manifests ) ) {
98- throw new Error ( `No descriptor found for ${ name } ` ) ;
82+ throw new Error ( `No descriptor found for ${ opts . name } ` ) ;
9983 }
10084
10185 const attestations = manifest . manifests . filter ( m => m . annotations ?. [ 'vnd.docker.reference.type' ] === 'attestation-manifest' ) ;
102- if ( ! platform ) {
86+ if ( ! opts . platform ) {
10387 return attestations ;
10488 }
89+ const platform = opts . platform ;
10590
10691 const manifestByDigest = new Map < string , Descriptor > ( ) ;
10792 for ( const m of manifest . manifests ) {
@@ -123,8 +108,8 @@ export class ImageTools {
123108 } ) ;
124109 }
125110
126- public async attestationDigests ( name : string , platform ?: Platform ) : Promise < Array < Digest > > {
127- return ( await this . attestationDescriptors ( name , platform ) ) . map ( attestation => attestation . digest ) ;
111+ public async attestationDigests ( opts : AttestationInspectOpts ) : Promise < Array < Digest > > {
112+ return ( await this . attestationDescriptors ( opts ) ) . map ( attestation => attestation . digest ) ;
128113 }
129114
130115 public async create ( opts : CreateOpts ) : Promise < CreateResult | undefined > {
@@ -205,4 +190,44 @@ export class ImageTools {
205190 }
206191 } ) ;
207192 }
193+
194+ private async inspect < T > ( opts : InspectOpts , format : string , parser : ( parsedOutput : unknown ) => T ) : Promise < T > {
195+ const cmd = await this . getInspectCommand ( [ opts . name , '--format' , format ] ) ;
196+ if ( ! opts . retryOnManifestUnknown ) {
197+ return await this . execInspect ( cmd . command , cmd . args , parser ) ;
198+ }
199+
200+ const retries = opts . retryLimit ?? 15 ;
201+ let lastError : Error | undefined ;
202+ for ( let attempt = 0 ; attempt < retries ; attempt ++ ) {
203+ try {
204+ return await this . execInspect ( cmd . command , cmd . args , parser ) ;
205+ } catch ( err ) {
206+ lastError = err as Error ;
207+ if ( ! ImageTools . isManifestUnknownError ( lastError . message ) || attempt === retries - 1 ) {
208+ throw lastError ;
209+ }
210+ core . info ( `buildx imagetools inspect command failed with MANIFEST_UNKNOWN, retrying attempt ${ attempt + 1 } /${ retries } ...\n${ lastError . message } ` ) ;
211+ await new Promise ( res => setTimeout ( res , Math . pow ( 2 , attempt ) * 100 ) ) ;
212+ }
213+ }
214+
215+ throw lastError ?? new Error ( `ImageTools inspect command failed for ${ opts . name } ` ) ;
216+ }
217+
218+ private async execInspect < T > ( command : string , args : Array < string > , parser : ( parsedOutput : unknown ) => T ) : Promise < T > {
219+ return await Exec . getExecOutput ( command , args , {
220+ ignoreReturnCode : true ,
221+ silent : true
222+ } ) . then ( res => {
223+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
224+ throw new Error ( res . stderr . trim ( ) ) ;
225+ }
226+ return parser ( JSON . parse ( res . stdout ) ) ;
227+ } ) ;
228+ }
229+
230+ private static isManifestUnknownError ( message : string ) : boolean {
231+ return / ( M A N I F E S T _ U N K N O W N | m a n i f e s t u n k n o w n | n o t f o u n d : n o t f o u n d ) / i. test ( message ) ;
232+ }
208233}
0 commit comments