Skip to content

Commit 1a0cee2

Browse files
committed
feat(release): add polling for get airgap build status
1 parent a8eda32 commit 1a0cee2

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

src/releases.spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { VendorPortalApi } from "./configuration";
2-
import { ReleaseChart, exportedForTesting, KotsSingleSpec, createReleaseFromChart, Release, CompatibilityResult } from "./releases";
2+
import { ReleaseChart, exportedForTesting, KotsSingleSpec, createReleaseFromChart, Release, CompatibilityResult, pollForAirgapReleaseStatus } from "./releases";
33
import * as mockttp from "mockttp";
44
import * as fs from "fs-extra";
55
import * as path from "path";
@@ -298,3 +298,30 @@ describe("createReleaseFromChart", () => {
298298
expect(release.charts?.length).toEqual(1);
299299
});
300300
});
301+
302+
describe("pollForAirgapReleaseStatus", () => {
303+
const mockServer = mockttp.getLocal();
304+
const apiClient = new VendorPortalApi();
305+
apiClient.apiToken = "abcd1234";
306+
apiClient.endpoint = "http://localhost:8080";
307+
// Start your mock server
308+
beforeEach(() => {
309+
mockServer.start(8080);
310+
});
311+
afterEach(() => mockServer.stop());
312+
313+
it("poll for airgapped release status", async () => {
314+
const data = {
315+
releases: [
316+
{
317+
sequence: 0,
318+
airgapBuildStatus: "built"
319+
}
320+
]
321+
};
322+
await mockServer.forGet("/app/1234abcd/channel/1/releases").thenReply(200, JSON.stringify(data));
323+
324+
const result = await pollForAirgapReleaseStatus(apiClient, "1234abcd", "1", 0, "built");
325+
expect(result).toEqual("built");
326+
});
327+
});

src/releases.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { zonedTimeToUtc } from "date-fns-tz";
1111
export interface Release {
1212
sequence: string;
1313
charts?: ReleaseChart[];
14+
airgapBuildStatus?: string;
1415
}
1516

1617
export 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+
4858
export 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

Comments
 (0)