[7.x] Addresses an issue where Chromium user-data-dirs aren't removed (#40284) (#41066)

* Addresses an issue where Chromium user-data-dirs aren't removed (#40284)

* Addresses an issue where Chromium user-data-dirs aren't cleaned properly, and moves to TS

* Fixing renovate config issues

* Fixing issues with typedef's missing

* Using prior-set typedefs for logger

* Fixing lint issues

* Lint fixes

* No module def
This commit is contained in:
Joel Griffith 2019-07-15 15:00:44 -07:00 committed by GitHub
parent e12f403ab6
commit 894556c5dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 117 additions and 95 deletions

View file

@ -320,7 +320,6 @@
"@types/opn": "^5.1.0",
"@types/podium": "^1.0.0",
"@types/prop-types": "^15.5.3",
"@types/puppeteer-core": "^1.9.0",
"@types/react": "^16.8.0",
"@types/react-dom": "^16.8.0",
"@types/react-redux": "^6.0.6",

View file

@ -42,7 +42,7 @@ function generatePngObservableFn(server) {
throw new Error(`LayoutParams.Dimensions is undefined.`);
}
const layout = new PreserveLayout(layoutParams.dimensions);
const layout = new PreserveLayout(layoutParams.dimensions);
const screenshots$ = urlScreenshotsObservable(url, conditionalHeaders, layout, browserTimezone);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore no module definition
import * as Chrome from 'puppeteer-core';
import { parse as parseUrl } from 'url';
import {

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
interface Opts {
export interface IArgOptions {
userDataDir: string;
viewport: { width: number; height: number };
disableSandbox: boolean;
@ -13,7 +13,7 @@ interface Opts {
server: string;
bypass?: string[];
};
verboseLogging: boolean;
verboseLogging?: boolean;
}
export const args = ({
@ -22,7 +22,7 @@ export const args = ({
disableSandbox,
proxyConfig,
verboseLogging,
}: Opts) => {
}: IArgOptions) => {
const flags = [
// Disable built-in Google Translate service
'--disable-translate',

View file

@ -6,21 +6,41 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
// @ts-ignore
import puppeteer from 'puppeteer-core';
import rimraf from 'rimraf';
import * as Rx from 'rxjs';
import { map, share, mergeMap, filter, partition } from 'rxjs/operators';
import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
import { HeadlessChromiumDriver } from '../driver';
import { args } from './args';
import { args, IArgOptions } from './args';
import { safeChildProcess } from '../../safe_child_process';
import { getChromeLogLocation } from '../paths';
import { Logger } from '../../../../types';
const compactWhitespace = str => {
type binaryPath = string;
type queueTimeout = number;
interface IBrowserConfig {
[key: string]: any;
}
const compactWhitespace = (str: string) => {
return str.replace(/\s+/, ' ');
};
export class HeadlessChromiumDriverFactory {
constructor(binaryPath, logger, browserConfig, queueTimeout) {
private binaryPath: binaryPath;
private logger: Logger;
private browserConfig: IBrowserConfig;
private queueTimeout: queueTimeout;
constructor(
binaryPath: binaryPath,
logger: any,
browserConfig: IBrowserConfig,
queueTimeout: queueTimeout
) {
this.binaryPath = binaryPath;
this.logger = logger.clone(['chromium-driver-factory']);
this.browserConfig = browserConfig;
@ -29,7 +49,10 @@ export class HeadlessChromiumDriverFactory {
type = 'chromium';
test({ viewport, browserTimezone }, logger) {
test(
{ viewport, browserTimezone }: { viewport: IArgOptions['viewport']; browserTimezone: string },
logger: any
) {
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-'));
const chromiumArgs = args({
userDataDir,
@ -49,7 +72,7 @@ export class HeadlessChromiumDriverFactory {
TZ: browserTimezone,
},
})
.catch(error => {
.catch((error: Error) => {
logger.warning(
`The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports: [${error}]`
);
@ -58,8 +81,14 @@ export class HeadlessChromiumDriverFactory {
});
}
create({ viewport, browserTimezone }) {
return Rx.Observable.create(async observer => {
create({
viewport,
browserTimezone,
}: {
viewport: IArgOptions['viewport'];
browserTimezone: string;
}): Rx.Observable<any> {
return Rx.Observable.create(async (observer: InnerSubscriber<any, any>) => {
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-'));
const chromiumArgs = args({
userDataDir,
@ -69,8 +98,8 @@ export class HeadlessChromiumDriverFactory {
proxyConfig: this.browserConfig.proxy,
});
let browser;
let page;
let browser: puppeteer.Browser;
let page: puppeteer.Page;
try {
browser = await puppeteer.launch({
userDataDir,
@ -88,6 +117,7 @@ export class HeadlessChromiumDriverFactory {
// which can cause the job to fail even if we bump timeouts in
// the config. Help alleviate errors like
// "TimeoutError: waiting for selector ".application" failed: timeout 30000ms exceeded"
// @ts-ignore outdated typedefs for puppteer
page.setDefaultTimeout(this.queueTimeout);
} catch (err) {
observer.error(new Error(`Error spawning Chromium browser: [${err}]`));
@ -107,19 +137,18 @@ export class HeadlessChromiumDriverFactory {
// https://pptr.dev/#?product=Puppeteer&version=v1.10.0&show=api-event-error
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page
const stderr$ = Rx.fromEvent(page, 'console').pipe(
filter(line => line._type === 'error'),
map(line => line._text),
const stderr$ = Rx.fromEvent(page as NodeJS.EventEmitter, 'console').pipe(
filter((line: any) => line._type === 'error'),
map((line: any) => line._text),
share()
);
const [consoleMessage$, message$] = stderr$.pipe(
partition(msg => msg.match(/\[\d+\/\d+.\d+:\w+:CONSOLE\(\d+\)\]/))
);
const [consoleMessage$, message$] = partition(
(msg: string) => !!msg.match(/\[\d+\/\d+.\d+:\w+:CONSOLE\(\d+\)\]/)
)(stderr$);
const driver$ = Rx.of(
new HeadlessChromiumDriver(page, {
maxScreenshotDimension: this.browserConfig.maxScreenshotDimension,
logger: this.logger,
})
);
@ -133,7 +162,7 @@ export class HeadlessChromiumDriverFactory {
);
const processRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe(
mergeMap(req => {
mergeMap((req: any) => {
const failure = req.failure && req.failure();
if (failure) {
return Rx.throwError(
@ -151,12 +180,12 @@ export class HeadlessChromiumDriverFactory {
);
const nssError$ = message$.pipe(
filter(line => line.includes('error while loading shared libraries: libnss3.so')),
filter((line: string) => line.includes('error while loading shared libraries: libnss3.so')),
mergeMap(() => Rx.throwError(new Error(`You must install nss for Reporting to work`)))
);
const fontError$ = message$.pipe(
filter(line =>
filter((line: string) =>
line.includes('Check failed: InitDefaultFont(). Could not find the default font')
),
mergeMap(() =>
@ -165,7 +194,7 @@ export class HeadlessChromiumDriverFactory {
);
const noUsableSandbox$ = message$.pipe(
filter(line => line.includes('No usable sandbox! Update your kernel')),
filter((line: string) => line.includes('No usable sandbox! Update your kernel')),
mergeMap(() =>
Rx.throwError(
new Error(
@ -196,7 +225,7 @@ export class HeadlessChromiumDriverFactory {
});
// unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium
return () => {
observer.add(() => {
this.logger.debug(`deleting chromium user data directory at [${userDataDir}]`);
// the unsubscribe function isn't `async` so we're going to make our best effort at
// deleting the userDataDir and if it fails log an error.
@ -207,7 +236,7 @@ export class HeadlessChromiumDriverFactory {
);
}
});
};
});
});
}
}

View file

@ -1,34 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import path from 'path';
export const paths = {
archivesPath: path.resolve(__dirname, '../../../.chromium'),
baseUrl: 'https://s3.amazonaws.com/headless-shell/',
packages: [{
platforms: ['darwin', 'freebsd', 'openbsd'],
archiveFilename: 'chromium-2fac04a-darwin.zip',
archiveChecksum: '36814b1629457aa178b4ecdf6cc1bc5f',
rawChecksum: '9b40e2efa7f4f1870835ee4cdaf1dd51',
binaryRelativePath: 'headless_shell-darwin/headless_shell',
}, {
platforms: ['linux'],
archiveFilename: 'chromium-2fac04a-linux.zip',
archiveChecksum: '5cd6b898a35f9dc0ba6f49d821b8a2a3',
rawChecksum: 'b3fd218d3c3446c388da4e6c8a82754c',
binaryRelativePath: 'headless_shell-linux/headless_shell'
}, {
platforms: ['win32'],
archiveFilename: 'chromium-2fac04a-windows.zip',
archiveChecksum: '1499a4d5847792d59b9c1a8ab7dc8b94',
rawChecksum: '08b48d2f3d23c4bc8b58779ca4a7b627',
binaryRelativePath: 'headless_shell-windows\\headless_shell.exe'
}]
};
export const getChromeLogLocation = (binaryPath) =>
path.join(binaryPath, '..', 'chrome_debug.log');

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import path from 'path';
export const paths = {
archivesPath: path.resolve(__dirname, '../../../.chromium'),
baseUrl: 'https://s3.amazonaws.com/headless-shell/',
packages: [
{
platforms: ['darwin', 'freebsd', 'openbsd'],
archiveFilename: 'chromium-2fac04a-darwin.zip',
archiveChecksum: '36814b1629457aa178b4ecdf6cc1bc5f',
rawChecksum: '9b40e2efa7f4f1870835ee4cdaf1dd51',
binaryRelativePath: 'headless_shell-darwin/headless_shell',
},
{
platforms: ['linux'],
archiveFilename: 'chromium-2fac04a-linux.zip',
archiveChecksum: '5cd6b898a35f9dc0ba6f49d821b8a2a3',
rawChecksum: 'b3fd218d3c3446c388da4e6c8a82754c',
binaryRelativePath: 'headless_shell-linux/headless_shell',
},
{
platforms: ['win32'],
archiveFilename: 'chromium-2fac04a-windows.zip',
archiveChecksum: '1499a4d5847792d59b9c1a8ab7dc8b94',
rawChecksum: '08b48d2f3d23c4bc8b58779ca4a7b627',
binaryRelativePath: 'headless_shell-windows\\headless_shell.exe',
},
],
};
export const getChromeLogLocation = (binaryPath: string) =>
path.join(binaryPath, '..', 'chrome_debug.log');

View file

@ -6,44 +6,44 @@
import * as Rx from 'rxjs';
import { take, share, mapTo, delay, tap, ignoreElements } from 'rxjs/operators';
import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
interface IChild {
kill: (signal: string) => Promise<any>;
}
// Our process can get sent various signals, and when these occur we wish to
// kill the subprocess and then kill our process as long as the observer isn't cancelled
export function safeChildProcess(childProcess, observer) {
export function safeChildProcess(childProcess: IChild, observer: InnerSubscriber<any, any>) {
const ownTerminateSignal$ = Rx.merge(
Rx.fromEvent(process, 'SIGTERM').pipe(mapTo('SIGTERM')),
Rx.fromEvent(process, 'SIGINT').pipe(mapTo('SIGINT')),
Rx.fromEvent(process, 'SIGBREAK').pipe(mapTo('SIGBREAK')),
)
.pipe(
take(1),
share()
);
Rx.fromEvent(process as NodeJS.EventEmitter, 'SIGTERM').pipe(mapTo('SIGTERM')),
Rx.fromEvent(process as NodeJS.EventEmitter, 'SIGINT').pipe(mapTo('SIGINT')),
Rx.fromEvent(process as NodeJS.EventEmitter, 'SIGBREAK').pipe(mapTo('SIGBREAK'))
).pipe(
take(1),
share()
);
// signals that will be sent to the child process as a result of the main process
// being sent these signals, or the exit being triggered
const signalForChildProcess$ = Rx.merge(
// SIGKILL when this process gets a terminal signal
ownTerminateSignal$.pipe(
mapTo('SIGKILL')
),
ownTerminateSignal$.pipe(mapTo('SIGKILL')),
// SIGKILL when this process forcefully exits
Rx.fromEvent(process, 'exit').pipe(
Rx.fromEvent(process as NodeJS.EventEmitter, 'exit').pipe(
take(1),
mapTo('SIGKILL')
),
)
);
// send termination signals
const terminate$ = Rx.merge(
signalForChildProcess$.pipe(
tap(signal => childProcess.kill(signal))
),
signalForChildProcess$.pipe(tap(signal => childProcess.kill(signal))),
ownTerminateSignal$.pipe(
delay(1),
tap(signal => process.kill(process.pid, signal)),
tap(signal => process.kill(process.pid, signal))
)
);
@ -58,7 +58,7 @@ export function safeChildProcess(childProcess, observer) {
// If a process exits ungracefully, we can try to help the user make sense of why
// by giving them a suggestion based on the code.
export function exitCodeSuggestion(code) {
export function exitCodeSuggestion(code: number | null) {
if (code === null) {
return 'Your report may be too large. Try removing some visualizations or increasing the RAM available to Kibana.';
}

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore no module definition
import * as puppeteer from 'puppeteer-core';
import { KbnServer, Logger } from '../../../types';
import { CHROMIUM } from '../../browsers/browser_types';

View file

@ -60,6 +60,7 @@ export interface Logger {
error: (message: string) => void;
warning: (message: string) => void;
clone?: (tags: string[]) => Logger;
isVerbose?: boolean;
}
export interface ViewZoomWidthHeight {

View file

@ -78,6 +78,7 @@
"@types/pngjs": "^3.3.1",
"@types/prop-types": "^15.5.3",
"@types/proper-lockfile": "^3.0.0",
"@types/puppeteer": "^1.12.4",
"@types/react": "^16.8.0",
"@types/react-dom": "^16.8.0",
"@types/react-redux": "^6.0.6",

View file

@ -4208,21 +4208,7 @@
resolved "https://registry.yarnpkg.com/@types/proper-lockfile/-/proper-lockfile-3.0.0.tgz#dcc7cc3714857a4ae6583331d2687e89dc5c94d2"
integrity sha512-+tfnsA3KNPDm7Sj9x5omRgvS6ALc+edjTZXYeR3kVEm+qmsrF+59yJUWZDreV/O0+EQ6t0YSWlzxfdV58UOEVg==
"@types/puppeteer-core@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@types/puppeteer-core/-/puppeteer-core-1.9.0.tgz#5ceb397e3ff769081fb07d71289b5009392d24d3"
integrity sha512-YJwGTq0a8xZxN7/QDeW59XMdKTRNzDTc8ZVBPDB6J13GgXn1+QzgMA8pAq1/bj2FD0R7xj3nYoZra10b0HLzFw==
dependencies:
"@types/puppeteer" "*"
"@types/puppeteer@*":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.11.1.tgz#f2fe2e08917af2b4dc4b03bd2b838c05cb9d8410"
integrity sha512-IvrvZfWjITUH7q4WrM25ul9xlIeLM3Oh+hV2FL7xQQSroVf8mX3lMZaN7XEsw6Bdfp99Qm7I4GcD+ak5+wIEfA==
dependencies:
"@types/node" "*"
"@types/puppeteer@^1.6.0":
"@types/puppeteer@^1.12.4", "@types/puppeteer@^1.6.0":
version "1.12.4"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1"
integrity sha512-aaGbJaJ9TuF9vZfTeoh876sBa+rYJWPwtsmHmYr28pGr42ewJnkDTq2aeSKEmS39SqUdkwLj73y/d7rBSp7mDQ==