[8.0] [Osquery] Add Osquery cypress tests to buildkite (#115902) (#120443)

* [Osquery] Add Osquery cypress tests to buildkite (#115902)

* hardcode agent version

Co-authored-by: Patryk Kopyciński <patryk.kopycinski@elastic.co>
Co-authored-by: Patryk Kopycinski <contact@patrykkopycinski.com>
This commit is contained in:
Kibana Machine 2021-12-14 10:15:55 -05:00 committed by GitHub
parent e203bf7520
commit 124f057fc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1048 additions and 557 deletions

View file

@ -0,0 +1,11 @@
steps:
- command: .buildkite/scripts/steps/functional/osquery_cypress.sh
label: 'Osquery Cypress Tests'
agents:
queue: ci-group-6
depends_on: build
timeout_in_minutes: 120
retry:
automatic:
- exit_status: '*'
limit: 1

View file

@ -86,6 +86,16 @@ const uploadPipeline = (pipelineContent) => {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/fleet_cypress.yml'));
}
if (
(await doAnyChangesMatch([
/^x-pack\/plugins\/osquery/,
/^x-pack\/test\/osquery_cypress/,
])) ||
process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites')
) {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/osquery_cypress.yml'));
}
if (await doAnyChangesMatch([/^x-pack\/plugins\/uptime/])) {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/uptime.yml'));
}

View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
source .buildkite/scripts/common/util.sh
.buildkite/scripts/bootstrap.sh
.buildkite/scripts/download_build_artifacts.sh
export JOB=kibana-osquery-cypress
echo "--- Osquery Cypress tests"
cd "$XPACK_DIR"
checks-reporter-with-killswitch "Osquery Cypress Tests" \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$KIBANA_BUILD_LOCATION" \
--config test/osquery_cypress/cli_config.ts

View file

@ -83,7 +83,7 @@
"**/hoist-non-react-statics": "^3.3.2",
"**/html-minifier/uglify-js": "^3.14.3",
"**/isomorphic-fetch/node-fetch": "^2.6.1",
"**/istanbul-instrumenter-loader/schema-utils": "1.0.0",
"**/istanbul-lib-coverage": "^3.2.0",
"**/json-schema": "^0.4.0",
"**/minimist": "^1.2.5",
"**/node-jose/node-forge": "^0.10.0",
@ -433,6 +433,7 @@
"@babel/types": "^7.16.0",
"@bazel/ibazel": "^0.15.10",
"@bazel/typescript": "^3.8.0",
"@cypress/code-coverage": "^3.9.11",
"@cypress/snapshot": "^2.1.7",
"@cypress/webpack-preprocessor": "^5.6.0",
"@elastic/eslint-config-kibana": "link:bazel-bin/packages/elastic-eslint-config-kibana",
@ -696,7 +697,9 @@
"cypress-file-upload": "^5.0.8",
"cypress-multi-reporters": "^1.5.0",
"cypress-pipe": "^2.0.0",
"cypress-react-selector": "^2.3.13",
"cypress-real-events": "^1.5.1",
"cypress-recurse": "^1.13.1",
"debug": "^2.6.9",
"delete-empty": "^2.0.0",
"dependency-check": "^4.1.0",
@ -751,7 +754,6 @@
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-path-inside": "^3.0.2",
"istanbul-instrumenter-loader": "^3.0.1",
"jest": "^26.6.3",
"jest-canvas-mock": "^2.3.1",
"jest-circus": "^26.6.3",
@ -788,7 +790,7 @@
"ncp": "^2.0.0",
"node-sass": "^6.0.1",
"null-loader": "^3.0.0",
"nyc": "^15.0.1",
"nyc": "^15.1.0",
"oboe": "^2.1.4",
"parse-link-header": "^1.0.1",
"pbf": "3.2.1",

View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
source test/scripts/jenkins_test_setup_xpack.sh
echo " -> Running osquery cypress tests"
cd "$XPACK_DIR"
checks-reporter-with-killswitch "Osquery Cypress Tests" \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$KIBANA_INSTALL_DIR" \
--config test/osquery_cypress/cli_config.ts
echo ""
echo ""

View file

@ -173,6 +173,14 @@ def functionalXpack(Map params = [:]) {
}
}
whenChanged([
'x-pack/plugins/osquery/',
]) {
if (githubPr.isPr()) {
task(kibanaPipeline.functionalTestProcess('xpack-osqueryCypress', './test/scripts/jenkins_osquery_cypress.sh'))
}
}
}
}

View file

@ -0,0 +1,4 @@
{
"excludeAfterRemap": true,
"include": ["public"]
}

View file

@ -1,14 +1,16 @@
{
"baseUrl": "http://localhost:5620",
"defaultCommandTimeout": 60000,
"execTimeout": 120000,
"pageLoadTimeout": 120000,
"nodeVersion": "system",
"defaultCommandTimeout": 6000,
"execTimeout": 12000,
"pageLoadTimeout": 12000,
"retries": {
"runMode": 2
},
"screenshotsFolder": "../../../target/kibana-osquery/cypress/screenshots",
"trashAssetsBeforeRuns": false,
"video": false,
"videosFolder": "../../../target/kibana-osquery/cypress/videos",
"viewportHeight": 900,
"viewportWidth": 1440
}
"viewportWidth": 1440,
"experimentalStudio": true
}

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FLEET_AGENT_POLICIES, navigateTo } from '../tasks/navigation';
import { addIntegration } from '../tasks/integrations';
import { checkResults, inputQuery, selectAllAgents, submitQuery } from '../tasks/live_query';
import { login } from '../tasks/login';
describe('Add Integration', () => {
const integration = 'Osquery Manager';
before(() => {
login();
});
it.skip('should open Osquery app', () => {
cy.visit('/app/osquery/live_queries');
cy.wait(3000);
cy.contains('Live queries history', { timeout: 60000 });
cy.contains('New live query').click();
cy.wait(3000);
cy.contains('Saved queries').click();
cy.wait(3000);
cy.contains('Saved queries', { timeout: 60000 });
cy.contains('Add saved query').click();
cy.wait(3000);
cy.contains('Packs').click();
cy.wait(3000);
cy.contains('Packs', { timeout: 60000 });
cy.contains('Add pack').click();
cy.wait(3000);
});
it('should display Osquery integration in the Policies list once installed ', () => {
addAndVerifyIntegration();
});
it.skip('should run live query', () => {
navigateTo('/app/osquery/live_queries/new');
cy.wait(1000);
selectAllAgents();
inputQuery();
submitQuery();
checkResults();
});
function addAndVerifyIntegration() {
navigateTo(FLEET_AGENT_POLICIES);
cy.contains('Default Fleet Server policy').click();
cy.contains('Add integration').click();
cy.contains(integration).click();
addIntegration();
cy.contains('osquery_manager-');
}
});

View file

@ -1,35 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { HEADER } from '../screens/osquery';
import { OSQUERY_NAVIGATION_LINK } from '../screens/navigation';
import { OSQUERY, NEW_LIVE_QUERY, openNavigationFlyout, navigateTo } from '../tasks/navigation';
import { addIntegration } from '../tasks/integrations';
import { checkResults, inputQuery, selectAllAgents, submitQuery } from '../tasks/live_query';
describe('Osquery Manager', () => {
before(() => addIntegration(Cypress.env('OSQUERY_POLICY')));
it('Runs live queries', () => {
navigateTo(NEW_LIVE_QUERY);
selectAllAgents();
inputQuery();
submitQuery();
checkResults();
});
it('Displays Osquery on the navigation flyout once installed ', () => {
openNavigationFlyout();
cy.get(OSQUERY_NAVIGATION_LINK).should('exist');
});
it('Displays Live queries history title when navigating to Osquery', () => {
navigateTo(OSQUERY);
cy.get(HEADER).should('have.text', 'Live queries history');
});
});

View file

@ -4,8 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/// <reference types="cypress" />
// / <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
@ -22,8 +21,11 @@
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
module.exports = (_on, _config) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
module.exports = (on: any, config: any) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
require('@cypress/code-coverage/task')(on, config);
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
return config;
};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const ADD_AGENT_BUTTON = 'addAgentButton';
export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab';
export const ENROLLMENT_TOKENS_TAB = 'fleet-enrollment-tokens-tab';

View file

@ -5,7 +5,22 @@
* 2.0.
*/
export const ADD_POLICY_BTN = '[data-test-subj="addIntegrationPolicyButton"]';
export const CREATE_PACKAGE_POLICY_SAVE_BTN = '[data-test-subj="createPackagePolicySaveButton"]';
export const ADD_POLICY_BTN = 'addIntegrationPolicyButton';
export const CREATE_PACKAGE_POLICY_SAVE_BTN = 'createPackagePolicySaveButton';
export const DATA_COLLECTION_SETUP_STEP = 'dataCollectionSetupStep';
export const INTEGRATIONS_CARD = '.euiCard__titleAnchor';
export const INTEGRATION_NAME_LINK = 'integrationNameLink';
export const CONFIRM_MODAL_BTN = 'confirmModalConfirmButton';
export const CONFIRM_MODAL_BTN_SEL = `[data-test-subj=${CONFIRM_MODAL_BTN}]`;
export const SETTINGS_TAB = 'tab-settings';
export const POLICIES_TAB = 'tab-policies';
export const UPDATE_PACKAGE_BTN = 'updatePackageBtn';
export const LATEST_VERSION = 'latestVersion';
export const PACKAGE_VERSION = 'packageVersionText';
export const SAVE_PACKAGE_CONFIRM = '[data-test-subj=confirmModalConfirmButton]';

View file

@ -0,0 +1,271 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable */
// / <reference types="cypress" />
// @ts-check
const dayjs = require('dayjs');
const duration = require('dayjs/plugin/duration');
// const { filterSpecsFromCoverage } = require('./support-utils');
dayjs.extend(duration);
/**
* Sends collected code coverage object to the backend code
* via "cy.task".
*/
const sendCoverage = (coverage: any, pathname = '/') => {
logMessage(`Saving code coverage for **${pathname}**`);
// const withoutSpecs = filterSpecsFromCoverage(coverage);
const appCoverageOnly = filterSupportFilesFromCoverage(coverage);
// stringify coverage object for speed
cy.task('combineCoverage', JSON.stringify(appCoverageOnly), {
log: false,
});
};
/**
* Consistently logs the given string to the Command Log
* so the user knows the log message is coming from this plugin.
* @param {string} s Message to log.
*/
const logMessage = (s: string) => {
cy.log(`${s} \`[@cypress/code-coverage]\``);
};
/**
* Removes support file from the coverage object.
* If there are more files loaded from support folder, also removes them
*/
const filterSupportFilesFromCoverage = (totalCoverage: any) => {
const integrationFolder = Cypress.config('integrationFolder');
const supportFile = Cypress.config('supportFile');
/** @type {string} Cypress run-time config has the support folder string */
// @ts-ignore
const supportFolder = Cypress.config('supportFolder');
const isSupportFile = (filename: string) => filename === supportFile;
let coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) =>
isSupportFile(filename)
);
// check the edge case
// if we have files from support folder AND the support folder is not same
// as the integration, or its prefix (this might remove all app source files)
// then remove all files from the support folder
if (!integrationFolder.startsWith(supportFolder)) {
// remove all covered files from support folder
coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) =>
filename.startsWith(supportFolder)
);
}
return coverage;
};
const registerHooks = () => {
let windowCoverageObjects: any[];
const hasE2ECoverage = () => Boolean(windowCoverageObjects.length);
// @ts-ignore
const hasUnitTestCoverage = () => Boolean(window.__coverage__);
before(() => {
// we need to reset the coverage when running
// in the interactive mode, otherwise the counters will
// keep increasing every time we rerun the tests
const logInstance = Cypress.log({
name: 'Coverage',
message: ['Reset [@cypress/code-coverage]'],
});
cy.task(
'resetCoverage',
{
// @ts-ignore
isInteractive: Cypress.config('isInteractive'),
},
{ log: false }
).then(() => {
logInstance.end();
});
});
beforeEach(() => {
// each object will have the coverage and url pathname
// to let the user know the coverage has been collected
windowCoverageObjects = [];
const saveCoverageObject = (win: any) => {
console.log('wwwww', win, win.windows?.__coverage__, win.__coverage__);
// if application code has been instrumented, the app iframe "window" has an object
const applicationSourceCoverage = win.__coverage__;
if (!applicationSourceCoverage) {
return;
}
if (
Cypress._.find(windowCoverageObjects, {
coverage: applicationSourceCoverage,
})
) {
// this application code coverage object is already known
// which can happen when combining `window:load` and `before` callbacks
return;
}
windowCoverageObjects.push({
coverage: applicationSourceCoverage,
pathname: win.location.pathname,
});
};
// save reference to coverage for each app window loaded in the test
cy.on('window:load', saveCoverageObject);
// save reference if visiting a page inside a before() hook
cy.window({ log: false }).then(saveCoverageObject);
});
afterEach(() => {
// save coverage after the test
// because now the window coverage objects have been updated
windowCoverageObjects.forEach((cover) => {
sendCoverage(cover.coverage, cover.pathname);
});
if (!hasE2ECoverage()) {
if (hasUnitTestCoverage()) {
logMessage(`👉 Only found unit test code coverage.`);
} else {
const expectBackendCoverageOnly = Cypress._.get(
Cypress.env('codeCoverage'),
'expectBackendCoverageOnly',
false
);
if (!expectBackendCoverageOnly) {
logMessage(`
Could not find any coverage information in your application
by looking at the window coverage object.
Did you forget to instrument your application?
See [code-coverage#instrument-your-application](https://github.com/cypress-io/code-coverage#instrument-your-application)
`);
}
}
}
});
after(() => {
// I wish I could fail the tests if there is no code coverage information
// but throwing an error here does not fail the test run due to
// https://github.com/cypress-io/cypress/issues/2296
// there might be server-side code coverage information
// we should grab it once after all tests finish
// @ts-ignore
const baseUrl = Cypress.config('baseUrl') || cy.state('window').origin;
// @ts-ignore
const runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl');
const specType = Cypress._.get(Cypress.spec, 'specType', 'integration');
const isIntegrationSpec = specType === 'integration';
if (runningEndToEndTests && isIntegrationSpec) {
// we can only request server-side code coverage
// if we are running end-to-end tests,
// otherwise where do we send the request?
const url = Cypress._.get(Cypress.env('codeCoverage'), 'url', '/__coverage__');
cy.request({
url,
log: false,
failOnStatusCode: false,
})
.then((r) => Cypress._.get(r, 'body.coverage', null))
.then((coverage) => {
if (!coverage) {
// we did not get code coverage - this is the
// original failed request
const expectBackendCoverageOnly = Cypress._.get(
Cypress.env('codeCoverage'),
'expectBackendCoverageOnly',
false
);
if (expectBackendCoverageOnly) {
throw new Error(`Expected to collect backend code coverage from ${url}`);
} else {
// we did not really expect to collect the backend code coverage
return;
}
}
sendCoverage(coverage, 'backend');
});
}
});
after(() => {
// collect and merge frontend coverage
// if spec bundle has been instrumented (using Cypress preprocessor)
// then we will have unit test coverage
// NOTE: spec iframe is NOT reset between the tests, so we can grab
// the coverage information only once after all tests have finished
// @ts-ignore
const unitTestCoverage = window.__coverage__;
if (unitTestCoverage) {
sendCoverage(unitTestCoverage, 'unit');
}
});
after(() => {
// when all tests finish, lets generate the coverage report
const logInstance = Cypress.log({
name: 'Coverage',
message: ['Generating report [@cypress/code-coverage]'],
});
cy.task('coverageReport', null, {
timeout: dayjs.duration(3, 'minutes').asMilliseconds(),
log: false,
}).then((coverageReportFolder) => {
logInstance.set('consoleProps', () => ({
'coverage report folder': coverageReportFolder,
}));
logInstance.end();
return coverageReportFolder;
});
});
};
// to disable code coverage commands and save time
// pass environment variable coverage=false
// cypress run --env coverage=false
// or
// CYPRESS_coverage=false cypress run
// see https://on.cypress.io/environment-variables
// to avoid "coverage" env variable being case-sensitive, convert to lowercase
const cyEnvs = Cypress._.mapKeys(Cypress.env(), (value, key) => key.toLowerCase());
if (cyEnvs.coverage === false) {
console.log('Skipping code coverage hooks');
} else if (Cypress.env('codeCoverageTasksRegistered') !== true) {
// register a hook just to log a message
before(() => {
logMessage(`
Code coverage tasks were not registered by the plugins file.
See [support issue](https://github.com/cypress-io/code-coverage/issues/179)
for possible workarounds.
`);
});
} else {
registerHooks();
}

View file

@ -5,6 +5,8 @@
* 2.0.
*/
// / <reference types="cypress" />
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
@ -22,6 +24,23 @@
// Import commands.js using ES2015 syntax:
import './commands';
// import './coverage';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
getBySel: typeof cy.get;
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getBySel(selector: string, ...args: any[]) {
return cy.get(`[data-test-subj=${selector}]`, ...args);
}
Cypress.Commands.add('getBySel', getBySel);
// Alternatively you can use CommonJS syntax:
// require('./commands')

View file

@ -5,18 +5,54 @@
* 2.0.
*/
import { CREATE_PACKAGE_POLICY_SAVE_BTN, SAVE_PACKAGE_CONFIRM } from '../screens/integrations';
import {
ADD_POLICY_BTN,
CONFIRM_MODAL_BTN,
CONFIRM_MODAL_BTN_SEL,
CREATE_PACKAGE_POLICY_SAVE_BTN,
DATA_COLLECTION_SETUP_STEP,
} from '../screens/integrations';
import { navigateTo, OSQUERY_INTEGRATION_PAGE } from './navigation';
export const addIntegration = () => {
cy.getBySel(ADD_POLICY_BTN).click();
cy.getBySel(DATA_COLLECTION_SETUP_STEP).find('.euiLoadingSpinner').should('not.exist');
cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).click();
// sometimes agent is assigned to default policy, sometimes not
closeModalIfVisible();
// TODO: allow adding integration version strings to this
export const addIntegration = (policyId: string) => {
navigateTo(OSQUERY_INTEGRATION_PAGE, { qs: { policyId } });
cy.get(CREATE_PACKAGE_POLICY_SAVE_BTN).click();
cy.get(SAVE_PACKAGE_CONFIRM).click();
// XXX: there is a race condition between the test going to the ui powered by the agent, and the agent having the integration ready to go
// so we wait.
// TODO: actually make this wait til the agent has been updated with the proper integration
cy.wait(5000);
return cy.reload();
cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN, { timeout: 60000 }).should('not.exist');
};
function closeModalIfVisible() {
cy.get('body').then(($body) => {
if ($body.find(CONFIRM_MODAL_BTN_SEL).length) {
cy.getBySel(CONFIRM_MODAL_BTN).click();
}
});
}
export const deleteIntegrations = async (integrationName: string) => {
const ids: string[] = [];
cy.contains(integrationName)
.each(($a) => {
const href = $a.attr('href') as string;
ids.push(href.substr(href.lastIndexOf('/') + 1));
})
.then(() => {
cy.request({
url: `/api/fleet/package_policies/delete`,
headers: { 'kbn-xsrf': 'cypress' },
body: `{ "packagePolicyIds": ${JSON.stringify(ids)} }`,
method: 'POST',
});
});
};
export const installPackageWithVersion = (integration: string, version: string) => {
cy.request({
url: `/api/fleet/epm/packages/${integration}-${version}`,
headers: { 'kbn-xsrf': 'cypress' },
body: '{ "force": true }',
method: 'POST',
});
};

View file

@ -5,12 +5,7 @@
* 2.0.
*/
import {
AGENT_FIELD,
ALL_AGENTS_OPTION,
LIVE_QUERY_EDITOR,
SUBMIT_BUTTON,
} from '../screens/live_query';
import { AGENT_FIELD, ALL_AGENTS_OPTION, LIVE_QUERY_EDITOR } from '../screens/live_query';
export const selectAllAgents = () => {
cy.get(AGENT_FIELD).first().click();
@ -19,7 +14,7 @@ export const selectAllAgents = () => {
export const inputQuery = () => cy.get(LIVE_QUERY_EDITOR).type('select * from processes;');
export const submitQuery = () => cy.get(SUBMIT_BUTTON).contains('Submit').click();
export const submitQuery = () => cy.contains('Submit').click();
export const checkResults = () =>
cy.get('[data-test-subj="dataGridRowCell"]').should('have.lengthOf.above', 0);
cy.get('[data-test-subj="dataGridRowCell"]', { timeout: 60000 }).should('have.lengthOf.above', 0);

View file

@ -0,0 +1,295 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as yaml from 'js-yaml';
import Url, { UrlObject } from 'url';
import { ROLES } from '../test';
/**
* Credentials in the `kibana.dev.yml` config file will be used to authenticate
* with Kibana when credentials are not provided via environment variables
*/
const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml';
/**
* The configuration path in `kibana.dev.yml` to the username to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username';
/**
* The configuration path in `kibana.dev.yml` to the password to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password';
/**
* The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the
* username to be used when authenticating with Kibana
*/
const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME';
/**
* The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the
* username to be used when authenticating with Kibana
*/
const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD';
/**
* The Kibana server endpoint used for authentication
*/
const LOGIN_API_ENDPOINT = '/internal/security/login';
/**
* cy.visit will default to the baseUrl which uses the default kibana test user
* This function will override that functionality in cy.visit by building the baseUrl
* directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts
*
* @param role string role/user to log in with
* @param route string route to visit
*/
export const getUrlWithRoute = (role: ROLES, route: string) => {
const url = Cypress.config().baseUrl;
const kibana = new URL(String(url));
const theUrl = `${Url.format({
auth: `${role}:changeme`,
username: role,
password: 'changeme',
protocol: kibana.protocol.replace(':', ''),
hostname: kibana.hostname,
port: kibana.port,
} as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`;
cy.log(`origin: ${theUrl}`);
return theUrl;
};
interface User {
username: string;
password: string;
}
/**
* Builds a URL with basic auth using the passed in user.
*
* @param user the user information to build the basic auth with
* @param route string route to visit
*/
export const constructUrlWithUser = (user: User, route: string) => {
const url = Cypress.config().baseUrl;
const kibana = new URL(String(url));
const hostname = kibana.hostname;
const username = user.username;
const password = user.password;
const protocol = kibana.protocol.replace(':', '');
const port = kibana.port;
const path = `${route.startsWith('/') ? '' : '/'}${route}`;
const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`;
const builtUrl = new URL(strUrl);
cy.log(`origin: ${builtUrl.href}`);
return builtUrl.href;
};
export const getCurlScriptEnvVars = () => ({
ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'),
ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'),
ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'),
KIBANA_URL: Cypress.config().baseUrl,
});
export const postRoleAndUser = (role: ROLES) => {
const env = getCurlScriptEnvVars();
const detectionsRoleScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`;
const detectionsRoleJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`;
const detectionsUserScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_user.sh`;
const detectionsUserJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_user.json`;
// post the role
cy.exec(`bash ${detectionsRoleScriptPath} ${detectionsRoleJsonPath}`, {
env,
});
// post the user associated with the role to elasticsearch
cy.exec(`bash ${detectionsUserScriptPath} ${detectionsUserJsonPath}`, {
env,
});
};
export const deleteRoleAndUser = (role: ROLES) => {
const env = getCurlScriptEnvVars();
const detectionsUserDeleteScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`;
// delete the role
cy.exec(`bash ${detectionsUserDeleteScriptPath}`, {
env,
});
};
export const loginWithUser = (user: User) => {
const url = Cypress.config().baseUrl;
cy.request({
body: {
providerType: 'basic',
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
currentURL: '/',
params: {
username: user.username,
password: user.password,
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: constructUrlWithUser(user, LOGIN_API_ENDPOINT),
});
};
export const loginWithRole = async (role: ROLES) => {
postRoleAndUser(role);
const theUrl = Url.format({
auth: `${role}:changeme`,
username: role,
password: 'changeme',
protocol: Cypress.env('protocol'),
hostname: Cypress.env('hostname'),
port: Cypress.env('configport'),
} as UrlObject);
cy.log(`origin: ${theUrl}`);
cy.request({
body: {
providerType: 'basic',
providerName: 'basic',
currentURL: '/',
params: {
username: role,
password: 'changeme',
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: getUrlWithRoute(role, LOGIN_API_ENDPOINT),
});
};
/**
* Authenticates with Kibana using, if specified, credentials specified by
* environment variables. The credentials in `kibana.dev.yml` will be used
* for authentication when the environment variables are unset.
*
* To speed the execution of tests, prefer this non-interactive authentication,
* which is faster than authentication via Kibana's interactive login page.
*/
export const login = (role?: ROLES) => {
if (role != null) {
loginWithRole(role);
} else if (credentialsProvidedByEnvironment()) {
loginViaEnvironmentCredentials();
} else {
loginViaConfig();
}
};
/**
* Returns `true` if the credentials used to login to Kibana are provided
* via environment variables
*/
const credentialsProvidedByEnvironment = (): boolean =>
Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null;
/**
* Authenticates with Kibana by reading credentials from the
* `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD`
* environment variables, and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaEnvironmentCredentials = () => {
const url = Cypress.config().baseUrl;
cy.log(
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
);
// programmatically authenticate without interacting with the Kibana login page
cy.request({
body: {
providerType: 'basic',
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
currentURL: '/',
params: {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-env' },
method: 'POST',
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
});
};
/**
* Authenticates with Kibana by reading credentials from the
* `kibana.dev.yml` file and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaConfig = () => {
cy.log(
`Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\``
);
// read the login details from `kibana.dev.yaml`
cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => {
const config = yaml.safeLoad(kibanaDevYml);
// programmatically authenticate without interacting with the Kibana login page
cy.request({
body: {
providerType: 'basic',
providerName: 'basic',
currentURL: '/',
params: {
username: 'elastic',
password: config.elasticsearch.password,
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
});
});
};
/**
* Get the configured auth details that were used to spawn cypress
*
* @returns the default Elasticsearch username and password for this environment
*/
export const getEnvAuth = (): User => {
if (credentialsProvidedByEnvironment()) {
return {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
};
} else {
let user: User = { username: '', password: '' };
cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => {
const config = yaml.safeLoad(devYml);
user = { username: config.elasticsearch.username, password: config.elasticsearch.password };
});
return user;
}
};
/**
* Authenticates with Kibana, visits the specified `url`, and waits for the
* Kibana global nav to be displayed before continuing
*/
export const loginAndWaitForPage = (url: string) => {
login();
cy.visit(url);
};

View file

@ -7,7 +7,10 @@
import { TOGGLE_NAVIGATION_BTN } from '../screens/navigation';
export const OSQUERY = 'app/osquery/live_queries';
export const INTEGRATIONS = 'app/integrations#/';
export const FLEET = 'app/fleet/';
export const FLEET_AGENT_POLICIES = 'app/fleet/policies';
export const OSQUERY = 'app/osquery';
export const NEW_LIVE_QUERY = 'app/osquery/live_queries/new';
export const OSQUERY_INTEGRATION_PAGE = '/app/fleet/integrations/osquery_manager/add-integration';
export const navigateTo = (page: string, opts?: Partial<Cypress.VisitOptions>) => {

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// For the source of these roles please consult the PR these were introduced https://github.com/elastic/kibana/pull/81866#issue-511165754
export enum ROLES {
soc_manager = 'soc_manager',
reader = 'reader',
t1_analyst = 't1_analyst',
t2_analyst = 't2_analyst',
hunter = 'hunter',
rule_author = 'rule_author',
platform_engineer = 'platform_engineer',
detections_admin = 'detections_admin',
}

View file

@ -1,13 +1,14 @@
{
"author": "Elastic",
"name": "osquery",
"version": "8.0.0",
"private": true,
"license": "Elastic-License",
"scripts": {
"cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress/cypress.json",
"cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/osquery_cypress/visual_config.ts",
"cypress:run": "../../../node_modules/.bin/cypress run --config-file ./cypress/cypress.json",
"cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/osquery_cypress/cli_config.ts"
}
"author": "Elastic",
"name": "osquery",
"version": "8.0.0",
"private": true,
"license": "Elastic-License",
"scripts": {
"cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress/cypress.json",
"cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/osquery_cypress/visual_config.ts",
"cypress:run": "../../../node_modules/.bin/cypress run --config-file ./cypress/cypress.json",
"cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/osquery_cypress/cli_config.ts",
"nyc": "../../../node_modules/.bin/nyc report --reporter=text-summary"
}
}

View file

@ -7,10 +7,8 @@
import { ToolingLog } from '@kbn/dev-utils';
import axios, { AxiosRequestConfig } from 'axios';
import { copyFile } from 'fs/promises';
import { ChildProcess, execFileSync, spawn } from 'child_process';
import { resolve } from 'path';
import { unlinkSync } from 'fs';
import { ChildProcess, spawn } from 'child_process';
import { getLatestVersion } from './artifact_manager';
import { Manager } from './resource_manager';
interface AgentManagerParams {
@ -21,15 +19,12 @@ interface AgentManagerParams {
}
export class AgentManager extends Manager {
private directoryPath: string;
private params: AgentManagerParams;
private log: ToolingLog;
private agentProcess?: ChildProcess;
private requestOptions: AxiosRequestConfig;
constructor(directoryPath: string, params: AgentManagerParams, log: ToolingLog) {
constructor(params: AgentManagerParams, log: ToolingLog) {
super();
// TODO: check if the file exists
this.directoryPath = directoryPath;
this.log = log;
this.params = params;
this.requestOptions = {
@ -43,27 +38,16 @@ export class AgentManager extends Manager {
};
}
public getBinaryPath() {
return resolve(this.directoryPath, 'elastic-agent');
}
public async setup() {
this.log.info('Running agent preconfig');
await axios.post(`${this.params.kibanaUrl}/api/fleet/agents/setup`, {}, this.requestOptions);
this.log.info('Updating the default agent output');
const {
data: {
items: [defaultOutput],
},
} = await axios.get(this.params.kibanaUrl + '/api/fleet/outputs', this.requestOptions);
await axios.put(
`${this.params.kibanaUrl}/api/fleet/outputs/${defaultOutput.id}`,
{ hosts: [this.params.esHost] },
return await axios.post(
`${this.params.kibanaUrl}/api/fleet/agents/setup`,
{},
this.requestOptions
);
}
public async startAgent() {
this.log.info('Getting agent enrollment key');
const { data: apiKeys } = await axios.get(
this.params.kibanaUrl + '/api/fleet/enrollment_api_keys',
@ -71,25 +55,28 @@ export class AgentManager extends Manager {
);
const policy = apiKeys.items[1];
this.log.info('Enrolling the agent');
const args = [
'enroll',
'--insecure',
'-f',
// TODO: parse the host/port out of the logs for the fleet server
'--url=http://localhost:8220',
`--enrollment-token=${policy.api_key}`,
];
const agentBinPath = this.getBinaryPath();
execFileSync(agentBinPath, args, { stdio: 'inherit' });
// Copy the config file
const configPath = resolve(__dirname, this.directoryPath, 'elastic-agent.yml');
this.log.info(`Copying agent config from ${configPath}`);
await copyFile(configPath, resolve('.', 'elastic-agent.yml'));
this.log.info('Running the agent');
this.agentProcess = spawn(agentBinPath, ['run', '-v'], { stdio: 'inherit' });
const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`;
this.log.info(artifact);
const args = [
'run',
'--add-host',
'host.docker.internal:host-gateway',
'--env',
'FLEET_ENROLL=1',
'--env',
`FLEET_URL=http://host.docker.internal:8220`,
'--env',
`FLEET_ENROLLMENT_TOKEN=${policy.api_key}`,
'--env',
'FLEET_INSECURE=true',
'--rm',
artifact,
];
this.agentProcess = spawn('docker', args, { stdio: 'inherit' });
// Wait til we see the agent is online
let done = false;
@ -106,6 +93,7 @@ export class AgentManager extends Manager {
throw new Error('Agent timed out while coming online');
}
}
return { policyId: policy.policy_id as string };
}
@ -121,6 +109,6 @@ export class AgentManager extends Manager {
});
delete this.agentProcess;
}
unlinkSync(resolve('.', 'elastic-agent.yml'));
return;
}
}

View file

@ -5,121 +5,6 @@
* 2.0.
*/
import axios, { AxiosResponse } from 'axios';
import { get } from 'lodash';
import { execSync } from 'child_process';
import { writeFileSync, unlinkSync, rmdirSync } from 'fs';
import { resolve } from 'path';
import { ToolingLog } from '@kbn/dev-utils';
import { Manager } from './resource_manager';
const archMap: { [key: string]: string } = {
x64: 'x86_64',
};
type ArtifactName = 'elastic-agent' | 'fleet-server';
async function getArtifact(
artifact: string,
urlExtractor: (data: AxiosResponse<any>, filename: string) => string,
log: ToolingLog,
version: string
) {
log.info(`Fetching ${version} of ${artifact}`);
const agents = await axios(
`https://artifacts-api.elastic.co/v1/versions/${version}/builds/latest`
);
const arch = archMap[process.arch] ?? process.arch;
const dirName = `${artifact}-${version}-${process.platform}-${arch}`;
const filename = dirName + '.tar.gz';
const url = urlExtractor(agents.data, filename);
if (!url) {
log.error(`Could not find url for ${artifact}: ${url}`);
throw new Error(`Unable to fetch ${artifact}`);
}
log.info(`Fetching ${filename} from ${url}`);
const agent = await axios(url as string, { responseType: 'arraybuffer' });
writeFileSync(filename, agent.data);
execSync(`tar xvf ${filename}`);
return resolve(filename);
}
// There has to be a better way to represent partial function application
type ArtifactFetcher = (
log: Parameters<typeof getArtifact>[2],
version: Parameters<typeof getArtifact>[3]
) => ReturnType<typeof getArtifact>;
type ArtifactFetchers = {
[artifactName in ArtifactName]: ArtifactFetcher;
};
const fetchers: ArtifactFetchers = {
'elastic-agent': getArtifact.bind(null, 'elastic-agent', (data, filename) =>
get(data, ['build', 'projects', 'beats', 'packages', filename, 'url'])
),
'fleet-server': getArtifact.bind(null, 'fleet-server', (data, filename) =>
get(data, ['build', 'projects', 'fleet-server', 'packages', filename, 'url'])
),
};
export type FetchArtifactsParams = {
[artifactName in ArtifactName]?: string;
};
type ArtifactPaths = FetchArtifactsParams;
export class ArtifactManager extends Manager {
private artifacts: ArtifactPaths;
private versions: FetchArtifactsParams;
private log: ToolingLog;
constructor(versions: FetchArtifactsParams, log: ToolingLog) {
super();
this.versions = versions;
this.log = log;
this.artifacts = {};
}
public fetchArtifacts = async () => {
this.log.info('Fetching artifacts');
await Promise.all(
Object.keys(this.versions).map(async (name: string) => {
const artifactName = name as ArtifactName;
const version = this.versions[artifactName];
if (!version) {
this.log.warning(`No version is specified for ${artifactName}, skipping`);
return;
}
const fetcher = fetchers[artifactName];
if (!fetcher) {
this.log.warning(`No fetcher is defined for ${artifactName}, skipping`);
}
this.artifacts[artifactName] = await fetcher(this.log, version);
})
);
};
public getArtifactDirectory(artifactName: string) {
const file = this.artifacts[artifactName as ArtifactName];
// this will break if the tarball name diverges from the directory that gets untarred
if (!file) {
throw new Error(`Unknown artifact ${artifactName}, unable to retreive directory`);
}
return file.replace('.tar.gz', '');
}
protected _cleanup() {
this.log.info('Cleaning up artifacts');
if (this.artifacts) {
for (const artifactName of Object.keys(this.artifacts)) {
const file = this.artifacts[artifactName as ArtifactName];
if (!file) {
this.log.warning(`Unknown artifact ${artifactName} encountered during cleanup, skipping`);
continue;
}
unlinkSync(file);
rmdirSync(this.getArtifactDirectory(artifactName), { recursive: true });
}
}
}
export async function getLatestVersion(): Promise<string> {
return '8.0.0-SNAPSHOT';
}

View file

@ -27,6 +27,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
// define custom es server here
// API Keys is enabled at the top level
'xpack.security.enabled=true',
'http.host=0.0.0.0',
],
},

View file

@ -6,44 +6,63 @@
*/
import { ChildProcess, spawn } from 'child_process';
import { copyFile } from 'fs/promises';
import { unlinkSync } from 'fs';
import { resolve } from 'path';
import { ToolingLog } from '@kbn/dev-utils';
import axios from 'axios';
import { Manager } from './resource_manager';
import { getLatestVersion } from './artifact_manager';
export interface ElasticsearchConfig {
esHost: string;
user: string;
password: string;
port: string;
}
export class FleetManager extends Manager {
private directoryPath: string;
private fleetProcess?: ChildProcess;
private esConfig: ElasticsearchConfig;
private log: ToolingLog;
constructor(directoryPath: string, esConfig: ElasticsearchConfig, log: ToolingLog) {
constructor(esConfig: ElasticsearchConfig, log: ToolingLog) {
super();
// TODO: check if the file exists
this.esConfig = esConfig;
this.directoryPath = directoryPath;
this.log = log;
}
public async setup(): Promise<void> {
this.log.info('Setting fleet up');
await copyFile(resolve(__dirname, 'fleet_server.yml'), resolve('.', 'fleet-server.yml'));
return new Promise((res, rej) => {
const env = {
ELASTICSEARCH_HOSTS: this.esConfig.esHost,
ELASTICSEARCH_USERNAME: this.esConfig.user,
ELASTICSEARCH_PASSWORD: this.esConfig.password,
};
const file = resolve(this.directoryPath, 'fleet-server');
// TODO: handle logging properly
this.fleetProcess = spawn(file, [], { stdio: 'inherit', env });
this.fleetProcess.on('error', rej);
// TODO: actually wait for the fleet server to start listening
setTimeout(res, 15000);
return new Promise(async (res, rej) => {
try {
const response = await axios.post(
`${this.esConfig.esHost}/_security/service/elastic/fleet-server/credential/token`
);
const serviceToken = response.data.token.value;
const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`;
this.log.info(artifact);
const host = 'host.docker.internal';
const args = [
'run',
'-p',
`8220:8220`,
'--add-host',
'host.docker.internal:host-gateway',
'--env',
'FLEET_SERVER_ENABLE=true',
'--env',
`FLEET_SERVER_ELASTICSEARCH_HOST=http://${host}:${this.esConfig.port}`,
'--env',
`FLEET_SERVER_SERVICE_TOKEN=${serviceToken}`,
'--rm',
artifact,
];
this.fleetProcess = spawn('docker', args, {
stdio: 'inherit',
});
this.fleetProcess.on('error', rej);
setTimeout(res, 15000);
} catch (error) {
rej(error);
}
});
}
@ -60,6 +79,5 @@ export class FleetManager extends Manager {
});
delete this.fleetProcess;
}
unlinkSync(resolve('.', 'fleet-server.yml'));
}
}

View file

@ -1,17 +0,0 @@
# mostly a stub config
output:
elasticsearch:
hosts: '${ELASTICSEARCH_HOSTS:localhost:9220}'
username: '${ELASTICSEARCH_USERNAME:elastic}'
password: '${ELASTICSEARCH_PASSWORD:changeme}'
fleet:
agent:
id: 1e4954ce-af37-4731-9f4a-407b08e69e42
logging:
level: '${LOG_LEVEL:DEBUG}'
logging:
to_stderr: true
http.enabled: true

View file

@ -12,40 +12,26 @@ import { withProcRunner } from '@kbn/dev-utils';
import { FtrProviderContext } from './ftr_provider_context';
import { ArtifactManager, FetchArtifactsParams } from './artifact_manager';
import { setupUsers } from './users';
import { AgentManager } from './agent';
import { FleetManager } from './fleet_server';
interface SetupParams {
artifacts: FetchArtifactsParams;
}
async function withFleetAgent(
{ getService }: FtrProviderContext,
params: SetupParams,
runner: (runnerEnv: Record<string, string>) => Promise<void>
) {
const log = getService('log');
const config = getService('config');
const artifactManager = new ArtifactManager(params.artifacts, log);
await artifactManager.fetchArtifacts();
const esHost = Url.format(config.get('servers.elasticsearch'));
const esConfig = {
user: config.get('servers.elasticsearch.username'),
password: config.get('servers.elasticsearch.password'),
esHost,
port: config.get('servers.elasticsearch.port'),
};
const fleetManager = new FleetManager(
artifactManager.getArtifactDirectory('fleet-server'),
esConfig,
log
);
const fleetManager = new FleetManager(esConfig, log);
const agentManager = new AgentManager(
artifactManager.getArtifactDirectory('elastic-agent'),
{
...esConfig,
kibanaUrl: Url.format({
@ -64,107 +50,56 @@ async function withFleetAgent(
process.exit(1);
});
await agentManager.setup();
await fleetManager.setup();
const { policyId } = await agentManager.setup();
await setupUsers(esConfig);
try {
await runner({
CYPRESS_OSQUERY_POLICY: policyId,
});
await runner({});
} finally {
fleetManager.cleanup();
agentManager.cleanup();
artifactManager.cleanup();
}
}
export async function OsqueryCypressCliTestRunner(context: FtrProviderContext) {
const log = context.getService('log');
const config = context.getService('config');
await withFleetAgent(
context,
{
artifacts: {
'elastic-agent': '7.15.0-SNAPSHOT',
'fleet-server': '7.15.0-SNAPSHOT',
},
},
(runnerEnv) =>
withProcRunner(log, async (procs) => {
await procs.run('cypress', {
cmd: 'yarn',
args: ['cypress:run'],
cwd: resolve(__dirname, '../../plugins/osquery'),
env: {
FORCE_COLOR: '1',
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_baseUrl: Url.format(config.get('servers.kibana')),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_protocol: config.get('servers.kibana.protocol'),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_hostname: config.get('servers.kibana.hostname'),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_configport: config.get('servers.kibana.port'),
CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')),
CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'),
CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'),
CYPRESS_KIBANA_URL: Url.format({
protocol: config.get('servers.kibana.protocol'),
hostname: config.get('servers.kibana.hostname'),
port: config.get('servers.kibana.port'),
}),
...runnerEnv,
...process.env,
},
wait: true,
});
})
);
await startOsqueryCypress(context, 'run');
}
export async function OsqueryCypressVisualTestRunner(context: FtrProviderContext) {
await startOsqueryCypress(context, 'open');
}
function startOsqueryCypress(context: FtrProviderContext, cypressCommand: string) {
const log = context.getService('log');
const config = context.getService('config');
await withFleetAgent(
context,
{
artifacts: {
'elastic-agent': '7.15.0-SNAPSHOT',
'fleet-server': '7.15.0-SNAPSHOT',
},
},
(runnerEnv) =>
withProcRunner(
log,
async (procs) =>
await procs.run('cypress', {
cmd: 'yarn',
args: ['cypress:open'],
cwd: resolve(__dirname, '../../plugins/osquery'),
env: {
FORCE_COLOR: '1',
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_baseUrl: Url.format(config.get('servers.kibana')),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_protocol: config.get('servers.kibana.protocol'),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_hostname: config.get('servers.kibana.hostname'),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_configport: config.get('servers.kibana.port'),
CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')),
CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'),
CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'),
CYPRESS_KIBANA_URL: Url.format({
protocol: config.get('servers.kibana.protocol'),
hostname: config.get('servers.kibana.hostname'),
port: config.get('servers.kibana.port'),
}),
...runnerEnv,
...process.env,
},
wait: true,
})
)
return withFleetAgent(context, (runnerEnv) =>
withProcRunner(log, async (procs) => {
await procs.run('cypress', {
cmd: 'yarn',
args: [`cypress:${cypressCommand}`],
cwd: resolve(__dirname, '../../plugins/osquery'),
env: {
FORCE_COLOR: '1',
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_baseUrl: Url.format(config.get('servers.kibana')),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_protocol: config.get('servers.kibana.protocol'),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_hostname: config.get('servers.kibana.hostname'),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_configport: config.get('servers.kibana.port'),
CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')),
CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'),
CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'),
CYPRESS_KIBANA_URL: Url.format({
protocol: config.get('servers.kibana.protocol'),
hostname: config.get('servers.kibana.hostname'),
port: config.get('servers.kibana.port'),
}),
...runnerEnv,
...process.env,
},
wait: true,
});
})
);
}

264
yarn.lock
View file

@ -1352,7 +1352,7 @@
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"
"@cypress/browserify-preprocessor@^3.0.1":
"@cypress/browserify-preprocessor@3.0.1", "@cypress/browserify-preprocessor@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-3.0.1.tgz#ab86335b0c061d11f5ad7df03f06b1877b836f71"
integrity sha512-sErmFSEr5287bLMRl0POGnyFtJCs/lSk5yxrUIJUIHZ8eDvtTEr0V93xRgLjJVG54gJU4MbpHy1mRPA9VZbtQA==
@ -1376,6 +1376,21 @@
through2 "^2.0.0"
watchify "3.11.1"
"@cypress/code-coverage@^3.9.11":
version "3.9.11"
resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.9.11.tgz#5d7d6da548d561001602b30accc7fa90dc487072"
integrity sha512-SA+fPILiiE0UHlMAwuv592D+wbKKdLbXz7BAN2a2RvW4fLbkVn1dXLATUFYf/6LkKrLaXJ3RENsoW9JqjBLzeQ==
dependencies:
"@cypress/browserify-preprocessor" "3.0.1"
chalk "4.1.2"
dayjs "1.10.7"
debug "4.3.2"
execa "4.1.0"
globby "11.0.4"
istanbul-lib-coverage "3.0.0"
js-yaml "3.14.1"
nyc "15.1.0"
"@cypress/request@^2.88.6":
version "2.88.6"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9"
@ -8101,29 +8116,6 @@ axobject-query@^2.2.0:
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=
dependencies:
chalk "^1.1.3"
esutils "^2.0.2"
js-tokens "^3.0.2"
babel-generator@^6.18.0:
version "6.26.1"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
dependencies:
babel-messages "^6.23.0"
babel-runtime "^6.26.0"
babel-types "^6.26.0"
detect-indent "^4.0.0"
jsesc "^1.3.0"
lodash "^4.17.4"
source-map "^0.5.7"
trim-right "^1.0.1"
babel-jest@^26.6.3:
version "26.6.3"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
@ -8148,13 +8140,6 @@ babel-loader@^8.2.2:
make-dir "^3.1.0"
schema-utils "^2.6.5"
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
dependencies:
babel-runtime "^6.22.0"
babel-plugin-add-module-exports@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.2.tgz#96cd610d089af664f016467fc4567c099cce2d9c"
@ -8394,7 +8379,7 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
babel-runtime@6.x, babel-runtime@^6.11.6, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
babel-runtime@6.x, babel-runtime@^6.11.6, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@ -8402,52 +8387,11 @@ babel-runtime@6.x, babel-runtime@^6.11.6, babel-runtime@^6.22.0, babel-runtime@^
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
babel-template@^6.16.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=
dependencies:
babel-runtime "^6.26.0"
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
lodash "^4.17.4"
babel-traverse@^6.18.0, babel-traverse@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=
dependencies:
babel-code-frame "^6.26.0"
babel-messages "^6.23.0"
babel-runtime "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
debug "^2.6.8"
globals "^9.18.0"
invariant "^2.2.2"
lodash "^4.17.4"
babel-types@^6.18.0, babel-types@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
dependencies:
babel-runtime "^6.26.0"
esutils "^2.0.2"
lodash "^4.17.4"
to-fast-properties "^1.0.3"
babelify@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5"
integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==
babylon@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
bach@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880"
@ -9448,6 +9392,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@ -11063,11 +11015,23 @@ cypress-pipe@^2.0.0:
resolved "https://registry.yarnpkg.com/cypress-pipe/-/cypress-pipe-2.0.0.tgz#577df7a70a8603d89a96dfe4092a605962181af8"
integrity sha512-KW9s+bz4tFLucH3rBGfjW+Q12n7S4QpUSSyxiGrgPOfoHlbYWzAGB3H26MO0VTojqf9NVvfd5Kt0MH5XMgbfyg==
cypress-react-selector@^2.3.13:
version "2.3.13"
resolved "https://registry.yarnpkg.com/cypress-react-selector/-/cypress-react-selector-2.3.13.tgz#468f3b42261ed04a7a5f9036d7373cf3894e2672"
integrity sha512-30z82/k9Mp5wgpXe/8DyVD2w5cXLmVAiGd/YEKoEto4jmkTWP9WfzSRvBbhd6mrr99HMUudlZUB3TwFgvPp3og==
dependencies:
resq "1.10.1"
cypress-real-events@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.5.1.tgz#5eeb86d2a7aad9aa6d5271e288a23e46373915cd"
integrity sha512-Jwi/IJePcZrKyhdtVddaf+mqJrj3y1vpREMDgtWwz+oxvj5FbBpeU0ASu9zpB3bMbsMo7g//buopZIe4jx3iSA==
cypress-recurse@^1.13.1:
version "1.13.1"
resolved "https://registry.yarnpkg.com/cypress-recurse/-/cypress-recurse-1.13.1.tgz#1d026d3381e4de7cf867a5ef592c4161da325fed"
integrity sha512-re0djeUInv0JwxhFBSIiZmrJfvUaLTjK9jWsD0oqpnvG1UXGWR69rkXMtMK5HZhxkL7GSk9JiIpm49aWpOnsFA==
cypress@^8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.5.0.tgz#5712ca170913f8344bf167301205c4217c1eb9bd"
@ -11487,10 +11451,10 @@ dateformat@^3.0.2:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
dayjs@^1.10.4:
version "1.10.4"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
dayjs@1.10.7, dayjs@^1.10.4:
version "1.10.7"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
debug-fabulous@1.X:
version "1.1.0"
@ -11501,7 +11465,7 @@ debug-fabulous@1.X:
memoizee "0.4.X"
object-assign "4.X"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9:
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@ -11905,13 +11869,6 @@ detect-file@^1.0.0:
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
detect-indent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
dependencies:
repeating "^2.0.0"
detect-indent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
@ -13679,7 +13636,7 @@ fancy-log@^1.3.2:
color-support "^1.1.3"
time-stamp "^1.0.0"
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3, fast-deep-equal@~3.1.3:
fast-deep-equal@^2.0.1, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3, fast-deep-equal@~3.1.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
@ -14602,6 +14559,11 @@ get-nonce@^1.0.0:
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
get-pixels@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/get-pixels/-/get-pixels-3.3.2.tgz#3f62fb8811932c69f262bba07cba72b692b4ff03"
@ -14911,11 +14873,6 @@ globals@^13.6.0, globals@^13.9.0:
dependencies:
type-fest "^0.20.2"
globals@^9.18.0:
version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
globalthis@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9"
@ -14949,6 +14906,18 @@ globby@11.0.1:
merge2 "^1.3.0"
slash "^3.0.0"
globby@11.0.4, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4:
version "11.0.4"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.1.1"
ignore "^5.1.4"
merge2 "^1.3.0"
slash "^3.0.0"
globby@^10.0.1:
version "10.0.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543"
@ -14963,18 +14932,6 @@ globby@^10.0.1:
merge2 "^1.2.3"
slash "^3.0.0"
globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4:
version "11.0.4"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.1.1"
ignore "^5.1.4"
merge2 "^1.3.0"
slash "^3.0.0"
globby@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
@ -17022,27 +16979,7 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
istanbul-instrumenter-loader@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz#9957bd59252b373fae5c52b7b5188e6fde2a0949"
integrity sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==
dependencies:
convert-source-map "^1.5.0"
istanbul-lib-instrument "^1.7.3"
loader-utils "^1.1.0"
schema-utils "^0.3.0"
istanbul-lib-coverage@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1:
version "3.0.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
istanbul-lib-coverage@^3.2.0:
istanbul-lib-coverage@3.0.0, istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1, istanbul-lib-coverage@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
@ -17054,19 +16991,6 @@ istanbul-lib-hook@^3.0.0:
dependencies:
append-transform "^2.0.0"
istanbul-lib-instrument@^1.7.3:
version "1.10.2"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==
dependencies:
babel-generator "^6.18.0"
babel-template "^6.16.0"
babel-traverse "^6.18.0"
babel-types "^6.18.0"
babylon "^6.18.0"
istanbul-lib-coverage "^1.2.1"
semver "^5.3.0"
istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
@ -17861,7 +17785,7 @@ js-string-escape@^1.0.1:
resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
"js-tokens@^3.0.0 || ^4.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
@ -17879,6 +17803,14 @@ js-yaml@3.14.0, js-yaml@^3.14.0:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^3.13.1, js-yaml@^3.9.0, js-yaml@~3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
@ -17956,11 +17888,6 @@ jsdom@^16.4.0:
ws "^7.2.3"
xml-name-validator "^3.0.0"
jsesc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
jsesc@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe"
@ -20678,10 +20605,10 @@ nwsapi@^2.0.9, nwsapi@^2.2.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
nyc@^15.0.1:
version "15.0.1"
resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.0.1.tgz#bd4d5c2b17f2ec04370365a5ca1fc0ed26f9f93d"
integrity sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==
nyc@15.1.0, nyc@^15.1.0:
version "15.1.0"
resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02"
integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==
dependencies:
"@istanbuljs/load-nyc-config" "^1.0.0"
"@istanbuljs/schema" "^0.1.2"
@ -20691,6 +20618,7 @@ nyc@^15.0.1:
find-cache-dir "^3.2.0"
find-up "^4.1.0"
foreground-child "^2.0.0"
get-package-type "^0.1.0"
glob "^7.1.6"
istanbul-lib-coverage "^3.0.0"
istanbul-lib-hook "^3.0.0"
@ -24465,13 +24393,6 @@ repeating@^1.1.2:
dependencies:
is-finite "^1.0.0"
repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
dependencies:
is-finite "^1.0.0"
replace-ext@1.0.0, replace-ext@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
@ -24729,6 +24650,13 @@ responselike@^2.0.0:
dependencies:
lowercase-keys "^2.0.0"
resq@1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/resq/-/resq-1.10.1.tgz#c05d1b3808016cceec4d485ceb375acb49565f53"
integrity sha512-zhp1iyUH02MLciv3bIM2bNtTFx/fqRsK4Jk73jcPqp00d/sMTTjOtjdTMAcgjrQKGx5DvQ/HSpeqaMW0atGRJA==
dependencies:
fast-deep-equal "^2.0.1"
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@ -25039,15 +24967,6 @@ scheduler@^0.18.0:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@1.0.0, schema-utils@^0.3.0, schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
dependencies:
ajv "^6.1.0"
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
schema-utils@2.7.0, schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
@ -25065,6 +24984,15 @@ schema-utils@^0.4.5:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
dependencies:
ajv "^6.1.0"
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
@ -27324,11 +27252,6 @@ to-camel-case@^1.0.0:
dependencies:
to-space-case "^1.0.0"
to-fast-properties@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@ -27502,11 +27425,6 @@ trim-newlines@^3.0.0:
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
trim-trailing-lines@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz#7aefbb7808df9d669f6da2e438cac8c46ada7684"