mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[OAS] Capture and commit serverless bundle (#184915)
## Summary Close https://github.com/elastic/kibana/issues/184719 Adds the ability to capture OAS for `serverless` build flavor in addition to `traditional`. By default the CLI will run for both, but this can be controlled by passing in one of two new flags: `--no-serverless` or `--no-traditional`. See `oas_docs/bundle.serverless.json` for output. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
8f3359c43d
commit
cf7196fa1f
8 changed files with 757 additions and 97 deletions
538
oas_docs/bundle.serverless.json
Normal file
538
oas_docs/bundle.serverless.json
Normal file
|
@ -0,0 +1,538 @@
|
||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"core_status_redactedResponse": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "A minimal representation of Kibana's operational status.",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"overall": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"available"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"degraded"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"unavailable"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"critical"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Service status levels as human and machine readable values."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"level"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"overall"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"core_status_response": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"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.",
|
||||||
|
"properties": {
|
||||||
|
"metrics": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Metric groups collected by Kibana.",
|
||||||
|
"properties": {
|
||||||
|
"collection_interval_in_millis": {
|
||||||
|
"description": "The interval at which metrics should be collected.",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"elasticsearch_client": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Current network metrics of Kibana's Elasticsearch client.",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"totalActiveSockets",
|
||||||
|
"totalIdleSockets",
|
||||||
|
"totalQueuedRequests"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"last_updated": {
|
||||||
|
"description": "The time metrics were collected.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"elasticsearch_client",
|
||||||
|
"last_updated",
|
||||||
|
"collection_interval_in_millis"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Kibana instance name.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"core": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Statuses of core Kibana services.",
|
||||||
|
"properties": {
|
||||||
|
"elasticsearch": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"description": "Human readable detail of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"documentationUrl": {
|
||||||
|
"description": "A URL to further documentation regarding this service.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"available"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"degraded"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"unavailable"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"critical"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Service status levels as human and machine readable values."
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"description": "An unstructured set of extra metadata about this service.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"description": "A human readable summary of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"level",
|
||||||
|
"summary",
|
||||||
|
"meta"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"savedObjects": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"description": "Human readable detail of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"documentationUrl": {
|
||||||
|
"description": "A URL to further documentation regarding this service.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"available"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"degraded"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"unavailable"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"critical"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Service status levels as human and machine readable values."
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"description": "An unstructured set of extra metadata about this service.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"description": "A human readable summary of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"level",
|
||||||
|
"summary",
|
||||||
|
"meta"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"elasticsearch",
|
||||||
|
"savedObjects"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"overall": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"description": "Human readable detail of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"documentationUrl": {
|
||||||
|
"description": "A URL to further documentation regarding this service.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"available"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"degraded"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"unavailable"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"critical"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Service status levels as human and machine readable values."
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"description": "An unstructured set of extra metadata about this service.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"description": "A human readable summary of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"level",
|
||||||
|
"summary",
|
||||||
|
"meta"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"description": "Human readable detail of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"documentationUrl": {
|
||||||
|
"description": "A URL to further documentation regarding this service.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"available"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"degraded"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"unavailable"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"critical"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Service status levels as human and machine readable values."
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"description": "An unstructured set of extra metadata about this service.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"description": "A human readable summary of the service status.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"level",
|
||||||
|
"summary",
|
||||||
|
"meta"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "A dynamic mapping of plugin ID to plugin status.",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"overall",
|
||||||
|
"core",
|
||||||
|
"plugins"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"description": "Unique, generated Kibana instance UUID. This UUID should persist even if the Kibana process restarts.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"build_date": {
|
||||||
|
"description": "The date and time of this build.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"build_flavor": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"serverless"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"traditional"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"description": "A semantic version number.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"number",
|
||||||
|
"build_hash",
|
||||||
|
"build_number",
|
||||||
|
"build_snapshot",
|
||||||
|
"build_flavor",
|
||||||
|
"build_date"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"uuid",
|
||||||
|
"version",
|
||||||
|
"status",
|
||||||
|
"metrics"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"apiKeyAuth": {
|
||||||
|
"in": "header",
|
||||||
|
"name": "Authorization",
|
||||||
|
"type": "apiKey"
|
||||||
|
},
|
||||||
|
"basicAuth": {
|
||||||
|
"scheme": "basic",
|
||||||
|
"type": "http"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"title": "Kibana HTTP APIs",
|
||||||
|
"version": "0.0.0"
|
||||||
|
},
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"paths": {
|
||||||
|
"/api/status": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "/api/status#0",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "The version of the API to use",
|
||||||
|
"in": "header",
|
||||||
|
"name": "elastic-api-version",
|
||||||
|
"schema": {
|
||||||
|
"default": "2023-10-31",
|
||||||
|
"enum": [
|
||||||
|
"2023-10-31"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Set to \"true\" to get the response in v7 format.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "v7format",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Set to \"true\" to get the response in v8 format.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "v8format",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json; Elastic-Api-Version=2023-10-31": {
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/core_status_response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/core_status_redactedResponse"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Kibana's operational status. A minimal response is sent for unauthorized users."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"503": {
|
||||||
|
"content": {
|
||||||
|
"application/json; Elastic-Api-Version=2023-10-31": {
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/core_status_response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/core_status_redactedResponse"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Kibana's operational status. A minimal response is sent for unauthorized users."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Get Kibana's current status",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"basicAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "http://localhost:5622"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -38,13 +38,18 @@ const projectType: ServerlessProjectType = 'es';
|
||||||
*/
|
*/
|
||||||
export function createTestServerlessInstances({
|
export function createTestServerlessInstances({
|
||||||
adjustTimeout,
|
adjustTimeout,
|
||||||
|
kibana = {},
|
||||||
}: {
|
}: {
|
||||||
adjustTimeout: (timeout: number) => void;
|
kibana?: {
|
||||||
}): TestServerlessUtils {
|
settings?: {};
|
||||||
|
cliArgs?: Partial<CliArgs>;
|
||||||
|
};
|
||||||
|
adjustTimeout?: (timeout: number) => void;
|
||||||
|
} = {}): TestServerlessUtils {
|
||||||
adjustTimeout?.(150_000);
|
adjustTimeout?.(150_000);
|
||||||
|
|
||||||
const esUtils = createServerlessES();
|
const esUtils = createServerlessES();
|
||||||
const kbUtils = createServerlessKibana();
|
const kbUtils = createServerlessKibana(kibana.settings, kibana.cliArgs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startES: async () => {
|
startES: async () => {
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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 fs from 'node:fs/promises';
|
||||||
|
import { encode } from 'node:querystring';
|
||||||
|
import type { ChildProcess } from 'node:child_process';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import * as Rx from 'rxjs';
|
||||||
|
import { startTSWorker } from '@kbn/dev-utils';
|
||||||
|
import { createTestEsCluster } from '@kbn/test';
|
||||||
|
import type { ToolingLog } from '@kbn/tooling-log';
|
||||||
|
import { createTestServerlessInstances } from '@kbn/core-test-helpers-kbn-server';
|
||||||
|
import type { Result } from './kibana_worker';
|
||||||
|
import { sortAndPrettyPrint } from './run_capture_oas_snapshot_cli';
|
||||||
|
import { buildFlavourEnvArgName } from './common';
|
||||||
|
|
||||||
|
interface CaptureOasSnapshotArgs {
|
||||||
|
log: ToolingLog;
|
||||||
|
buildFlavour: 'serverless' | 'traditional';
|
||||||
|
outputFile: string;
|
||||||
|
update: boolean;
|
||||||
|
filters?: {
|
||||||
|
pathStartsWith?: string[];
|
||||||
|
excludePathsMatching?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const MB = 1024 * 1024;
|
||||||
|
const twoDeci = (num: number) => Math.round(num * 100) / 100;
|
||||||
|
|
||||||
|
export async function captureOasSnapshot({
|
||||||
|
log,
|
||||||
|
filters = {},
|
||||||
|
buildFlavour,
|
||||||
|
update,
|
||||||
|
outputFile,
|
||||||
|
}: CaptureOasSnapshotArgs): Promise<void> {
|
||||||
|
const { excludePathsMatching = [], pathStartsWith } = filters;
|
||||||
|
// internal consts
|
||||||
|
const port = 5622;
|
||||||
|
// We are only including /api/status for now
|
||||||
|
excludePathsMatching.push(
|
||||||
|
'/{path*}',
|
||||||
|
// Our internal asset paths
|
||||||
|
'/XXXXXXXXXXXX/'
|
||||||
|
);
|
||||||
|
|
||||||
|
let esCluster: undefined | { stop(): Promise<void> };
|
||||||
|
let kbWorker: undefined | ChildProcess;
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.info('Starting es...');
|
||||||
|
esCluster = await log.indent(4, async () => {
|
||||||
|
if (buildFlavour === 'serverless') {
|
||||||
|
const { startES } = createTestServerlessInstances();
|
||||||
|
return await startES();
|
||||||
|
}
|
||||||
|
const cluster = createTestEsCluster({ log });
|
||||||
|
await cluster.start();
|
||||||
|
return { stop: () => cluster.cleanup() };
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info('Starting Kibana...');
|
||||||
|
kbWorker = await log.indent(4, async () => {
|
||||||
|
log.info('Loading core with all plugins enabled so that we can capture OAS for all...');
|
||||||
|
const { msg$, proc } = startTSWorker<Result>({
|
||||||
|
log,
|
||||||
|
src: require.resolve('./kibana_worker'),
|
||||||
|
env: { ...process.env, [buildFlavourEnvArgName]: buildFlavour },
|
||||||
|
});
|
||||||
|
await Rx.firstValueFrom(
|
||||||
|
msg$.pipe(
|
||||||
|
Rx.map((msg) => {
|
||||||
|
if (msg !== 'ready')
|
||||||
|
throw new Error(`received unexpected message from worker (expected "ready"): ${msg}`);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return proc;
|
||||||
|
});
|
||||||
|
|
||||||
|
const qs = encode({
|
||||||
|
access: 'public',
|
||||||
|
version: '2023-10-31', // hard coded for now, we can make this configurable later
|
||||||
|
pathStartsWith,
|
||||||
|
excludePathsMatching,
|
||||||
|
});
|
||||||
|
const url = `http://localhost:${port}/api/oas?${qs}`;
|
||||||
|
log.info(`Fetching OAS at ${url}...`);
|
||||||
|
const result = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'kbn-xsrf': 'kbn-oas-snapshot',
|
||||||
|
authorization: `Basic ${Buffer.from('elastic:changeme').toString('base64')}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (result.status !== 200) {
|
||||||
|
log.error(`Failed to fetch OAS: ${JSON.stringify(result, null, 2)}`);
|
||||||
|
throw new Error(`Failed to fetch OAS: ${result.status}`);
|
||||||
|
}
|
||||||
|
const currentOas = await result.json();
|
||||||
|
log.info(`Recieved OAS, writing to ${outputFile}...`);
|
||||||
|
if (update) {
|
||||||
|
await fs.writeFile(outputFile, sortAndPrettyPrint(currentOas));
|
||||||
|
const { size: sizeBytes } = await fs.stat(outputFile);
|
||||||
|
log.success(`OAS written to ${outputFile}. File size ~${twoDeci(sizeBytes / MB)} MB.`);
|
||||||
|
} else {
|
||||||
|
log.success(
|
||||||
|
`OAS recieved, not writing to file. Got OAS for ${
|
||||||
|
Object.keys(currentOas.paths).length
|
||||||
|
} paths.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log.error(`Failed to capture OAS: ${JSON.stringify(err, null, 2)}`);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
kbWorker?.kill('SIGILL');
|
||||||
|
await esCluster?.stop();
|
||||||
|
}
|
||||||
|
}
|
9
packages/kbn-capture-oas-snapshot-cli/src/common.ts
Normal file
9
packages/kbn-capture-oas-snapshot-cli/src/common.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const buildFlavourEnvArgName = 'CAPTURE_OAS_SNAPSHOT_WORKER_BUILD_FLAVOR';
|
|
@ -6,9 +6,13 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server';
|
import {
|
||||||
|
createRootWithCorePlugins,
|
||||||
|
createTestServerlessInstances,
|
||||||
|
} from '@kbn/core-test-helpers-kbn-server';
|
||||||
import { set } from '@kbn/safer-lodash-set';
|
import { set } from '@kbn/safer-lodash-set';
|
||||||
import { PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH } from '@kbn/core-plugins-server-internal/src/constants';
|
import { PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH } from '@kbn/core-plugins-server-internal/src/constants';
|
||||||
|
import { buildFlavourEnvArgName } from './common';
|
||||||
|
|
||||||
export type Result = 'ready';
|
export type Result = 'ready';
|
||||||
|
|
||||||
|
@ -16,6 +20,10 @@ export type Result = 'ready';
|
||||||
if (!process.send) {
|
if (!process.send) {
|
||||||
throw new Error('worker must be run in a node.js fork');
|
throw new Error('worker must be run in a node.js fork');
|
||||||
}
|
}
|
||||||
|
const buildFlavour = process.env[buildFlavourEnvArgName];
|
||||||
|
if (!buildFlavour) throw new Error(`env arg ${buildFlavourEnvArgName} must be provided`);
|
||||||
|
|
||||||
|
const serverless = buildFlavour === 'serverless';
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
logging: {
|
logging: {
|
||||||
|
@ -30,7 +38,8 @@ export type Result = 'ready';
|
||||||
};
|
};
|
||||||
set(settings, PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH, true);
|
set(settings, PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH, true);
|
||||||
|
|
||||||
const root = createRootWithCorePlugins(settings, {
|
const cliArgs = {
|
||||||
|
serverless,
|
||||||
basePath: false,
|
basePath: false,
|
||||||
cache: false,
|
cache: false,
|
||||||
dev: true,
|
dev: true,
|
||||||
|
@ -40,11 +49,21 @@ export type Result = 'ready';
|
||||||
oss: false,
|
oss: false,
|
||||||
runExamples: false,
|
runExamples: false,
|
||||||
watch: false,
|
watch: false,
|
||||||
});
|
};
|
||||||
|
|
||||||
await root.preboot();
|
if (serverless) {
|
||||||
await root.setup();
|
// Satisfy spaces config for serverless:
|
||||||
await root.start();
|
set(settings, 'xpack.spaces.allowFeatureVisibility', false);
|
||||||
|
const { startKibana } = createTestServerlessInstances({
|
||||||
|
kibana: { settings, cliArgs },
|
||||||
|
});
|
||||||
|
await startKibana();
|
||||||
|
} else {
|
||||||
|
const root = createRootWithCorePlugins(settings, cliArgs);
|
||||||
|
await root.preboot();
|
||||||
|
await root.setup();
|
||||||
|
await root.start();
|
||||||
|
}
|
||||||
|
|
||||||
const result: Result = 'ready';
|
const result: Result = 'ready';
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs/promises';
|
|
||||||
import { encode } from 'node:querystring';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import { run } from '@kbn/dev-cli-runner';
|
import { run } from '@kbn/dev-cli-runner';
|
||||||
import { startTSWorker } from '@kbn/dev-utils';
|
|
||||||
import { createTestEsCluster } from '@kbn/test';
|
|
||||||
import * as Rx from 'rxjs';
|
|
||||||
import { REPO_ROOT } from '@kbn/repo-info';
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import type { Result } from './kibana_worker';
|
import { captureOasSnapshot } from './capture_oas_snapshot';
|
||||||
|
|
||||||
const OAS_FILE_PATH = path.resolve(REPO_ROOT, './oas_docs/bundle.json');
|
|
||||||
|
|
||||||
export const sortAndPrettyPrint = (object: object) => {
|
export const sortAndPrettyPrint = (object: object) => {
|
||||||
const keys = new Set<string>();
|
const keys = new Set<string>();
|
||||||
|
@ -29,84 +21,45 @@ export const sortAndPrettyPrint = (object: object) => {
|
||||||
return JSON.stringify(object, Array.from(keys).sort(), 2);
|
return JSON.stringify(object, Array.from(keys).sort(), 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MB = 1024 * 1024;
|
const OAS_OUTPUT_DIR = path.resolve(REPO_ROOT, './oas_docs');
|
||||||
const twoDeci = (num: number) => Math.round(num * 100) / 100;
|
|
||||||
|
|
||||||
run(
|
run(
|
||||||
async ({ log, flagsReader, addCleanupTask }) => {
|
async ({ log, flagsReader }) => {
|
||||||
|
const serverless = flagsReader.boolean('serverless');
|
||||||
|
const traditional = flagsReader.boolean('traditional');
|
||||||
|
if (!serverless && !traditional) {
|
||||||
|
log.error(
|
||||||
|
'Not capturing any OAS, remove one or both of `--no-serverless` or `--no-traditional` flags to run this CLI'
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const update = flagsReader.boolean('update');
|
const update = flagsReader.boolean('update');
|
||||||
const pathStartsWith = flagsReader.arrayOfStrings('include-path');
|
const pathStartsWith = flagsReader.arrayOfStrings('include-path');
|
||||||
const excludePathsMatching = flagsReader.arrayOfStrings('exclude-path') ?? [];
|
const excludePathsMatching = flagsReader.arrayOfStrings('exclude-path') ?? [];
|
||||||
|
|
||||||
// internal consts
|
if (traditional) {
|
||||||
const port = 5622;
|
log.info('Capturing OAS for traditional Kibana...');
|
||||||
// We are only including /api/status for now
|
await captureOasSnapshot({
|
||||||
excludePathsMatching.push(
|
|
||||||
'/{path*}',
|
|
||||||
// Our internal asset paths
|
|
||||||
'/XXXXXXXXXXXX/'
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info('Starting es...');
|
|
||||||
await log.indent(4, async () => {
|
|
||||||
const cluster = createTestEsCluster({ log });
|
|
||||||
await cluster.start();
|
|
||||||
addCleanupTask(() => cluster.cleanup());
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info('Starting Kibana...');
|
|
||||||
await log.indent(4, async () => {
|
|
||||||
log.info('Loading core with all plugins enabled so that we can capture OAS for all...');
|
|
||||||
const { msg$, proc } = startTSWorker<Result>({
|
|
||||||
log,
|
log,
|
||||||
src: require.resolve('./kibana_worker'),
|
buildFlavour: 'traditional',
|
||||||
|
outputFile: path.resolve(OAS_OUTPUT_DIR, 'bundle.json'),
|
||||||
|
filters: { pathStartsWith, excludePathsMatching },
|
||||||
|
update,
|
||||||
});
|
});
|
||||||
await Rx.firstValueFrom(
|
log.success('Captured OAS for traditional Kibana.');
|
||||||
msg$.pipe(
|
}
|
||||||
Rx.map((msg) => {
|
|
||||||
if (msg !== 'ready')
|
|
||||||
throw new Error(`received unexpected message from worker (expected "ready"): ${msg}`);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
addCleanupTask(() => proc.kill('SIGILL'));
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
if (serverless) {
|
||||||
const qs = encode({
|
log.info('Capturing OAS for serverless Kibana...');
|
||||||
access: 'public',
|
await captureOasSnapshot({
|
||||||
version: '2023-10-31', // hard coded for now, we can make this configurable later
|
log,
|
||||||
pathStartsWith,
|
buildFlavour: 'serverless',
|
||||||
excludePathsMatching,
|
outputFile: path.resolve(OAS_OUTPUT_DIR, 'bundle.serverless.json'),
|
||||||
|
filters: { pathStartsWith, excludePathsMatching },
|
||||||
|
update,
|
||||||
});
|
});
|
||||||
const url = `http://localhost:${port}/api/oas?${qs}`;
|
log.success('Captured OAS for serverless Kibana.');
|
||||||
log.info(`Fetching OAS at ${url}...`);
|
|
||||||
const result = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
'kbn-xsrf': 'kbn-oas-snapshot',
|
|
||||||
authorization: `Basic ${Buffer.from('elastic:changeme').toString('base64')}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (result.status !== 200) {
|
|
||||||
log.error(`Failed to fetch OAS: ${JSON.stringify(result, null, 2)}`);
|
|
||||||
throw new Error(`Failed to fetch OAS: ${result.status}`);
|
|
||||||
}
|
|
||||||
const currentOas = await result.json();
|
|
||||||
log.info(`Recieved OAS, writing to ${OAS_FILE_PATH}...`);
|
|
||||||
if (update) {
|
|
||||||
await fs.writeFile(OAS_FILE_PATH, sortAndPrettyPrint(currentOas));
|
|
||||||
const { size: sizeBytes } = await fs.stat(OAS_FILE_PATH);
|
|
||||||
log.success(`OAS written to ${OAS_FILE_PATH}. File size ~${twoDeci(sizeBytes / MB)} MB.`);
|
|
||||||
} else {
|
|
||||||
log.success(
|
|
||||||
`OAS recieved, not writing to file. Got OAS for ${
|
|
||||||
Object.keys(currentOas.paths).length
|
|
||||||
} paths.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
log.error(`Failed to capture OAS: ${JSON.stringify(err, null, 2)}`);
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -114,15 +67,19 @@ run(
|
||||||
Get the current OAS from Kibana's /api/oas API
|
Get the current OAS from Kibana's /api/oas API
|
||||||
`,
|
`,
|
||||||
flags: {
|
flags: {
|
||||||
boolean: ['update'],
|
boolean: ['update', 'serverless', 'traditional'],
|
||||||
string: ['include-path', 'exclude-path'],
|
string: ['include-path', 'exclude-path'],
|
||||||
default: {
|
default: {
|
||||||
fix: false,
|
fix: false,
|
||||||
|
serverless: true,
|
||||||
|
traditional: true,
|
||||||
},
|
},
|
||||||
help: `
|
help: `
|
||||||
--include-path Path to include. Path must start with provided value. Can be passed multiple times.
|
--include-path Path to include. Path must start with provided value. Can be passed multiple times.
|
||||||
--exclude-path Path to exclude. Path must NOT start with provided value. Can be passed multiple times.
|
--exclude-path Path to exclude. Path must NOT start with provided value. Can be passed multiple times.
|
||||||
--update Write the current OAS to ${chalk.cyan(OAS_FILE_PATH)}.
|
--update Write the current OAS bundles to ${chalk.cyan(OAS_OUTPUT_DIR)}.
|
||||||
|
--no-serverless Whether to skip OAS for serverless Kibana. Defaults to false.
|
||||||
|
--no-traditional Whether to skip OAS for traditional Kibana. Defaults to false.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,6 @@
|
||||||
"@kbn/dev-cli-runner",
|
"@kbn/dev-cli-runner",
|
||||||
"@kbn/test",
|
"@kbn/test",
|
||||||
"@kbn/dev-utils",
|
"@kbn/dev-utils",
|
||||||
|
"@kbn/tooling-log",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ChildProcess from 'child_process';
|
import ChildProcess, { type ForkOptions } from 'child_process';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import * as Rx from 'rxjs';
|
import * as Rx from 'rxjs';
|
||||||
|
|
||||||
|
@ -16,23 +16,29 @@ import { observeLines } from '@kbn/stdio-dev-helpers';
|
||||||
|
|
||||||
// import type { Result } from './kibana_worker';
|
// import type { Result } from './kibana_worker';
|
||||||
|
|
||||||
interface StartTSWorkerArgs {
|
interface StartTSWorkerArgs extends ForkOptions {
|
||||||
log: SomeDevLog;
|
log: SomeDevLog;
|
||||||
/** Path to worker source */
|
/** Path to worker source. Best practice to `require.resolve('../relative/paths')` */
|
||||||
src: string;
|
src: string;
|
||||||
/** Defaults to repo root */
|
|
||||||
cwd?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a TS file as the src of a NodeJS Worker with some built-in handling
|
* Provide a TS file as the src of a NodeJS Worker with some built-in handling
|
||||||
* of std streams and debugging.
|
* of std streams and debugging.
|
||||||
*/
|
*/
|
||||||
export function startTSWorker<Message>({ log, src, cwd = REPO_ROOT }: StartTSWorkerArgs) {
|
export function startTSWorker<Message>({
|
||||||
const fork = ChildProcess.fork(require.resolve(src), {
|
log,
|
||||||
execArgv: ['--require=@kbn/babel-register/install'],
|
src,
|
||||||
|
cwd = REPO_ROOT,
|
||||||
|
execArgv = [],
|
||||||
|
stdio = ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||||
|
...forkOptions
|
||||||
|
}: StartTSWorkerArgs) {
|
||||||
|
const fork = ChildProcess.fork(src, {
|
||||||
|
execArgv: ['--require=@kbn/babel-register/install', ...execArgv],
|
||||||
cwd,
|
cwd,
|
||||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
stdio,
|
||||||
|
...forkOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const msg$ = Rx.merge(
|
const msg$ = Rx.merge(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue