Skip to content

Commit

Permalink
POC for setup mode via notReady server
Browse files Browse the repository at this point in the history
  • Loading branch information
legrego authored and azasypkin committed Jul 1, 2021
1 parent ba85f45 commit eb23cad
Show file tree
Hide file tree
Showing 29 changed files with 444 additions and 56 deletions.
8 changes: 7 additions & 1 deletion src/core/server/capabilities/capabilities_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,13 @@ export class CapabilitiesService {
public setup(setupDeps: SetupDeps): CapabilitiesSetup {
this.logger.debug('Setting up capabilities service');

registerRoutes(setupDeps.http, this.resolveCapabilities);
registerRoutes(setupDeps.http.createRouter(''), this.resolveCapabilities);

// The not ready server has no need for real capabilities.
// Returning the un-augmented defaults is sufficient.
setupDeps.http.notReadyServer?.registerRoutes('', (notReadyRouter) => {
registerRoutes(notReadyRouter, async () => defaultCapabilities);
});

return {
registerProvider: (provider: CapabilitiesProvider) => {
Expand Down
5 changes: 2 additions & 3 deletions src/core/server/capabilities/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
*/

import { CapabilitiesResolver } from '../resolve_capabilities';
import { InternalHttpServiceSetup } from '../../http';
import { IRouter } from '../../http';
import { registerCapabilitiesRoutes } from './resolve_capabilities';

export function registerRoutes(http: InternalHttpServiceSetup, resolver: CapabilitiesResolver) {
const router = http.createRouter('');
export function registerRoutes(router: IRouter, resolver: CapabilitiesResolver) {
registerCapabilitiesRoutes(router, resolver);
}
52 changes: 49 additions & 3 deletions src/core/server/core_app/core_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ export class CoreApp {
this.env = core.env;
}

setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) {
setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins, notReadyServerUiPlugins: UiPlugins) {
this.logger.debug('Setting up core app.');
this.registerDefaultRoutes(coreSetup, uiPlugins);
this.registerDefaultRoutes(coreSetup, uiPlugins, notReadyServerUiPlugins);
this.registerStaticDirs(coreSetup);
}

private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) {
private registerDefaultRoutes(
coreSetup: InternalCoreSetup,
uiPlugins: UiPlugins,
notReadyServerUiPlugins: UiPlugins
) {
const httpSetup = coreSetup.http;
const router = httpSetup.createRouter('');
const resources = coreSetup.httpResources.createRegistrar(router);
Expand All @@ -51,6 +55,40 @@ export class CoreApp {
});
});

httpSetup.notReadyServer?.registerRoutes('', (notReadyRouter) => {
const notReadyResources = coreSetup.httpResources.createRegistrar(notReadyRouter);

// TODO: need better mechanism for this, -OR- make setup mode render at the root instead.
notReadyRouter.get({ path: '/', validate: false }, async (context, req, res) => {
const defaultRoute = '/app/setup';
const basePath = httpSetup.basePath.get(req);
const url = `${basePath}${defaultRoute}`;

return res.redirected({
headers: {
location: url,
},
});
});

registerBundleRoutes({
router: notReadyRouter,
uiPlugins: notReadyServerUiPlugins,
packageInfo: this.env.packageInfo,
serverBasePath: coreSetup.http.basePath.serverBasePath,
});

notReadyResources.register(
{
path: '/app/{id}/{any*}',
validate: false,
},
async (context, request, response) => {
return response.renderAnonymousCoreApp({ renderTarget: 'notReady' });
}
);
});

// remove trailing slash catch-all
router.get(
{
Expand Down Expand Up @@ -130,8 +168,16 @@ export class CoreApp {
}

private registerStaticDirs(coreSetup: InternalCoreSetup) {
coreSetup.http.notReadyServer?.registerStaticDir(
'/ui/{path*}',
Path.resolve(__dirname, './assets')
);
coreSetup.http.registerStaticDir('/ui/{path*}', Path.resolve(__dirname, './assets'));

coreSetup.http.notReadyServer?.registerStaticDir(
'/node_modules/@kbn/ui-framework/dist/{path*}',
fromRoot('node_modules/@kbn/ui-framework/dist')
);
coreSetup.http.registerStaticDir(
'/node_modules/@kbn/ui-framework/dist/{path*}',
fromRoot('node_modules/@kbn/ui-framework/dist')
Expand Down
38 changes: 21 additions & 17 deletions src/core/server/http/http_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class HttpService
private readonly env: Env;
private notReadyServer?: HttpServer;
private internalSetup?: InternalHttpServiceSetup;
private notReadyServerRequestHandlerContext?: RequestHandlerContextContainer;
private requestHandlerContext?: RequestHandlerContextContainer;

constructor(private readonly coreContext: CoreContext) {
Expand All @@ -75,6 +76,7 @@ export class HttpService
}

public async setup(deps: SetupDeps) {
this.notReadyServerRequestHandlerContext = deps.context.createContextContainer();
this.requestHandlerContext = deps.context.createContextContainer();
this.configSubscription = this.config$.subscribe(() => {
if (this.httpServer.isListening()) {
Expand Down Expand Up @@ -191,27 +193,29 @@ export class HttpService

const notReadySetup = await this.runNotReadyServer(config);

// We cannot use the real context container since the core services may not yet be ready
const fakeContext: RequestHandlerContextContainer = new Proxy(
context.createContextContainer(),
{
get: (target, property, receiver) => {
if (property === 'createHandler') {
return Reflect.get(target, property, receiver);
}
throw new Error(`Unexpected access from fake context: ${String(property)}`);
},
}
);

return {
registerStaticDir: notReadySetup.registerStaticDir.bind(notReadySetup),
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
pluginOpaqueId: PluginOpaqueId,
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) =>
this.notReadyServerRequestHandlerContext!.registerContext(
pluginOpaqueId,
contextName,
provider
),
registerRoutes: (path: string, registerCallback: (router: IRouter) => void) => {
const router = new Router(
path,
this.log,
fakeContext.createHandler.bind(null, this.coreContext.coreId)
const enhanceHandler = this.notReadyServerRequestHandlerContext!.createHandler.bind(
null,
this.coreContext.coreId
);

const router = new Router(path, this.log, enhanceHandler);

registerCallback(router);
notReadySetup.registerRouterAfterListening(router);
},
Expand Down
11 changes: 11 additions & 0 deletions src/core/server/http/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,22 @@ export interface HttpServiceSetup {
* Provides common {@link HttpServerInfo | information} about the running http server.
*/
getServerInfo: () => HttpServerInfo;

notReadyServer?: InternalNotReadyHttpServiceSetup;
}

/** @internal */
export interface InternalNotReadyHttpServiceSetup {
registerRoutes(path: string, callback: (router: IRouter) => void): void;
registerStaticDir: (path: string, dirPath: string) => void;
registerRouteHandlerContext: <
Context extends RequestHandlerContext,
ContextName extends keyof Context
>(
pluginOpaqueId: PluginOpaqueId,
contextName: ContextName,
provider: RequestHandlerContextProvider<Context, ContextName>
) => RequestHandlerContextContainer;
}

/** @internal */
Expand Down
1 change: 1 addition & 0 deletions src/core/server/http_resources/http_resources_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
vars: {
apmConfig,
},
renderTarget: options.renderTarget,
});

return response.ok({
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/http_resources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface HttpResourcesRenderOptions {
* All HTML pages are already pre-configured with `content-security-policy` header that cannot be overridden.
* */
headers?: ResponseHeaders;

renderTarget?: 'notReady' | 'primary';
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/core/server/i18n/i18n_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export class I18nService {
this.log.debug(`Using translation files: [${translationFiles.join(', ')}]`);
await initTranslations(locale, translationFiles);

http.notReadyServer?.registerRoutes('', (notReadyRouter) => {
registerRoutes({ router: notReadyRouter, locale });
});

const router = http.createRouter('');
registerRoutes({ router, locale });

Expand Down
31 changes: 31 additions & 0 deletions src/core/server/not_ready_core_route_handler_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

// eslint-disable-next-line max-classes-per-file
import { InternalCoreSetup } from './internal_types';
import { InternalUiSettingsServiceSetup, IUiSettingsClient } from './ui_settings';

class NotReadyCoreUiSettingsRouteHandlerContext {
#client?: IUiSettingsClient;
constructor(private readonly uiSettingsSetup: InternalUiSettingsServiceSetup) {}

public get client() {
if (this.#client == null) {
this.#client = this.uiSettingsSetup.defaultsClient();
}
return this.#client;
}
}

export class NotReadyCoreRouteHandlerContext {
readonly uiSettings: NotReadyCoreUiSettingsRouteHandlerContext;

constructor(private readonly coreSetup: InternalCoreSetup) {
this.uiSettings = new NotReadyCoreUiSettingsRouteHandlerContext(this.coreSetup.uiSettings);
}
}
2 changes: 2 additions & 0 deletions src/core/server/plugins/discovery/plugin_manifest_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const KNOWN_MANIFEST_FIELDS = (() => {
const manifestFields: { [P in keyof PluginManifest]: boolean } = {
id: true,
kibanaVersion: true,
type: true,
version: true,
configPath: true,
requiredPlugins: true,
Expand Down Expand Up @@ -182,6 +183,7 @@ export async function parseManifest(
id: manifest.id,
version: manifest.version,
kibanaVersion: expectedKibanaVersion,
type: manifest.type ?? 'primary',
configPath: manifest.configPath || snakeCase(manifest.id),
requiredPlugins: Array.isArray(manifest.requiredPlugins) ? manifest.requiredPlugins : [],
optionalPlugins: Array.isArray(manifest.optionalPlugins) ? manifest.optionalPlugins : [],
Expand Down
6 changes: 6 additions & 0 deletions src/core/server/plugins/plugins_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
await this.handleDiscoveryErrors(error$);
await this.handleDiscoveredPlugins(plugin$);

const notReadyServerUiPlugins = this.pluginsSystem.notReadyServerUiPlugins();
const uiPlugins = this.pluginsSystem.uiPlugins();

return {
Expand All @@ -107,6 +108,11 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
public: uiPlugins,
browserConfigs: this.generateUiPluginsConfigs(uiPlugins),
},
notReadyServerUiPlugins: {
internal: this.uiPluginInternalInfo,
public: notReadyServerUiPlugins,
browserConfigs: this.generateUiPluginsConfigs(notReadyServerUiPlugins),
},
};
}

Expand Down
42 changes: 22 additions & 20 deletions src/core/server/plugins/plugins_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,30 +212,32 @@ export class PluginsSystem {
* Get a Map of all discovered UI plugins in topological order.
*/
public uiPlugins() {
return new Map(this.allUiPlugins().filter(([, { type }]) => type === 'primary'));
}

public notReadyServerUiPlugins() {
return new Map(this.allUiPlugins().filter(([, { type }]) => type === 'notReady'));
}

private allUiPlugins(): Array<[string, DiscoveredPlugin]> {
const uiPluginNames = [...this.getTopologicallySortedPluginNames().keys()].filter(
(pluginName) => this.plugins.get(pluginName)!.includesUiPlugin
);
const publicPlugins = new Map<PluginName, DiscoveredPlugin>(
uiPluginNames.map((pluginName) => {
const plugin = this.plugins.get(pluginName)!;
return [
pluginName,
{
id: pluginName,
configPath: plugin.manifest.configPath,
requiredPlugins: plugin.manifest.requiredPlugins.filter((p) =>
uiPluginNames.includes(p)
),
optionalPlugins: plugin.manifest.optionalPlugins.filter((p) =>
uiPluginNames.includes(p)
),
requiredBundles: plugin.manifest.requiredBundles,
},
];
})
);

return publicPlugins;
return uiPluginNames.map((pluginName) => {
const plugin = this.plugins.get(pluginName)!;
return [
pluginName,
{
id: pluginName,
type: plugin.manifest.type,
configPath: plugin.manifest.configPath,
requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => uiPluginNames.includes(p)),
optionalPlugins: plugin.manifest.optionalPlugins.filter((p) => uiPluginNames.includes(p)),
requiredBundles: plugin.manifest.requiredBundles,
},
];
});
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/core/server/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export interface PluginManifest {
*/
readonly kibanaVersion: string;

readonly type: 'notReady' | 'primary';

/**
* Root {@link ConfigPath | configuration path} used by the plugin, defaults
* to "id" in snake_case format.
Expand Down Expand Up @@ -271,6 +273,8 @@ export interface DiscoveredPlugin {
* duplicated here.
*/
readonly requiredBundles: readonly PluginName[];

readonly type: 'notReady' | 'primary';
}

/**
Expand Down
Loading

0 comments on commit eb23cad

Please sign in to comment.