From aecdbb7bc040e77cb15473424e85db16d0a5eeb3 Mon Sep 17 00:00:00 2001 From: Zhongnan Su Date: Mon, 8 Nov 2021 15:24:02 -0800 Subject: [PATCH] add reporting osd server configurations Signed-off-by: Zhongnan Su --- dashboards-reports/opensearch_dashboards.json | 3 +- dashboards-reports/server/config/config.ts | 69 +++++++++++++++++++ .../server/config/createConfig.ts | 53 ++++++++++++++ dashboards-reports/server/config/index.ts | 19 +++++ dashboards-reports/server/config/schema.ts | 41 +++++++++++ dashboards-reports/server/index.ts | 18 +++-- dashboards-reports/server/plugin.ts | 45 ++++++++---- dashboards-reports/server/routes/index.ts | 8 +-- .../server/routes/lib/createReport.ts | 12 ++-- dashboards-reports/server/routes/report.ts | 25 +++---- .../server/routes/reportDefinition.ts | 13 ++-- .../converters/__tests__/backendToUi.test.ts | 19 ++--- 12 files changed, 254 insertions(+), 71 deletions(-) create mode 100644 dashboards-reports/server/config/config.ts create mode 100644 dashboards-reports/server/config/createConfig.ts create mode 100644 dashboards-reports/server/config/index.ts create mode 100644 dashboards-reports/server/config/schema.ts diff --git a/dashboards-reports/opensearch_dashboards.json b/dashboards-reports/opensearch_dashboards.json index d1034d2a..962332e8 100644 --- a/dashboards-reports/opensearch_dashboards.json +++ b/dashboards-reports/opensearch_dashboards.json @@ -5,5 +5,6 @@ "requiredPlugins": ["navigation", "data", "opensearchDashboardsUtils"], "optionalPlugins": ["share"], "server": true, - "ui": true + "ui": true, + "configPath": ["opensearch_reporting"] } diff --git a/dashboards-reports/server/config/config.ts b/dashboards-reports/server/config/config.ts new file mode 100644 index 00000000..a93452df --- /dev/null +++ b/dashboards-reports/server/config/config.ts @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { + CoreSetup, + Logger, + PluginInitializerContext, +} from '../../../../src/core/server'; +import { ReportingConfigType } from './schema'; +import { get } from 'lodash'; +import { first, map } from 'rxjs/operators'; +import { createConfig$ } from './createConfig'; + +interface Config { + get(key1: Key1): BaseType[Key1]; + get( + key1: Key1, + key2: Key2 + ): BaseType[Key1][Key2]; +} + +interface OsdServerConfigType { + server: { + basePath: string; + host: string; + name: string; + port: number; + protocol: string; + }; +} + +export interface ReportingConfig extends Config { + osdConfig: Config; +} + +export const buildConfig = async ( + initContext: PluginInitializerContext, + core: CoreSetup, + logger: Logger +): Promise => { + const config$ = initContext.config.create(); + const serverInfo = core.http.getServerInfo(); + const osdConfig = { + server: { + basePath: core.http.basePath.serverBasePath, + host: serverInfo.hostname, + name: serverInfo.name, + port: serverInfo.port, + protocol: serverInfo.protocol, + }, + }; + + const reportingConfig$ = createConfig$(core, config$, logger); + const reportingConfig = await reportingConfig$.pipe(first()).toPromise(); + return { + get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null), + osdConfig: { + get: (...keys: string[]) => get(osdConfig, keys.join('.'), null), + }, + }; +}; diff --git a/dashboards-reports/server/config/createConfig.ts b/dashboards-reports/server/config/createConfig.ts new file mode 100644 index 00000000..f8f30cbe --- /dev/null +++ b/dashboards-reports/server/config/createConfig.ts @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { CoreSetup, Logger } from '../../../../src/core/server'; + +import { ReportingConfigType } from './schema'; + +/* + * Set up dynamic config defaults + */ +export function createConfig$( + core: CoreSetup, + config$: Observable, + logger: Logger +) { + return config$.pipe( + map((config) => { + const { osd_server: reportingServer } = config; + const serverInfo = core.http.getServerInfo(); + // osd_server.hostname, default to server.host + const osdServerHostname = reportingServer.hostname + ? reportingServer.hostname + : serverInfo.hostname; + + // osd_server.port, default to server.port + const osdServerPort = reportingServer.port + ? reportingServer.port + : serverInfo.port; + // osd_server.protocol, default to server.protocol + const osdServerProtocol = reportingServer.protocol + ? reportingServer.protocol + : serverInfo.protocol; + return { + ...config, + osd_server: { + hostname: osdServerHostname, + port: osdServerPort, + protocol: osdServerProtocol, + }, + }; + }) + ); +} diff --git a/dashboards-reports/server/config/index.ts b/dashboards-reports/server/config/index.ts new file mode 100644 index 00000000..54d7d8f4 --- /dev/null +++ b/dashboards-reports/server/config/index.ts @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { PluginConfigDescriptor } from '../../../../src/core/server'; +import { ConfigSchema, ReportingConfigType } from './schema'; +export { buildConfig } from './config'; +export { ConfigSchema, ReportingConfigType }; + +export const config: PluginConfigDescriptor = { + schema: ConfigSchema, +}; diff --git a/dashboards-reports/server/config/schema.ts b/dashboards-reports/server/config/schema.ts new file mode 100644 index 00000000..672d7c6c --- /dev/null +++ b/dashboards-reports/server/config/schema.ts @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { schema, TypeOf } from '@osd/config-schema'; + +const OsdServerSchema = schema.object({ + hostname: schema.maybe( + schema.string({ + validate(value) { + if (value === '0') { + return 'must not be "0" for the headless browser to correctly resolve the host'; + } + }, + hostname: true, + }) + ), + port: schema.maybe(schema.number()), + protocol: schema.maybe( + schema.string({ + validate(value) { + if (!/^https?$/.test(value)) { + return 'must be "http" or "https"'; + } + }, + }) + ), +}); // default values are all dynamic in createConfig$ + +export const ConfigSchema = schema.object({ + osd_server: OsdServerSchema, +}); + +export type ReportingConfigType = TypeOf; diff --git a/dashboards-reports/server/index.ts b/dashboards-reports/server/index.ts index 965b5a1e..00f91201 100644 --- a/dashboards-reports/server/index.ts +++ b/dashboards-reports/server/index.ts @@ -24,21 +24,19 @@ * permissions and limitations under the License. */ -import { - HttpServerInfo, - PluginInitializerContext, -} from '../../../src/core/server'; +import { PluginInitializerContext } from '../../../src/core/server'; +import { ReportingConfigType } from './config'; import { ReportsDashboardsPlugin } from './plugin'; -export type AccessInfoType = { - basePath: string; - serverInfo: HttpServerInfo; -}; +export { config } from './config'; +export { ReportingConfig } from './config/config'; +export { ReportsDashboardsPlugin as Plugin }; // This exports static code and TypeScript types, // as well as, OpenSearch Dashboards Platform `plugin()` initializer. - -export function plugin(initializerContext: PluginInitializerContext) { +export function plugin( + initializerContext: PluginInitializerContext +) { return new ReportsDashboardsPlugin(initializerContext); } diff --git a/dashboards-reports/server/plugin.ts b/dashboards-reports/server/plugin.ts index 2103933c..885455e0 100644 --- a/dashboards-reports/server/plugin.ts +++ b/dashboards-reports/server/plugin.ts @@ -42,8 +42,9 @@ import { import registerRoutes from './routes'; import { pollAndExecuteJob } from './executor/executor'; import { POLL_INTERVAL } from './utils/constants'; -import { AccessInfoType } from 'server'; import { NotificationsPlugin } from './clusters/notificationsPlugin'; +import { buildConfig, ReportingConfigType } from './config'; +import { ReportingConfig } from './config/config'; export interface ReportsPluginRequestContext { logger: Logger; @@ -61,24 +62,40 @@ export class ReportsDashboardsPlugin Plugin { private readonly logger: Logger; private readonly semaphore: SemaphoreInterface; - - constructor(initializerContext: PluginInitializerContext) { - this.logger = initializerContext.logger.get(); - + private readonly initializerContext: PluginInitializerContext< + ReportingConfigType + >; + private reportingConfig?: ReportingConfig; + + constructor(context: PluginInitializerContext) { + this.logger = context.logger.get(); + this.initializerContext = context; const timeoutError = new Error('Server busy'); timeoutError.statusCode = 503; this.semaphore = withTimeout(new Semaphore(1), 180000, timeoutError); } - public setup(core: CoreSetup) { + public async setup(core: CoreSetup) { this.logger.debug('reports-dashboards: Setup'); - const config = core.http.getServerInfo(); - const serverBasePath = core.http.basePath.serverBasePath; - const accessInfo: AccessInfoType = { - basePath: serverBasePath, - serverInfo: config, - }; + try { + const config = await buildConfig( + this.initializerContext, + core, + this.logger + ); + this.reportingConfig = config; + this.logger.debug('Setup complete'); + } catch (error) { + this.logger.error( + `Error in Reporting setup, reporting may not function properly` + ); + this.logger.error(error); + } + + if (!this.reportingConfig) { + throw new Error('Reporting Config is not initialized'); + } const router = core.http.createRouter(); // Deprecated API. Switch to the new opensearch client as soon as https://github.com/elastic/kibana/issues/35508 done. @@ -97,7 +114,7 @@ export class ReportsDashboardsPlugin ); // Register server side APIs - registerRoutes(router, accessInfo); + registerRoutes(router, this.reportingConfig); // put logger into route handler context, so that we don't need to pass through parameters core.http.registerRouteHandlerContext( @@ -108,7 +125,7 @@ export class ReportsDashboardsPlugin logger: this.logger, semaphore: this.semaphore, opensearchReportsClient, - notificationsClient + notificationsClient, }; } ); diff --git a/dashboards-reports/server/routes/index.ts b/dashboards-reports/server/routes/index.ts index efb87139..52949450 100644 --- a/dashboards-reports/server/routes/index.ts +++ b/dashboards-reports/server/routes/index.ts @@ -30,11 +30,11 @@ import registerReportSourceRoute from './reportSource'; import registerMetricRoute from './metric'; import registerNotificationRoute from './notifications'; import { IRouter } from '../../../../src/core/server'; -import { AccessInfoType } from 'server'; +import { ReportingConfig } from 'server/config/config'; -export default function (router: IRouter, accessInfo: AccessInfoType) { - registerReportRoute(router, accessInfo); - registerReportDefinitionRoute(router, accessInfo); +export default function (router: IRouter, config: ReportingConfig) { + registerReportRoute(router, config); + registerReportDefinitionRoute(router, config); registerReportSourceRoute(router); registerMetricRoute(router); registerNotificationRoute(router); diff --git a/dashboards-reports/server/routes/lib/createReport.ts b/dashboards-reports/server/routes/lib/createReport.ts index 56bf0d39..92977158 100644 --- a/dashboards-reports/server/routes/lib/createReport.ts +++ b/dashboards-reports/server/routes/lib/createReport.ts @@ -46,14 +46,14 @@ import { SetCookie, Headers } from 'puppeteer-core'; import { updateReportState } from './updateReportState'; import { saveReport } from './saveReport'; import { SemaphoreInterface } from 'async-mutex'; -import { AccessInfoType } from 'server'; +import { ReportingConfig } from 'server'; import _ from 'lodash'; export const createReport = async ( request: OpenSearchDashboardsRequest, context: RequestHandlerContext, report: ReportSchemaType, - accessInfo: AccessInfoType, + config: ReportingConfig, savedReportId?: string ): Promise => { const isScheduledTask = false; @@ -73,10 +73,10 @@ export const createReport = async ( request.query.dateFormat || DATA_REPORT_CONFIG.excelDateFormat; // @ts-ignore const csvSeparator = request.query.csvSeparator || ','; - const { - basePath, - serverInfo: { protocol, port, hostname }, - } = accessInfo; + const protocol = config.get('osd_server', 'protocol'); + const hostname = config.get('osd_server', 'hostname'); + const port = config.get('osd_server', 'port'); + const basePath = config.osdConfig.get('server', 'basePath'); let createReportResult: CreateReportResultType; let reportId; diff --git a/dashboards-reports/server/routes/report.ts b/dashboards-reports/server/routes/report.ts index 268a0234..74229930 100644 --- a/dashboards-reports/server/routes/report.ts +++ b/dashboards-reports/server/routes/report.ts @@ -42,13 +42,13 @@ import { } from './utils/converters/backendToUi'; import { addToMetric } from './utils/metricHelper'; import { validateReport } from '../../server/utils/validationHelper'; -import { AccessInfoType } from 'server'; +import { ReportingConfig } from 'server'; -export default function (router: IRouter, accessInfo: AccessInfoType) { - const { - basePath, - serverInfo: { protocol, port, hostname }, - } = accessInfo; +export default function (router: IRouter, config: ReportingConfig) { + const protocol = config.get('osd_server', 'protocol'); + const hostname = config.get('osd_server', 'hostname'); + const port = config.get('osd_server', 'port'); + const basePath = config.osdConfig.get('server', 'basePath'); // generate report (with provided metadata) router.post( { @@ -71,7 +71,6 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { //@ts-ignore const logger: Logger = context.reporting_plugin.logger; let report = request.body; - // input validation try { report.report_definition.report_params.core_params.origin = `${protocol}://${hostname}:${port}${basePath}`; @@ -87,12 +86,7 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { } try { - const reportData = await createReport( - request, - context, - report, - accessInfo - ); + const reportData = await createReport(request, context, report, config); // if not deliver to user himself , no need to send actual file data to client const delivery = report.report_definition.delivery; @@ -136,7 +130,6 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { addToMetric('report', 'download', 'count'); //@ts-ignore const logger: Logger = context.reporting_plugin.logger; - let report: any; try { const savedReportId = request.params.reportId; // @ts-ignore @@ -160,7 +153,7 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { request, context, report, - accessInfo, + config, savedReportId ); addToMetric('report', 'download', 'count', report); @@ -231,7 +224,7 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { request, context, report, - accessInfo, + config, reportId ); addToMetric('report', 'create_from_definition', 'count', report); diff --git a/dashboards-reports/server/routes/reportDefinition.ts b/dashboards-reports/server/routes/reportDefinition.ts index 2f59ced7..471efa00 100644 --- a/dashboards-reports/server/routes/reportDefinition.ts +++ b/dashboards-reports/server/routes/reportDefinition.ts @@ -42,13 +42,14 @@ import { updateReportDefinition } from './lib/updateReportDefinition'; import { DEFAULT_MAX_SIZE } from './utils/constants'; import { addToMetric } from './utils/metricHelper'; import { validateReportDefinition } from '../../server/utils/validationHelper'; -import { AccessInfoType } from 'server'; +import { ReportingConfig } from 'server'; + +export default function (router: IRouter, config: ReportingConfig) { + const protocol = config.get('osd_server', 'protocol'); + const hostname = config.get('osd_server', 'hostname'); + const port = config.get('osd_server', 'port'); + const basePath = config.osdConfig.get('server', 'basePath'); -export default function (router: IRouter, accessInfo: AccessInfoType) { - const { - basePath, - serverInfo: { protocol, port, hostname }, - } = accessInfo; // Create report Definition router.post( { diff --git a/dashboards-reports/server/routes/utils/converters/__tests__/backendToUi.test.ts b/dashboards-reports/server/routes/utils/converters/__tests__/backendToUi.test.ts index 895695d4..c09588c4 100644 --- a/dashboards-reports/server/routes/utils/converters/__tests__/backendToUi.test.ts +++ b/dashboards-reports/server/routes/utils/converters/__tests__/backendToUi.test.ts @@ -24,7 +24,7 @@ * permissions and limitations under the License. */ -import { AccessInfoType } from 'server'; +import { ReportingConfig } from 'server/config/config'; import { BackendReportInstanceType, BACKEND_DELIVERY_FORMAT, @@ -83,19 +83,10 @@ const input: BackendReportInstanceType = { status: BACKEND_REPORT_STATE.success, }; -const testAccessInfo: AccessInfoType = { - basePath: '', - serverInfo: { - name: '', - hostname: 'localhost', - port: 5601, - protocol: 'http', - }, -}; +const sampleServerBasePath = '/test'; const output = { - query_url: - "/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?_g=(time:(from:'2020-11-11T00:32:00.000Z',to:'2020-11-11T01:02:00.000Z'))", + query_url: `${sampleServerBasePath}/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?_g=(time:(from:'2020-11-11T00:32:00.000Z',to:'2020-11-11T01:02:00.000Z'))`, time_from: 1605054720000, time_to: 1605056520000, last_updated: 1605056644321, @@ -107,7 +98,7 @@ const output = { report_source: 'Dashboard', description: 'some random', core_params: { - base_url: '/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f', + base_url: `${sampleServerBasePath}/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f`, report_format: 'pdf', header: '

test header

', footer: '

fake footer

', @@ -141,7 +132,7 @@ const output = { describe('test backend to ui model conversion', () => { test('convert backend to ui report', async () => { - const res = backendToUiReport(input, testAccessInfo.basePath); + const res = backendToUiReport(input, sampleServerBasePath); expect(res).toEqual(output); }, 20000); });