import { combineLatest , ConnectableObservable , EMPTY , Observable , Subscription } from 'rxjs' ; import { first , map , publishReplay , tap } from 'rxjs/operators' ; import type { PublicMethodsOf } from '@osd/utility-types' ; import { PathConfigType } from '@osd/utils' ; import { Config as LegacyConfigClass } from '../../../legacy/server/config' ; import { CoreService } from '../../types' ; import { Config } from '../config' ; import { CoreContext } from '../core_context' ; import { CspConfigType , config as cspConfig } from '../csp' ; import { DevConfig , DevConfigType , config as devConfig } from '../dev' ; import { BasePathProxyServer , HttpConfig , HttpConfigType , config as httpConfig } from '../http' ; import { Logger } from '../logging' ; import { LegacyServiceSetupDeps , LegacyServiceStartDeps , LegacyConfig , LegacyVars } from './types' ; import { CoreSetup , CoreStart } from '..' ; interface LegacyOsdServer { applyLoggingConfiguration : ( settings : Readonly < LegacyVars > ) => void ; listen : ( ) => Promise < void > ; ready : ( ) => Promise < void > ; close : ( ) => Promise < void > ; } function getLegacyRawConfig ( config , pathConfig ) { const rawConfig = config . toRaw ( ) ; if ( rawConfig . opensearch !== undefined ) { delete rawConfig . opensearch ; } return { ... rawConfig , path : pathConfig , } ; } internal export type ILegacyService = PublicMethodsOf < LegacyService > ; internal export class LegacyService implements CoreService { /** Symbol to represent the legacy platform as a fake "plugin". Used by the ContextService */ public readonly legacyId = Symbol ( ) ; private readonly log : Logger ; private readonly devConfig$ : Observable < DevConfig > ; private readonly httpConfig$ : Observable < HttpConfig > ; private osdServer ? : LegacyOsdServer ; private configSubscription ? : Subscription ; private setupDeps ? : LegacyServiceSetupDeps ; private update$ ? : ConnectableObservable < [ Config , PathConfigType ] > ; private legacyRawConfig ? : LegacyConfig ; private settings ? : LegacyVars ; constructor ( private readonly coreContext ) { const { logger , configService } = coreContext ; this . log = logger . get ( 'legacy-service' ) ; this . devConfig$ = configService . atPath < DevConfigType > ( devConfig . path ) . pipe ( map ( ( rawConfig ) => new DevConfig ( rawConfig ) ) ) ; this . httpConfig$ = combineLatest ( configService . atPath < HttpConfigType > ( httpConfig . path ) , configService . atPath < CspConfigType > ( cspConfig . path ) ) . pipe ( map ( ( [ http , csp ] ) => new HttpConfig ( http , csp ) ) ) ; } public async setupLegacyConfig ( ) { this . update$ = combineLatest ( [ this . coreContext . configService . getConfig$ ( ) , this . coreContext . configService . atPath < PathConfigType > ( 'path' ) , ] ) . pipe ( tap ( ( [ config , pathConfig ] ) => { if ( this . osdServer !== undefined ) { this . osdServer . applyLoggingConfiguration ( getLegacyRawConfig ( config , pathConfig ) ) ; } } ) , tap ( { error : ( err ) => this . log . error ( err ) } ) , publishReplay ( 1 ) ) as ConnectableObservable < [ Config , PathConfigType ] > ; this . configSubscription = this . update$ . connect ( ) ; this . settings = await this . update$ . pipe ( first ( ) , map ( ( [ config , pathConfig ] ) => getLegacyRawConfig ( config , pathConfig ) ) ) . toPromise ( ) ; this . legacyRawConfig = LegacyConfigClass . withDefaultSchema ( this . settings ) ; return { settings : this . settings , legacyConfig : this . legacyRawConfig ! , } ; } public async setup ( setupDeps ) { this . log . debug ( 'setting up legacy service' ) ; if ( ! this . legacyRawConfig ) { throw new Error ( 'Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()' ) ; } this . legacyRawConfig ! . set ( 'server.uuid' , setupDeps . core . environment . instanceUuid ) ; this . setupDeps = setupDeps ; } public async start ( startDeps ) { const { setupDeps } = this ; if ( ! setupDeps || ! this . legacyRawConfig ) { throw new Error ( 'Legacy service is not setup yet.' ) ; } this . log . debug ( 'starting legacy service' ) ; if ( this . coreContext . env . isDevClusterMaster ) { await this . createClusterManager ( this . legacyRawConfig ! ) ; } else { this . osdServer = await this . createOsdServer ( this . settings ! , this . legacyRawConfig ! , setupDeps , startDeps ) ; } } public async stop ( ) { this . log . debug ( 'stopping legacy service' ) ; if ( this . configSubscription !== undefined ) { this . configSubscription . unsubscribe ( ) ; this . configSubscription = undefined ; } if ( this . osdServer !== undefined ) { await this . osdServer . close ( ) ; this . osdServer = undefined ; } } private async createClusterManager ( config ) { const basePathProxy$ = this . coreContext . env . cliArgs . basePath ? combineLatest ( [ this . devConfig$ , this . httpConfig$ ] ) . pipe ( first ( ) , map ( ( [ dev , http ] ) => new BasePathProxyServer ( this . coreContext . logger . get ( 'server' ) , http , dev ) ) ) : EMPTY ; const { ClusterManager } = require ( './cluster_manager' ) ; return new ClusterManager ( this . coreContext . env . cliArgs , config , await basePathProxy$ . toPromise ( ) ) ; } private async createOsdServer ( settings , config , setupDeps , startDeps ) { const coreStart = { capabilities : startDeps . core . capabilities , opensearch : startDeps . core . opensearch , http : { auth : startDeps . core . http . auth , basePath : startDeps . core . http . basePath , getServerInfo : startDeps . core . http . getServerInfo , } , savedObjects : { getScopedClient : startDeps . core . savedObjects . getScopedClient , createScopedRepository : startDeps . core . savedObjects . createScopedRepository , createInternalRepository : startDeps . core . savedObjects . createInternalRepository , createSerializer : startDeps . core . savedObjects . createSerializer , getTypeRegistry : startDeps . core . savedObjects . getTypeRegistry , } , metrics : { collectionInterval : startDeps . core . metrics . collectionInterval , getOpsMetrics$ : startDeps . core . metrics . getOpsMetrics$ , } , uiSettings : { asScopedToClient : startDeps . core . uiSettings . asScopedToClient } , auditTrail : startDeps . core . auditTrail , coreUsageData : { getCoreUsageData : ( ) => { throw new Error ( 'core.start.coreUsageData.getCoreUsageData is unsupported in legacy' ) ; } , } , } ; const router = setupDeps . core . http . createRouter ( '' , this . legacyId ) ; const coreSetup = { capabilities : setupDeps . core . capabilities , context : setupDeps . core . context , opensearch : { legacy : setupDeps . core . opensearch . legacy , } , http : { createCookieSessionStorageFactory : setupDeps . core . http . createCookieSessionStorageFactory , registerRouteHandlerContext : setupDeps . core . http . registerRouteHandlerContext . bind ( null , this . legacyId ) , createRouter : ( ) => router , resources : setupDeps . core . httpResources . createRegistrar ( router ) , registerOnPreRouting : setupDeps . core . http . registerOnPreRouting , registerOnPreAuth : setupDeps . core . http . registerOnPreAuth , registerAuth : setupDeps . core . http . registerAuth , registerOnPostAuth : setupDeps . core . http . registerOnPostAuth , registerOnPreResponse : setupDeps . core . http . registerOnPreResponse , basePath : setupDeps . core . http . basePath , auth : { get : setupDeps . core . http . auth . get , isAuthenticated : setupDeps . core . http . auth . isAuthenticated , } , csp : setupDeps . core . http . csp , getServerInfo : setupDeps . core . http . getServerInfo , } , logging : { configure : ( config$ ) => setupDeps . core . logging . configure ( [ ] , config$ ) , } , metrics : { collectionInterval : setupDeps . core . metrics . collectionInterval , getOpsMetrics$ : setupDeps . core . metrics . getOpsMetrics$ , } , savedObjects : { setClientFactoryProvider : setupDeps . core . savedObjects . setClientFactoryProvider , addClientWrapper : setupDeps . core . savedObjects . addClientWrapper , registerType : setupDeps . core . savedObjects . registerType , getImportExportObjectLimit : setupDeps . core . savedObjects . getImportExportObjectLimit , } , status : { isStatusPageAnonymous : setupDeps . core . status . isStatusPageAnonymous , core$ : setupDeps . core . status . core$ , overall$ : setupDeps . core . status . overall$ , set : ( ) => { throw new Error ( `core.status.set is unsupported in legacy` ) ; } , get dependencies$ ( ) { throw new Error ( `core.status.dependencies$ is unsupported in legacy` ) ; } , get derivedStatus$ ( ) { throw new Error ( `core.status.derivedStatus$ is unsupported in legacy` ) ; } , } , uiSettings : { register : setupDeps . core . uiSettings . register , } , auditTrail : setupDeps . core . auditTrail , getStartServices : ( ) => Promise . resolve ( [ coreStart , startDeps . plugins , { } ] ) , } ; const OsdServer = require ( '../../../legacy/server/osd_server' ) ; const osdServer = new OsdServer ( settings , config , { env : { mode : this . coreContext . env . mode , packageInfo : this . coreContext . env . packageInfo , } , setupDeps : { core : coreSetup , plugins : setupDeps . plugins , } , startDeps : { core : coreStart , plugins : startDeps . plugins , } , __internals : { hapiServer : setupDeps . core . http . server , uiPlugins : setupDeps . uiPlugins , rendering : setupDeps . core . rendering , } , logger : this . coreContext . logger , } ) ; if ( this . coreContext . env . cliArgs . repl && process . env . osdWorkerType === 'server' ) { require ( './cli' ) . startRepl ( osdServer ) ; } const { autoListen } = await this . httpConfig$ . pipe ( first ( ) ) . toPromise ( ) ; if ( autoListen ) { try { await osdServer . listen ( ) ; } catch ( err ) { await osdServer . close ( ) ; throw err ; } } else { await osdServer . ready ( ) ; } return osdServer ; } }