@elastic/synthetics
Version:
Elastic synthetic monitoring agent
274 lines (271 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.warnIfThrottled = exports.pushLegacy = exports.catchIncorrectSettings = exports.validateSettings = exports.validatePush = exports.loadSettings = exports.formatDuplicateError = exports.push = void 0;
/**
* 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.
*
*/
const promises_1 = require("fs/promises");
const enquirer_1 = require("enquirer");
const colors_1 = require("kleur/colors");
const monitor_1 = require("./monitor");
const monitor_2 = require("../dsl/monitor");
const helpers_1 = require("../helpers");
const config_1 = require("../config");
const kibana_api_1 = require("./kibana_api");
const utils_1 = require("./utils");
const run_local_1 = require("./run-local");
const globals_1 = require("../core/globals");
async function push(monitors, options) {
if (parseInt(process.env.CHUNK_SIZE) > 250) {
throw (0, helpers_1.error)('Invalid CHUNK_SIZE. CHUNK_SIZE must be less than or equal to 250');
}
const duplicates = trackDuplicates(monitors);
if (duplicates.size > 0) {
throw (0, helpers_1.error)(formatDuplicateError(duplicates));
}
(0, helpers_1.progress)(`Pushing monitors for '${options.id}' project in kibana '${options.space}' space`);
/**
* Legacy API for kibana which does not support bulk operations
*/
if (!(0, utils_1.isBulkAPISupported)(options.kibanaVersion)) {
return await pushLegacy(monitors, options);
}
const { monitors: remote } = await (0, kibana_api_1.bulkGetMonitors)(options);
(0, helpers_1.progress)(`preparing ${monitors.length} monitors`);
const { schemas, sizes } = await (0, monitor_1.buildMonitorSchema)(monitors, true);
const local = (0, monitor_1.getLocalMonitors)(schemas);
const { newIDs, changedIDs, removedIDs, unchangedIDs } = (0, monitor_1.diffMonitors)(local, remote);
(0, utils_1.logDiff)(newIDs, changedIDs, removedIDs, unchangedIDs);
if ((0, globals_1.inDebugMode)()) {
(0, utils_1.logGroups)(sizes, newIDs, changedIDs, removedIDs, unchangedIDs);
// show bundle size for the whole project
let totalSize = 0;
for (const value of sizes.values()) {
totalSize += value;
}
(0, helpers_1.progress)(`total size of the ${sizes.size} monitors payload is ` +
(0, utils_1.printBytes)(totalSize));
}
if (options.dryRun) {
(0, helpers_1.progress)('Running browser monitors in dry run mode');
await (0, run_local_1.runLocal)(schemas);
(0, helpers_1.progress)('Dry run completed');
return;
}
const updatedMonitors = new Set([...changedIDs, ...newIDs]);
if (updatedMonitors.size > 0) {
const updatedMonSchemas = schemas.filter(s => updatedMonitors.has(s.id));
const batches = (0, utils_1.getSizedBatches)(updatedMonSchemas, sizes, kibana_api_1.MAX_PAYLOAD_SIZE_KB, kibana_api_1.BATCH_SIZE);
if (batches.length > 1) {
(0, helpers_1.progress)(`Monitors will be pushed as ${batches.length} batches.`);
}
for (const batch of batches) {
await (0, helpers_1.liveProgress)((0, kibana_api_1.bulkPutMonitors)(options, batch), `creating or updating ${batch.length} monitors`);
}
}
if (removedIDs.size > 0) {
if (updatedMonitors.size === 0 && unchangedIDs.size === 0) {
await confirmDelete(`Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`, options.yes);
}
else {
await confirmDelete(`Deleting ${removedIDs.size} monitors. Do you want to continue?`, options.yes);
}
const batches = (0, utils_1.getBatches)(Array.from(removedIDs), kibana_api_1.BATCH_SIZE);
for (const batch of batches) {
await (0, helpers_1.liveProgress)((0, kibana_api_1.bulkDeleteMonitors)(options, batch), `deleting ${batch.length} monitors`);
}
}
(0, helpers_1.done)(`Pushed: ${(0, colors_1.underline)((0, helpers_1.getMonitorManagementURL)(options.url))} `);
}
exports.push = push;
async function confirmDelete(message, skip) {
const { deleteMonitors } = await (0, enquirer_1.prompt)({
type: 'confirm',
skip() {
(0, helpers_1.write)('');
if (skip || !process.stdout.isTTY) {
this.initial = process.env.TEST_OVERRIDE ?? true;
return true;
}
return false;
},
name: 'deleteMonitors',
message,
initial: false,
});
if (!deleteMonitors) {
throw (0, helpers_1.warn)('Push command Aborted');
}
}
function trackDuplicates(monitors) {
const monitorMap = new Map();
const duplicates = new Set();
for (const monitor of monitors) {
const id = monitor.config.id;
if (monitorMap.has(id)) {
duplicates.add(monitorMap.get(id));
duplicates.add(monitor);
}
monitorMap.set(id, monitor);
}
return duplicates;
}
function formatDuplicateError(monitors) {
let outer = (0, colors_1.bold)(`Aborted: Duplicate monitors found\n`);
let inner = '';
for (const monitor of monitors) {
const { config, source } = monitor;
inner += `* ${config.id} - ${source.file}:${source.line}:${source.column}\n`;
}
outer += (0, helpers_1.indent)(inner);
return outer;
}
exports.formatDuplicateError = formatDuplicateError;
const INSTALLATION_HELP = `Run 'npx @elastic/synthetics init' to create project with default settings.`;
async function loadSettings(configPath, ignoreMissing = false) {
try {
const config = await (0, config_1.readConfig)(process.env['NODE_ENV'] || 'development', configPath);
// Missing config file, fake throw to capture as missing file
if (Object.keys(config).length === 0) {
throw '';
}
return config.project || {};
}
catch (e) {
if (!ignoreMissing) {
throw (0, helpers_1.error)(`Aborted (missing synthetics config file), Project not set up correctly.
${INSTALLATION_HELP}`);
}
}
}
exports.loadSettings = loadSettings;
async function validatePush(opts, settings) {
validateSettings(opts);
await catchIncorrectSettings(settings, opts);
}
exports.validatePush = validatePush;
function validateSettings(opts) {
const INVALID = 'Aborted. Invalid synthetics project settings.';
let reason = '';
if (!opts.id) {
reason = `Set project id via
- CLI '--id <id>'
- Config file 'project.id' field`;
}
else if (!opts.locations && !opts.privateLocations) {
reason = `Set default location for all monitors via
- CLI '--locations <values...> or --privateLocations <values...>'
- Config file 'monitors.locations' | 'monitors.privateLocations' field`;
}
else if (!opts.schedule) {
reason = `Set default schedule in minutes for all monitors via
- CLI '--schedule <mins>'
- Config file 'monitors.schedule' field`;
}
else if (opts.schedule && !monitor_2.ALLOWED_SCHEDULES.includes(opts.schedule)) {
reason = `Set default schedule(${opts.schedule}) to one of the allowed values - ${monitor_2.ALLOWED_SCHEDULES.join(',')}`;
}
else if ((opts.locations ?? []).length > 0 &&
(opts?.playwrightOptions?.clientCertificates ?? []).filter(cert => {
return cert.certPath || cert.keyPath || cert.pfxPath;
}).length > 0) {
reason = `Certificate path options (certPath, keyPath, pfxPath) are not supported on globally managed locations, use in-memory alternatives (cert, key, pfx) when running on cloud.`;
}
if (!reason)
return;
throw (0, helpers_1.error)(`${INVALID}
${reason}
${INSTALLATION_HELP}`);
}
exports.validateSettings = validateSettings;
async function overrideSettings(configPath, oldValue, newValue) {
const cwd = process.cwd();
configPath = configPath ?? (await (0, config_1.findSyntheticsConfig)(cwd, cwd));
if (!configPath) {
throw (0, helpers_1.warn)(`Unable to find synthetics config file: ${configPath}`);
}
const config = await (0, promises_1.readFile)(configPath, 'utf-8');
const updatedConfig = config.replace(`id: '${oldValue}'`, `id: '${newValue}'`);
await (0, promises_1.writeFile)(configPath, updatedConfig, 'utf-8');
}
async function catchIncorrectSettings(settings, options) {
let override = !settings.id;
if (settings.id && settings.id !== options.id) {
// Add an extra line to make it easier to read the prompt
(0, helpers_1.write)('');
({ override } = await (0, enquirer_1.prompt)({
type: 'confirm',
name: 'override',
skip() {
if (options.yes) {
this.initial = process.env.TEST_OVERRIDE ?? true;
return true;
}
return false;
},
message: `Monitors were pushed under the '${settings.id}' project. Are you sure you want to push them under the new '${options.id}' (note that this will duplicate the monitors, the old ones being orphaned)`,
initial: false,
}));
if (!override) {
throw (0, helpers_1.warn)('Push command Aborted');
}
}
if (override) {
await overrideSettings(options.config, settings.id, options.id);
}
}
exports.catchIncorrectSettings = catchIncorrectSettings;
async function pushLegacy(monitors, options) {
if ((0, utils_1.isLightweightMonitorSupported)(monitors, options)) {
throw (0, helpers_1.error)(`Aborted: Lightweight monitors are not supported in ${options.kibanaVersion}. Please upgrade to 8.5.0 or above.`);
}
let schemas = [];
let sizes = new Map();
if (monitors.length > 0) {
(0, helpers_1.progress)(`preparing ${monitors.length} monitors`);
({ schemas, sizes } = await (0, monitor_1.buildMonitorSchema)(monitors, false));
const batches = (0, utils_1.getSizedBatches)(schemas, sizes, kibana_api_1.MAX_PAYLOAD_SIZE_KB, 10);
if (batches.length > 1) {
(0, helpers_1.progress)(`Monitors will be pushed as ${batches.length} batches.`);
}
for (const batch of batches) {
await (0, helpers_1.liveProgress)((0, kibana_api_1.createMonitorsLegacy)({ schemas: batch, keepStale: true, options }), `creating or updating ${batch.length} monitors`);
}
}
else {
await confirmDelete(`Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`, options.yes);
}
await (0, helpers_1.liveProgress)((0, kibana_api_1.createMonitorsLegacy)({ schemas, keepStale: false, options }), `deleting all stale monitors`);
(0, helpers_1.done)(`Pushed: ${(0, colors_1.underline)((0, helpers_1.getMonitorManagementURL)(options.url))}`);
}
exports.pushLegacy = pushLegacy;
// prints warning if any of the monitors has throttling settings enabled during push
function warnIfThrottled(monitors) {
const throttled = monitors.some(monitor => monitor.config.throttling != null);
if (throttled) {
(0, helpers_1.warn)(helpers_1.THROTTLING_WARNING_MSG);
}
}
exports.warnIfThrottled = warnIfThrottled;
//# sourceMappingURL=index.js.map