Skip to content

Commit

Permalink
[HTTP/OAS] Added response schemas for /api/status (#181277)
Browse files Browse the repository at this point in the history
## Summary

Part #180056. Adds new response
schemas to the `/api/status` endpoint for the purposes of OAS
generation.

## How to test

1. Start ES
2. Add `server.oas.enabled: true` to `kibana.dev.yml`
3. Start Kibana `yarn start --no-base-path`
4. `curl -s -uelastic:changeme
http://localhost:5601/api/oas\?pathStartsWith\=/api/status | jq`

<details>

<summary>output</summary>

```json
{
  "openapi": "3.0.0",
  "info": {
    "title": "Kibana HTTP APIs",
    "version": "0.0.0"
  },
  "servers": [
    {
      "url": "http://localhost:5601"
    }
  ],
  "paths": {
    "/api/status": {
      "get": {
        "summary": "Get Kibana's current status.",
        "responses": {
          "200": {
            "description": "Get Kibana's current status.",
            "content": {
              "application/json; Elastic-Api-Version=2023-10-31": {
                "schema": {
                  "description": "Kibana's operational status. A minimal response is sent for unauthorized users.",
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/core.status.response"
                    },
                    {
                      "$ref": "#/components/schemas/core.status.redactedResponse"
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Get Kibana's current status.",
            "content": {
              "application/json; Elastic-Api-Version=2023-10-31": {
                "schema": {
                  "description": "Kibana's operational status. A minimal response is sent for unauthorized users.",
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/core.status.response"
                    },
                    {
                      "$ref": "#/components/schemas/core.status.redactedResponse"
                    }
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "elastic-api-version",
            "description": "The version of the API to use",
            "schema": {
              "type": "string",
              "enum": [
                "2023-10-31"
              ],
              "default": "2023-10-31"
            }
          },
          {
            "name": "v7format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set to \"true\" to get the response in v7 format."
          },
          {
            "name": "v8format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set to \"true\" to get the response in v8 format."
          }
        ],
        "operationId": "/api/status#0"
      }
    }
  },
  "components": {
    "schemas": {
      "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.",
        "type": "object",
        "properties": {
          "name": {
            "description": "Kibana instance name.",
            "type": "string"
          },
          "uuid": {
            "description": "Unique, generated Kibana instance UUID. This UUID should persist even if the Kibana process restarts.",
            "type": "string"
          },
          "version": {
            "type": "object",
            "properties": {
              "number": {
                "description": "A semantic version number.",
                "type": "string"
              },
              "build_hash": {
                "description": "A unique hash value representing the git commit of this Kibana build.",
                "type": "string"
              },
              "build_number": {
                "description": "A monotonically increasing number, each subsequent build will have a higher number.",
                "type": "number"
              },
              "build_snapshot": {
                "description": "Whether this build is a snapshot build.",
                "type": "boolean"
              },
              "build_flavor": {
                "description": "The build flavour determines configuration and behavior of Kibana. On premise users will almost always run the \"traditional\" flavour, while other flavours are reserved for Elastic-specific use cases.",
                "anyOf": [
                  {
                    "enum": [
                      "serverless"
                    ],
                    "type": "string"
                  },
                  {
                    "enum": [
                      "traditional"
                    ],
                    "type": "string"
                  }
                ]
              },
              "build_date": {
                "description": "The date and time of this build.",
                "type": "string"
              }
            },
            "additionalProperties": false,
            "required": [
              "number",
              "build_hash",
              "build_number",
              "build_snapshot",
              "build_flavor",
              "build_date"
            ]
          },
          "status": {
            "type": "object",
            "properties": {
              "overall": {
                "type": "object",
                "properties": {
                  "level": {
                    "description": "Service status levels as human and machine readable values.",
                    "anyOf": [
                      {
                        "enum": [
                          "available"
                        ],
                        "type": "string"
                      },
                      {
                        "enum": [
                          "degraded"
                        ],
                        "type": "string"
                      },
                      {
                        "enum": [
                          "unavailable"
                        ],
                        "type": "string"
                      },
                      {
                        "enum": [
                          "critical"
                        ],
                        "type": "string"
                      }
                    ]
                  },
                  "summary": {
                    "description": "A human readable summary of the service status.",
                    "type": "string"
                  },
                  "detail": {
                    "description": "Human readable detail of the service status.",
                    "type": "string"
                  },
                  "documentationUrl": {
                    "description": "A URL to further documentation regarding this service.",
                    "type": "string"
                  },
                  "meta": {
                    "description": "An unstructured set of extra metadata about this service.",
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "additionalProperties": false,
                "required": [
                  "level",
                  "summary",
                  "meta"
                ]
              },
              "core": {
                "description": "Statuses of core Kibana services.",
                "type": "object",
                "properties": {
                  "elasticsearch": {
                    "type": "object",
                    "properties": {
                      "level": {
                        "description": "Service status levels as human and machine readable values.",
                        "anyOf": [
                          {
                            "enum": [
                              "available"
                            ],
                            "type": "string"
                          },
                          {
                            "enum": [
                              "degraded"
                            ],
                            "type": "string"
                          },
                          {
                            "enum": [
                              "unavailable"
                            ],
                            "type": "string"
                          },
                          {
                            "enum": [
                              "critical"
                            ],
                            "type": "string"
                          }
                        ]
                      },
                      "summary": {
                        "description": "A human readable summary of the service status.",
                        "type": "string"
                      },
                      "detail": {
                        "description": "Human readable detail of the service status.",
                        "type": "string"
                      },
                      "documentationUrl": {
                        "description": "A URL to further documentation regarding this service.",
                        "type": "string"
                      },
                      "meta": {
                        "description": "An unstructured set of extra metadata about this service.",
                        "type": "object",
                        "additionalProperties": {}
                      }
                    },
                    "additionalProperties": false,
                    "required": [
                      "level",
                      "summary",
                      "meta"
                    ]
                  },
                  "savedObjects": {
                    "type": "object",
                    "properties": {
                      "level": {
                        "description": "Service status levels as human and machine readable values.",
                        "anyOf": [
                          {
                            "enum": [
                              "available"
                            ],
                            "type": "string"
                          },
                          {
                            "enum": [
                              "degraded"
                            ],
                            "type": "string"
                          },
                          {
                            "enum": [
                              "unavailable"
                            ],
                            "type": "string"
                          },
                          {
                            "enum": [
                              "critical"
                            ],
                            "type": "string"
                          }
                        ]
                      },
                      "summary": {
                        "description": "A human readable summary of the service status.",
                        "type": "string"
                      },
                      "detail": {
                        "description": "Human readable detail of the service status.",
                        "type": "string"
                      },
                      "documentationUrl": {
                        "description": "A URL to further documentation regarding this service.",
                        "type": "string"
                      },
                      "meta": {
                        "description": "An unstructured set of extra metadata about this service.",
                        "type": "object",
                        "additionalProperties": {}
                      }
                    },
                    "additionalProperties": false,
                    "required": [
                      "level",
                      "summary",
                      "meta"
                    ]
                  }
                },
                "additionalProperties": false,
                "required": [
                  "elasticsearch",
                  "savedObjects"
                ]
              },
              "plugins": {
                "description": "A dynamic mapping of plugin ID to plugin status.",
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "level": {
                      "description": "Service status levels as human and machine readable values.",
                      "anyOf": [
                        {
                          "enum": [
                            "available"
                          ],
                          "type": "string"
                        },
                        {
                          "enum": [
                            "degraded"
                          ],
                          "type": "string"
                        },
                        {
                          "enum": [
                            "unavailable"
                          ],
                          "type": "string"
                        },
                        {
                          "enum": [
                            "critical"
                          ],
                          "type": "string"
                        }
                      ]
                    },
                    "summary": {
                      "description": "A human readable summary of the service status.",
                      "type": "string"
                    },
                    "detail": {
                      "description": "Human readable detail of the service status.",
                      "type": "string"
                    },
                    "documentationUrl": {
                      "description": "A URL to further documentation regarding this service.",
                      "type": "string"
                    },
                    "meta": {
                      "description": "An unstructured set of extra metadata about this service.",
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "additionalProperties": false,
                  "required": [
                    "level",
                    "summary",
                    "meta"
                  ]
                }
              }
            },
            "additionalProperties": false,
            "required": [
              "overall",
              "core",
              "plugins"
            ]
          },
          "metrics": {
            "description": "Metric groups collected by Kibana.",
            "type": "object",
            "properties": {
              "elasticsearch_client": {
                "description": "Current network metrics of Kibana's Elasticsearch client.",
                "type": "object",
                "properties": {
                  "totalActiveSockets": {
                    "description": "Count of network sockets currently in use.",
                    "type": "number"
                  },
                  "totalIdleSockets": {
                    "description": "Count of network sockets currently idle.",
                    "type": "number"
                  },
                  "totalQueuedRequests": {
                    "description": "Count of requests not yet assigned to sockets.",
                    "type": "number"
                  }
                },
                "additionalProperties": false,
                "required": [
                  "totalActiveSockets",
                  "totalIdleSockets",
                  "totalQueuedRequests"
                ]
              },
              "last_updated": {
                "description": "The time metrics were collected.",
                "type": "string"
              },
              "collection_interval_in_millis": {
                "description": "The interval at which metrics should be collected.",
                "type": "number"
              }
            },
            "additionalProperties": false,
            "required": [
              "elasticsearch_client",
              "last_updated",
              "collection_interval_in_millis"
            ]
          }
        },
        "additionalProperties": false,
        "required": [
          "name",
          "uuid",
          "version",
          "status",
          "metrics"
        ]
      },
      "core.status.redactedResponse": {
        "description": "A minimal representation of Kibana's operational status.",
        "type": "object",
        "properties": {
          "status": {
            "type": "object",
            "properties": {
              "overall": {
                "type": "object",
                "properties": {
                  "level": {
                    "description": "Service status levels as human and machine readable values.",
                    "anyOf": [
                      {
                        "enum": [
                          "available"
                        ],
                        "type": "string"
                      },
                      {
                        "enum": [
                          "degraded"
                        ],
                        "type": "string"
                      },
                      {
                        "enum": [
                          "unavailable"
                        ],
                        "type": "string"
                      },
                      {
                        "enum": [
                          "critical"
                        ],
                        "type": "string"
                      }
                    ]
                  }
                },
                "additionalProperties": false,
                "required": [
                  "level"
                ]
              }
            },
            "additionalProperties": false,
            "required": [
              "overall"
            ]
          }
        },
        "additionalProperties": false,
        "required": [
          "status"
        ]
      }
    },
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization"
      }
    }
  },
  "security": [
    {
      "basicAuth": []
    }
  ]
}
```

</details>

Related to #181622

## Notes

* Tip from @lcawl : "If you want to see Bump previews of your files too,
I’ve been doing it via the preview command per
https://docs.bump.sh/help/continuous-integration/cli/#bump-preview-file"
  • Loading branch information
jloleysens authored May 7, 2024
1 parent a8a6003 commit 1e80b01
Show file tree
Hide file tree
Showing 20 changed files with 559 additions and 49 deletions.
2 changes: 1 addition & 1 deletion packages/core/http/core-http-server/src/router/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export interface RouteConfigOptions<Method extends RouteMethod> {
idleSocket?: number;
};

/** Human-friendly description of this endpoint */
/** A short, human-friendly description of this endpoint */
description?: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@ 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, type RedactedStatusHttpBody } from './status_response_schemas';

const SNAPSHOT_POSTFIX = /-SNAPSHOT$/;

Expand Down Expand Up @@ -53,14 +49,6 @@ 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',
Expand Down Expand Up @@ -100,21 +88,43 @@ 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.`,
},
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({
meta: { description: 'Set to "true" to get the response in v7 format.' },
})
),
v8format: schema.maybe(
schema.boolean({
meta: { description: 'Set to "true" to get the response in v8 format.' },
})
),
},
}
),
{
validate: ({ v7format, v8format }) => {
if (typeof v7format === 'boolean' && typeof v8format === 'boolean') {
return `provide only one format option: v7format or v8format`;
}
},
meta: {
description: `Return status in a specific format. If both v7 and v8 are requested the request will be rejected.`,
},
}
),
},
response: {
200: {
body: statusResponse,
},
503: {
body: statusResponse,
},
},
},
},
async (context, req, res) => {
Expand Down Expand Up @@ -217,7 +227,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,180 @@
/*
* 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';

const serviceStatusLevelId: () => Type<ServiceStatusLevelId> = () =>
schema.oneOf(
[
schema.literal('available'),
schema.literal('degraded'),
schema.literal('unavailable'),
schema.literal('critical'),
],
{ meta: { description: 'Service status levels as human and machine readable values.' } }
);

const statusInfoServiceStatus: () => Type<
Omit<ServiceStatus, 'level'> & { level: ServiceStatusLevelId }
> = () =>
schema.object({
level: serviceStatusLevelId(),
summary: schema.string({
meta: { description: 'A human readable summary of the service status.' },
}),
detail: schema.maybe(
schema.string({ meta: { description: 'Human readable detail of the service status.' } })
),
documentationUrl: schema.maybe(
schema.string({
meta: { description: 'A URL to further documentation regarding this service.' },
})
),
meta: schema.recordOf(schema.string(), schema.any(), {
meta: { description: 'An unstructured set of extra metadata about this service.' },
}),
});

const statusInfoCoreStatus: () => Type<StatusInfoCoreStatus> = () =>
schema.object(
{
elasticsearch: statusInfoServiceStatus(),
savedObjects: statusInfoServiceStatus(),
},
{ meta: { description: 'Statuses of core Kibana services.' } }
);

/** Only include a subset of fields for OAS documentation, for now */
const serverMetrics: () => Type<Partial<ServerMetrics>> = () =>
schema.object(
{
elasticsearch_client: schema.object(
{
totalActiveSockets: schema.number({
meta: { description: 'Count of network sockets currently in use.' },
}),
totalIdleSockets: schema.number({
meta: { description: 'Count of network sockets currently idle.' },
}),
totalQueuedRequests: schema.number({
meta: { description: 'Count of requests not yet assigned to sockets.' },
}),
},
{ meta: { description: `Current network metrics of Kibana's Elasticsearch client.` } }
),
last_updated: schema.string({ meta: { description: 'The time metrics were collected.' } }),
collection_interval_in_millis: schema.number({
meta: { description: 'The interval at which metrics should be collected.' },
}),
},
{
meta: {
description: 'Metric groups collected by Kibana.',
},
}
);

const buildFlavour: () => Type<BuildFlavor> = () =>
schema.oneOf([schema.literal('serverless'), schema.literal('traditional')], {
meta: {
description:
'The build flavour determines configuration and behavior of Kibana. On premise users will almost always run the "traditional" flavour, while other flavours are reserved for Elastic-specific use cases.',
},
});

const serverVersion: () => Type<ServerVersion> = () =>
schema.object({
number: schema.string({
meta: { description: 'A semantic version number.' },
}),
build_hash: schema.string({
meta: {
description: 'A unique hash value representing the git commit of this Kibana build.',
},
}),
build_number: schema.number({
meta: {
description:
'A monotonically increasing number, each subsequent build will have a higher number.',
},
}),
build_snapshot: schema.boolean({
meta: { description: 'Whether this build is a snapshot build.' },
}),
build_flavor: buildFlavour(),
build_date: schema.string({ meta: { description: 'The date and time of this build.' } }),
});

const statusInfo: () => Type<StatusInfo> = () =>
schema.object({
overall: statusInfoServiceStatus(),
core: statusInfoCoreStatus(),
plugins: schema.recordOf(schema.string(), statusInfoServiceStatus(), {
meta: { description: 'A dynamic mapping of plugin ID to plugin status.' },
}),
});

/** Excluding metrics for brevity, for now */
const fullStatusResponse: () => Type<Omit<StatusResponse, 'metrics'>> = () =>
schema.object(
{
name: schema.string({ meta: { description: 'Kibana instance name.' } }),
uuid: schema.string({
meta: {
description:
'Unique, generated Kibana instance UUID. This UUID should persist even if the Kibana process restarts.',
},
}),
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.`,
},
}
);

const redactedStatusResponse = () =>
schema.object(
{
status: schema.object({
overall: schema.object({
level: serviceStatusLevelId(),
}),
}),
},
{
meta: {
id: 'core.status.redactedResponse',
description: `A minimal representation of Kibana's operational status.`,
},
}
);

/** Lazily load this schema */
export const statusResponse = () =>
schema.oneOf([fullStatusResponse(), redactedStatusResponse()], {
meta: {
description: `Kibana's operational status. A minimal response is sent for unauthorized users.`,
},
});

export type RedactedStatusHttpBody = TypeOf<typeof redactedStatusResponse>;
2 changes: 2 additions & 0 deletions packages/kbn-config-schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export const schema = {
export type Schema = typeof schema;

import {
META_FIELD_X_OAS_ANY,
META_FIELD_X_OAS_REF_ID,
META_FIELD_X_OAS_OPTIONAL,
META_FIELD_X_OAS_DEPRECATED,
Expand All @@ -253,6 +254,7 @@ import {
} from './src/oas_meta_fields';

export const metaFields = Object.freeze({
META_FIELD_X_OAS_ANY,
META_FIELD_X_OAS_REF_ID,
META_FIELD_X_OAS_OPTIONAL,
META_FIELD_X_OAS_DEPRECATED,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-config-schema/src/oas_meta_fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export const META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES =
'x-oas-get-additional-properties' as const;
export const META_FIELD_X_OAS_REF_ID = 'x-oas-ref-id' as const;
export const META_FIELD_X_OAS_DEPRECATED = 'x-oas-deprecated' as const;
export const META_FIELD_X_OAS_ANY = 'x-oas-any-type' as const;
8 changes: 8 additions & 0 deletions packages/kbn-config-schema/src/types/any_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* Side Public License, v 1.
*/

import { get } from 'lodash';
import { schema } from '../..';
import { META_FIELD_X_OAS_ANY } from '../oas_meta_fields';

test('works for any value', () => {
expect(schema.any().validate(true)).toBe(true);
Expand All @@ -30,6 +32,12 @@ test('includes namespace in failure', () => {
);
});

test('meta', () => {
expect(get(schema.any().getSchema().describe(), 'metas[0]')).toEqual({
[META_FIELD_X_OAS_ANY]: true,
});
});

describe('#defaultValue', () => {
test('returns default when undefined', () => {
expect(schema.any({ defaultValue: true }).validate(undefined)).toBe(true);
Expand Down
3 changes: 2 additions & 1 deletion packages/kbn-config-schema/src/types/any_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

import typeDetect from 'type-detect';
import { internals } from '../internals';
import { META_FIELD_X_OAS_ANY } from '../oas_meta_fields';
import { Type, TypeOptions } from './type';

export class AnyType extends Type<any> {
constructor(options?: TypeOptions<any>) {
super(internals.any(), options);
super(internals.any().meta({ [META_FIELD_X_OAS_ANY]: true }), options);
}

protected handleError(type: string, { value }: Record<string, any>) {
Expand Down
Loading

0 comments on commit 1e80b01

Please sign in to comment.