@@ -11,6 +11,7 @@ import { zonedTimeToUtc } from "date-fns-tz";
1111export interface Release {
1212 sequence : string ;
1313 charts ?: ReleaseChart [ ] ;
14+ airgapBuildStatus ?: string ;
1415}
1516
1617export interface ReleaseChart {
@@ -45,6 +46,15 @@ export const exportedForTesting = {
4546 reportCompatibilityResultByAppId
4647} ;
4748
49+ export class StatusError extends Error {
50+ statusCode : number ;
51+
52+ constructor ( message : string , statusCode : number ) {
53+ super ( message ) ;
54+ this . statusCode = statusCode ;
55+ }
56+ }
57+
4858export async function createRelease ( vendorPortalApi : VendorPortalApi , appSlug : string , yamlDir : string ) : Promise < Release > {
4959 const http = await vendorPortalApi . client ( ) ;
5060
@@ -341,3 +351,56 @@ async function reportCompatibilityResultByAppId(vendorPortalApi: VendorPortalApi
341351 // discard the response body
342352 await res . readBody ( ) ;
343353}
354+
355+ export async function pollForAirgapReleaseStatus ( vendorPortalApi : VendorPortalApi , appId : string , channelId : string , releaseSequence : number , expectedStatus : string , timeout : number = 120 , sleeptimeMs : number = 5000 ) : Promise < string > {
356+ // get airgapped build release from the api, look for the status of the id to be ${status}
357+ // if it's not ${status}, sleep for 5 seconds and try again
358+ // if it is ${status}, return the release with that status
359+ // iterate for timeout/sleeptime times
360+ const iterations = ( timeout * 1000 ) / sleeptimeMs ;
361+ for ( let i = 0 ; i < iterations ; i ++ ) {
362+ try {
363+ const release = await getAirgapBuildRelease ( vendorPortalApi , appId , channelId , releaseSequence ) ;
364+ if ( release . airgapBuildStatus === expectedStatus ) {
365+ return release . airgapBuildStatus ;
366+ }
367+ if ( release . airgapBuildStatus === "failed" ) {
368+ console . debug ( `Airgapped build release ${ releaseSequence } failed` ) ;
369+ return "failed" ;
370+ }
371+ console . debug ( `Airgapped build release ${ releaseSequence } is not ready, sleeping for ${ sleeptimeMs / 1000 } seconds` ) ;
372+ await new Promise ( f => setTimeout ( f , sleeptimeMs ) ) ;
373+ } catch ( err ) {
374+ if ( err instanceof StatusError ) {
375+ if ( err . statusCode >= 500 ) {
376+ // 5xx errors are likely transient, so we should retry
377+ console . debug ( `Got HTTP error with status ${ err . statusCode } , sleeping for ${ sleeptimeMs / 1000 } seconds` ) ;
378+ } else {
379+ console . debug ( `Got HTTP error with status ${ err . statusCode } , exiting` ) ;
380+ throw err ;
381+ }
382+ } else {
383+ throw err ;
384+ }
385+ }
386+ }
387+ }
388+
389+
390+ async function getAirgapBuildRelease ( vendorPortalApi : VendorPortalApi , appId : string , channelId : string , releaseSequence : number ) : Promise < Release > {
391+ const http = await vendorPortalApi . client ( ) ;
392+ const uri = `${ vendorPortalApi . endpoint } /app/${ appId } /channel/${ channelId } /releases` ;
393+ const res = await http . get ( uri ) ;
394+ if ( res . message . statusCode != 200 ) {
395+ // discard the response body
396+ await res . readBody ( ) ;
397+ throw new Error ( `Failed to get airgap build release: Server responded with ${ res . message . statusCode } ` ) ;
398+ }
399+ const body : any = JSON . parse ( await res . readBody ( ) ) ;
400+ console . debug ( `Airgapped build release body: ${ JSON . stringify ( body ) } ` ) ;
401+ const release = body . releases . find ( ( r : any ) => r . sequence === releaseSequence ) ;
402+ return {
403+ sequence : release . sequence ,
404+ airgapBuildStatus : release . airgapBuildStatus ,
405+ }
406+ }
0 commit comments