Migrate /api/settings to KP (#77554)

This commit is contained in:
Josh Dover 2020-09-16 14:24:11 -06:00 committed by GitHub
parent c178c8abff
commit b3aabe98aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 298 additions and 82 deletions

View file

@ -494,6 +494,10 @@ in their infrastructure.
|This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation):
|{kib-repo}blob/{branch}/x-pack/plugins/xpack_legacy/README.md[xpackLegacy]
|Contains HTTP endpoints and UiSettings that are slated for removal.
|===
include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1]

View file

@ -21,9 +21,9 @@ import type { PublicMethodsOf } from '@kbn/utility-types';
import { ContextService, ContextSetup } from './context_service';
import { contextMock } from '../../utils/context.mock';
const createSetupContractMock = () => {
const createSetupContractMock = (mockContext = {}) => {
const setupContract: jest.Mocked<ContextSetup> = {
createContextContainer: jest.fn().mockImplementation(() => contextMock.create()),
createContextContainer: jest.fn().mockImplementation(() => contextMock.create(mockContext)),
};
return setupContract;
};

View file

@ -52,6 +52,7 @@ export { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_object
export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
export { metricsServiceMock } from './metrics/metrics_service.mock';
export { renderingMock } from './rendering/rendering_service.mock';
export { contextServiceMock } from './context/context_service.mock';
export function pluginInitializerContextConfigMock<T>(config: T) {
const globalConfig: SharedGlobalConfig = {

View file

@ -21,15 +21,13 @@ import { IContextContainer } from './context';
export type ContextContainerMock = jest.Mocked<IContextContainer<any>>;
const createContextMock = () => {
const createContextMock = (mockContext = {}) => {
const contextMock: ContextContainerMock = {
registerContext: jest.fn(),
createHandler: jest.fn((id, handler) => (...args: any[]) =>
Promise.resolve(handler({}, ...args))
),
createHandler: jest.fn(),
};
contextMock.createHandler.mockImplementation((pluginId, handler) => (...args) =>
handler({}, ...args)
handler(mockContext, ...args)
);
return contextMock;
};

View file

@ -7,7 +7,7 @@
import { resolve } from 'path';
import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
import { setupXPackMain } from './server/lib/setup_xpack_main';
import { xpackInfoRoute, settingsRoute } from './server/routes/api/v1';
import { xpackInfoRoute } from './server/routes/api/v1';
export const xpackMain = (kibana) => {
return new kibana.Plugin({
@ -29,7 +29,6 @@ export const xpackMain = (kibana) => {
// register routes
xpackInfoRoute(server);
settingsRoute(server, this.kbnServer);
},
});
};

View file

@ -5,4 +5,3 @@
*/
export { xpackInfoRoute } from './xpack_info';
export { settingsRoute } from './settings';

View file

@ -1,68 +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 { boomify } from 'boom';
import { get } from 'lodash';
import { KIBANA_SETTINGS_TYPE } from '../../../../../../../plugins/monitoring/common/constants';
const getClusterUuid = async (callCluster) => {
const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid' });
return uuid;
};
export function settingsRoute(server, kbnServer) {
server.route({
path: '/api/settings',
method: 'GET',
async handler(req) {
const { server } = req;
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
const callCluster = (...args) => callWithRequest(req, ...args); // All queries from HTTP API must use authentication headers from the request
try {
const { usageCollection } = server.newPlatform.setup.plugins;
const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE);
let settings = await settingsCollector.fetch(callCluster);
if (!settings) {
settings = settingsCollector.getEmailValueStructure(null);
}
const uuid = await getClusterUuid(callCluster);
const snapshotRegex = /-snapshot/i;
const config = server.config();
const status = kbnServer.status.toJSON();
const kibana = {
uuid: config.get('server.uuid'),
name: config.get('server.name'),
index: config.get('kibana.index'),
host: config.get('server.host'),
port: config.get('server.port'),
locale: config.get('i18n.locale'),
transport_address: `${config.get('server.host')}:${config.get('server.port')}`,
version: kbnServer.version.replace(snapshotRegex, ''),
snapshot: snapshotRegex.test(kbnServer.version),
status: get(status, 'overall.state'),
};
return {
cluster_uuid: uuid,
settings: {
...settings,
kibana,
},
};
} catch (err) {
req.log(['error'], err); // FIXME doesn't seem to log anything useful if ES times out
if (err.isBoom) {
return err;
} else {
return boomify(err, { statusCode: err.statusCode, message: err.message });
}
}
},
});
}

View file

@ -10,6 +10,7 @@ import { Plugin } from './plugin';
import { configSchema } from './config';
import { deprecations } from './deprecations';
export { KibanaSettingsCollector } from './kibana_monitoring/collectors';
export { MonitoringConfig } from './config';
export const plugin = (initContext: PluginInitializerContext) => new Plugin(initContext);
export const config: PluginConfigDescriptor<TypeOf<typeof configSchema>> = {

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Collector } from '../../../../../../src/plugins/usage_collection/server';
import { KIBANA_SETTINGS_TYPE } from '../../../common/constants';
import { MonitoringConfig } from '../../config';
@ -38,11 +40,19 @@ export async function checkForEmailValue(
}
}
interface EmailSettingData {
xpack: { default_admin_email: string | null };
}
export interface KibanaSettingsCollector extends Collector<EmailSettingData | undefined> {
getEmailValueStructure(email: string | null): EmailSettingData;
}
export function getSettingsCollector(usageCollection: any, config: MonitoringConfig) {
return usageCollection.makeStatsCollector({
type: KIBANA_SETTINGS_TYPE,
isReady: () => true,
async fetch() {
async fetch(this: KibanaSettingsCollector) {
let kibanaSettingsData;
const defaultAdminEmail = await checkForEmailValue(config);
@ -64,7 +74,7 @@ export function getSettingsCollector(usageCollection: any, config: MonitoringCon
// returns undefined if there was no result
return kibanaSettingsData;
},
getEmailValueStructure(email: string) {
getEmailValueStructure(email: string | null) {
return {
xpack: {
default_admin_email: email,

View file

@ -8,6 +8,8 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { getSettingsCollector } from './get_settings_collector';
import { MonitoringConfig } from '../../config';
export { KibanaSettingsCollector } from './get_settings_collector';
export function registerCollectors(
usageCollection: UsageCollectionSetup,
config: MonitoringConfig

View file

@ -0,0 +1,3 @@
# Xpack Leagcy
Contains HTTP endpoints and UiSettings that are slated for removal.

View file

@ -0,0 +1,10 @@
{
"id": "xpackLegacy",
"version": "8.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": false,
"requiredPlugins": [
"usageCollection"
]
}

View file

@ -0,0 +1,11 @@
/*
* 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 { PluginInitializerContext } from '../../../../src/core/server';
import { XpackLegacyPlugin } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) =>
new XpackLegacyPlugin(initializerContext);

View file

@ -0,0 +1,48 @@
/*
* 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 {
CoreStart,
CoreSetup,
Plugin,
PluginInitializerContext,
} from '../../../../src/core/server';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
import { registerSettingsRoute } from './routes/settings';
interface SetupPluginDeps {
usageCollection: UsageCollectionSetup;
}
export class XpackLegacyPlugin implements Plugin {
constructor(private readonly initContext: PluginInitializerContext) {}
public async setup(core: CoreSetup, { usageCollection }: SetupPluginDeps) {
const router = core.http.createRouter();
const globalConfig = await this.initContext.config.legacy.globalConfig$
.pipe(first())
.toPromise();
const serverInfo = core.http.getServerInfo();
registerSettingsRoute({
router,
usageCollection,
overallStatus$: core.status.overall$,
config: {
kibanaIndex: globalConfig.kibana.index,
kibanaVersion: this.initContext.env.packageInfo.version,
uuid: this.initContext.env.instanceUuid,
server: serverInfo,
},
});
}
public start(core: CoreStart) {}
public stop() {}
}

View file

@ -0,0 +1,105 @@
/*
* 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 { BehaviorSubject } from 'rxjs';
import { UnwrapPromise } from '@kbn/utility-types';
import supertest from 'supertest';
import {
LegacyAPICaller,
ServiceStatus,
ServiceStatusLevels,
} from '../../../../../src/core/server';
import { contextServiceMock } from '../../../../../src/core/server/mocks';
import { createHttpServer } from '../../../../../src/core/server/test_utils';
import { registerSettingsRoute } from './settings';
type HttpService = ReturnType<typeof createHttpServer>;
type HttpSetup = UnwrapPromise<ReturnType<HttpService['setup']>>;
describe('/api/stats', () => {
let server: HttpService;
let httpSetup: HttpSetup;
let overallStatus$: BehaviorSubject<ServiceStatus>;
let mockApiCaller: jest.Mocked<LegacyAPICaller>;
beforeEach(async () => {
mockApiCaller = jest.fn().mockResolvedValue({ cluster_uuid: 'yyy-yyyyy' });
server = createHttpServer();
httpSetup = await server.setup({
context: contextServiceMock.createSetupContract({
core: {
elasticsearch: {
legacy: {
client: {
callAsCurrentUser: mockApiCaller,
},
},
},
},
}),
});
overallStatus$ = new BehaviorSubject<ServiceStatus>({
level: ServiceStatusLevels.available,
summary: 'everything is working',
});
const usageCollection = {
getCollectorByType: jest.fn().mockReturnValue({
fetch: jest
.fn()
.mockReturnValue({ xpack: { default_admin_email: 'kibana-machine@elastic.co' } }),
}),
} as any;
const router = httpSetup.createRouter('');
registerSettingsRoute({
router,
overallStatus$,
usageCollection,
config: {
kibanaIndex: '.kibana-test',
kibanaVersion: '8.8.8-SNAPSHOT',
server: {
name: 'mykibana',
hostname: 'mykibana.com',
port: 1234,
},
uuid: 'xxx-xxxxx',
},
});
await server.start();
});
afterEach(async () => {
await server.stop();
});
it('successfully returns data', async () => {
const response = await supertest(httpSetup.server.listener).get('/api/settings').expect(200);
expect(response.body).toMatchObject({
cluster_uuid: 'yyy-yyyyy',
settings: {
xpack: {
default_admin_email: 'kibana-machine@elastic.co',
},
kibana: {
uuid: 'xxx-xxxxx',
name: 'mykibana',
index: '.kibana-test',
host: 'mykibana.com',
locale: 'en',
transport_address: `mykibana.com:1234`,
version: '8.8.8',
snapshot: true,
status: 'green',
},
},
});
});
});

View file

@ -0,0 +1,93 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { IRouter, ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server';
import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server';
import { KIBANA_SETTINGS_TYPE } from '../../../monitoring/common/constants';
import { KibanaSettingsCollector } from '../../../monitoring/server';
const SNAPSHOT_REGEX = /-snapshot/i;
export function registerSettingsRoute({
router,
usageCollection,
overallStatus$,
config,
}: {
router: IRouter;
usageCollection: UsageCollectionSetup;
overallStatus$: Observable<ServiceStatus>;
config: {
kibanaIndex: string;
kibanaVersion: string;
uuid: string;
server: {
name: string;
hostname: string;
port: number;
};
};
}) {
router.get(
{
path: '/api/settings',
validate: false,
},
async (context, req, res) => {
const { callAsCurrentUser } = context.core.elasticsearch.legacy.client;
const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE) as
| KibanaSettingsCollector
| undefined;
if (!settingsCollector) {
return res.internalError();
}
const settings =
(await settingsCollector.fetch(callAsCurrentUser)) ??
settingsCollector.getEmailValueStructure(null);
const { cluster_uuid: uuid } = await callAsCurrentUser('info', {
filterPath: 'cluster_uuid',
});
const overallStatus = await overallStatus$.pipe(first()).toPromise();
const kibana = {
uuid: config.uuid,
name: config.server.name,
index: config.kibanaIndex,
host: config.server.hostname,
port: config.server.port,
locale: i18n.getLocale(),
transport_address: `${config.server.hostname}:${config.server.port}`,
version: config.kibanaVersion.replace(SNAPSHOT_REGEX, ''),
snapshot: SNAPSHOT_REGEX.test(config.kibanaVersion),
status: ServiceStatusToLegacyState[overallStatus.level.toString()],
};
return res.ok({
body: {
cluster_uuid: uuid,
settings: {
...settings,
kibana,
},
},
});
}
);
}
const ServiceStatusToLegacyState: Record<string, string> = {
[ServiceStatusLevels.critical.toString()]: 'red',
[ServiceStatusLevels.unavailable.toString()]: 'red',
[ServiceStatusLevels.degraded.toString()]: 'yellow',
[ServiceStatusLevels.available.toString()]: 'green',
};

View file

@ -12,7 +12,7 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./security'));
loadTestFile(require.resolve('./spaces'));
loadTestFile(require.resolve('./monitoring'));
loadTestFile(require.resolve('./xpack_main'));
loadTestFile(require.resolve('./xpack_legacy'));
loadTestFile(require.resolve('./features'));
loadTestFile(require.resolve('./telemetry'));
loadTestFile(require.resolve('./logstash'));

View file

@ -5,7 +5,7 @@
*/
export default function ({ loadTestFile }) {
describe('xpack_main', () => {
describe('xpack_legacy', () => {
loadTestFile(require.resolve('./settings'));
});
}