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

[HTTP/OAS] Added response schemas for /api/status #181277

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0b4572f
added response schemas for /api/status
jloleysens Apr 22, 2024
a2b40b4
added descriptions
jloleysens Apr 22, 2024
91c44e2
fixed how descriptions are added to OAS and updated snapshot
jloleysens Apr 22, 2024
deb50aa
fix import and types
jloleysens Apr 22, 2024
2657980
Merge branch 'main' into http/added-response-schema-for-status
jloleysens Apr 23, 2024
d01eaae
Merge branch 'main' into http/added-response-schema-for-status
jloleysens Apr 24, 2024
c0d1958
Merge branch 'main' into http/added-response-schema-for-status
jloleysens Apr 25, 2024
028a0b8
Merge branch 'main' into http/added-response-schema-for-status
jloleysens May 2, 2024
ca0d408
updated to use lazy loading
jloleysens May 2, 2024
a66ce77
added unknown schema handling test case
jloleysens May 2, 2024
fe94397
re-add reference genration snapshot
jloleysens May 2, 2024
7d9c6e1
Merge branch 'main' into http/added-response-schema-for-status
jloleysens May 2, 2024
e6e5c1d
update tests
jloleysens May 2, 2024
74f93db
Merge branch 'main' into http/added-response-schema-for-status
jloleysens May 6, 2024
89d8654
summary
jloleysens May 6, 2024
216fd31
update comment
jloleysens May 6, 2024
e2d1657
added header description and moved summary to correct spot
jloleysens May 6, 2024
c3e8c4a
map any type correctly, I think
jloleysens May 6, 2024
586ad4f
added meta field to any type
jloleysens May 6, 2024
9e4536e
add another description
jloleysens May 6, 2024
998ee4e
remove unused var
jloleysens May 6, 2024
db7d495
update snapshots...
jloleysens May 6, 2024
c791c95
added a lot of missing descriptions
jloleysens May 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import type { PluginName } from '@kbn/core-base-common';
import type { IRouter } from '@kbn/core-http-server';
import type { MetricsServiceSetup } from '@kbn/core-metrics-server';
import type { CoreIncrementUsageCounter } from '@kbn/core-usage-data-server';
import type { StatusResponse } from '@kbn/core-status-common-internal';
import {
type ServiceStatus,
type ServiceStatusLevel,
type CoreStatus,
ServiceStatusLevels,
} from '@kbn/core-status-common';
import { type ServiceStatus, type CoreStatus, ServiceStatusLevels } from '@kbn/core-status-common';
import { StatusResponse } from '@kbn/core-status-common-internal';
import { calculateLegacyStatus, type LegacyStatusInfo } from '../legacy_status';
import {
statusResponse,
redactedStatusResponse,
type RedactedStatusHttpBody,
} from './status_response_schemas';

const SNAPSHOT_POSTFIX = /-SNAPSHOT$/;

Expand Down Expand Up @@ -53,19 +53,17 @@ interface StatusHttpBody extends Omit<StatusResponse, 'status'> {
status: StatusInfo | LegacyStatusInfo;
}

export interface RedactedStatusHttpBody {
status: {
overall: {
level: ServiceStatusLevel;
};
};
}

const SERVICE_UNAVAILABLE_NOT_REPORTED: ServiceStatus = {
level: ServiceStatusLevels.unavailable,
summary: 'Status not yet reported',
};

const responseSchema = schema.oneOf([redactedStatusResponse, statusResponse], {
meta: {
description: `Kibana's operational status. A minimal response is sent for unauthorized users.`,
},
});
jloleysens marked this conversation as resolved.
Show resolved Hide resolved

export const registerStatusRoute = ({
router,
config,
Expand Down Expand Up @@ -100,21 +98,32 @@ export const registerStatusRoute = ({
// ROUTE_TAG_ACCEPT_JWT from '@kbn/security-plugin/server' that cannot be imported here directly.
tags: ['api', 'security:acceptJWT'],
access: 'public', // needs to be public to allow access from "system" users like k8s readiness probes.
description: `Get Kibana's current status.`,
Copy link
Contributor

@lcawl lcawl May 6, 2024

Choose a reason for hiding this comment

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

   description: `Get Kibana's current status.`,

In the current output, I see this string used in the operation summary. This is indeed the more critical field from a docs point of view, however we'll ultimately want to have both a summary and an (optional) description. Per https://spec.openapis.org/oas/latest#operation-object, summary is "A short summary of what the operation does" and description is "A verbose explanation of the operation behavior. CommonMark syntax MAY be used for rich text representation". For example, you can see the different details I think we'd put in those areas in

"description": "You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges.\n",

},
validate: {
query: schema.object(
{
v7format: schema.maybe(schema.boolean()),
v8format: schema.maybe(schema.boolean()),
},
{
validate: ({ v7format, v8format }) => {
if (typeof v7format === 'boolean' && typeof v8format === 'boolean') {
return `provide only one format option: v7format or v8format`;
}
request: {
query: schema.object(
{
v7format: schema.maybe(schema.boolean()),
v8format: schema.maybe(schema.boolean()),
},
}
),
{
validate: ({ v7format, v8format }) => {
if (typeof v7format === 'boolean' && typeof v8format === 'boolean') {
return `provide only one format option: v7format or v8format`;
}
},
}
),
},
response: {
200: {
body: responseSchema,
},
503: {
body: responseSchema,
},
},
},
},
async (context, req, res) => {
Expand Down Expand Up @@ -217,7 +226,7 @@ const getRedactedStatusResponse = ({
const body: RedactedStatusHttpBody = {
status: {
overall: {
level: coreOverall.level,
level: coreOverall.level.toString(),
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import type { IRouter } from '@kbn/core-http-server';
import { ServiceStatusLevels } from '@kbn/core-status-common';
import type { RedactedStatusHttpBody } from './status';
import type { RedactedStatusHttpBody } from './status_response_schemas';

export const registerPrebootStatusRoute = ({ router }: { router: IRouter }) => {
router.get(
Expand All @@ -25,7 +25,7 @@ export const registerPrebootStatusRoute = ({ router }: { router: IRouter }) => {
const body: RedactedStatusHttpBody = {
status: {
overall: {
level: ServiceStatusLevels.unavailable,
level: ServiceStatusLevels.unavailable.toString(),
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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
* 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.
*/

import { schema, type Type, type TypeOf } from '@kbn/config-schema';
import type { BuildFlavor } from '@kbn/config';
import type { ServiceStatusLevelId, ServiceStatus } from '@kbn/core-status-common';

import type {
StatusResponse,
StatusInfoCoreStatus,
ServerMetrics,
StatusInfo,
ServerVersion,
} from '@kbn/core-status-common-internal';

export const serviceStatusLevelId: Type<ServiceStatusLevelId> = schema.oneOf([
schema.literal('available'),
schema.literal('degraded'),
schema.literal('unavailable'),
schema.literal('critical'),
]);

export const statusInfoServiceStatus: Type<
Omit<ServiceStatus, 'level'> & { level: ServiceStatusLevelId }
> = schema.object({
level: serviceStatusLevelId,
summary: schema.string(),
detail: schema.maybe(schema.string()),
documentationUrl: schema.maybe(schema.string()),
meta: schema.recordOf(schema.string(), schema.any()),
});

export const statusInfoCoreStatus: Type<StatusInfoCoreStatus> = schema.object({
elasticsearch: statusInfoServiceStatus,
savedObjects: statusInfoServiceStatus,
});

/** Only include a subset of fields for OAS documentation, for now */
export const serverMetrics: Type<Partial<ServerMetrics>> = schema.object({
elasticsearch_client: schema.object({
totalActiveSockets: schema.number(),
totalIdleSockets: schema.number(),
totalQueuedRequests: schema.number(),
}),
last_updated: schema.string(),
collection_interval_in_millis: schema.number(),
});

export const buildFlavour: Type<BuildFlavor> = schema.oneOf([
schema.literal('serverless'),
schema.literal('traditional'),
]);
export const serverVersion: Type<ServerVersion> = schema.object({
number: schema.string(),
build_hash: schema.string(),
build_number: schema.number(),
build_snapshot: schema.boolean(),
build_flavor: buildFlavour,
build_date: schema.string(),
});

export const statusInfo: Type<StatusInfo> = schema.object({
overall: statusInfoServiceStatus,
core: statusInfoCoreStatus,
plugins: schema.recordOf(schema.string(), statusInfoServiceStatus),
});

/** Excluding metrics for brevity, for now */
export const statusResponse: Type<Omit<StatusResponse, 'metrics'>> = schema.object(
{
name: schema.string(),
uuid: schema.string(),
version: serverVersion,
status: statusInfo,
metrics: serverMetrics,
},
{
meta: {
id: 'core.status.response',
description: `Kibana's operational status as well as a detailed breakdown of plugin statuses indication of various loads (like event loop utilization and network traffic) at time of request.`,
},
}
);

export const redactedStatusResponse = schema.object(
{
status: schema.object({
overall: schema.object({
level: serviceStatusLevelId,
}),
}),
},
{
meta: {
id: 'core.status.redactedResponse',
},
}
);

export type RedactedStatusHttpBody = TypeOf<typeof redactedStatusResponse>;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 20 additions & 5 deletions packages/kbn-router-to-openapispec/src/generate_oas.test.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,18 @@ const getRouterDefaults = () => ({
isVersioned: false,
path: '/foo/{id}',
method: 'get',
options: {
tags: ['foo'],
description: 'route',
},
validationSchemas: {
request: {
params: schema.object({ id: schema.string({ maxLength: 36 }) }),
query: schema.object({ page: schema.number({ max: 999, min: 1, defaultValue: 1 }) }),
params: schema.object({
id: schema.string({ maxLength: 36, meta: { description: 'id' } }),
}),
query: schema.object({
page: schema.number({ max: 999, min: 1, defaultValue: 1, meta: { description: 'page' } }),
}),
body: testSchema,
},
response: {
Expand All @@ -64,24 +72,31 @@ const getRouterDefaults = () => ({
},
},
},
options: { tags: ['foo'] },
handler: jest.fn(),
});

const getVersionedRouterDefaults = () => ({
method: 'get',
path: '/bar',
options: {
description: 'versioned route',
access: 'public',
},
handlers: [
{
fn: jest.fn(),
options: {
validate: {
request: { body: schema.object({ foo: schema.string() }) },
request: {
body: schema.object({ foo: schema.string() }, { meta: { description: 'foo' } }),
},
response: {
[200]: { body: schema.object({ fooResponse: schema.string() }) },
[200]: {
body: schema.object(
{ fooResponse: schema.string() },
{ meta: { description: 'fooResponse' } }
),
},
},
},
version: 'oas-test-version-1',
Expand Down
5 changes: 3 additions & 2 deletions packages/kbn-router-to-openapispec/src/generate_oas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const extractVersionedResponses = (
const schema = converter.convert(maybeSchema);
acc[statusCode] = {
...acc[statusCode],
description: route.options.description ?? 'No description',
description: '',
content: {
...((acc[statusCode] ?? {}) as OpenAPIV3.ResponseObject).content,
[getVersionedContentString(handler.options.version)]: {
Expand Down Expand Up @@ -184,6 +184,7 @@ const processVersionedRouter = (
handler && extractValidationSchemaFromVersionedHandler(handler)?.request?.body
);
const path: OpenAPIV3.PathItemObject = {
description: route.options.description ?? '',
[route.method]: {
requestBody: hasBody
? {
Expand Down Expand Up @@ -219,7 +220,6 @@ const extractResponses = (route: InternalRouterRoute, converter: OasConverter) =
const oasSchema = converter.convert(schema.body);
acc[statusCode] = {
...acc[statusCode],
description: route.options.description ?? 'No description',
content: {
...((acc[statusCode] ?? {}) as OpenAPIV3.ResponseObject).content,
[getVersionedContentString(LATEST_SERVERLESS_VERSION)]: {
Expand Down Expand Up @@ -271,6 +271,7 @@ const processRouter = (
}

const path: OpenAPIV3.PathItemObject = {
description: route.options.description ?? '',
[route.method]: {
requestBody: !!validationSchemas?.body
? {
Expand Down