Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api Deprecations #193668

Closed
wants to merge 13 commits into from
16 changes: 14 additions & 2 deletions packages/core/deprecations/core-deprecations-common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface BaseDeprecationDetails {
* Predefined types are necessary to reduce having similar definitions with different keywords
* across kibana deprecations.
*/
deprecationType?: 'config' | 'feature';
deprecationType?: 'config' | 'feature' | 'api';
/** (optional) link to the documentation for more details on the deprecation. */
documentationUrl?: string;
/** (optional) specify the fix for this deprecation requires a full kibana restart. */
Expand Down Expand Up @@ -91,7 +91,19 @@ export interface FeatureDeprecationDetails extends BaseDeprecationDetails {
/**
* @public
*/
export type DeprecationsDetails = ConfigDeprecationDetails | FeatureDeprecationDetails;
export interface ApiDeprecationDetails extends BaseDeprecationDetails {
routePath: string;
routeMethod: string;
deprecationType: 'api';
}

/**
* @public
*/
export type DeprecationsDetails =
| ConfigDeprecationDetails
| FeatureDeprecationDetails
| ApiDeprecationDetails;

/**
* @public
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,90 @@
# @kbn/core-deprecations-server-internal

This package contains the internal types and implementation of Core's server-side `deprecations` service.


/** Router:
* register Usage counters side core setup
* DOMIANID: CORE.ROUTER
* At router definition
* Call deprecations.registerDeprecation
* - group all renamed etc

* GroupId: Method / path
* when route is called
if deprecation is triggered based on rename/ path/ remove
- increment counter: GroupId, domainId, type: 'count'


set: ['body.a'],
unset: ['body.a'],

{
"deprecations": [
{
"configPath": "xpack.reporting.roles.enabled",
"title": "The \"xpack.reporting.roles\" setting is deprecated",
"level": "warning",
"message": "The default mechanism for Reporting privileges will work differently in future versions, which will affect the behavior of this cluster. Set \"xpack.reporting.roles.enabled\" to \"false\" to adopt the future behavior before upgrading.",
"correctiveActions": {
"manualSteps": [
"Set \"xpack.reporting.roles.enabled\" to \"false\" in kibana.yml.",
"Remove \"xpack.reporting.roles.allow\" in kibana.yml, if present.",
"Go to Management > Security > Roles to create one or more roles that grant the Kibana application privilege for Reporting.",
"Grant Reporting privileges to users by assigning one of the new roles."
],
api: {
path: 'some-path',
method: 'POST',
body: {
extra_param: 123,
},
},
},
"deprecationType": "config",
"requireRestart": true,
"domainId": "xpack.reporting"
}
]
}


domainId: 'routesDeprecations'
counterName: '{RouteAPIGroupingID}',
counterType: 'count',

RouteAPIGroupingID
If Route level: method, route

For fixed:
counterType: 'count:fixed',

We count all deprecations

{
'RouteAPIGroupingID': {
message: '',
deprecationDetails
}
}


Approach 1:
In memory:
1. Store in memory the deprecation details defined at routes setup

3. enrich some text (last called etc) filter out diff 0
4. send result

SO and get rid of Usage counter

interface UsageCountersParams {
/** The domainId used to create the Counter API */
domainId: string;
/** The name of the counter. Optional, will return all counters in the same domainId that match the rest of filters if omitted */
counterName?: string;
/** The 2. on UA api call we do matching between usage counters and the deprecation details in memorytype of counter. Optional, will return all counters in the same domainId that match the rest of filters if omitted */
counterType?: string;
/** Namespace of the counter. Optional, counters of the 'default' namespace will be returned if omitted */
namespace?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ export class DeprecationsFactory {
})
);

return deprecationsInfo.flat();
return [
...deprecationsInfo.flat(),
// ...apiDeprecations,
];
};

private createDeprecationInfo = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class DeprecationsService
if (!this.deprecationsFactory) {
throw new Error('`setup` must be called before `start`');
}

return {
asScopedToClient: this.createScopedDeprecations(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const registerGetRoute = (router: InternalDeprecationRouter) => {
{
path: '/',
validate: false,
options: {
deprecated: true,
},
},
async (context, req, res) => {
const deprecationsClient = (await context.core).deprecations.client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export { isKibanaRequest, isRealRequest, ensureRawRequest, CoreKibanaRequest } f
export { isSafeMethod } from './src/route';
export { HapiResponseAdapter } from './src/response_adapter';
export { kibanaResponseFactory, lifecycleResponseFactory, KibanaResponse } from './src/response';
export { buildDeprecations } from './src/deprecations';
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import {
RouteInputDeprecation,
RouteInputDeprecationLocation,
RouteInputDeprecationDescription,
} from '@kbn/core-http-server';
import { get } from 'lodash';

interface BuilderCommonArgs {
location: RouteInputDeprecationLocation;
}

export type RouteInputDeprecationInternalDescription = RouteInputDeprecationDescription & {
message: string;
check: (v: unknown) => boolean;
};

export const buildRename =
({ location }: BuilderCommonArgs) =>
(oldPath: string, newPath: string) => ({
type: 'renamed' as const,
location,
message: `"${oldPath}" has been removed. Use "${newPath}" instead.`,
new: newPath,
old: oldPath,
check: (input: unknown) => Boolean(get(input, oldPath)),
});

export const buildRemove =
({ location }: BuilderCommonArgs) =>
(path: string) => ({
type: 'removed' as const,
location,
message: `"${path}" has been removed.`,
path,
check: (input: unknown) => Boolean(get(input, path)),
});

export function buildDeprecations({
body: bodyFactory,
query: queryFactory,
}: RouteInputDeprecation): RouteInputDeprecationInternalDescription[] {
const deprecations: RouteInputDeprecationInternalDescription[] = [];
for (const [factory, location] of [
[bodyFactory, 'body'],
[queryFactory, 'query'],
] as const) {
if (factory) {
deprecations.push(
...(factory({
rename: buildRename({ location }),
remove: buildRemove({ location }),
}) as RouteInputDeprecationInternalDescription[])
);
}
}
return deprecations;
}
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ export class CoreKibanaRequest<
xsrfRequired:
((request.route?.settings as RouteOptions)?.app as KibanaRouteOptions)?.xsrfRequired ??
true, // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8
deprecated: ((request.route?.settings as RouteOptions)?.app as KibanaRouteOptions)
?.deprecated,
access: this.getAccess(request),
tags: request.route?.settings?.tags || [],
timeout: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { EventEmitter } from 'node:events';
import type { Request, ResponseToolkit } from '@hapi/hapi';
import apm from 'elastic-apm-node';
import { isConfigSchema } from '@kbn/config-schema';
Expand Down Expand Up @@ -142,7 +143,13 @@ export interface RouterOptions {

/** @internal */
export interface InternalRegistrarOptions {
/** @default false */
isVersioned: boolean;
/**
* Whether this route should emit "route events" like postValidate
* @default true
*/
events: boolean;
}

/** @internal */
Expand All @@ -162,12 +169,18 @@ interface InternalGetRoutesOptions {
excludeVersionedRoutes?: boolean;
}

/** @internal */
type RouterEvents =
/** Just before registered handlers are called */
'onPostValidate';

/**
* @internal
*/
export class Router<Context extends RequestHandlerContextBase = RequestHandlerContextBase>
implements IRouter<Context>
{
private static ee = new EventEmitter();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use descriptive var-name, e.g. eventEmitter.

public routes: Array<Readonly<InternalRouterRoute>> = [];
public pluginId?: symbol;
public get: InternalRegistrar<'get', Context>;
Expand All @@ -188,7 +201,10 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
<P, Q, B>(
route: RouteConfig<P, Q, B, Method>,
handler: RequestHandler<P, Q, B, Context, Method>,
internalOptions: { isVersioned: boolean } = { isVersioned: false }
internalOptions: InternalRegistrarOptions = {
isVersioned: false,
events: true,
}
) => {
route = prepareRouteConfigValidation(route);
const routeSchemas = routeSchemasFromRouteConfig(route, method);
Expand All @@ -200,6 +216,9 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
request: req,
responseToolkit,
handler: this.enhanceWithContext(handler),
emit: internalOptions.events
? { onPostValidation: this.emitPostValidate }
: undefined,
}),
method,
path: getRouteFullPath(this.routerPath, route.path),
Expand All @@ -217,6 +236,14 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
this.patch = buildMethod('patch');
}

public static on(event: RouterEvents, cb: (req: CoreKibanaRequest) => void) {
Router.ee.on(event, cb);
}

public static off(event: RouterEvents, cb: (req: CoreKibanaRequest) => void) {
Router.ee.off(event, cb);
}

public getRoutes({ excludeVersionedRoutes }: InternalGetRoutesOptions = {}) {
if (excludeVersionedRoutes) {
return this.routes.filter((route) => !route.isVersioned);
Expand Down Expand Up @@ -246,15 +273,25 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
});
}

/** Should be private, just exposed for convenience for the versioned router */
public emitPostValidate = (request: KibanaRequest) => {
const postValidate: RouterEvents = 'onPostValidate';
Router.ee.emit(postValidate, request);
};

private async handle<P, Q, B>({
routeSchemas,
request,
responseToolkit,
emit,
handler,
}: {
request: Request;
responseToolkit: ResponseToolkit;
handler: RequestHandlerEnhanced<P, Q, B, typeof request.method>;
emit?: {
onPostValidation: (req: KibanaRequest) => void;
};
routeSchemas?: RouteValidator<P, Q, B>;
}) {
let kibanaRequest: KibanaRequest<P, Q, B, typeof request.method>;
Expand All @@ -266,6 +303,8 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
return hapiResponseAdapter.toBadRequest(error.message);
}

emit?.onPostValidation(kibanaRequest);

try {
const kibanaResponse = await handler(kibanaRequest, kibanaResponseFactory);
if (kibanaRequest.protocol === 'http2' && kibanaResponse.options.headers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class CoreVersionedRoute implements VersionedRoute {
options: this.getRouteConfigOptions(),
},
this.requestHandler,
{ isVersioned: true }
{ isVersioned: true, events: false }
);
}

Expand Down Expand Up @@ -192,6 +192,8 @@ export class CoreVersionedRoute implements VersionedRoute {
req.query = {};
}

this.router.router.emitPostValidate(req);

const response = await handler.fn(ctx, req, res);

if (this.router.isDev && validation?.response?.[response.status]?.body) {
Expand Down
Loading