Skip to content

Commit

Permalink
SCANNPM-2 Use axios instance to re-use fetching logic
Browse files Browse the repository at this point in the history
  • Loading branch information
7PH committed Apr 22, 2024
1 parent f8b76dd commit 7882e09
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 125 deletions.
33 changes: 0 additions & 33 deletions src/http-agent.ts

This file was deleted.

41 changes: 17 additions & 24 deletions src/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ import {
SONARQUBE_JRE_PROVISIONING_MIN_VERSION,
UNARCHIVE_SUFFIX,
} from './constants';
import { getHttpAgents } from './http-agent';
import { extractArchive, getCachedFileLocation } from './file';
import { log, LogLevel } from './logging';
import { getProxyUrl } from './proxy';
import { fetch } from './request';
import { JREFullData, PlatformInfo, ScannerProperties, ScannerProperty } from './types';
import { extractArchive, getCachedFileLocation } from './file';

const finished = promisify(stream.finished);

Expand All @@ -52,7 +50,7 @@ export async function serverSupportsJREProvisioning(
log(LogLevel.DEBUG, 'Detecting SonarQube server version');
const SQServerInfo =
semver.coerce(parameters[ScannerProperty.SonarScannerInternalSqVersion]) ??
(await fetchServerVersion(parameters[ScannerProperty.SonarHostUrl], parameters));
(await fetchServerVersion(parameters[ScannerProperty.SonarHostUrl]));
log(LogLevel.INFO, 'SonarQube server version: ', SQServerInfo.version);

const supports = semver.satisfies(SQServerInfo, `>=${SONARQUBE_JRE_PROVISIONING_MIN_VERSION}`);
Expand All @@ -62,14 +60,20 @@ export async function serverSupportsJREProvisioning(

async function fetchLatestSupportedJRE(properties: ScannerProperties, platformInfo: PlatformInfo) {
const serverUrl = properties[ScannerProperty.SonarHostUrl];
const token = properties[ScannerProperty.SonarToken];
const url = serverUrl + API_V2_JRE_ENDPOINT;

const jreInfoUrl = `${serverUrl}/api/v2/analysis/jres?os=${platformInfo.os}&arch=${platformInfo.arch}`;
log(LogLevel.DEBUG, `Downloading JRE from: ${jreInfoUrl}`);
log(LogLevel.DEBUG, `Downloading JRE for ${platformInfo.os} ${platformInfo.arch} from ${url}`);
console.log(`Downloading JRE for ${platformInfo.os} ${platformInfo.arch} from ${url}`);

const { data } = await fetch(token, { url: jreInfoUrl });
const { data } = await fetch({
url,
params: {
os: platformInfo.os,
arch: platformInfo.arch,
},
});

log(LogLevel.DEBUG, 'file info: ', data);
log(LogLevel.DEBUG, 'JRE information: ', data);

return data;
}
Expand All @@ -79,7 +83,6 @@ export async function handleJREProvisioning(
platformInfo: PlatformInfo,
): Promise<JREFullData | undefined> {
const serverUrl = properties[ScannerProperty.SonarHostUrl];
const token = properties[ScannerProperty.SonarToken];

log(LogLevel.DEBUG, 'Detecting latest version of JRE');
const latestJREData = await fetchLatestSupportedJRE(properties, platformInfo);
Expand All @@ -91,8 +94,6 @@ export async function handleJREProvisioning(
latestJREData.filename + UNARCHIVE_SUFFIX,
);

const proxyUrl = getProxyUrl(properties);

if (cachedJRE) {
log(LogLevel.INFO, 'Using Cached JRE');
properties[ScannerProperty.SonarScannerWasJRECacheHit] = 'true';
Expand Down Expand Up @@ -123,11 +124,10 @@ export async function handleJREProvisioning(
const url = serverUrl + API_V2_JRE_ENDPOINT + `/${latestJREData.filename}`;
log(LogLevel.DEBUG, `Downloading ${url} to ${archivePath}`);

const response = await fetch(token, {
const response = await fetch({
url,
method: 'GET',
responseType: 'stream',
...getHttpAgents(proxyUrl),
});

const totalLength = response.headers['content-length'];
Expand Down Expand Up @@ -193,19 +193,13 @@ async function validateChecksum(filePath: string, expectedChecksum: string) {
}
}

export async function fetchServerVersion(
sonarHostUrl: string,
parameters: ScannerProperties,
): Promise<SemVer> {
const token = parameters[ScannerProperty.SonarToken];
const proxyUrl = getProxyUrl(parameters);
export async function fetchServerVersion(sonarHostUrl: string): Promise<SemVer> {
let version: SemVer | null = null;
try {
// Try and fetch the new version endpoint first
log(LogLevel.DEBUG, `Fetching API V2 ${API_V2_VERSION_ENDPOINT}`);
const response = await fetch(token, {
const response = await fetch({
url: sonarHostUrl + API_V2_VERSION_ENDPOINT,
...getHttpAgents(proxyUrl),
});
version = semver.coerce(response.data);
} catch (error: unknown) {
Expand All @@ -215,9 +209,8 @@ export async function fetchServerVersion(
LogLevel.DEBUG,
`Unable to fetch API V2 ${API_V2_VERSION_ENDPOINT}: ${error}. Falling back on ${API_OLD_VERSION_ENDPOINT}`,
);
const response = await fetch(token, {
const response = await fetch({
url: sonarHostUrl + API_OLD_VERSION_ENDPOINT,
...getHttpAgents(proxyUrl),
});
version = semver.coerce(response.data);
} catch (error: unknown) {
Expand Down
48 changes: 40 additions & 8 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,45 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import axios, { AxiosRequestConfig } from 'axios';
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import { getProxyUrl } from './proxy';
import { ScannerProperties, ScannerProperty } from './types';

export function fetch(token: string, config: AxiosRequestConfig) {
return axios({
headers: {
Authorization: `Bearer ${token}`,
},
...config,
});
// The axios instance is private to this module
let _axiosInstance: AxiosInstance | null = null;

export function getHttpAgents(
properties: ScannerProperties,
): Pick<AxiosRequestConfig, 'httpAgent' | 'httpsAgent'> {
const agents: Pick<AxiosRequestConfig, 'httpAgent' | 'httpsAgent'> = {};
const proxyUrl = getProxyUrl(properties);

if (proxyUrl) {
agents.httpsAgent = new HttpsProxyAgent({ proxy: proxyUrl.toString() });
agents.httpAgent = new HttpProxyAgent({ proxy: proxyUrl.toString() });
}
return agents;
}

export function initializeAxios(properties: ScannerProperties) {
const token = properties[ScannerProperty.SonarToken];
const agents = getHttpAgents(properties);

if (!_axiosInstance) {
_axiosInstance = axios.create({
headers: {
Authorization: `Bearer ${token}`,
},
...agents,
});
}
}

export function fetch(config: AxiosRequestConfig) {
if (!_axiosInstance) {
throw new Error('Axios instance is not initialized');
}

return _axiosInstance.request(config);
}
15 changes: 9 additions & 6 deletions src/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,18 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { version } from '../package.json';
import { handleJREProvisioning, serverSupportsJREProvisioning } from './java';
import { log, LogLevel, setLogLevel } from './logging';
import { LogLevel, log, setLogLevel } from './logging';
import { getPlatformInfo } from './platform';
import { getProperties } from './properties';
import { ScannerProperty, JreMetaData, ScanOptions } from './types';
import { version } from '../package.json';
import { initializeAxios } from './request';
import { JreMetaData, ScanOptions, ScannerProperty } from './types';

export async function scan(scanOptions: ScanOptions, cliArgs?: string[]) {
const startTimestampMs = Date.now();
const properties = getProperties(scanOptions, startTimestampMs, cliArgs);

const serverUrl = properties[ScannerProperty.SonarHostUrl];
const explicitJREPathOverride = properties[ScannerProperty.SonarScannerJavaExePath];

if (properties[ScannerProperty.SonarVerbose] === 'true') {
setLogLevel(LogLevel.DEBUG);
log(LogLevel.DEBUG, 'Setting the log level to DEBUG due to verbose mode');
Expand All @@ -42,6 +40,11 @@ export async function scan(scanOptions: ScanOptions, cliArgs?: string[]) {
log(LogLevel.DEBUG, `Overriding the log level to ${properties[ScannerProperty.SonarLogLevel]}`);
}

initializeAxios(properties);

const serverUrl = properties[ScannerProperty.SonarHostUrl];
const explicitJREPathOverride = properties[ScannerProperty.SonarScannerJavaExePath];

log(LogLevel.INFO, 'Version: ', version);

log(LogLevel.DEBUG, 'Finding platform info');
Expand Down
41 changes: 0 additions & 41 deletions test/unit/http-agent.test.ts

This file was deleted.

30 changes: 17 additions & 13 deletions test/unit/java.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import path from 'path';
import fs from 'fs';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import fs from 'fs';
import path from 'path';
import { SONARQUBE_JRE_PROVISIONING_MIN_VERSION } from '../../src/constants';
import * as file from '../../src/file';
import {
fetchServerVersion,
handleJREProvisioning,
serverSupportsJREProvisioning,
} from '../../src/java';
import * as request from '../../src/request';
import * as file from '../../src/file';
import { JreMetaData, PlatformInfo, ScannerProperties, ScannerProperty } from '../../src/types';
import { SONARQUBE_JRE_PROVISIONING_MIN_VERSION } from '../../src/constants';
import axios from 'axios';

const mock = new MockAdapter(axios);

Expand All @@ -40,27 +40,27 @@ const MOCKED_PROPERTIES: ScannerProperties = {

beforeEach(() => {
jest.clearAllMocks();
request.initializeAxios(MOCKED_PROPERTIES);
mock.reset();
jest.spyOn(request, 'fetch');
});

describe('java', () => {
describe('version should be detected correctly', () => {
it('the SonarQube version should be fetched correctly when new endpoint does not exist', async () => {
const token = 'dummy-token';
mock.onGet('http://sonarqube.com/api/server/version').reply(200, '3.2.2');

mock.onGet('http://sonarqube.com/api/v2/analysis/version').reply(404, 'Not Found');

const serverSemver = await fetchServerVersion('http://sonarqube.com', MOCKED_PROPERTIES);
const serverSemver = await fetchServerVersion('http://sonarqube.com');
expect(serverSemver.toString()).toEqual('3.2.2');
expect(request.fetch).toHaveBeenCalledTimes(2);
});

it('the SonarQube version should be fetched correctly using the new endpoint', async () => {
mock.onGet('http://sonarqube.com/api/server/version').reply(200, '3.2.1.12313');

const serverSemver = await fetchServerVersion('http://sonarqube.com', MOCKED_PROPERTIES);
const serverSemver = await fetchServerVersion('http://sonarqube.com');
expect(serverSemver.toString()).toEqual('3.2.1');
});

Expand All @@ -69,7 +69,7 @@ describe('java', () => {
mock.onGet('http://sonarqube.com/api/v2/server/version').reply(404, 'Not Found');

expect(async () => {
await fetchServerVersion('http://sonarqube.com', MOCKED_PROPERTIES);
await fetchServerVersion('http://sonarqube.com');
}).rejects.toBeDefined();
});

Expand All @@ -79,7 +79,7 @@ describe('java', () => {
.reply(200, '<!DOCTYPE><HTML><BODY>FORBIDDEN</BODY></HTML>');

expect(async () => {
await fetchServerVersion('http://sonarqube.com', MOCKED_PROPERTIES);
await fetchServerVersion('http://sonarqube.com');
}).rejects.toBeDefined();
});
});
Expand Down Expand Up @@ -132,9 +132,12 @@ describe('java', () => {
});

mock
.onGet(
`https://sonarcloud.io/api/v2/analysis/jres?os=${platformInfo.os}&arch=${platformInfo.arch}`,
)
.onGet(`https://sonarcloud.io/api/v2/analysis/jres`, {
params: {
os: platformInfo.os,
arch: platformInfo.arch,
},
})
.reply(200, serverResponse);

mock
Expand Down Expand Up @@ -168,6 +171,7 @@ describe('java', () => {
return Promise.resolve(null);
});
});

it('should download the JRE', async () => {
await handleJREProvisioning(
{
Expand Down
Loading

0 comments on commit 7882e09

Please sign in to comment.