mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
# Backport This will backport the following commits from `main` to `9.0`: - [[Build] Fix parallel stderr (#223177)](https://github.com/elastic/kibana/pull/223177) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Brad White","email":"Ikuni17@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-06-11T22:49:30Z","message":"[Build] Fix parallel stderr (#223177)\n\n## Summary\n- Caused by #217929\n- Fixes errors not being correctly surfaced when running tasks in\nparallel, see:\n[logs](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6363#0197545d-e878-4dfb-97a5-0ab7d11af95c/7318-7837)\n- Added tests for `bufferLogs: true`\n\n### Testing\n- [Error\nbuild](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6391)\n- Future errors will be under the \"Finalizing Kibana Artifacts\" header\ninstead of the last artifact's logs. See\n2aa4e6523add9b77ba4e79f5863c5cbd5bc396aa\n- [Good\nbuild](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6392)","sha":"fe9c921b3ed8614d2c7b9ae193fe1f83ef7c0d42","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Operations","release_note:skip","backport:prev-minor","backport:prev-major","v9.1.0"],"title":"[Build] Fix parallel stderr","number":223177,"url":"https://github.com/elastic/kibana/pull/223177","mergeCommit":{"message":"[Build] Fix parallel stderr (#223177)\n\n## Summary\n- Caused by #217929\n- Fixes errors not being correctly surfaced when running tasks in\nparallel, see:\n[logs](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6363#0197545d-e878-4dfb-97a5-0ab7d11af95c/7318-7837)\n- Added tests for `bufferLogs: true`\n\n### Testing\n- [Error\nbuild](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6391)\n- Future errors will be under the \"Finalizing Kibana Artifacts\" header\ninstead of the last artifact's logs. See\n2aa4e6523add9b77ba4e79f5863c5cbd5bc396aa\n- [Good\nbuild](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6392)","sha":"fe9c921b3ed8614d2c7b9ae193fe1f83ef7c0d42"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/223177","number":223177,"mergeCommit":{"message":"[Build] Fix parallel stderr (#223177)\n\n## Summary\n- Caused by #217929\n- Fixes errors not being correctly surfaced when running tasks in\nparallel, see:\n[logs](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6363#0197545d-e878-4dfb-97a5-0ab7d11af95c/7318-7837)\n- Added tests for `bufferLogs: true`\n\n### Testing\n- [Error\nbuild](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6391)\n- Future errors will be under the \"Finalizing Kibana Artifacts\" header\ninstead of the last artifact's logs. See\n2aa4e6523add9b77ba4e79f5863c5cbd5bc396aa\n- [Good\nbuild](https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/6392)","sha":"fe9c921b3ed8614d2c7b9ae193fe1f83ef7c0d42"}}]}] BACKPORT--> Co-authored-by: Brad White <Ikuni17@users.noreply.github.com>
This commit is contained in:
parent
a441fd60e7
commit
b1f63fb45c
6 changed files with 188 additions and 108 deletions
|
@ -186,14 +186,19 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions
|
|||
artifactTasks.push(Tasks.CreateDockerContexts);
|
||||
}
|
||||
|
||||
await Promise.allSettled(
|
||||
const results = await Promise.allSettled(
|
||||
// createRunner for each task to ensure each task gets its own Build instance
|
||||
artifactTasks.map(async (task) => await createRunner({ config, log, bufferLogs: true })(task))
|
||||
);
|
||||
|
||||
log.write('--- Finalizing Kibana artifacts');
|
||||
|
||||
if (results.some((result) => result.status === 'rejected')) {
|
||||
throw new Error('One or more artifact tasks failed. Check the logs for details.');
|
||||
}
|
||||
|
||||
/**
|
||||
* finalize artifacts by writing sha1sums of each into the target directory
|
||||
*/
|
||||
log.write('--- Finalizing Kibana artifacts');
|
||||
await globalRun(Tasks.WriteShaSums);
|
||||
}
|
||||
|
|
45
src/dev/build/lib/__mocks__/get_config.ts
Normal file
45
src/dev/build/lib/__mocks__/get_config.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { Config } from '../config';
|
||||
|
||||
export function getMockConfig() {
|
||||
return new Config(
|
||||
true,
|
||||
false,
|
||||
{
|
||||
version: '8.0.0',
|
||||
engines: {
|
||||
node: '*',
|
||||
},
|
||||
workspaces: {
|
||||
packages: [],
|
||||
},
|
||||
} as any,
|
||||
'1.2.3',
|
||||
REPO_ROOT,
|
||||
{
|
||||
buildNumber: 1234,
|
||||
buildSha: 'abcd1234',
|
||||
buildShaShort: 'abcd',
|
||||
buildVersion: '8.0.0',
|
||||
buildDate: '2023-05-15T23:12:09.000Z',
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
'',
|
||||
'',
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
{},
|
||||
{}
|
||||
);
|
||||
}
|
|
@ -7,46 +7,14 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { createAbsolutePathSerializer } from '@kbn/jest-serializers';
|
||||
|
||||
import { Config } from './config';
|
||||
import { Build } from './build';
|
||||
import { getMockConfig } from './__mocks__/get_config';
|
||||
|
||||
expect.addSnapshotSerializer(createAbsolutePathSerializer());
|
||||
|
||||
const config = new Config(
|
||||
true,
|
||||
false,
|
||||
{
|
||||
version: '8.0.0',
|
||||
engines: {
|
||||
node: '*',
|
||||
},
|
||||
workspaces: {
|
||||
packages: [],
|
||||
},
|
||||
} as any,
|
||||
'1.2.3',
|
||||
REPO_ROOT,
|
||||
{
|
||||
buildNumber: 1234,
|
||||
buildSha: 'abcd1234',
|
||||
buildShaShort: 'abcd',
|
||||
buildVersion: '8.0.0',
|
||||
buildDate: '2023-05-15T23:12:09+0000',
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
'',
|
||||
'',
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
{},
|
||||
{}
|
||||
);
|
||||
const config = getMockConfig();
|
||||
|
||||
const linuxPlatform = config.getPlatform('linux', 'x64');
|
||||
const linuxArmPlatform = config.getPlatform('linux', 'arm64');
|
||||
|
|
|
@ -13,6 +13,8 @@ import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log';
|
|||
import { createStripAnsiSerializer, createRecursiveSerializer } from '@kbn/jest-serializers';
|
||||
|
||||
import { exec } from './exec';
|
||||
import { Build } from './build';
|
||||
import { getMockConfig } from './__mocks__/get_config';
|
||||
|
||||
const testWriter = new ToolingLogCollectingWriter();
|
||||
const log = new ToolingLog();
|
||||
|
@ -26,28 +28,88 @@ expect.addSnapshotSerializer(
|
|||
)
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
testWriter.messages.length = 0;
|
||||
});
|
||||
jest.mock('./build', () => ({
|
||||
Build: jest.fn().mockImplementation(() => ({
|
||||
getBufferLogs: jest.fn().mockReturnValue(true),
|
||||
getBuildDesc: jest.fn().mockReturnValue('test-build'),
|
||||
getBuildArch: jest.fn().mockReturnValue('x64'),
|
||||
})),
|
||||
}));
|
||||
|
||||
it('executes a command, logs the command, and logs the output', async () => {
|
||||
await exec(log, process.execPath, ['-e', 'console.log("hi")']);
|
||||
expect(testWriter.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
" debg $ <nodedir>/node -e console.log(\\"hi\\")",
|
||||
" debg hi",
|
||||
]
|
||||
`);
|
||||
});
|
||||
const config = getMockConfig();
|
||||
|
||||
it('logs using level: option', async () => {
|
||||
await exec(log, process.execPath, ['-e', 'console.log("hi")'], {
|
||||
level: 'info',
|
||||
describe('exec', () => {
|
||||
let mockBuild: jest.Mocked<Build>;
|
||||
|
||||
beforeEach(() => {
|
||||
testWriter.messages.length = 0;
|
||||
|
||||
jest.clearAllMocks();
|
||||
mockBuild = new Build(config, true) as jest.Mocked<Build>;
|
||||
});
|
||||
|
||||
it('executes a command, logs the command, and logs the output', async () => {
|
||||
await exec(log, process.execPath, ['-e', 'console.log("hi")']);
|
||||
expect(testWriter.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
" debg $ <nodedir>/node -e console.log(\\"hi\\")",
|
||||
" debg hi",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('logs using level: option', async () => {
|
||||
await exec(log, process.execPath, ['-e', 'console.log("hi")'], {
|
||||
level: 'info',
|
||||
});
|
||||
expect(testWriter.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
" info $ <nodedir>/node -e console.log(\\"hi\\")",
|
||||
" info hi",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('collects and logs output when bufferLogs is true', async () => {
|
||||
await exec(log, process.execPath, ['-e', 'console.log("buffered output")'], {
|
||||
build: mockBuild,
|
||||
});
|
||||
|
||||
expect(testWriter.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"--- ✅ test-build [x64]",
|
||||
" │ debg $ <nodedir>/node -e console.log(\\"buffered output\\")",
|
||||
" │ debg buffered output",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws error when command fails when bufferLogs is true', async () => {
|
||||
try {
|
||||
await expect(
|
||||
await exec(log, process.execPath, ['-e', 'process.exit(1)'], {
|
||||
build: mockBuild,
|
||||
})
|
||||
).rejects.toThrow();
|
||||
} catch (error) {
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"Command failed with exit code 1: <nodedir>/node -e process.exit(1)"`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('handles stderr output when bufferLogs is true', async () => {
|
||||
await exec(log, process.execPath, ['-e', 'console.error("error output: exit code 123")'], {
|
||||
build: mockBuild,
|
||||
});
|
||||
|
||||
expect(testWriter.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"--- ✅ test-build [x64]",
|
||||
" │ debg $ <nodedir>/node -e console.error(\\"error output: exit code 123\\")",
|
||||
" │ERROR error output: exit code 123",
|
||||
]
|
||||
`);
|
||||
});
|
||||
expect(testWriter.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
" info $ <nodedir>/node -e console.log(\\"hi\\")",
|
||||
" info hi",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -23,12 +23,43 @@ interface Options {
|
|||
build?: Build;
|
||||
}
|
||||
|
||||
interface LogLine {
|
||||
level: Exclude<LogLevel, 'silent'>;
|
||||
chunk: string;
|
||||
}
|
||||
|
||||
const handleBufferChunk = (chunk: Buffer, level: LogLine['level']): LogLine => {
|
||||
return {
|
||||
level,
|
||||
chunk: chunk.toString().trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const outputBufferedLogs = (
|
||||
log: ToolingLog,
|
||||
build: Build,
|
||||
logBuildCmd: () => void,
|
||||
logs: LogLine[] | undefined,
|
||||
success: boolean
|
||||
) => {
|
||||
log.write(`--- ${success ? '✅' : '❌'} ${build.getBuildDesc()} [${build.getBuildArch()}]`);
|
||||
|
||||
log.indent(4, () => {
|
||||
logBuildCmd();
|
||||
|
||||
if (logs?.length) {
|
||||
logs.forEach((line) => log[line.level](line.chunk));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export async function exec(
|
||||
log: ToolingLog,
|
||||
cmd: string,
|
||||
args: string[],
|
||||
{ level = 'debug', cwd, env, exitAfter, build }: Options = {}
|
||||
) {
|
||||
const logBuildCmd = () => log[level](chalk.dim('$'), cmd, ...args);
|
||||
const bufferLogs = build && build?.getBufferLogs();
|
||||
|
||||
const proc = execa(cmd, args, {
|
||||
|
@ -39,26 +70,26 @@ export async function exec(
|
|||
});
|
||||
|
||||
if (bufferLogs) {
|
||||
const stdout$ = fromEvent<Buffer>(proc.stdout!, 'data').pipe(map((chunk) => chunk.toString()));
|
||||
const stderr$ = fromEvent<Buffer>(proc.stderr!, 'data').pipe(map((chunk) => chunk.toString()));
|
||||
const isDockerBuild = cmd === './build_docker.sh';
|
||||
const stdout$ = fromEvent<Buffer>(proc.stdout!, 'data').pipe<LogLine>(
|
||||
map((chunk) => handleBufferChunk(chunk, level))
|
||||
);
|
||||
// docker build uses stderr as a normal output stream
|
||||
const stderr$ = fromEvent<Buffer>(proc.stderr!, 'data').pipe<LogLine>(
|
||||
map((chunk) => handleBufferChunk(chunk, isDockerBuild ? level : 'error'))
|
||||
);
|
||||
const close$ = fromEvent(proc, 'close');
|
||||
|
||||
await merge(stdout$, stderr$)
|
||||
.pipe(takeUntil(close$), toArray())
|
||||
.toPromise()
|
||||
.then((logs) => {
|
||||
log.write(`--- ${build.getBuildDesc()} [${build.getBuildArch()}]`);
|
||||
|
||||
log.indent(4, () => {
|
||||
log[level](chalk.dim('$'), cmd, ...args);
|
||||
|
||||
if (logs?.length) {
|
||||
logs.forEach((line: string) => log[level](line.trim()));
|
||||
}
|
||||
});
|
||||
const logs = await merge(stdout$, stderr$).pipe(takeUntil(close$), toArray()).toPromise();
|
||||
await proc
|
||||
.then(() => {
|
||||
outputBufferedLogs(log, build, logBuildCmd, logs, true);
|
||||
})
|
||||
.catch((error) => {
|
||||
outputBufferedLogs(log, build, logBuildCmd, logs, false);
|
||||
throw error;
|
||||
});
|
||||
} else {
|
||||
log[level](chalk.dim('$'), cmd, ...args);
|
||||
logBuildCmd();
|
||||
|
||||
await watchStdioForLine(proc, (line: string) => log[level](line), exitAfter);
|
||||
}
|
||||
|
|
|
@ -10,48 +10,17 @@
|
|||
import fetch from 'node-fetch';
|
||||
import pRetry from 'p-retry';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
|
||||
import { FetchAgentVersionsList } from './fetch_agent_versions_list';
|
||||
import { Build, Config, write } from '../lib';
|
||||
import { Build, write } from '../lib';
|
||||
import { getMockConfig } from '../lib/__mocks__/get_config';
|
||||
|
||||
jest.mock('node-fetch');
|
||||
jest.mock('p-retry');
|
||||
jest.mock('../lib');
|
||||
|
||||
const config = new Config(
|
||||
true,
|
||||
false,
|
||||
{
|
||||
version: '8.0.0',
|
||||
engines: {
|
||||
node: '*',
|
||||
},
|
||||
workspaces: {
|
||||
packages: [],
|
||||
},
|
||||
} as any,
|
||||
'1.2.3',
|
||||
REPO_ROOT,
|
||||
{
|
||||
buildNumber: 1234,
|
||||
buildSha: 'abcd1234',
|
||||
buildShaShort: 'abcd',
|
||||
buildVersion: '8.0.0',
|
||||
buildDate: '2023-05-15T23:12:09.000Z',
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
'',
|
||||
'',
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
{},
|
||||
{}
|
||||
);
|
||||
const config = getMockConfig();
|
||||
|
||||
const mockedFetch = fetch as jest.MockedFunction<typeof fetch>;
|
||||
const mockedPRetry = pRetry as jest.MockedFunction<typeof pRetry>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue