From 4dd5b744af446ab44fd8d7ae5b25b72e83de166f Mon Sep 17 00:00:00 2001 From: Kim Joar Bekkelund Date: Sat, 27 May 2017 12:54:37 +0100 Subject: [PATCH] don't prevalidate config --- platform/config/Config.ts | 26 ------- platform/config/ConfigService.ts | 45 ++++++++--- .../__snapshots__/index.test.ts.snap | 3 - platform/config/__tests__/index.test.ts | 7 -- platform/config/index.ts | 1 - platform/config/schema.ts | 28 ------- platform/logger/LoggerConfig.ts | 2 + platform/root/index.ts | 11 +-- .../elasticsearch/ElasticsearchConfigs.ts | 4 +- platform/server/elasticsearch/index.ts | 11 +-- platform/server/http/HttpConfig.ts | 2 + platform/server/http/index.ts | 10 +-- platform/server/index.ts | 78 +++++++++++++++++-- platform/server/kibana/KibanaConfig.ts | 4 +- platform/server/kibana/index.ts | 11 +-- platform/server/pid/PidConfig.ts | 2 + platform/server/pid/index.ts | 17 ++-- .../server/savedObjects/SavedObjectsFacade.ts | 1 - platform/server/savedObjects/index.ts | 1 - platform/types.ts | 8 ++ 20 files changed, 151 insertions(+), 121 deletions(-) delete mode 100644 platform/config/Config.ts delete mode 100644 platform/config/__tests__/__snapshots__/index.test.ts.snap delete mode 100644 platform/config/__tests__/index.test.ts delete mode 100644 platform/config/schema.ts diff --git a/platform/config/Config.ts b/platform/config/Config.ts deleted file mode 100644 index 03e00c400938472..000000000000000 --- a/platform/config/Config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { schema, ConfigType } from './schema'; -import { Env } from '../env'; - -import { LoggerConfig } from '../logger' - -export class Config { - // TODO Consider whether or not these should live here on the config object - // or if we should move them into each service. That way you'd have to depend - // on the relevant service, e.g. `elasticsearchService.getConfig()` instead - // of getting it from here. It kinda feels better when considering plugins - // and so on. - - static create(config: T, env: Env) { - // TODO schema needs to be defined a runtime, e.g. plugins, modules - return new Config(schema.validate(config), env); - } - - private constructor( - private readonly config: ConfigType, - readonly env: Env - ) {} - - atPath(path: T): ConfigType[T] { - return this.config[path]; - } -} diff --git a/platform/config/ConfigService.ts b/platform/config/ConfigService.ts index 4f14fa9fdf0b1f4..cf7002600757173 100644 --- a/platform/config/ConfigService.ts +++ b/platform/config/ConfigService.ts @@ -1,10 +1,11 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { isEqual } from 'lodash'; -import { Config } from './Config'; import { getRawConfig, applyArgv } from './readConfig'; import { Env } from '../env'; import { logger } from '../logger'; +import { Setting, Any, TypeOf } from '../lib/schema'; +import { ConfigWithSchema } from '../types'; const log = logger.get('settings'); @@ -16,26 +17,24 @@ export class ConfigService { private readonly rawConfigFromFile$: BehaviorSubject = new BehaviorSubject(undefined) - private readonly config$: Observable; + private readonly rawConfig$: Observable; constructor( private readonly argv: {[key: string]: any}, private readonly env: Env ) { - this.config$ = this.rawConfigFromFile$ + this.rawConfig$ = this.rawConfigFromFile$ .filter(rawConfig => rawConfig !== undefined) // we need to specify the type here, as we _know_ `RawConfig` no longer // can be `undefined`. .map(rawConfig => applyArgv(argv, rawConfig)) // we only care about reloading the config if there are changes - .distinctUntilChanged((prev, next) => isEqual(prev, next)) - .map(config => Config.create(config, env)); + .distinctUntilChanged((prev, next) => isEqual(prev, next)); } /** * Reads the initial Kibana config */ - // TODO inject schema? start() { this.loadConfig(); } @@ -58,7 +57,35 @@ export class ConfigService { this.rawConfigFromFile$.complete(); } - getConfig() { - return this.config$; + atPath( + path: string, + ConfigClass: ConfigWithSchema + ) { + return this.getDistinctRawConfig(path) + .map(value => { + const config = ConfigClass.schema.validate(value); + return new ConfigClass(config, this.env); + }); + } + + optionalAtPath( + path: string, + ConfigClass: ConfigWithSchema + ) { + return this.getDistinctRawConfig(path) + .map(value => { + if (value === undefined) { + return undefined; + } + + const config = ConfigClass.schema.validate(value); + return new ConfigClass(config, this.env); + }); + } + + private getDistinctRawConfig(path: string) { + return this.rawConfig$ + .map(config => config[path]) + .distinctUntilChanged((prev, next) => isEqual(prev, next)) } -} \ No newline at end of file +} diff --git a/platform/config/__tests__/__snapshots__/index.test.ts.snap b/platform/config/__tests__/__snapshots__/index.test.ts.snap deleted file mode 100644 index a92aec0bbd8d545..000000000000000 --- a/platform/config/__tests__/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws error for unknown keys 1`] = `"missing definitions in schema for keys [foo]"`; diff --git a/platform/config/__tests__/index.test.ts b/platform/config/__tests__/index.test.ts deleted file mode 100644 index 4fdea415e0991ad..000000000000000 --- a/platform/config/__tests__/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Config } from '../'; -import { Env } from '../../env'; - -test('throws error for unknown keys', () => { - expect(() => Config.create({ foo: 'bar' }, Env.createDefault())) - .toThrowErrorMatchingSnapshot(); -}); diff --git a/platform/config/index.ts b/platform/config/index.ts index 4bc4e6ec8fc888d..34ae35688d76e5b 100644 --- a/platform/config/index.ts +++ b/platform/config/index.ts @@ -1,2 +1 @@ export { ConfigService } from './ConfigService'; -export { Config } from './Config'; diff --git a/platform/config/schema.ts b/platform/config/schema.ts deleted file mode 100644 index a4989414417e827..000000000000000 --- a/platform/config/schema.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { object, maybe, string, boolean, TypeOf } from '../lib/schema'; -import { loggerSchema as logging } from '../logger'; -import { elasticsearchSchema as elasticsearch } from '../server/elasticsearch'; -import { httpSchema as server } from '../server/http'; -import { pidSchema as pid } from '../server/pid'; -import { kibanaSchema as kibana } from '../server/kibana'; - -export const schema = object({ - elasticsearch, - kibana, - server, - logging, - pid: maybe(pid), - // this is only here to parse `./config/kibana.dev.yml` - optimize: maybe( - object({ - sourceMaps: string(), - unsafeCache: boolean({ - defaultValue: true - }), - lazyPrebuild: boolean({ - defaultValue: false - }) - }) - ) -}); - -export type ConfigType = TypeOf; diff --git a/platform/logger/LoggerConfig.ts b/platform/logger/LoggerConfig.ts index 35070c4e382f11e..30084fd368c84ac 100644 --- a/platform/logger/LoggerConfig.ts +++ b/platform/logger/LoggerConfig.ts @@ -19,6 +19,8 @@ export const loggerSchema = object({ export type LoggingSchema = TypeOf; export class LoggerConfig { + static schema = loggerSchema; + readonly dest: string; private readonly silent: boolean; private readonly quiet: boolean; diff --git a/platform/root/index.ts b/platform/root/index.ts index 4794da6a1040a0a..7226069a12ecfe0 100644 --- a/platform/root/index.ts +++ b/platform/root/index.ts @@ -4,6 +4,7 @@ import { Server } from '../server'; import { Env } from '../env'; import { ConfigService } from '../config'; import { loggerService, logger, LoggerConfig } from '../logger'; +import { HttpModule } from '../server/http' const log = logger.get('root'); @@ -23,16 +24,16 @@ export class Root { start() { this.configService.start(); - const config$ = this.configService.getConfig(); - - const loggingConfig$ = config$ - .map(config => new LoggerConfig(config.atPath('logging'))); + const loggingConfig$ = this.configService.atPath( + 'logging', + LoggerConfig + ); loggerService.upgrade(loggingConfig$); log.info('starting the server'); - this.server = new Server(config$); + this.server = new Server(this.configService); this.server.start(); } diff --git a/platform/server/elasticsearch/ElasticsearchConfigs.ts b/platform/server/elasticsearch/ElasticsearchConfigs.ts index a7c76033377c5a6..bcaa1c7954f99da 100644 --- a/platform/server/elasticsearch/ElasticsearchConfigs.ts +++ b/platform/server/elasticsearch/ElasticsearchConfigs.ts @@ -1,11 +1,13 @@ import { ElasticsearchConfig } from './ElasticsearchConfig'; -import { ElasticsearchConfigsSchema } from './schema'; +import { elasticsearchSchema, ElasticsearchConfigsSchema } from './schema'; import { Env } from '../../env'; export type ElasticsearchClusterType = 'data' | 'admin'; export class ElasticsearchConfigs { + static schema = elasticsearchSchema; + private readonly elasticsearchConfigs: { data: ElasticsearchConfig, admin: ElasticsearchConfig diff --git a/platform/server/elasticsearch/index.ts b/platform/server/elasticsearch/index.ts index 4cc6b19e98f6615..2c299bd282b8d55 100644 --- a/platform/server/elasticsearch/index.ts +++ b/platform/server/elasticsearch/index.ts @@ -5,22 +5,19 @@ import { ElasticsearchFacade } from './ElasticsearchFacade'; import { createElasticsearchRoutes } from './api'; import { RouterFactory } from '../http'; import { IsRouteable } from '../../types'; -import { Config } from '../../config'; import { Schema } from '../../types'; import { ElasticsearchConfigs } from './ElasticsearchConfigs'; -export { elasticsearchSchema } from './schema'; +import { elasticsearchSchema } from './schema'; export { ElasticsearchService, ElasticsearchFacade, ElasticsearchConfigs }; export class ElasticsearchModule implements IsRouteable { + static config = ElasticsearchConfigs; + readonly service: ElasticsearchService; readonly facade: ElasticsearchFacade; - readonly config$: Observable; - constructor(config$: Observable) { - this.config$ = config$.map(config => - new ElasticsearchConfigs(config.atPath('elasticsearch'), config.env) - ); + constructor(readonly config$: Observable) { this.service = new ElasticsearchService(this.config$); this.facade = new ElasticsearchFacade(this.service); } diff --git a/platform/server/http/HttpConfig.ts b/platform/server/http/HttpConfig.ts index d2ac0e22d8a2b0d..0ec681faa14b3e6 100644 --- a/platform/server/http/HttpConfig.ts +++ b/platform/server/http/HttpConfig.ts @@ -39,6 +39,8 @@ export const httpSchema = object({ export type HttpSchema = TypeOf; export class HttpConfig { + static schema = httpSchema; + host: string; port: number; maxPayload: ByteSizeValue; diff --git a/platform/server/http/index.ts b/platform/server/http/index.ts index b2b62d0acaca1d9..d7faf8b9de6df14 100644 --- a/platform/server/http/index.ts +++ b/platform/server/http/index.ts @@ -1,22 +1,20 @@ import { Observable } from 'rxjs'; -import { Config } from '../../config'; import { HttpService } from './HttpService'; import { HttpConfig } from './HttpConfig'; import { Routers } from './Routing'; +import { httpSchema } from './HttpConfig'; export { httpSchema } from './HttpConfig'; export { KibanaRequest, KibanaResponse, RouterFactory, Router } from './Routing'; export { HttpService, Routers }; export class HttpModule { + static config = HttpConfig; + readonly service: HttpService; - readonly config$: Observable; - constructor(config$: Observable, routers: Routers) { - this.config$ = config$.map(config => - new HttpConfig(config.atPath('server'), config.env) - ); + constructor(readonly config$: Observable, routers: Routers) { this.service = new HttpService(this.config$, routers.routers$); } } \ No newline at end of file diff --git a/platform/server/index.ts b/platform/server/index.ts index a908195f4803dc1..5342679faa23614 100644 --- a/platform/server/index.ts +++ b/platform/server/index.ts @@ -1,17 +1,19 @@ import { Observable, ReplaySubject } from 'rxjs'; +import { isEqual } from 'lodash'; +import { ConfigService } from '../config'; import { HttpModule, Routers } from './http'; import { PidModule } from './pid'; import { SavedObjectsModule } from './savedObjects'; import { ElasticsearchModule } from './elasticsearch'; import { KibanaModule } from './kibana'; import { logger } from '../logger'; -import { Config } from '../config'; import * as schema from '../lib/schema'; import { KibanaRequestHelpers } from '../types'; const log = logger.get('server'); + export class Server { private readonly elasticsearch: ElasticsearchModule; private readonly http: HttpModule; @@ -21,11 +23,32 @@ export class Server { private readonly routers: Routers; constructor( - private readonly config$: Observable + private readonly configService: ConfigService ) { - this.pid = new PidModule(config$); - this.elasticsearch = new ElasticsearchModule(config$); - this.kibana = new KibanaModule(config$); + const pidConfig$ = configService.optionalAtPath( + 'pid', PidModule.config + ); + + const elasticsearchConfigs$ = configService.atPath( + 'elasticsearch', ElasticsearchModule.config + ); + + const kibanaConfig$ = configService.atPath( + 'kibana', KibanaModule.config + ); + + const httpConfig$ = configService.atPath( + 'server', HttpModule.config + ); + + // const configObservables = configService.getConfigFor({ + // ...coreConfigs, + // pid: PidModule.config + // }) + + this.pid = new PidModule(pidConfig$); + this.elasticsearch = new ElasticsearchModule(elasticsearchConfigs$); + this.kibana = new KibanaModule(kibanaConfig$); this.savedObjects = new SavedObjectsModule( this.kibana.config$, this.elasticsearch.service @@ -53,8 +76,51 @@ export class Server { savedObjects: this.savedObjects.facade }; + // const kibanaForPlugins: KibanaHelpers = { + // services: { + // elasticsearch: this.elasticsearch.service + // } + // } + + // const plugins = [ + // watcher, + // monitoring + // ] + + // topologicalSort(plugins).map(plugin => plugin(kibanaForPlugins)) + // type Monitoring = { + // key: string + // } + + // type MonitoringHelper = { + // monitoring: Monitoring + // } + + // type TestHelper = { + // test: { + // foo: number + // } + // } + + // type MyHelper = { + // test?: TestHelper, + // monitoring: Monitoring + // } + + // class KibanaPlugin { + // constructor(obj: T) { + // } + // } + + // class WatcherPlugin extends KibanaPlugin { + // } + + // new WatcherPlugin({ + // monitoring + // }) + this.routers = new Routers(kibana); - this.http = new HttpModule(config$, this.routers); + this.http = new HttpModule(httpConfig$, this.routers); } start() { diff --git a/platform/server/kibana/KibanaConfig.ts b/platform/server/kibana/KibanaConfig.ts index 7c6172c21e5b447..f2b2eac429d9d0e 100644 --- a/platform/server/kibana/KibanaConfig.ts +++ b/platform/server/kibana/KibanaConfig.ts @@ -1,6 +1,8 @@ -import { KibanaConfigSchema } from './schema'; +import { kibanaSchema, KibanaConfigSchema } from './schema'; export class KibanaConfig { + static schema = kibanaSchema; + readonly index: string; constructor(config: KibanaConfigSchema) { diff --git a/platform/server/kibana/index.ts b/platform/server/kibana/index.ts index 01c49ec60a7e1b6..9ced8d32ba63153 100644 --- a/platform/server/kibana/index.ts +++ b/platform/server/kibana/index.ts @@ -1,19 +1,16 @@ import { Observable } from 'rxjs'; -import { Config } from '../../config'; import { ElasticsearchFacade } from '../elasticsearch'; import { SavedObjectsFacade } from '../savedObjects' import { KibanaConfig } from './KibanaConfig'; -export { kibanaSchema } from './schema'; +import { kibanaSchema } from './schema'; export { KibanaConfig }; export class KibanaModule { - readonly config$: Observable; + static config = KibanaConfig; - constructor(config$: Observable) { - this.config$ = config$.map(config => - new KibanaConfig(config.atPath('kibana')) - ) + + constructor(readonly config$: Observable) { } } \ No newline at end of file diff --git a/platform/server/pid/PidConfig.ts b/platform/server/pid/PidConfig.ts index c605ed31d2f76b0..76ed9b7ecfc07cb 100644 --- a/platform/server/pid/PidConfig.ts +++ b/platform/server/pid/PidConfig.ts @@ -33,6 +33,8 @@ function pick< } export class PidConfig { + static schema = pidSchema; + file: string; exclusive: boolean; diff --git a/platform/server/pid/index.ts b/platform/server/pid/index.ts index 0ea9c208c4ecb57..6171277f2292da5 100644 --- a/platform/server/pid/index.ts +++ b/platform/server/pid/index.ts @@ -1,25 +1,18 @@ +import { isEqual } from 'lodash'; import { Observable } from 'rxjs'; -import { Config } from '../../config'; import { Service } from '../../types'; import { PidService } from './PidService'; -import { PidConfig } from './PidConfig'; +import { PidConfig, pidSchema } from './PidConfig'; export { PidConfig, pidSchema } from './PidConfig'; export class PidModule { - readonly service: PidService; - readonly config$: Observable; - - constructor(config$: Observable) { - this.config$ = config$.map(config => { - const pid = config.atPath('pid'); + static config = PidConfig; - return pid !== undefined - ? new PidConfig(pid) - : undefined - }) + readonly service: PidService; + constructor(readonly config$: Observable) { this.service = new PidService(this.config$); } } \ No newline at end of file diff --git a/platform/server/savedObjects/SavedObjectsFacade.ts b/platform/server/savedObjects/SavedObjectsFacade.ts index 6024c3f44bab02d..4f48c30998c164c 100644 --- a/platform/server/savedObjects/SavedObjectsFacade.ts +++ b/platform/server/savedObjects/SavedObjectsFacade.ts @@ -1,7 +1,6 @@ import { Observable } from 'rxjs'; import { ElasticsearchService } from '../elasticsearch/ElasticsearchService'; -import { Config } from '../../config'; import { KibanaConfig } from '../kibana/index'; import { KibanaRequest } from '../http'; diff --git a/platform/server/savedObjects/index.ts b/platform/server/savedObjects/index.ts index cc9d089de598133..7b61fa6b1339766 100644 --- a/platform/server/savedObjects/index.ts +++ b/platform/server/savedObjects/index.ts @@ -3,7 +3,6 @@ import { Observable } from 'rxjs'; import { SavedObjectsFacade } from './SavedObjectsFacade'; import { ElasticsearchService } from '../elasticsearch'; import { KibanaConfig } from '../kibana'; -import { Config } from '../../config'; import { RouterFactory } from '../http/Routing'; import { IsRouteable } from '../../types'; import { Schema } from '../../types'; diff --git a/platform/types.ts b/platform/types.ts index e6d87de1fb56442..a6f21c8790507f6 100644 --- a/platform/types.ts +++ b/platform/types.ts @@ -2,6 +2,7 @@ import * as schema from './lib/schema'; import { RouterFactory, Router } from './server/http'; import { ElasticsearchFacade } from './server/elasticsearch'; import { SavedObjectsFacade } from './server/savedObjects'; +import { Env } from './env' export type Schema = typeof schema; @@ -19,3 +20,10 @@ export type KibanaRequestHelpers = { savedObjects: SavedObjectsFacade, [key: string]: any } + +export interface ConfigWithSchema { + schema: Schema; + + // require that the constructor matches the schema + new (val: schema.TypeOf, env: Env): Config; +}