diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 246c605f4bfe6..3ce5329e42517 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -28,22 +28,25 @@ import { getChromeLogLocation } from '../paths'; import { puppeteerLaunch } from '../puppeteer'; import { args } from './args'; -type binaryPath = string; type BrowserConfig = CaptureConfig['browser']['chromium']; type ViewportConfig = CaptureConfig['viewport']; export class HeadlessChromiumDriverFactory { - private binaryPath: binaryPath; + private binaryPath: string; private captureConfig: CaptureConfig; private browserConfig: BrowserConfig; private userDataDir: string; private getChromiumArgs: (viewport: ViewportConfig) => string[]; - constructor(binaryPath: binaryPath, logger: LevelLogger, captureConfig: CaptureConfig) { + constructor(binaryPath: string, captureConfig: CaptureConfig, logger: LevelLogger) { this.binaryPath = binaryPath; this.captureConfig = captureConfig; this.browserConfig = captureConfig.browser.chromium; + if (this.browserConfig.disableSandbox) { + logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`); + } + this.userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); this.getChromiumArgs = (viewport: ViewportConfig) => args({ diff --git a/x-pack/plugins/reporting/server/browsers/chromium/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/index.ts index 5f89662c94da2..cebcd228b01c3 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/index.ts @@ -4,16 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { BrowserDownload } from '../'; import { CaptureConfig } from '../../../server/types'; import { LevelLogger } from '../../lib'; import { HeadlessChromiumDriverFactory } from './driver_factory'; +import { paths } from './paths'; -export { paths } from './paths'; - -export async function createDriverFactory( - binaryPath: string, - logger: LevelLogger, - captureConfig: CaptureConfig -): Promise { - return new HeadlessChromiumDriverFactory(binaryPath, logger, captureConfig); -} +export const chromium: BrowserDownload = { + paths, + createDriverFactory: (binaryPath: string, captureConfig: CaptureConfig, logger: LevelLogger) => + new HeadlessChromiumDriverFactory(binaryPath, captureConfig, logger), +}; diff --git a/x-pack/plugins/reporting/server/browsers/create_browser_driver_factory.ts b/x-pack/plugins/reporting/server/browsers/create_browser_driver_factory.ts deleted file mode 100644 index f3486a48ba7b1..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/create_browser_driver_factory.ts +++ /dev/null @@ -1,45 +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 { first } from 'rxjs/operators'; -import { ReportingConfig } from '../'; -import { LevelLogger } from '../lib'; -import { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; -import { ensureBrowserDownloaded } from './download'; -import { chromium } from './index'; -import { installBrowser } from './install'; - -export async function createBrowserDriverFactory( - config: ReportingConfig, - logger: LevelLogger -): Promise { - const captureConfig = config.get('capture'); - const browserConfig = captureConfig.browser.chromium; - const browserAutoDownload = captureConfig.browser.autoDownload; - const browserType = captureConfig.browser.type; - const dataDir = await config.kbnConfig.get('path', 'data').pipe(first()).toPromise(); - - if (browserConfig.disableSandbox) { - logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`); - } - if (browserAutoDownload) { - await ensureBrowserDownloaded(browserType, logger); - } - - try { - const { binaryPath } = await installBrowser(logger, chromium, dataDir); - return chromium.createDriverFactory(binaryPath, logger, captureConfig); - } catch (error) { - if (error.cause && ['EACCES', 'EEXIST'].includes(error.cause.code)) { - logger.error( - `Error code ${error.cause.code}: Insufficient permissions for extracting the browser archive. ` + - `Make sure the Kibana data directory (path.data) is owned by the same user that is running Kibana.` - ); - } - - throw error; // reject the promise with the original error - } -} diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts index b334510d71947..add14448e2f1d 100644 --- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts +++ b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts @@ -56,7 +56,7 @@ async function ensureDownloaded(browsers: BrowserDownload[], logger: LevelLogger const path = resolvePath(archivesPath, archiveFilename); if (existsSync(path) && (await md5(path)) === archiveChecksum) { - logger.info(`Browser archive exists in ${path}`); + logger.debug(`Browser archive exists in ${path}`); return; } diff --git a/x-pack/plugins/reporting/server/browsers/index.ts b/x-pack/plugins/reporting/server/browsers/index.ts index 7f6e40fb433b6..be5b869ba523b 100644 --- a/x-pack/plugins/reporting/server/browsers/index.ts +++ b/x-pack/plugins/reporting/server/browsers/index.ts @@ -4,20 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as chromiumDefinition from './chromium'; +import { first } from 'rxjs/operators'; +import { LevelLogger } from '../lib'; +import { CaptureConfig } from '../types'; +import { chromium } from './chromium'; +import { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; +import { installBrowser } from './install'; +import { ReportingConfig } from '..'; export { ensureAllBrowsersDownloaded } from './download'; -export { createBrowserDriverFactory } from './create_browser_driver_factory'; - export { HeadlessChromiumDriver } from './chromium/driver'; export { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; +export { chromium } from './chromium'; -export const chromium = { - paths: chromiumDefinition.paths, - createDriverFactory: chromiumDefinition.createDriverFactory, -}; +type CreateDriverFactory = ( + binaryPath: string, + captureConfig: CaptureConfig, + logger: LevelLogger +) => HeadlessChromiumDriverFactory; export interface BrowserDownload { + createDriverFactory: CreateDriverFactory; paths: { archivesPath: string; baseUrl: string; @@ -30,3 +37,13 @@ export interface BrowserDownload { }>; }; } + +export const initializeBrowserDriverFactory = async ( + config: ReportingConfig, + logger: LevelLogger +) => { + const { binaryPath$ } = installBrowser(chromium, config, logger); + const binaryPath = await binaryPath$.pipe(first()).toPromise(); + const captureConfig = config.get('capture'); + return chromium.createDriverFactory(binaryPath, captureConfig, logger); +}; diff --git a/x-pack/plugins/reporting/server/browsers/install.ts b/x-pack/plugins/reporting/server/browsers/install.ts index 01526af307022..49361b7b6014d 100644 --- a/x-pack/plugins/reporting/server/browsers/install.ts +++ b/x-pack/plugins/reporting/server/browsers/install.ts @@ -6,9 +6,13 @@ import fs from 'fs'; import path from 'path'; +import * as Rx from 'rxjs'; +import { first } from 'rxjs/operators'; import { promisify } from 'util'; +import { ReportingConfig } from '../'; import { LevelLogger } from '../lib'; import { BrowserDownload } from './'; +import { ensureBrowserDownloaded } from './download'; // @ts-ignore import { md5 } from './download/checksum'; // @ts-ignore @@ -19,37 +23,60 @@ const chmod = promisify(fs.chmod); interface Package { platforms: string[]; } -interface PathResponse { - binaryPath: string; -} /** * "install" a browser by type into installs path by extracting the downloaded * archive. If there is an error extracting the archive an `ExtractError` is thrown */ -export async function installBrowser( - logger: LevelLogger, +export function installBrowser( browser: BrowserDownload, - installsPath: string -): Promise { - const pkg = browser.paths.packages.find((p: Package) => p.platforms.includes(process.platform)); + config: ReportingConfig, + logger: LevelLogger +): { binaryPath$: Rx.Subject } { + const binaryPath$ = new Rx.Subject(); + const backgroundInstall = async () => { + const captureConfig = config.get('capture'); + const { autoDownload, type: browserType } = captureConfig.browser; + if (autoDownload) { + await ensureBrowserDownloaded(browserType, logger); + } + + const pkg = browser.paths.packages.find((p: Package) => p.platforms.includes(process.platform)); + if (!pkg) { + throw new Error(`Unsupported platform: ${JSON.stringify(browser, null, 2)}`); + } + + const dataDir = await config.kbnConfig.get('path', 'data').pipe(first()).toPromise(); + const binaryPath = path.join(dataDir, pkg.binaryRelativePath); + + try { + const binaryChecksum = await md5(binaryPath).catch(() => ''); - if (!pkg) { - throw new Error(`Unsupported platform: ${JSON.stringify(browser, null, 2)}`); - } + if (binaryChecksum !== pkg.binaryChecksum) { + const archive = path.join(browser.paths.archivesPath, pkg.archiveFilename); + logger.info(`Extracting [${archive}] to [${binaryPath}]`); + await extract(archive, dataDir); + await chmod(binaryPath, '755'); + } + } catch (error) { + if (error.cause && ['EACCES', 'EEXIST'].includes(error.cause.code)) { + logger.error( + `Error code ${error.cause.code}: Insufficient permissions for extracting the browser archive. ` + + `Make sure the Kibana data directory (path.data) is owned by the same user that is running Kibana.` + ); + } - const binaryPath = path.join(installsPath, pkg.binaryRelativePath); - const binaryChecksum = await md5(binaryPath).catch(() => ''); + throw error; // reject the promise with the original error + } + + logger.debug(`Browser executable: ${binaryPath}`); + + binaryPath$.next(binaryPath); // subscribers wait for download and extract to complete + }; - if (binaryChecksum !== pkg.binaryChecksum) { - const archive = path.join(browser.paths.archivesPath, pkg.archiveFilename); - logger.debug(`Extracting [${archive}] to [${binaryPath}]`); - await extract(archive, installsPath); - await chmod(binaryPath, '755'); - } + backgroundInstall(); - logger.debug(`Browser installed at ${binaryPath}`); return { - binaryPath, + binaryPath$, }; } diff --git a/x-pack/plugins/reporting/server/config/config.ts b/x-pack/plugins/reporting/server/config/config.ts index 4142ab6f0ae43..2a09ebea9619c 100644 --- a/x-pack/plugins/reporting/server/config/config.ts +++ b/x-pack/plugins/reporting/server/config/config.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable } from 'rxjs'; import { get } from 'lodash'; -import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { first, map } from 'rxjs/operators'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { LevelLogger } from '../lib'; +import { createConfig$ } from './create_config'; import { ReportingConfigType } from './schema'; // make config.get() aware of the value type it returns @@ -55,11 +57,12 @@ export interface ReportingConfig extends Config { kbnConfig: Config; } -export const buildConfig = ( +export const buildConfig = async ( initContext: PluginInitializerContext, core: CoreSetup, - reportingConfig: ReportingConfigType -): ReportingConfig => { + logger: LevelLogger +): Promise => { + const config$ = initContext.config.create(); const { http } = core; const serverInfo = http.getServerInfo(); @@ -77,6 +80,8 @@ export const buildConfig = ( }, }; + const reportingConfig$ = createConfig$(core, config$, logger); + const reportingConfig = await reportingConfig$.pipe(first()).toPromise(); return { get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null), // spreading arguments as an array allows the return type to be known by the compiler kbnConfig: { diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index 1c4c840cfa312..8ad8042a93105 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -45,7 +45,11 @@ describe('Reporting server createConfig$', () => { mockInitContext = makeMockInitContext({ kibanaServer: {}, }); - mockLogger = ({ warn: jest.fn(), debug: jest.fn() } as unknown) as LevelLogger; + mockLogger = ({ + warn: jest.fn(), + debug: jest.fn(), + clone: jest.fn().mockImplementation(() => mockLogger), + } as unknown) as LevelLogger; }); afterEach(() => { diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts index 67df7dacc77ab..5c66bd599dd9a 100644 --- a/x-pack/plugins/reporting/server/config/create_config.ts +++ b/x-pack/plugins/reporting/server/config/create_config.ts @@ -23,8 +23,9 @@ import { ReportingConfigType } from './schema'; export function createConfig$( core: CoreSetup, config$: Observable, - logger: LevelLogger + parentLogger: LevelLogger ) { + const logger = parentLogger.clone(['config']); return config$.pipe( map((config) => { // encryption key diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index caa64a7414005..a89b952702e1b 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -7,7 +7,6 @@ import { PluginConfigDescriptor } from 'kibana/server'; import { ConfigSchema, ReportingConfigType } from './schema'; export { buildConfig } from './config'; -export { createConfig$ } from './create_config'; export { ConfigSchema, ReportingConfigType }; export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index e7786b3b753fb..94b138ffcae0b 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -26,7 +26,6 @@ import { ESQueueInstance } from './lib/create_queue'; import { EnqueueJobFn } from './lib/enqueue_job'; export interface ReportingInternalSetup { - browserDriverFactory: HeadlessChromiumDriverFactory; elasticsearch: ElasticsearchServiceSetup; licensing: LicensingPluginSetup; basePath: BasePath['get']; @@ -44,6 +43,7 @@ interface ReportingInternalStart { export class ReportingCore { private pluginSetupDeps?: ReportingInternalSetup; private pluginStartDeps?: ReportingInternalStart; + private browserDriverFactory?: HeadlessChromiumDriverFactory; private readonly pluginSetup$ = new Rx.ReplaySubject(); private readonly pluginStart$ = new Rx.ReplaySubject(); private exportTypesRegistry = getExportTypesRegistry(); @@ -63,6 +63,10 @@ export class ReportingCore { return this.pluginStart$.pipe(first(), mapTo(true)).toPromise(); } + public setBrowserDriverFactory(browserDriverFactory: HeadlessChromiumDriverFactory) { + this.browserDriverFactory = browserDriverFactory; + } + /* * Internal module dependencies */ @@ -93,7 +97,10 @@ export class ReportingCore { } public getScreenshotsObservable(): ScreenshotsObservableFn { - const { browserDriverFactory } = this.getPluginSetupDeps(); + const { browserDriverFactory } = this; + if (!browserDriverFactory) { + throw new Error(`"browserDriverFactory" dependency hasn't initialized yet`); + } return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); } diff --git a/x-pack/plugins/reporting/server/lib/validate/index.ts b/x-pack/plugins/reporting/server/lib/validate/index.ts index 404cbcda31a09..7c439d6023d5f 100644 --- a/x-pack/plugins/reporting/server/lib/validate/index.ts +++ b/x-pack/plugins/reporting/server/lib/validate/index.ts @@ -7,8 +7,8 @@ import { i18n } from '@kbn/i18n'; import { ElasticsearchServiceSetup } from 'kibana/server'; import { ReportingConfig } from '../../'; -import { LevelLogger } from '../../lib'; import { HeadlessChromiumDriverFactory } from '../../browsers/chromium/driver_factory'; +import { LevelLogger } from '../../lib'; import { validateBrowser } from './validate_browser'; import { validateMaxContentLength } from './validate_max_content_length'; @@ -16,8 +16,9 @@ export async function runValidations( config: ReportingConfig, elasticsearch: ElasticsearchServiceSetup, browserFactory: HeadlessChromiumDriverFactory, - logger: LevelLogger + parentLogger: LevelLogger ) { + const logger = parentLogger.clone(['validations']); try { await Promise.all([ validateBrowser(browserFactory, logger), diff --git a/x-pack/plugins/reporting/server/plugin.test.ts b/x-pack/plugins/reporting/server/plugin.test.ts new file mode 100644 index 0000000000000..b2bcd6b9c97ce --- /dev/null +++ b/x-pack/plugins/reporting/server/plugin.test.ts @@ -0,0 +1,103 @@ +/* + * 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. + */ +jest.mock('./browsers/install', () => ({ + installBrowser: jest.fn().mockImplementation(() => ({ + binaryPath$: { + pipe: jest.fn().mockImplementation(() => ({ + toPromise: () => Promise.resolve(), + })), + }, + })), +})); + +import { coreMock } from 'src/core/server/mocks'; +import { ReportingPlugin } from './plugin'; +import { createMockConfigSchema } from './test_helpers'; + +const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); + +describe('Reporting Plugin', () => { + let configSchema: any; + let initContext: any; + let coreSetup: any; + let coreStart: any; + let pluginSetup: any; + let pluginStart: any; + + beforeEach(async () => { + configSchema = createMockConfigSchema(); + initContext = coreMock.createPluginInitializerContext(configSchema); + coreSetup = await coreMock.createSetup(configSchema); + coreStart = await coreMock.createStart(); + pluginSetup = ({ + licensing: {}, + usageCollection: { + makeUsageCollector: jest.fn(), + registerCollector: jest.fn(), + }, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + } as unknown) as any; + pluginStart = ({ + data: { + fieldFormats: {}, + }, + } as unknown) as any; + }); + + it('has a sync setup process', () => { + const plugin = new ReportingPlugin(initContext); + + expect(plugin.setup(coreSetup, pluginSetup)).not.toHaveProperty('then'); + }); + + it('logs setup issues', async () => { + const plugin = new ReportingPlugin(initContext); + // @ts-ignore overloading error logger + plugin.logger.error = jest.fn(); + coreSetup.elasticsearch = null; + plugin.setup(coreSetup, pluginSetup); + + await sleep(5); + + // @ts-ignore overloading error logger + expect(plugin.logger.error.mock.calls[0][0]).toMatch( + /Error in Reporting setup, reporting may not function properly/ + ); + // @ts-ignore overloading error logger + expect(plugin.logger.error).toHaveBeenCalledTimes(2); + }); + + it('has a sync startup process', async () => { + const plugin = new ReportingPlugin(initContext); + plugin.setup(coreSetup, pluginSetup); + await sleep(5); + expect(plugin.start(coreStart, pluginStart)).not.toHaveProperty('then'); + }); + + it('logs start issues', async () => { + const plugin = new ReportingPlugin(initContext); + // @ts-ignore overloading error logger + plugin.logger.error = jest.fn(); + plugin.setup(coreSetup, pluginSetup); + await sleep(5); + plugin.start(null as any, pluginStart); + await sleep(10); + // @ts-ignore overloading error logger + expect(plugin.logger.error.mock.calls[0][0]).toMatch( + /Error in Reporting start, reporting may not function properly/ + ); + // @ts-ignore overloading error logger + expect(plugin.logger.error).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index d0d25f6d9e0ae..a3c89c7b8a8ce 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable } from 'rxjs'; -import { first } from 'rxjs/operators'; +import * as Rx from 'rxjs'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server'; -import { ReportingCore } from './core'; -import { ReportingConfigType } from './config'; -import { createBrowserDriverFactory } from './browsers'; -import { buildConfig, createConfig$ } from './config'; +import { ReportingCore } from './'; +import { initializeBrowserDriverFactory } from './browsers'; +import { buildConfig, ReportingConfigType } from './config'; import { createQueueFactory, enqueueJobFactory, LevelLogger, runValidations } from './lib'; import { registerRoutes } from './routes'; import { setFieldFormats } from './services'; @@ -22,61 +20,83 @@ export class ReportingPlugin private readonly initializerContext: PluginInitializerContext; private logger: LevelLogger; private reportingCore?: ReportingCore; - private config$: Observable; + + // Setup some observables for modules that need to await setup/start + public readonly setup$ = new Rx.Subject(); + public readonly start$ = new Rx.Subject(); constructor(context: PluginInitializerContext) { this.logger = new LevelLogger(context.logger.get()); this.initializerContext = context; - this.config$ = context.config.create(); } - public async setup(core: CoreSetup, plugins: ReportingSetupDeps) { + public setup(core: CoreSetup, plugins: ReportingSetupDeps) { const { elasticsearch, http } = core; const { licensing, security } = plugins; const { initializerContext: initContext } = this; const router = http.createRouter(); const basePath = http.basePath.get; - const coreConfig = await createConfig$(core, this.config$, this.logger) - .pipe(first()) - .toPromise(); // apply computed defaults to config - const reportingConfig = buildConfig(initContext, core, coreConfig); // combine kbnServer configs - this.reportingCore = new ReportingCore(reportingConfig); - - const browserDriverFactory = await createBrowserDriverFactory(reportingConfig, this.logger); - - this.reportingCore.pluginSetup({ - browserDriverFactory, - elasticsearch, - licensing, - basePath, - router, - security, - }); + // async background setup + (async () => { + const config = await buildConfig(initContext, core, this.logger); + const reportingCore = new ReportingCore(config); + + reportingCore.pluginSetup({ + elasticsearch, + licensing, + basePath, + router, + security, + }); - runValidations(reportingConfig, elasticsearch, browserDriverFactory, this.logger); - registerReportingUsageCollector(this.reportingCore, plugins); - registerRoutes(this.reportingCore, this.logger); + registerReportingUsageCollector(reportingCore, plugins); + registerRoutes(reportingCore, this.logger); + this.reportingCore = reportingCore; + + this.logger.debug('Setup complete'); + this.setup$.next(true); + })().catch((e) => { + this.logger.error(`Error in Reporting setup, reporting may not function properly`); + this.logger.error(e); + }); return {}; } - public async start(core: CoreStart, plugins: ReportingStartDeps) { + public start(core: CoreStart, plugins: ReportingStartDeps) { + // use data plugin for csv formats + setFieldFormats(plugins.data.fieldFormats); + const { logger } = this; const reportingCore = this.getReportingCore(); + const config = reportingCore.getConfig(); + const { elasticsearch } = reportingCore.getPluginSetupDeps(); - const esqueue = await createQueueFactory(reportingCore, logger); - const enqueueJob = enqueueJobFactory(reportingCore, logger); + // async background start + (async () => { + const browserDriverFactory = await initializeBrowserDriverFactory(config, logger); + reportingCore.setBrowserDriverFactory(browserDriverFactory); - reportingCore.pluginStart({ - savedObjects: core.savedObjects, - uiSettings: core.uiSettings, - esqueue, - enqueueJob, - }); + const esqueue = await createQueueFactory(reportingCore, logger); + const enqueueJob = enqueueJobFactory(reportingCore, logger); - setFieldFormats(plugins.data.fieldFormats); - logger.info('reporting plugin started'); + reportingCore.pluginStart({ + savedObjects: core.savedObjects, + uiSettings: core.uiSettings, + esqueue, + enqueueJob, + }); + + // run self-check validations + runValidations(config, elasticsearch, browserDriverFactory, this.logger); + + this.logger.debug('Start complete'); + this.start$.next(true); + })().catch((e) => { + this.logger.error(`Error in Reporting start, reporting may not function properly`); + this.logger.error(e); + }); return {}; } diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index 8c35f79ec0fb4..29cf55bc5c72e 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -22,7 +22,7 @@ interface ListQuery { } const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export async function registerJobInfoRoutes(reporting: ReportingCore) { +export function registerJobInfoRoutes(reporting: ReportingCore) { const config = reporting.getConfig(); const setupDeps = reporting.getPluginSetupDeps(); const userHandler = authorizedUserPreRoutingFactory(reporting); diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts index 5b0d740e031ab..97e22e2ca2863 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts @@ -6,8 +6,7 @@ import { Page } from 'puppeteer'; import * as Rx from 'rxjs'; -import { HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers'; -import { createDriverFactory } from '../browsers/chromium'; +import { chromium, HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers'; import * as contexts from '../export_types/common/lib/screenshots/constants'; import { LevelLogger } from '../lib'; import { CaptureConfig, ElementsPositionAndAttribute } from '../types'; @@ -113,8 +112,12 @@ export const createMockBrowserDriverFactory = async ( maxAttempts: 1, }; - const binaryPath = '/usr/local/share/common/secure/'; - const mockBrowserDriverFactory = await createDriverFactory(binaryPath, logger, captureConfig); + const binaryPath = '/usr/local/share/common/secure/super_awesome_binary'; + const mockBrowserDriverFactory = await chromium.createDriverFactory( + binaryPath, + captureConfig, + logger + ); const mockPage = {} as Page; const mockBrowserDriver = new HeadlessChromiumDriver(mockPage, { inspect: true, diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index b04e697d0a118..669381a92c522 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -7,49 +7,65 @@ jest.mock('../routes'); jest.mock('../usage'); jest.mock('../browsers'); -jest.mock('../browsers'); jest.mock('../lib/create_queue'); jest.mock('../lib/enqueue_job'); jest.mock('../lib/validate'); import { of } from 'rxjs'; +import { first } from 'rxjs/operators'; import { coreMock } from 'src/core/server/mocks'; import { ReportingConfig, ReportingCore } from '../'; +import { + chromium, + HeadlessChromiumDriverFactory, + initializeBrowserDriverFactory, +} from '../browsers'; import { ReportingInternalSetup } from '../core'; import { ReportingPlugin } from '../plugin'; import { ReportingSetupDeps, ReportingStartDeps } from '../types'; +(initializeBrowserDriverFactory as jest.Mock< + Promise +>).mockImplementation(() => Promise.resolve({} as HeadlessChromiumDriverFactory)); + +(chromium as any).createDriverFactory.mockImplementation(() => ({})); + const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => { return { security: setupMock.security, licensing: { license$: of({ isAvailable: true, isActive: true, type: 'basic' }), } as any, - usageCollection: {} as any, + usageCollection: { + makeUsageCollector: jest.fn(), + registerCollector: jest.fn(), + } as any, }; }; +export const createMockConfigSchema = (overrides?: any) => ({ + index: '.reporting', + kibanaServer: { + hostname: 'localhost', + port: '80', + }, + capture: { + browser: { + chromium: { + disableSandbox: true, + }, + }, + }, + ...overrides, +}); + export const createMockStartDeps = (startMock?: any): ReportingStartDeps => ({ data: startMock.data, }); const createMockReportingPlugin = async (config: ReportingConfig): Promise => { - const mockConfig = { - index: '.reporting', - kibanaServer: { - hostname: 'localhost', - port: '80', - }, - capture: { - browser: { - chromium: { - disableSandbox: true, - }, - }, - }, - ...config, - }; - const plugin = new ReportingPlugin(coreMock.createPluginInitializerContext(mockConfig)); + const mockConfigSchema = createMockConfigSchema(config); + const plugin = new ReportingPlugin(coreMock.createPluginInitializerContext(mockConfigSchema)); const setupMock = coreMock.createSetup(); const coreStartMock = coreMock.createStart(); const startMock = { @@ -57,8 +73,10 @@ const createMockReportingPlugin = async (config: ReportingConfig): Promise