From 2ab8a5ced075da08f3aeb9526a76b60b1a964602 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:21:23 -0500 Subject: [PATCH] [Security Solution][Endpoint] Cypress test improvements to capture Agent diagnostics file when test fails (#202965) ## Summary - the Cypress `parallel` runner was updated to set tooling logging level first from Env. variables before falling back to the value defined in the Cypress configuration file - The env. value to set, if wanting to enable a specific logging level, is `TOOLING_LOG_LEVEL`. The values supported are the same as those used with `ToolingLog` ([here](https://github.com/elastic/kibana/blob/b6287708f687d4e3288851052c0c6ae4ade8ce60/packages/kbn-tooling-log/src/log_levels.ts#L10)): `silent`, `error`, `warning`, `success`, `info`, `debug`, `verbose` - This change makes it easier to run Cypress tests locally with (for example) a logging level of `verbose` for our tooling without having to modify the Cypress configuration file. Example: `export TOOLING_LOG_LEVEL=verbose && yarn cypress:dw:open` - Added two new methods to our scripting VM service clients (for Vagrant and Multipass): - `download`: allow you to pull files out of the VM and save them locally - `upload`: uploads a local file to the VM. (upload already existed as `transfer` - which has now been marked as deprecated). - Added new service function on our Fleet scripting module to enable us to set the logging level on a Fleet Agent - Cypress tests were adjusted to automatically set the agent logging to debug when running in CI - A new Cypress task that allows for an Agent Diagnostic file (which includes the Endpoint Log) to be retrieved from the host VM and stored with the CI job (under the artifacts tab) - A few tests were updated to include this step for failed test --- .../public/management/cypress/cypress.d.ts | 8 ++ .../response_console/file_operations.cy.ts | 9 ++ .../cypress/support/data_loaders.ts | 67 +++++++++++++ .../support/setup_tooling_log_level.ts | 14 ++- .../public/management/cypress/types.ts | 5 + .../fleet_server/fleet_server_services.ts | 19 +++- .../scripts/endpoint/common/fleet_services.ts | 91 +++++++++++++++++ .../scripts/endpoint/common/types.ts | 10 +- .../scripts/endpoint/common/vm_services.ts | 98 ++++++++++++++++--- .../scripts/run_cypress/parallel.ts | 8 +- .../run_cypress/parallel_serverless.ts | 12 ++- .../scripts/run_cypress/utils.ts | 21 ++++ 12 files changed, 331 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts index eb42649827908..f5bb7071d859f 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts @@ -11,6 +11,7 @@ import type { CasePostRequest } from '@kbn/cases-plugin/common/api'; import type { UsageRecord } from '@kbn/security-solution-serverless/server/types'; +import type { HostVmTransferResponse } from '../../../scripts/endpoint/common/types'; import type { DeletedEndpointHeartbeats, IndexedEndpointHeartbeats, @@ -30,6 +31,7 @@ import type { UninstallAgentFromHostTaskOptions, IsAgentAndEndpointUninstalledFromHostTaskOptions, LogItTaskOptions, + CaptureHostVmAgentDiagnosticsOptions, } from './types'; import type { DeleteIndexedFleetEndpointPoliciesResponse, @@ -267,6 +269,12 @@ declare global { arg: LogItTaskOptions, options?: Partial ): Chainable; + + task( + name: 'captureHostVmAgentDiagnostics', + arg: CaptureHostVmAgentDiagnosticsOptions, + options?: Partial + ): Chainable>; } } } diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/file_operations.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/file_operations.cy.ts index 94619aba2a8f7..c36c9153a248b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/file_operations.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/file_operations.cy.ts @@ -67,6 +67,15 @@ describe.skip('Response console', { tags: ['@ess', '@serverless'] }, () => { } }); + afterEach(function () { + if (Cypress.env('IS_CI') && this.currentTest?.isFailed() && createdHost) { + cy.task('captureHostVmAgentDiagnostics', { + hostname: createdHost.hostname, + fileNamePrefix: this.currentTest?.fullTitle(), + }); + } + }); + it('"get-file --path" - should retrieve a file', () => { const downloadsFolder = Cypress.config('downloadsFolder'); diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index 3abf04765ca5e..a85feb74f1d9e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -11,6 +11,10 @@ import type { CasePostRequest } from '@kbn/cases-plugin/common'; import execa from 'execa'; import type { KbnClient } from '@kbn/test'; import type { ToolingLog } from '@kbn/tooling-log'; +import { REPO_ROOT } from '@kbn/repo-info'; +// This is a Cypress module and only used by Cypress, so disabling "should" be safe +// eslint-disable-next-line import/no-nodejs-modules +import { mkdir } from 'node:fs/promises'; import type { IndexedEndpointHeartbeats } from '../../../../common/endpoint/data_loaders/index_endpoint_hearbeats'; import { deleteIndexedEndpointHeartbeats, @@ -53,6 +57,7 @@ import type { LoadUserAndRoleCyTaskOptions, CreateUserAndRoleCyTaskOptions, LogItTaskOptions, + CaptureHostVmAgentDiagnosticsOptions, } from '../types'; import type { DeletedIndexedEndpointRuleAlerts, @@ -75,6 +80,7 @@ import { deleteAgentPolicy, fetchAgentPolicyEnrollmentKey, getOrCreateDefaultAgentPolicy, + setAgentLoggingLevel, } from '../../../../scripts/endpoint/common/fleet_services'; import { startElasticAgentWithDocker } from '../../../../scripts/endpoint/common/elastic_agent_service'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; @@ -433,6 +439,7 @@ ${s1Info.status} log, kbnClient, }); + await setAgentLoggingLevel(kbnClient, newHost.agentId, 'debug', log); await waitForEndpointToStreamData(kbnClient, newHost.agentId, 360000); return newHost; } catch (err) { @@ -531,5 +538,65 @@ ${s1Info.status} await startEndpointHost(hostName); return null; }, + + /** + * Generates an Agent Diagnostics archive (ZIP) directly on the Host VM and saves it to a directory + * that is then included with the list of Artifacts that are captured with Buildkite job. + * + * ### Usage: + * + * This task is best used from a `afterEach()` by checking if the test failed and if so (and it + * was a test that was running against a host VM), then capture the diagnostics file + * + * @param hostname + * + * @example + * + * describe('something', () => { + * let hostVm; + * + * afterEach(function() { // << Important: Note the use of `function()` here instead of arrow function + * if (this.currentTest?.isFailed() && hostVm) { + * cy.task('captureHostVmAgentDiagnostics', { hostname: hostVm.hostname }); + * } + * }); + * + * //... + * }) + */ + captureHostVmAgentDiagnostics: async ({ + hostname, + fileNamePrefix = '', + }: CaptureHostVmAgentDiagnosticsOptions) => { + const { log } = await stackServicesPromise; + + log.info(`Capturing agent diagnostics for host VM [${hostname}]`); + + const vmClient = getHostVmClient(hostname, undefined, undefined, log); + const fileName = `elastic-agent-diagnostics-${hostname}-${new Date() + .toISOString() + .replace(/:/g, '.')}.zip`; + const vmDiagnosticsFile = `/tmp/${fileName}`; + const localDiagnosticsDir = `${REPO_ROOT}/target/test_failures`; + const localDiagnosticsFile = `${localDiagnosticsDir}/${ + fileNamePrefix + ? // Insure the file name prefix does not have characters that can't be used in file names + `${fileNamePrefix.replace(/[><:"/\\|?*'`{} ]/g, '_')}-` + : '' + }${fileName}`; + + await mkdir(localDiagnosticsDir, { recursive: true }); + + // generate diagnostics file on the host and then download it + await vmClient.exec( + `sudo /opt/Elastic/Agent/elastic-agent diagnostics --file ${vmDiagnosticsFile}` + ); + return vmClient.download(vmDiagnosticsFile, localDiagnosticsFile).then((response) => { + log.info(`Agent diagnostic file for host [${hostname}] has been downloaded and is available at: + ${response.filePath} +`); + return { filePath: response.filePath }; + }); + }, }); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/setup_tooling_log_level.ts b/x-pack/plugins/security_solution/public/management/cypress/support/setup_tooling_log_level.ts index b4901bef9321a..a8c85fad1c3a7 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/setup_tooling_log_level.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/setup_tooling_log_level.ts @@ -16,10 +16,20 @@ export const setupToolingLogLevel = (config: Cypress.PluginConfigOptions) => { const log = createToolingLogger(); const defaultToolingLogLevel = config.env.TOOLING_LOG_LEVEL; - log.info(`Cypress config 'env.TOOLING_LOG_LEVEL': ${defaultToolingLogLevel}`); + log.info(` + +Cypress Configuration File: ${config.configFile} + +'env.TOOLING_LOG_LEVEL' set to: ${defaultToolingLogLevel} + +*** FYI: *** To help with test failures, an environmental variable named 'TOOLING_LOG_LEVEL' can be set + with a value of 'verbose' in order to capture more data in the logs. This environment + property can be set either in the runtime environment (ex. local shell or buildkite) or + directly in the Cypress configuration file \`env: {}\` section. + + `); if (defaultToolingLogLevel && defaultToolingLogLevel !== createToolingLogger.defaultLogLevel) { createToolingLogger.defaultLogLevel = defaultToolingLogLevel; - log.info(`Default log level for 'createToolingLogger()' set to ${defaultToolingLogLevel}`); } }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/types.ts b/x-pack/plugins/security_solution/public/management/cypress/types.ts index 8beb150a64d5a..e23ff82fa8479 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/types.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/types.ts @@ -81,3 +81,8 @@ export interface LogItTaskOptions { level: keyof Pick; data: any; } + +export interface CaptureHostVmAgentDiagnosticsOptions { + hostname: string; + fileNamePrefix?: string; +} diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts index 51260c5ac6053..cc9a75148d9dc 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts @@ -277,6 +277,7 @@ const startFleetServerWithDocker = async ({ const isServerless = await isServerlessKibanaFlavor(kbnClient); const esURL = new URL(await getFleetElasticsearchOutputHost(kbnClient)); const containerName = `dev-fleet-server.${port}`; + let fleetServerVersionInfo = ''; log.info( `Starting a new fleet server using Docker\n Agent version: ${agentVersion}\n Server URL: ${fleetServerUrl}` @@ -284,10 +285,11 @@ const startFleetServerWithDocker = async ({ let retryAttempt = isServerless ? 0 : 1; const attemptServerlessFleetServerSetup = async (): Promise => { + fleetServerVersionInfo = ''; + return log.indent(4, async () => { const hostname = `dev-fleet-server.${port}.${Math.random().toString(32).substring(2, 6)}`; let containerId = ''; - let fleetServerVersionInfo = ''; if (isLocalhost(esURL.hostname)) { esURL.hostname = localhostRealIp; @@ -361,8 +363,17 @@ const startFleetServerWithDocker = async ({ } fleetServerVersionInfo = isServerless - ? // `/usr/bin/fleet-server` process does not seem to support a `--version` type of argument - 'Running latest standalone fleet server' + ? ( + await execa + .command(`docker exec ${containerName} /usr/bin/fleet-server --version`) + .catch((err) => { + log.verbose( + `Failed to retrieve fleet-server (serverless/standalone) version information from running instance.`, + err + ); + return { stdout: 'Unable to retrieve version information (serverless)' }; + }) + ).stdout : ( await execa('docker', [ 'exec', @@ -424,7 +435,7 @@ Kill container: ${chalk.cyan(`docker kill ${containerId}`)} const response: StartedServer = await attemptServerlessFleetServerSetup(); - log.info(`Done. Fleet server up and running`); + log.info(`Done. Fleet server up and running (version: ${fleetServerVersionInfo})`); return response; }; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts index a07823194fa69..7c89beec9150b 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts @@ -56,12 +56,14 @@ import type { DeleteAgentPolicyResponse, EnrollmentAPIKey, GenerateServiceTokenResponse, + GetActionStatusResponse, GetAgentsRequest, GetEnrollmentAPIKeysResponse, GetOutputsResponse, PostAgentUnenrollResponse, UpdateAgentPolicyRequest, UpdateAgentPolicyResponse, + PostNewAgentActionResponse, } from '@kbn/fleet-plugin/common/types'; import semver from 'semver'; import axios from 'axios'; @@ -1499,3 +1501,92 @@ export const updateAgentPolicy = async ( .catch(catchAxiosErrorFormatAndThrow) .then((response) => response.data.item); }; + +/** + * Sets the log level on a Fleet agent and waits a bit of time to allow it for to + * complete (but does not error if it does not complete) + * + * @param kbnClient + * @param agentId + * @param logLevel + * @param log + */ +export const setAgentLoggingLevel = async ( + kbnClient: KbnClient, + agentId: string, + logLevel: 'debug' | 'info' | 'warning' | 'error', + log: ToolingLog = createToolingLogger() +): Promise => { + log.debug(`Setting fleet agent [${agentId}] logging level to [${logLevel}]`); + + const response = await kbnClient + .request({ + method: 'POST', + path: `/api/fleet/agents/${agentId}/actions`, + body: { action: { type: 'SETTINGS', data: { log_level: logLevel } } }, + headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 }, + }) + .then((res) => res.data); + + // Wait to see if the action completes, but don't `throw` if it does not + await waitForFleetAgentActionToComplete(kbnClient, response.item.id) + .then(() => { + log.debug(`Fleet action to set agent [${agentId}] logging level to [${logLevel}] completed!`); + }) + .catch((err) => { + log.debug(err.message); + }); + + return response; +}; + +/** + * Retrieve fleet agent action statuses + * @param kbnClient + */ +export const fetchFleetAgentActionStatus = async ( + kbnClient: KbnClient +): Promise => { + return kbnClient + .request({ + method: 'GET', + path: agentRouteService.getActionStatusPath(), + query: { perPage: 1000 }, + headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 }, + }) + .then((response) => response.data); +}; + +/** + * Check and wait until a Fleet Agent action is complete. + * @param kbnClient + * @param actionId + * @param timeout + * + * @throws + */ +export const waitForFleetAgentActionToComplete = async ( + kbnClient: KbnClient, + actionId: string, + timeout: number = 20_000 +): Promise => { + await pRetry( + async (attempts) => { + const { items: actionList } = await fetchFleetAgentActionStatus(kbnClient); + const actionInfo = actionList.find((action) => action.actionId === actionId); + + if (!actionInfo) { + throw new Error( + `Fleet Agent action id [${actionId}] was not found in list of actions retrieved from fleet!` + ); + } + + if (actionInfo.status === 'IN_PROGRESS') { + throw new Error( + `Fleet agent action id [${actionId}] remains in progress after [${attempts}] attempts to check its status` + ); + } + }, + { maxTimeout: 2_000, maxRetryTime: timeout } + ); +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/types.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/types.ts index 38256f1c774bd..e3e41c41b77f3 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/types.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/types.ts @@ -14,8 +14,12 @@ export interface HostVm { exec: (command: string) => Promise; mount: (localDir: string, hostVmDir: string) => Promise; unmount: (hostVmDir: string) => Promise; - /** Uploads/copies a file from the local machine to the VM */ + /** @deprecated use `upload` */ transfer: (localFilePath: string, destFilePath: string) => Promise; + /** Uploads/copies a file from the local machine to the VM */ + upload: (localFilePath: string, destFilePath: string) => Promise; + /** Downloads a file from the host VM to the local machine */ + download: (vmFilePath: string, localFilePath: string) => Promise; destroy: () => Promise; info: () => string; stop: () => void; @@ -33,8 +37,8 @@ export interface HostVmMountResponse { unmount: () => Promise; } export interface HostVmTransferResponse { - /** The file path of the file on the host vm */ + /** The path of the file that was either uploaded to the host VM or downloaded to the local machine from the VM */ filePath: string; - /** Delete the file from the host VM */ + /** Delete the file from the host VM or from the local machine depending on what client method was used */ delete: () => Promise; } diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts index 084e068768e8f..fc1301c9fed9a 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts @@ -8,15 +8,19 @@ import type { ToolingLog } from '@kbn/tooling-log'; import execa from 'execa'; import chalk from 'chalk'; +import path from 'path'; import { userInfo } from 'os'; -import { join as pathJoin, dirname } from 'path'; +import { unlink as deleteFile } from 'fs/promises'; +import { dump } from './utils'; import type { DownloadedAgentInfo } from './agent_downloads_service'; import { BaseDataGenerator } from '../../../common/endpoint/data_generators/base_data_generator'; import { createToolingLogger } from '../../../common/endpoint/data_loaders/utils'; import type { HostVm, HostVmExecResponse, SupportedVmManager } from './types'; const baseGenerator = new BaseDataGenerator(); -export const DEFAULT_VAGRANTFILE = pathJoin(__dirname, 'vagrant', 'Vagrantfile'); +export const DEFAULT_VAGRANTFILE = path.join(__dirname, 'vagrant', 'Vagrantfile'); + +const MAX_BUFFER = 1024 * 1024 * 5; // 5MB export interface BaseVmCreateOptions { name: string; @@ -75,9 +79,16 @@ export const createMultipassHostVmClient = ( log: ToolingLog = createToolingLogger() ): HostVm => { const exec = async (command: string): Promise => { - const execResponse = await execa.command(`multipass exec ${name} -- ${command}`); - - log.verbose(execResponse); + const execResponse = await execa + .command(`multipass exec ${name} -- ${command}`, { maxBuffer: MAX_BUFFER }) + .catch((e) => { + log.error(dump(e)); + throw e; + }); + + log.verbose( + `exec response from host [${name}] for command [${command}]:\n${dump(execResponse)}` + ); return { stdout: execResponse.stdout, @@ -125,11 +136,11 @@ export const createMultipassHostVmClient = ( log.verbose(`multipass stop response:\n`, response); }; - const transfer: HostVm['transfer'] = async (localFilePath, destFilePath) => { + const upload: HostVm['upload'] = async (localFilePath, destFilePath) => { const response = await execa.command( `multipass transfer ${localFilePath} ${name}:${destFilePath}` ); - log.verbose(`Transferred file to VM [${name}]:`, response); + log.verbose(`Uploaded file to VM [${name}]:`, response); return { filePath: destFilePath, @@ -139,6 +150,27 @@ export const createMultipassHostVmClient = ( }; }; + const download: HostVm['download'] = async (vmFilePath: string, localFilePath: string) => { + const localFileAbsolutePath = path.resolve(localFilePath); + const response = await execa.command( + `multipass transfer ${name}:${vmFilePath} ${localFilePath}` + ); + log.verbose(`Downloaded file from VM [${name}]:`, response); + + return { + filePath: localFileAbsolutePath, + delete: async () => { + return deleteFile(localFileAbsolutePath).then(() => { + return { + stdout: 'success', + stderr: '', + exitCode: 0, + }; + }); + }, + }; + }; + return { type: 'multipass', name, @@ -147,7 +179,9 @@ export const createMultipassHostVmClient = ( info, mount, unmount, - transfer, + transfer: upload, + upload, + download, start, stop, }; @@ -217,7 +251,7 @@ const createVagrantVm = async ({ }: CreateVagrantVmOptions): Promise => { log.debug(`Using Vagrantfile: ${vagrantFile}`); - const VAGRANT_CWD = dirname(vagrantFile); + const VAGRANT_CWD = path.dirname(vagrantFile); // Destroy the VM running (if any) with the provided vagrant file before re-creating it try { @@ -273,18 +307,24 @@ export const createVagrantHostVmClient = ( vagrantFile: string = DEFAULT_VAGRANTFILE, log: ToolingLog = createToolingLogger() ): HostVm => { - const VAGRANT_CWD = dirname(vagrantFile); + const VAGRANT_CWD = path.dirname(vagrantFile); const execaOptions: execa.Options = { env: { VAGRANT_CWD, }, stdio: ['inherit', 'pipe', 'pipe'], + maxBuffer: MAX_BUFFER, }; log.debug(`Creating Vagrant VM client for [${name}] with vagrantfile [${vagrantFile}]`); const exec = async (command: string): Promise => { - const execResponse = await execa.command(`vagrant ssh -- ${command}`, execaOptions); + const execResponse = await execa + .command(`vagrant ssh -- ${command}`, execaOptions) + .catch((e) => { + log.error(dump(e)); + throw e; + }); log.verbose(execResponse); @@ -328,12 +368,12 @@ export const createVagrantHostVmClient = ( log.verbose('vagrant suspend response:\n', response); }; - const transfer: HostVm['transfer'] = async (localFilePath, destFilePath) => { + const upload: HostVm['upload'] = async (localFilePath, destFilePath) => { const response = await execa.command( `vagrant upload ${localFilePath} ${destFilePath}`, execaOptions ); - log.verbose(`Transferred file to VM [${name}]:`, response); + log.verbose(`Uploaded file to VM [${name}]:`, response); return { filePath: destFilePath, @@ -343,6 +383,34 @@ export const createVagrantHostVmClient = ( }; }; + const download: HostVm['download'] = async (vmFilePath, localFilePath) => { + const localFileAbsolutePath = path.resolve(localFilePath); + + // Vagrant will auto-mount the directory that includes the Vagrant file to the VM under `/vagrant`, + // and it keeps that sync'd to the local system. So we first copy the file in the VM there so we + // can retrieve it from the local machine + await exec(`cp ${vmFilePath} /vagrant`).catch((e) => { + log.error(`Error while attempting to copy file on VM:\n${dump(e)}`); + throw e; + }); + + // Now move the file from the local vagrant directory to the desired location + await execa.command(`mv ${VAGRANT_CWD}/${path.basename(vmFilePath)} ${localFileAbsolutePath}`); + + return { + filePath: localFileAbsolutePath, + delete: async () => { + return deleteFile(localFileAbsolutePath).then(() => { + return { + stdout: 'success', + stderr: '', + exitCode: 0, + }; + }); + }, + }; + }; + return { type: 'vagrant', name, @@ -351,7 +419,9 @@ export const createVagrantHostVmClient = ( info, mount, unmount, - transfer, + transfer: upload, + upload, + download, start, stop, }; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts index 0f93e4fceb10c..77c01622a5df4 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts @@ -32,7 +32,7 @@ import { createKbnClient } from '../endpoint/common/stack_services'; import type { StartedFleetServer } from '../endpoint/common/fleet_server/fleet_server_services'; import { startFleetServer } from '../endpoint/common/fleet_server/fleet_server_services'; import { renderSummaryTable } from './print_run'; -import { parseTestFileConfig, retrieveIntegrations } from './utils'; +import { parseTestFileConfig, retrieveIntegrations, setDefaultToolingLoggingLevel } from './utils'; import { getFTRConfig } from './get_ftr_config'; export const cli = () => { @@ -70,9 +70,9 @@ ${JSON.stringify(argv, null, 2)} const cypressConfigFilePath = require.resolve(`../../${argv.configFile}`) as string; const cypressConfigFile = await import(cypressConfigFilePath); - if (cypressConfigFile.env?.TOOLING_LOG_LEVEL) { - createToolingLogger.defaultLogLevel = cypressConfigFile.env.TOOLING_LOG_LEVEL; - } + // Adjust tooling log level based on the `TOOLING_LOG_LEVEL` property, which can be + // defined in the cypress config file or set in the `env` + setDefaultToolingLoggingLevel(cypressConfigFile?.env?.TOOLING_LOG_LEVEL); const log = prefixedOutputLogger('cy.parallel()', createToolingLogger()); diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 0b426cf1e8c20..7a68b596ea5d1 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -28,7 +28,12 @@ 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 { getOnBeforeHook, parseTestFileConfig, retrieveIntegrations } from './utils'; +import { + getOnBeforeHook, + parseTestFileConfig, + retrieveIntegrations, + setDefaultToolingLoggingLevel, +} from './utils'; import { prefixedOutputLogger } from '../endpoint/common/utils'; import type { ProductType, Credentials, ProjectHandler } from './project_handler/project_handler'; @@ -363,9 +368,8 @@ ${JSON.stringify(argv, null, 2)} cypressConfigFile.env.grepTags = '@serverlessQA --@skipInServerless --@skipInServerlessMKI'; } - if (cypressConfigFile.env?.TOOLING_LOG_LEVEL) { - createToolingLogger.defaultLogLevel = cypressConfigFile.env.TOOLING_LOG_LEVEL; - } + setDefaultToolingLoggingLevel(cypressConfigFile?.env?.TOOLING_LOG_LEVEL); + // eslint-disable-next-line require-atomic-updates log = prefixedOutputLogger('cy.parallel(svl)', createToolingLogger()); diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts index ba1974565e10c..6e292eff9a384 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts @@ -12,6 +12,8 @@ import generate from '@babel/generator'; import type { ExpressionStatement, ObjectExpression, ObjectProperty } from '@babel/types'; import { schema, type TypeOf } from '@kbn/config-schema'; import chalk from 'chalk'; +import type { ToolingLogTextWriterConfig } from '@kbn/tooling-log'; +import { createToolingLogger } from '../../common/endpoint/data_loaders/utils'; /** * Retrieve test files using a glob pattern. @@ -156,3 +158,22 @@ export const getOnBeforeHook = (module: unknown, beforeSpecFilePath: string): Fu return module.onBeforeHook; }; + +/** + * Sets the default log level for `ToolingLog` instances created by `createToolingLogger()`: + * `x-pack/plugins/security_solution/common/endpoint/data_loaders/utils.ts:148`. + * It will first check the NodeJs `process.env` to see if an Environment Variable was set + * and then, if provided, it will use the value defined in the Cypress Config. file. + */ +export const setDefaultToolingLoggingLevel = (defaultFallbackLoggingLevel?: string) => { + const logLevel = + process.env.TOOLING_LOG_LEVEL || + process.env.CYPRESS_TOOLING_LOG_LEVEL || + defaultFallbackLoggingLevel || + ''; + + if (logLevel) { + createToolingLogger('info').info(`Setting tooling log level to [${logLevel}]`); + createToolingLogger.defaultLogLevel = logLevel as ToolingLogTextWriterConfig['level']; + } +};