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
2 changes: 0 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,8 +470,6 @@ const AXIOS_LEGACY_CONSUMERS = [
'x-pack/solutions/security/plugins/security_solution/common/endpoint/format_axios_error.ts',
'x-pack/solutions/security/plugins/security_solution/common/endpoint/utils/**/*.{js,mjs,ts,tsx}',
'x-pack/solutions/security/plugins/security_solution/scripts/endpoint/**/*.{js,mjs,ts,tsx}',
'x-pack/solutions/security/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts',
'x-pack/solutions/security/plugins/security_solution/scripts/run_cypress/project_handler/**/*.{js,mjs,ts,tsx}',
'x-pack/solutions/security/plugins/security_solution/scripts/telemetry/**/*.{js,mjs,ts,tsx}',
'x-pack/solutions/security/plugins/security_solution/server/integration_tests/**/*.{js,mjs,ts,tsx}',
'x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/scripts/**/*.{js,mjs,ts,tsx}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import crypto from 'crypto';
import fs from 'fs';
import { exec } from 'child_process';
import { createFailError } from '@kbn/dev-cli-errors';
import axios, { AxiosError } from 'axios';
import path from 'path';
import os from 'os';
import pRetry from 'p-retry';

import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants';
import { catchAxiosErrorFormatAndThrow } from '../../common/endpoint/format_axios_error';
import { createToolingLogger } from '../../common/endpoint/data_loaders/utils';
import { renderSummaryTable } from './print_run';
import {
Expand Down Expand Up @@ -58,9 +56,10 @@ let log: ToolingLog = new ToolingLog({
});

const API_HEADERS = Object.freeze({
'Content-Type': 'application/json',
'kbn-xsrf': 'cypress-creds',
'x-elastic-internal-origin': 'security-solution',
[ELASTIC_HTTP_VERSION_HEADER]: [INITIAL_REST_VERSION],
[ELASTIC_HTTP_VERSION_HEADER]: INITIAL_REST_VERSION,
});
const PROVIDERS = Object.freeze({
providerType: 'basic',
Expand All @@ -83,15 +82,17 @@ export function proxyHealthcheck(proxyUrl: string): Promise<boolean> {
const fetchHealthcheck = async (attemptNum: number) => {
log.info(`Retry number ${attemptNum} to check if Elasticsearch is green.`);

const response = await axios.get(`${proxyUrl}/healthcheck`);
const response = await fetch(`${proxyUrl}/healthcheck`);
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

log.info(`The proxy service is available.`);
return response.status === 200;
};
const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError) {
log.info(`The proxy service is not available. A retry will be triggered soon...`);
}
onFailedAttempt: () => {
log.info(`The proxy service is not available. A retry will be triggered soon...`);
},
retries: 4,
factor: 2,
Expand All @@ -110,19 +111,22 @@ export function waitForEsStatusGreen(
const fetchHealthStatusAttempt = async (attemptNum: number) => {
log.info(`Retry number ${attemptNum} to check if Elasticsearch is green.`);

const response = await axios
.get(`${esUrl}/_cluster/health?wait_for_status=green&timeout=50s`, {
headers: {
Authorization: `Basic ${auth}`,
},
})
.catch(catchAxiosErrorFormatAndThrow);
const response = await fetch(`${esUrl}/_cluster/health?wait_for_status=green&timeout=50s`, {
headers: {
Authorization: `Basic ${auth}`,
},
});
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

const data = (await response.json()) as { status: string };

log.info(`${projectId}: Elasticsearch is ready with status ${response.data.status}.`);
log.info(`${projectId}: Elasticsearch is ready with status ${data.status}.`);
};
const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError && error.code === 'ENOTFOUND') {
onFailedAttempt: (error: Error) => {
if ((error as { cause?: { code?: string } }).cause?.code === 'ENOTFOUND') {
log.info(
`${projectId}: The Elasticsearch URL is not yet reachable. A retry will be triggered soon...`
);
Expand All @@ -144,22 +148,25 @@ export function waitForKibanaAvailable(
): Promise<void> {
const fetchKibanaStatusAttempt = async (attemptNum: number) => {
log.info(`Retry number ${attemptNum} to check if kibana is available.`);
const response = await axios
.get(`${kbUrl}/api/status`, {
headers: {
Authorization: `Basic ${auth}`,
},
})
.catch(catchAxiosErrorFormatAndThrow);
if (response.data.status.overall.level !== 'available') {
const response = await fetch(`${kbUrl}/api/status`, {
headers: {
Authorization: `Basic ${auth}`,
},
});
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

const data = (await response.json()) as { status: { overall: { level: string } } };
if (data.status.overall.level !== 'available') {
throw new Error(`${projectId}: Kibana is not available. A retry will be triggered soon...`);
} else {
log.info(`${projectId}: Kibana status overall is ${response.data.status.overall.level}.`);
log.info(`${projectId}: Kibana status overall is ${data.status.overall.level}.`);
}
};
const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError && error.code === 'ENOTFOUND') {
onFailedAttempt: (error: Error) => {
if ((error as { cause?: { code?: string } }).cause?.code === 'ENOTFOUND') {
log.info(
`${projectId}: The Kibana URL is not yet reachable. A retry will be triggered soon...`
);
Expand All @@ -179,17 +186,18 @@ export function waitForEsAccess(esUrl: string, auth: string, projectId: string):
const fetchEsAccessAttempt = async (attemptNum: number) => {
log.info(`Retry number ${attemptNum} to check if can be accessed.`);

await axios
.get(`${esUrl}`, {
headers: {
Authorization: `Basic ${auth}`,
},
})
.catch(catchAxiosErrorFormatAndThrow);
const response = await fetch(`${esUrl}`, {
headers: {
Authorization: `Basic ${auth}`,
},
});
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}
};
const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError && error.code === 'ENOTFOUND') {
onFailedAttempt: (error: Error) => {
if ((error as { cause?: { code?: string } }).cause?.code === 'ENOTFOUND') {
log.info(
`${projectId}: The elasticsearch url is not yet reachable. A retry will be triggered soon...`
);
Expand All @@ -213,15 +221,18 @@ function waitForKibanaLogin(kbUrl: string, credentials: Credentials): Promise<vo

const fetchLoginStatusAttempt = async (attemptNum: number) => {
log.info(`Retry number ${attemptNum} to check if login can be performed.`);
axios
.post(`${kbUrl}/internal/security/login`, body, {
headers: API_HEADERS,
})
.catch(catchAxiosErrorFormatAndThrow);
const response = await fetch(`${kbUrl}/internal/security/login`, {
method: 'POST',
headers: API_HEADERS,
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}
};
const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError && error.code === 'ENOTFOUND') {
onFailedAttempt: (error: Error) => {
if ((error as { cause?: { code?: string } }).cause?.code === 'ENOTFOUND') {
log.info('Project is not reachable. A retry will be triggered soon...');
} else {
log.error(`${error.message}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 2.0.
*/

import axios, { AxiosError } from 'axios';
import pRetry from 'p-retry';
import type {
ProductType,
Expand Down Expand Up @@ -76,48 +75,57 @@ export class CloudHandler extends ProjectHandler {
}

try {
const response = await axios.post(
`${this.baseEnvUrl}/api/v1/serverless/projects/security`,
body,
{
headers: {
Authorization: `ApiKey ${this.apiKey}`,
},
}
);
const response = await fetch(`${this.baseEnvUrl}/api/v1/serverless/projects/security`, {
method: 'POST',
headers: {
Authorization: `ApiKey ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is JSON.stringify(body) really needed here? In axios its not needed so I guess it does the serialization behind the scenes if its needed indeed. Right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! Responded in Slack, but will duplicate here for the records:

Axios does JSON.stringify for request body (+adds a correct Content-Type) and JSON.parse for response body automatically. The native fetch is more low-level, it only accepts strings, Blob, FormData, ArrayBuffer, URLSearchParams, or a ReadableStream for a request body (if one passes object it will just call toString() on it that will result into something like "[Object]") and requires consumers to parse body manually with json(), text() and so on (e.g. for perf reasons you might not want to call JSON.parse automatically and pay for that).

Using native fetch requires more boilerplate for sure, maybe eventually, when we all use fetch we can have some higher-level abstraction

});
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

const data = (await response.json()) as {
name: string;
id: string;
region_id: string;
endpoints: { elasticsearch: string; kibana: string };
type: string;
};
return {
name: response.data.name,
id: response.data.id,
region: response.data.region_id,
es_url: `${response.data.endpoints.elasticsearch}:443`,
kb_url: `${response.data.endpoints.kibana}:443`,
product: response.data.type,
name: data.name,
id: data.id,
region: data.region_id,
es_url: `${data.endpoints.elasticsearch}:443`,
kb_url: `${data.endpoints.kibana}:443`,
product: data.type,
};
} catch (error) {
if (error instanceof AxiosError) {
const errorData = JSON.stringify(error.response?.data);
this.log.error(`${error.response?.status}:${errorData}`);
} else {
this.log.error(`${error.message}`);
}
this.log.error(`${error.message}`);
}
}

// Method to invoke the delete project API for serverless.
async deleteSecurityProject(projectId: string, projectName: string): Promise<void> {
try {
await axios.delete(`${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}`, {
headers: {
Authorization: `ApiKey ${this.apiKey}`,
},
});
const response = await fetch(
`${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}`,
{
method: 'DELETE',
headers: {
Authorization: `ApiKey ${this.apiKey}`,
},
}
);
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

this.log.info(`Project ${projectName} was successfully deleted!`);
} catch (error) {
if (error instanceof AxiosError) {
this.log.error(`${error.response?.status}:${error.response?.data}`);
} else {
this.log.error(`${error.message}`);
}
this.log.error(`${error.message}`);
}
}

Expand All @@ -126,25 +134,32 @@ export class CloudHandler extends ProjectHandler {
this.log.info(`${projectId} : Reseting credentials`);

const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => {
const response = await axios.post(
const response = await fetch(
`${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}/_reset-internal-credentials`,
{},
{
method: 'POST',
headers: {
Authorization: `ApiKey ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
}
);
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

const data = (await response.json()) as { password: string; username: string };
this.log.info('Credentials have been reset');
return {
password: response.data.password,
username: response.data.username,
password: data.password,
username: data.username,
};
};

const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError && error.code === 'ENOTFOUND') {
onFailedAttempt: (error: Error) => {
if ((error as { cause?: { code?: string } }).cause?.code === 'ENOTFOUND') {
this.log.info('Project is not reachable. A retry will be triggered soon..');
} else {
this.log.error(`${error.message}`);
Expand All @@ -162,24 +177,29 @@ export class CloudHandler extends ProjectHandler {
waitForProjectInitialized(projectId: string): Promise<void> {
const fetchProjectStatusAttempt = async (attemptNum: number) => {
this.log.info(`Retry number ${attemptNum} to check if project is initialized.`);
const response = await axios.get(
const response = await fetch(
`${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}/status`,
{
headers: {
Authorization: `ApiKey ${this.apiKey}`,
},
}
);
if (response.data.phase !== 'initialized') {
this.log.info(response.data);
if (!response.ok) {
throw new Error(`${response.status}:${await response.text()}`);
}

const data = (await response.json()) as { phase: string };
if (data.phase !== 'initialized') {
this.log.info(data);
throw new Error('Project is not initialized. A retry will be triggered soon...');
} else {
this.log.info('Project is initialized');
}
};
const retryOptions = {
onFailedAttempt: (error: Error | AxiosError) => {
if (error instanceof AxiosError && error.code === 'ENOTFOUND') {
onFailedAttempt: (error: Error) => {
if ((error as { cause?: { code?: string } }).cause?.code === 'ENOTFOUND') {
this.log.info('Project is not reachable. A retry will be triggered soon...');
} else {
this.log.warning(`${error.message}`);
Expand Down
Loading
Loading