Skip to content

Commit a37aaf7

Browse files
jloleysensNicholasPeretti
authored andcommitted
[OAS] Reduce CI time for OAS extraction (elastic#235336)
1 parent c957977 commit a37aaf7

File tree

13 files changed

+104
-109
lines changed

13 files changed

+104
-109
lines changed

packages/kbn-capture-oas-snapshot-cli/src/capture_oas_snapshot.ts

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,13 @@
88
*/
99

1010
import fs from 'node:fs/promises';
11-
import { encode } from 'node:querystring';
1211
import type { ChildProcess } from 'node:child_process';
13-
import fetch from 'node-fetch';
1412
import * as Rx from 'rxjs';
1513
import { startTSWorker } from '@kbn/dev-utils';
16-
import { createTestEsCluster } from '@kbn/test';
1714
import type { ToolingLog } from '@kbn/tooling-log';
18-
import { createTestServerlessInstances } from '@kbn/core-test-helpers-kbn-server';
1915
import type { Result } from './kibana_worker';
2016
import { sortAndPrettyPrint } from './run_capture_oas_snapshot_cli';
21-
import { buildFlavourEnvArgName } from './common';
17+
import { buildFlavourEnvArgName, filtersJsonEnvArgName } from './common';
2218

2319
interface CaptureOasSnapshotArgs {
2420
log: ToolingLog;
@@ -42,68 +38,51 @@ export async function captureOasSnapshot({
4238
outputFile,
4339
}: CaptureOasSnapshotArgs): Promise<void> {
4440
const { excludePathsMatching = [], pathStartsWith } = filters;
45-
// internal consts
46-
const port = 5622;
4741
// We are only including /api/status for now
4842
excludePathsMatching.push(
4943
'/{path*}',
5044
// Our internal asset paths
5145
'/XXXXXXXXXXXX/'
5246
);
5347

54-
let esCluster: undefined | { stop(): Promise<void> };
55-
let kbWorker: undefined | ChildProcess;
56-
5748
try {
58-
log.info('Starting es...');
59-
esCluster = await log.indent(4, async () => {
60-
if (buildFlavour === 'serverless') {
61-
const { startES } = createTestServerlessInstances();
62-
return await startES();
63-
}
64-
const cluster = createTestEsCluster({ log });
65-
await cluster.start();
66-
return { stop: () => cluster.cleanup() };
67-
});
68-
6949
log.info('Starting Kibana...');
70-
kbWorker = await log.indent(4, async () => {
50+
51+
const currentOas = await log.indent(4, async () => {
7152
log.info('Loading core with all plugins enabled so that we can capture OAS for all...');
72-
const { msg$, proc } = startTSWorker<Result>({
73-
log,
74-
src: require.resolve('./kibana_worker'),
75-
env: { ...process.env, [buildFlavourEnvArgName]: buildFlavour },
76-
});
77-
await Rx.firstValueFrom(
78-
msg$.pipe(
79-
Rx.map((msg) => {
80-
if (msg !== 'ready')
81-
throw new Error(`received unexpected message from worker (expected "ready"): ${msg}`);
82-
})
83-
)
84-
);
85-
return proc;
53+
let proc: undefined | ChildProcess;
54+
try {
55+
const worker = startTSWorker<Result>({
56+
log,
57+
src: require.resolve('./kibana_worker'),
58+
env: {
59+
...process.env,
60+
[buildFlavourEnvArgName]: buildFlavour,
61+
[filtersJsonEnvArgName]: JSON.stringify({
62+
access: 'public',
63+
version: '2023-10-31', // hard coded for now, we can make this configurable later
64+
pathStartsWith,
65+
excludePathsMatching,
66+
}),
67+
},
68+
});
69+
proc = worker.proc;
70+
return await Rx.firstValueFrom(
71+
worker.msg$.pipe(
72+
Rx.map((result) => {
73+
try {
74+
return JSON.parse(result);
75+
} catch (e) {
76+
throw new Error('expected JSON, received:' + result);
77+
}
78+
})
79+
)
80+
);
81+
} finally {
82+
proc?.kill('SIGKILL');
83+
}
8684
});
8785

88-
const qs = encode({
89-
access: 'public',
90-
version: '2023-10-31', // hard coded for now, we can make this configurable later
91-
pathStartsWith,
92-
excludePathsMatching,
93-
});
94-
const url = `http://localhost:${port}/api/oas?${qs}`;
95-
log.info(`Fetching OAS at ${url}...`);
96-
const result = await fetch(url, {
97-
headers: {
98-
'kbn-xsrf': 'kbn-oas-snapshot',
99-
authorization: `Basic ${Buffer.from('elastic:changeme').toString('base64')}`,
100-
},
101-
});
102-
if (result.status !== 200) {
103-
log.error(`Failed to fetch OAS: ${JSON.stringify(result, null, 2)}`);
104-
throw new Error(`Failed to fetch OAS: ${result.status}`);
105-
}
106-
const currentOas = await result.json();
10786
log.info(`Recieved OAS, writing to ${outputFile}...`);
10887
if (update) {
10988
await fs.writeFile(outputFile, sortAndPrettyPrint(currentOas));
@@ -117,10 +96,7 @@ export async function captureOasSnapshot({
11796
);
11897
}
11998
} catch (err) {
120-
log.error(`Failed to capture OAS: ${JSON.stringify(err, null, 2)}`);
99+
log.error(`Failed to capture OAS: ${err}`);
121100
throw err;
122-
} finally {
123-
kbWorker?.kill('SIGILL');
124-
await esCluster?.stop();
125101
}
126102
}

packages/kbn-capture-oas-snapshot-cli/src/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
export const filtersJsonEnvArgName = 'CAPTURE_OAS_SNAPSHOT_WORKER_FILTERS_JSON';
1011
export const buildFlavourEnvArgName = 'CAPTURE_OAS_SNAPSHOT_WORKER_BUILD_FLAVOR';

packages/kbn-capture-oas-snapshot-cli/src/kibana_worker.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@
99

1010
import {
1111
createRootWithCorePlugins,
12-
createTestServerlessInstances,
12+
createServerlessKibana,
1313
} from '@kbn/core-test-helpers-kbn-server';
1414
import { set } from '@kbn/safer-lodash-set';
15+
import type { Root } from '@kbn/core-root-server-internal';
1516
import { PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH } from '@kbn/core-plugins-server-internal/src/constants';
16-
import { buildFlavourEnvArgName } from './common';
17+
import { buildFlavourEnvArgName, filtersJsonEnvArgName } from './common';
1718

18-
export type Result = 'ready';
19+
export type Result = string;
1920

2021
(async () => {
2122
if (!process.send) {
2223
throw new Error('worker must be run in a node.js fork');
2324
}
25+
const filtersString = process.env[filtersJsonEnvArgName];
26+
if (!filtersString) throw new Error(`env arg ${filtersJsonEnvArgName} must be provided`);
27+
const filters = JSON.parse(filtersString);
2428
const buildFlavour = process.env[buildFlavourEnvArgName];
2529
if (!buildFlavour) throw new Error(`env arg ${buildFlavourEnvArgName} must be provided`);
2630

@@ -52,25 +56,25 @@ export type Result = 'ready';
5256
watch: false,
5357
};
5458

59+
let root: Root;
60+
61+
set(settings, 'migrations.skip', true);
62+
set(settings, 'elasticsearch.skipStartupConnectionCheck', true);
63+
5564
if (serverless) {
5665
// Satisfy spaces config for serverless:
5766
set(settings, 'xpack.spaces.allowFeatureVisibility', false);
5867
set(settings, 'xpack.spaces.allowSolutionVisibility', false);
59-
const { startKibana } = createTestServerlessInstances({
60-
kibana: { settings, cliArgs },
61-
});
62-
await startKibana();
68+
root = createServerlessKibana(settings, cliArgs);
6369
} else {
64-
const root = createRootWithCorePlugins(settings, cliArgs);
65-
await root.preboot();
66-
await root.setup();
67-
await root.start();
70+
root = createRootWithCorePlugins(settings, cliArgs);
6871
}
69-
70-
const result: Result = 'ready';
71-
72-
process.send(result);
72+
await root.preboot();
73+
await root.setup();
74+
const { http } = await root.start();
75+
const oas = await http.generateOas({ baseUrl: 'http://localhost:5622', filters });
76+
process.send(JSON.stringify(oas));
7377
})().catch((error) => {
74-
process.stderr.write(`UNHANDLED ERROR: ${error.stack}`);
78+
process.stderr.write(`UNHANDLED ERROR: ${error}`);
7579
process.exit(1);
7680
});

packages/kbn-capture-oas-snapshot-cli/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
"@kbn/safer-lodash-set",
2020
"@kbn/core-plugins-server-internal",
2121
"@kbn/dev-cli-runner",
22-
"@kbn/test",
2322
"@kbn/dev-utils",
2423
"@kbn/tooling-log",
24+
"@kbn/core-root-server-internal",
2525
]
2626
}

packages/kbn-check-saved-objects-cli/src/compatibility/extract_mappings_from_plugins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export async function extractMappingsFromPlugins(
3535
msg$.pipe(
3636
Rx.map((result) => {
3737
log.debug('message received from worker', result);
38-
proc.kill('SIGILL');
38+
proc.kill('SIGKILL');
3939
return result.mappings;
4040
}),
4141
Rx.defaultIfEmpty(undefined)

packages/kbn-check-saved-objects-cli/src/mappings_additions/extract_field_lists_from_plugins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export async function extractFieldListsFromPlugins(log: SomeDevLog): Promise<Res
3232
msg$.pipe(
3333
Rx.map((outcome) => {
3434
log.debug('message received from worker', outcome);
35-
proc.kill('SIGILL');
35+
proc.kill('SIGKILL');
3636
return outcome;
3737
}),
3838
Rx.defaultIfEmpty(undefined)

src/core/packages/http/server-internal/src/http_service.ts

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import type {
4848
InternalHttpServicePreboot,
4949
InternalHttpServiceSetup,
5050
InternalHttpServiceStart,
51+
GenerateOasArgs,
5152
} from './types';
5253
import { registerCoreHandlers } from './register_lifecycle_handlers';
5354
import type { ExternalUrlConfigType } from './external_url';
@@ -226,11 +227,12 @@ export class HttpService
226227
return this.internalSetup;
227228
}
228229

229-
// this method exists because we need the start contract to create the `CoreStart` used to start
230+
// this method exists because we need the start contract to create `CoreStart` used to start
230231
// the `plugin` and `legacy` services.
231232
public getStartContract(): InternalHttpServiceStart {
232233
return {
233234
...pick(this.internalSetup!, ['auth', 'basePath', 'getServerInfo', 'staticAssets']),
235+
generateOas: (args: GenerateOasArgs) => this.generateOas(args),
234236
isListening: () => this.httpServer.isListening(),
235237
};
236238
}
@@ -259,6 +261,24 @@ export class HttpService
259261
return this.getStartContract();
260262
}
261263

264+
private generateOas({ pluginId, baseUrl, filters }: GenerateOasArgs) {
265+
// Potentially quite expensive
266+
return firstValueFrom(
267+
of(1).pipe(
268+
HttpService.generateOasSemaphore.acquire(),
269+
mergeMap(async () => {
270+
return generateOpenApiDocument(this.httpServer.getRouters({ pluginId }), {
271+
baseUrl,
272+
title: 'Kibana HTTP APIs',
273+
version: '0.0.0', // TODO get a better version here
274+
filters,
275+
env: { serverless: this.env.packageInfo.buildFlavor === 'serverless' },
276+
});
277+
})
278+
)
279+
);
280+
}
281+
262282
private registerOasApi(config: HttpConfig) {
263283
const basePath = this.internalSetup?.basePath;
264284
const server = this.internalSetup?.server;
@@ -305,32 +325,19 @@ export class HttpService
305325
} catch (e) {
306326
return h.response({ message: e.message }).code(400);
307327
}
308-
return await firstValueFrom(
309-
of(1).pipe(
310-
HttpService.generateOasSemaphore.acquire(),
311-
mergeMap(async () => {
312-
try {
313-
// Potentially quite expensive
314-
const result = await generateOpenApiDocument(
315-
this.httpServer.getRouters({ pluginId: query.pluginId }),
316-
{
317-
baseUrl,
318-
title: 'Kibana HTTP APIs',
319-
version: '0.0.0', // TODO get a better version here
320-
filters,
321-
env: { serverless: this.env.packageInfo.buildFlavor === 'serverless' },
322-
}
323-
);
324-
return h.response(result);
325-
} catch (e) {
326-
this.log.error(e);
327-
return h.response({ message: e.message }).code(500);
328-
}
329-
})
330-
)
331-
);
328+
try {
329+
const result = await this.generateOas({
330+
baseUrl,
331+
filters,
332+
pluginId: query.pluginId,
333+
});
334+
return h.response(result);
335+
} catch (e) {
336+
return h.response({ message: e.message }).code(500);
337+
}
332338
},
333339
options: {
340+
auth: false,
334341
app: {
335342
access: 'public',
336343
security: {
@@ -340,7 +347,6 @@ export class HttpService
340347
},
341348
},
342349
},
343-
auth: false,
344350
cache: {
345351
privacy: 'public',
346352
otherwise: 'must-revalidate',

src/core/packages/http/server-internal/src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
} from '@kbn/core-http-server';
2121
import type { CoreKibanaRequest } from '@kbn/core-http-router-server-internal';
2222
import type { PostValidationMetadata } from '@kbn/core-http-server';
23+
import type { GenerateOpenApiDocumentOptionsFilters } from '@kbn/router-to-openapispec';
2324
import type { HttpServerSetup } from './http_server';
2425
import type { ExternalUrlConfig } from './external_url';
2526
import type { InternalStaticAssets } from './static_assets';
@@ -81,6 +82,14 @@ export interface InternalHttpServiceSetup
8182
/** @internal */
8283
export interface InternalHttpServiceStart extends Omit<HttpServiceStart, 'staticAssets'> {
8384
staticAssets: InternalStaticAssets;
85+
generateOas: (args: GenerateOasArgs) => Promise<object>;
8486
/** Indicates if the http server is listening on the configured port */
8587
isListening: () => boolean;
8688
}
89+
90+
/** @internal */
91+
export interface GenerateOasArgs {
92+
pluginId?: string;
93+
baseUrl: string;
94+
filters?: GenerateOpenApiDocumentOptionsFilters;
95+
}

src/core/packages/http/server-mocks/src/http_service.mock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ const createInternalStartContractMock = () => {
253253
const basePath = createBasePathMock();
254254
const mock: InternalHttpServiceStartMock = {
255255
...createStartContractMock(),
256+
generateOas: jest.fn(),
256257
staticAssets: createInternalStaticAssetsMock(basePath),
257258
isListening: jest.fn(),
258259
};

src/core/test-helpers/kbn-server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export {
1414
createRootWithCorePlugins,
1515
createTestServers,
1616
createTestServerlessInstances,
17+
createServerlessKibana,
1718
request,
1819
} from './src';
1920

0 commit comments

Comments
 (0)