Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions __tests__/distributors/jetbrains-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ const core = await import('@actions/core');
const {JetBrainsDistribution} =
await import('../../src/distributions/jetbrains/installer.js');
const {RetryingHttpClient} = await import('../../src/retrying-http-client.js');
const {MAX_PAGINATION_PAGES} = await import('../../src/util.js');

const JETBRAINS_RELEASES_URL =
'https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?per_page=100';

function release(tagName: string, prerelease: boolean) {
return {
tag_name: tagName,
name: tagName,
prerelease
};
}

function nextPageHeader(page: number) {
return {
link: `<${JETBRAINS_RELEASES_URL}&page=${page}>; rel="next"`
};
}

function response(
statusCode: number,
Expand Down Expand Up @@ -110,6 +128,138 @@ describe('getAvailableVersions', () => {
expect(availableVersions.length).toBe(length);
}, 10_000);

it('continues a stable request after an all-prerelease page', async () => {
jest.spyOn(HttpClient.prototype, 'head').mockResolvedValue({
message: {statusCode: 200}
} as any);
spyHttpClient
.mockResolvedValueOnce({
statusCode: 200,
headers: nextPageHeader(2),
result: [release('jbr-release-26.0.0b1.1', true)]
})
.mockResolvedValueOnce({
statusCode: 200,
headers: {},
result: [release('jbr-release-21.0.11b1163.116', false)]
});
const distribution = new JetBrainsDistribution({
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

const availableVersions = await distribution['getAvailableVersions']();

expect(availableVersions.map(version => version.tag_name)).toContain(
'jbr-release-21.0.11b1163.116'
);
expect(availableVersions.map(version => version.tag_name)).not.toContain(
'jbr-release-26.0.0b1.1'
);
expect(spyHttpClient).toHaveBeenCalledTimes(2);
});

it('continues an EA request after an all-stable page', async () => {
jest.spyOn(HttpClient.prototype, 'head').mockResolvedValue({
message: {statusCode: 200}
} as any);
spyHttpClient
.mockResolvedValueOnce({
statusCode: 200,
headers: nextPageHeader(2),
result: [release('jbr-release-21.0.11b1163.116', false)]
})
.mockResolvedValueOnce({
statusCode: 200,
headers: {},
result: [release('jbr-release-26.0.0b1.1', true)]
});
const distribution = new JetBrainsDistribution({
version: '26-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

const availableVersions = await distribution['getAvailableVersions']();

expect(availableVersions.map(version => version.tag_name)).toEqual([
'jbr-release-26.0.0b1.1'
]);
expect(spyHttpClient).toHaveBeenCalledTimes(2);
});

it('stops pagination when a raw GitHub page is empty', async () => {
spyHttpClient
.mockResolvedValueOnce({
statusCode: 200,
headers: nextPageHeader(2),
result: [release('jbr-release-21.0.11b1163.116', false)]
})
.mockResolvedValueOnce({
statusCode: 200,
headers: nextPageHeader(3),
result: []
});
const distribution = new JetBrainsDistribution({
version: '26-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

await distribution['getAvailableVersions']();

expect(spyHttpClient).toHaveBeenCalledTimes(2);
});

it('stops at the pagination safeguard', async () => {
spyHttpClient.mockResolvedValue({
statusCode: 200,
headers: nextPageHeader(2),
result: [release('jbr-release-21.0.11b1163.116', false)]
});
const distribution = new JetBrainsDistribution({
version: '26-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

const availableVersions = await distribution['getAvailableVersions']();

expect(availableVersions).toEqual([]);
expect(spyHttpClient).toHaveBeenCalledTimes(MAX_PAGINATION_PAGES);
expect(core.warning).toHaveBeenCalledWith(
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing JetBrains Runtime releases.`
);
});

it('ignores pagination links with an unexpected origin', async () => {
spyHttpClient.mockResolvedValueOnce({
statusCode: 200,
headers: {
link: '<https://example.com/releases?page=2>; rel="next"'
},
result: [release('jbr-release-21.0.11b1163.116', false)]
});
const distribution = new JetBrainsDistribution({
version: '26-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

await distribution['getAvailableVersions']();

expect(spyHttpClient).toHaveBeenCalledTimes(1);
expect(core.warning).toHaveBeenCalledWith(
'Ignoring pagination link with unexpected origin: https://example.com/releases?page=2'
);
});

it('retries a GitHub rate limit using Retry-After', async () => {
spyHttpClient.mockRestore();
const sleep = jest.fn(async () => undefined);
Expand Down
49 changes: 28 additions & 21 deletions dist/setup/282.index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const modules = {



const JETBRAINS_RELEASES_URL = 'https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?per_page=100';
const GITHUB_API_ORIGIN = 'https://api.github.com';
class JetBrainsDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O {
constructor(installerOptions) {
super('JetBrains', installerOptions);
Expand Down Expand Up @@ -74,34 +76,39 @@ class JetBrainsDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for JBR took'); // eslint-disable-line no-console
}
// need to iterate through all pages to retrieve the list of all versions
// GitHub API doesn't provide way to retrieve the count of pages to iterate so infinity loop
let page_index = 1;
const rawVersions = [];
const bearerToken = process.env.GITHUB_TOKEN;
while (true) {
const requestArguments = `per_page=100&page=${page_index}`;
const requestHeaders = {};
if (bearerToken) {
requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
}
const rawUrl = `https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?${requestArguments}`;
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o() && page_index === 1) {
// url is identical except page_index so print it once for debug
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Gathering available versions from '${rawUrl}'`);
}
const paginationPageResult = (await this.http.getJson(rawUrl, requestHeaders)).result;
const requestHeaders = {};
if (bearerToken) {
requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
}
let releasesUrl = JETBRAINS_RELEASES_URL;
let pageCount = 0;
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Gathering available versions from '${releasesUrl}'`);
}
while (releasesUrl) {
pageCount++;
const response = await this.http.getJson(releasesUrl, requestHeaders);
const paginationPageResult = response.result;
if (!paginationPageResult || paginationPageResult.length === 0) {
// break infinity loop because we have reached end of pagination
break;
}
const paginationPage = paginationPageResult.filter(version => this.stable ? !version.prerelease : version.prerelease);
if (!paginationPage || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
rawVersions.push(...paginationPageResult.filter(version => this.stable ? !version.prerelease : version.prerelease));
const nextUrl = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getNextPageUrlFromLinkHeader */ .rC)(response.headers);
if (nextUrl && !(0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .validatePaginationUrl */ .SA)(nextUrl, GITHUB_API_ORIGIN)) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .warning */ .$e(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
releasesUrl = null;
}
else {
releasesUrl = nextUrl;
}
if (pageCount >= _util_js__WEBPACK_IMPORTED_MODULE_5__/* .MAX_PAGINATION_PAGES */ .Tp) {
if (releasesUrl) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .warning */ .$e(`Reached pagination safeguard limit (${_util_js__WEBPACK_IMPORTED_MODULE_5__/* .MAX_PAGINATION_PAGES */ .Tp} pages) while listing JetBrains Runtime releases.`);
}
break;
}
rawVersions.push(...paginationPage);
page_index++;
}
if (this.stable) {
// Add versions not available from the API but are downloadable
Expand Down
77 changes: 48 additions & 29 deletions src/distributions/jetbrains/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ import {
JavaInstallerOptions,
JavaInstallerResults
} from '../base-models.js';
import {cacheJdkDir, extractJdkFile, isVersionSatisfies} from '../../util.js';
import {
cacheJdkDir,
extractJdkFile,
getNextPageUrlFromLinkHeader,
isVersionSatisfies,
MAX_PAGINATION_PAGES,
validatePaginationUrl
} from '../../util.js';
import {OutgoingHttpHeaders} from 'http';
import {HttpCodes} from '@actions/http-client';

const JETBRAINS_RELEASES_URL =
'https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?per_page=100';
const GITHUB_API_ORIGIN = 'https://api.github.com';

export class JetBrainsDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('JetBrains', installerOptions);
Expand Down Expand Up @@ -96,46 +107,54 @@ export class JetBrainsDistribution extends JavaBase {
console.time('Retrieving available versions for JBR took'); // eslint-disable-line no-console
}

// need to iterate through all pages to retrieve the list of all versions
// GitHub API doesn't provide way to retrieve the count of pages to iterate so infinity loop
let page_index = 1;
const rawVersions: IJetBrainsRawVersion[] = [];
const bearerToken = process.env.GITHUB_TOKEN;
const requestHeaders: OutgoingHttpHeaders = {};
if (bearerToken) {
requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
}
let releasesUrl: string | null = JETBRAINS_RELEASES_URL;
let pageCount = 0;

while (true) {
const requestArguments = `per_page=100&page=${page_index}`;
const requestHeaders: OutgoingHttpHeaders = {};

if (bearerToken) {
requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
}

const rawUrl = `https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?${requestArguments}`;

if (core.isDebug() && page_index === 1) {
// url is identical except page_index so print it once for debug
core.debug(`Gathering available versions from '${rawUrl}'`);
}
if (core.isDebug()) {
core.debug(`Gathering available versions from '${releasesUrl}'`);
}

const paginationPageResult = (
await this.http.getJson<IJetBrainsRawVersion[]>(rawUrl, requestHeaders)
).result;
while (releasesUrl) {
pageCount++;
const response = await this.http.getJson<IJetBrainsRawVersion[]>(
releasesUrl,
requestHeaders
);
const paginationPageResult = response.result;
if (!paginationPageResult || paginationPageResult.length === 0) {
// break infinity loop because we have reached end of pagination
break;
}

const paginationPage: IJetBrainsRawVersion[] =
paginationPageResult.filter(version =>
rawVersions.push(
...paginationPageResult.filter(version =>
this.stable ? !version.prerelease : version.prerelease
)
);

const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
if (nextUrl && !validatePaginationUrl(nextUrl, GITHUB_API_ORIGIN)) {
core.warning(
`Ignoring pagination link with unexpected origin: ${nextUrl}`
);
if (!paginationPage || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break;
releasesUrl = null;
} else {
releasesUrl = nextUrl;
}

rawVersions.push(...paginationPage);
page_index++;
if (pageCount >= MAX_PAGINATION_PAGES) {
if (releasesUrl) {
core.warning(
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing JetBrains Runtime releases.`
);
}
break;
}
}

if (this.stable) {
Expand Down
Loading