11import { getApplicationDetails } from "./applications" ;
22import { VendorPortalApi } from "./configuration" ;
3+ import { Release } from "./releases" ;
34
45export class Channel {
56 name : string ;
@@ -9,6 +10,15 @@ export class Channel {
910 buildAirgapAutomatically ?: boolean ;
1011}
1112
13+ export class StatusError extends Error {
14+ statusCode : number ;
15+
16+ constructor ( message : string , statusCode : number ) {
17+ super ( message ) ;
18+ this . statusCode = statusCode ;
19+ }
20+ }
21+
1222export const exportedForTesting = {
1323 getChannelByApplicationId,
1424 findChannelDetailsInOutput
@@ -107,3 +117,56 @@ async function findChannelDetailsInOutput(channels: any[], { slug, name }: Chann
107117 }
108118 return Promise . reject ( { channel : null , reason : `Could not find channel with slug ${ slug } or name ${ name } ` } ) ;
109119}
120+
121+ export async function pollForAirgapReleaseStatus ( vendorPortalApi : VendorPortalApi , appId : string , channelId : string , releaseSequence : number , expectedStatus : string , timeout : number = 120 , sleeptimeMs : number = 5000 ) : Promise < string > {
122+ // get airgapped build release from the api, look for the status of the id to be ${status}
123+ // if it's not ${status}, sleep for 5 seconds and try again
124+ // if it is ${status}, return the release with that status
125+ // iterate for timeout/sleeptime times
126+ const iterations = ( timeout * 1000 ) / sleeptimeMs ;
127+ for ( let i = 0 ; i < iterations ; i ++ ) {
128+ try {
129+ const release = await getAirgapBuildRelease ( vendorPortalApi , appId , channelId , releaseSequence ) ;
130+ if ( release . airgapBuildStatus === expectedStatus ) {
131+ return release . airgapBuildStatus ;
132+ }
133+ if ( release . airgapBuildStatus === "failed" ) {
134+ console . debug ( `Airgapped build release ${ releaseSequence } failed` ) ;
135+ return "failed" ;
136+ }
137+ console . debug ( `Airgapped build release ${ releaseSequence } is not ready, sleeping for ${ sleeptimeMs / 1000 } seconds` ) ;
138+ await new Promise ( f => setTimeout ( f , sleeptimeMs ) ) ;
139+ } catch ( err ) {
140+ if ( err instanceof StatusError ) {
141+ if ( err . statusCode >= 500 ) {
142+ // 5xx errors are likely transient, so we should retry
143+ console . debug ( `Got HTTP error with status ${ err . statusCode } , sleeping for ${ sleeptimeMs / 1000 } seconds` ) ;
144+ await new Promise ( f => setTimeout ( f , sleeptimeMs ) ) ;
145+ } else {
146+ console . debug ( `Got HTTP error with status ${ err . statusCode } , exiting` ) ;
147+ throw err ;
148+ }
149+ } else {
150+ throw err ;
151+ }
152+ }
153+ }
154+ throw new Error ( `Airgapped build release ${ releaseSequence } did not reach status ${ expectedStatus } in ${ timeout } seconds` ) ;
155+ }
156+
157+ async function getAirgapBuildRelease ( vendorPortalApi : VendorPortalApi , appId : string , channelId : string , releaseSequence : number ) : Promise < Release > {
158+ const http = await vendorPortalApi . client ( ) ;
159+ const uri = `${ vendorPortalApi . endpoint } /app/${ appId } /channel/${ channelId } /releases` ;
160+ const res = await http . get ( uri ) ;
161+ if ( res . message . statusCode != 200 ) {
162+ // discard the response body
163+ await res . readBody ( ) ;
164+ throw new Error ( `Failed to get airgap build release: Server responded with ${ res . message . statusCode } ` ) ;
165+ }
166+ const body : any = JSON . parse ( await res . readBody ( ) ) ;
167+ const release = body . releases . find ( ( r : any ) => r . sequence === releaseSequence ) ;
168+ return {
169+ sequence : release . sequence ,
170+ airgapBuildStatus : release . airgapBuildStatus
171+ } ;
172+ }
0 commit comments