#!/usr/bin/env node /** * MIT License * * Copyright (c) 2020-present, Elastic NV * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ import { program, Option } from 'commander'; import { cwd } from 'process'; import { bold } from 'kleur/colors'; import { resolve } from 'path'; import { CliArgs, PushOptions, RunOptions } from './common_types'; import { reporters } from './reporters'; import { normalizeOptions, parseThrottling, getCommonCommandOpts, collectOpts, parseFileOption, } from './options'; import { globalSetup } from './loader'; import { run } from './'; import { runner } from './core/globals'; import { SyntheticsLocations } from './dsl/monitor'; import { push, loadSettings, validatePush, warnIfThrottled } from './push'; import { formatLocations, getLocations, renderLocations, LocationCmdOptions, } from './locations'; import { Generator } from './generator'; import { error, write } from './helpers'; import { LocationsMap } from './locations/public-locations'; import { createLightweightMonitors } from './push/monitor'; import { getVersion } from './push/kibana_api'; import { installTransform } from './core/transform'; import { totp, TOTPCmdOptions } from './core/mfa'; import { setGlobalProxy } from './helpers'; /* eslint-disable-next-line @typescript-eslint/no-var-requires */ const { name, version } = require('../package.json'); const proxySettings = {}; const { params, pattern, playwrightOpts, auth, authMandatory, configOpt, tags, match, fields, maintenanceWindows, } = getCommonCommandOpts(); program .name(`npx ${name}`) .usage('[options] [dir] [files] file') .addOption(configOpt) .addOption(pattern) .addOption(tags) .addOption(match) .addOption(params) .addOption( new Option('--reporter ', `output reporter format`).choices( Object.keys(reporters) ) ) .option('--inline', 'read journeys from stdin instead of reading from files') .option('-r, --require ', 'module(s) to preload') .option('--sandbox', 'enable chromium sand-boxing') .option( '--rich-events', 'preset flag used when running monitors directly via Heartbeat' ) .option('--no-headless', 'run with the browser in headful mode') .option( '--capability ', 'Enable capabilities through feature flags' ) .addOption( new Option( '--screenshots [flag]', 'Control whether to capture screenshots at the end of each step' ).choices(['on', 'off', 'only-on-failure']) ) .option( '--dry-run', "don't actually execute anything, report only registered journeys" ) .option( '--outfd ', 'specify a file descriptor for logs. Default is stdout', parseInt ) .option( '--ws-endpoint ', 'Browser WebSocket endpoint to connect to' ) .option( '--pause-on-error', 'pause on error until a keypress is made in the console. Useful during development' ) .option( '--ignore-https-errors', 'ignores any HTTPS errors in sites being tested, including ones related to unrecognized certs or signatures. This can be insecure!' ) .option( '--quiet-exit-code', 'always return 0 as an exit code status, regardless of test pass / fail. Only return > 0 exit codes on internal errors where the suite could not be run' ) .option( '--throttling ', 'JSON object to throttle network conditions for download and upload throughput in megabits/second and latency in milliseconds. Ex: { "download": 10, "upload": 5, "latency": 200 }.', parseThrottling ) .option( '--no-throttling', 'Turns off default network throttling.', parseThrottling ) .addOption(playwrightOpts) .version(version) .description('Run synthetic tests') .action(async (cliArgs: CliArgs) => { const tearDown = await globalSetup(cliArgs, program.args); try { const options = await normalizeOptions(cliArgs, 'run'); const results = await run(options); /** * Exit with error status if any journey fails */ if (!options.quietExitCode) { for (const result of Object.values(results)) { if (result.status === 'failed') { process.exit(1); } } } } catch (e) { console.error(e); process.exit(1); } finally { tearDown(); } }); // Push command program .command('push') .description( 'Push all journeys in the current directory to create monitors within the Kibana monitor management UI' ) .addOption(authMandatory) .option( '--schedule ', "schedule in minutes for the pushed monitors. Setting `10`, for example, configures monitors which don't have an interval defined to run every 10 minutes.", parseInt ) .addOption( new Option( '--locations ', 'default list of locations from which your monitors will run.' ).choices(SyntheticsLocations) ) .option( '--private-locations ', 'default list of private locations from which your monitors will run.' ) .option('--url ', 'Kibana URL to upload the project monitors') .option( '--id ', 'project id that will be used for logically grouping monitors' ) .option( '--space ', 'the target Kibana spaces for the pushed monitors — spaces help you organise pushed monitors.' ) .option('-y, --yes', 'skip all questions and run non-interactively') .option( '--proxy-uri ', 'proxy uri to use when pushing to kibana', collectOpts('uri', proxySettings) ) .option( '--proxy-token ', 'auth token to use the proxy', collectOpts('token', proxySettings) ) .option( '--proxy-ca ', 'provide a CA override for proxy endpoint, as a path to be loaded or as string', collectOpts('ca', proxySettings, parseFileOption) ) .option( '--proxy-cert ', 'provide a cert override for proxy endpoint, as a path to be loaded or as string', collectOpts('cert', proxySettings, parseFileOption) ) .option( '--proxy-no-verify', 'disable TLS verification for the proxy connection', collectOpts('noVerify', proxySettings), false ) .addOption(pattern) .addOption(tags) .addOption(fields) .addOption(match) .addOption(params) .addOption(playwrightOpts) .addOption(configOpt) .addOption(maintenanceWindows) .action(async cmdOpts => { cmdOpts = { ...cmdOpts, ...program.opts() }; const workDir = cwd(); const tearDown = await globalSetup({ inline: false, ...cmdOpts }, [ workDir, ]); try { const settings = await loadSettings(cmdOpts.config); const options = (await normalizeOptions( { ...settings, ...cmdOpts, ...{ proxy: proxySettings, }, }, 'push' )) as PushOptions; //Set up global proxy agent if any of the related options are set setGlobalProxy(options.proxy ?? {}); await validatePush(options, settings); const monitors = runner._buildMonitors(options); if ((options as CliArgs).throttling == null) { warnIfThrottled(monitors); } options.kibanaVersion = await getVersion(options); monitors.push(...(await createLightweightMonitors(workDir, options))); await push(monitors, options); } catch (e) { e && console.error(e); process.exit(1); } finally { tearDown(); } }); // Init command program .command('init [dir]') .description('Initialize Elastic synthetics project') .action(async (dir = '') => { try { const generator = await new Generator(resolve(process.cwd(), dir)); await generator.setup(); } catch (e) { e && error(e); process.exit(1); } }); // Locations command program .command('locations') .description( `List all locations to run the synthetics monitors. Pass optional '--url' and '--auth' to list private locations.` ) .option('--url ', 'Kibana URL to fetch all public and private locations') .addOption(auth) .option( '--proxy-uri ', 'proxy uri to use when pushing to kibana', collectOpts('uri', proxySettings) ) .option( '--proxy-token ', 'auth token to use the proxy', collectOpts('token', proxySettings) ) .option( '--proxy-ca ', 'provide a CA override for proxy endpoint, as a path to be loaded or as string', collectOpts('ca', proxySettings, parseFileOption('--proxy-ca')) ) .option( '--proxy-cert ', 'provide a cert override for proxy endpoint, as a path to be loaded or as string', collectOpts('cert', proxySettings, parseFileOption('--proxy-cert')) ) .option( '--proxy-no-verify', 'disable TLS verification for the proxy connection', collectOpts('noVerify', proxySettings), false ) .action(async (cmdOpts: LocationCmdOptions) => { const revert = installTransform(); const url = cmdOpts.url ?? (await loadSettings(null, true))?.url; try { const settings = await loadSettings(null, true); const options = (await normalizeOptions({ ...settings, ...cmdOpts, ...{ proxy: proxySettings, }, })) as RunOptions; //Set up global proxy agent if any of the related options are set setGlobalProxy(options.proxy ?? {}); if (url && cmdOpts.auth) { const allLocations = await getLocations({ url, auth: cmdOpts.auth, }); renderLocations(formatLocations(allLocations)); } else { renderLocations({ publicLocations: Object.keys(LocationsMap) }); } } catch (e) { e && error(e); process.exit(1); } finally { revert(); } }); // TOTP command program .command('totp ') .description( 'Generate a Time-based One-Time token using the provided secret.' ) .option( '--issuer ', 'Provider or Service the secret is associated with.' ) .option('--label