diff --git a/.buildkite/scripts/common/util.sh b/.buildkite/scripts/common/util.sh index 9cd42e68efc14..556a4e5dc1576 100755 --- a/.buildkite/scripts/common/util.sh +++ b/.buildkite/scripts/common/util.sh @@ -40,19 +40,23 @@ check_for_changed_files() { echo "'$1' caused changes to the following files:" echo "$GIT_CHANGES" echo "" - echo "Auto-committing & pushing these changes now." git config --global user.name kibanamachine git config --global user.email '42973632+kibanamachine@users.noreply.github.com' gh pr checkout "${BUILDKITE_PULL_REQUEST}" git add -A -- . ':!config/node.options' ':!config/kibana.yml' - git commit -m "$CUSTOM_FIX_MESSAGE" - git push - # Wait to ensure all commits arrive before we terminate the build - sleep 300 - # Still exit with error to fail the current build, a new build should be started after the push + # If COLLECT_COMMITS_MARKER_FILE is set, we're in batch mode (e.g., called from quick checks runner) + # Just record the commit for later batch push + # Otherwise, commit and push immediately (standalone usage) + if [[ -n "${COLLECT_COMMITS_MARKER_FILE:-}" ]]; then + echo "Auto-committing these changes (will push after all checks complete)." + echo "$CUSTOM_FIX_MESSAGE" >> "$COLLECT_COMMITS_MARKER_FILE" + else + echo "Auto-committing and pushing these changes." + git push + fi exit 1 else echo -e "\n${RED}ERROR: '$1' caused changes to the following files:${C_RESET}\n" diff --git a/.gitignore b/.gitignore index 45e402c588e96..2221199247a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,6 @@ x-pack/solutions/security/test/security_solution_playwright/.env .dependency-graph-log.json .moon/cache + +# Batched commits marker, e.g.: from quick checks +.collect_commits_marker diff --git a/src/core/test-helpers/kbn-server/moon.yml b/src/core/test-helpers/kbn-server/moon.yml index 3d6c6e2076ec3..ead774d99356d 100644 --- a/src/core/test-helpers/kbn-server/moon.yml +++ b/src/core/test-helpers/kbn-server/moon.yml @@ -23,11 +23,11 @@ dependsOn: - '@kbn/config' - '@kbn/core-lifecycle-server-internal' - '@kbn/core-root-server-internal' - - '@kbn/repo-info' - '@kbn/repo-packages' - '@kbn/es' - '@kbn/dev-utils' - '@kbn/safer-lodash-set' + - '@kbn/repo-info' tags: - shared-server - package diff --git a/src/core/test-helpers/kbn-server/tsconfig.json b/src/core/test-helpers/kbn-server/tsconfig.json index 7d096b08d702f..c5a481b6ccf66 100644 --- a/src/core/test-helpers/kbn-server/tsconfig.json +++ b/src/core/test-helpers/kbn-server/tsconfig.json @@ -16,11 +16,11 @@ "@kbn/config", "@kbn/core-lifecycle-server-internal", "@kbn/core-root-server-internal", - "@kbn/repo-info", "@kbn/repo-packages", "@kbn/es", "@kbn/dev-utils", "@kbn/safer-lodash-set", + "@kbn/repo-info", ], "exclude": [ "target/**/*", diff --git a/src/dev/run_quick_checks.ts b/src/dev/run_quick_checks.ts index eb4938a671aae..6a4cf52adf56f 100644 --- a/src/dev/run_quick_checks.ts +++ b/src/dev/run_quick_checks.ts @@ -10,7 +10,7 @@ import { execFile } from 'child_process'; import { availableParallelism } from 'os'; import { isAbsolute, join } from 'path'; -import { existsSync, readdirSync, readFileSync } from 'fs'; +import { existsSync, readdirSync, readFileSync, unlinkSync } from 'fs'; import { run, RunOptions } from '@kbn/dev-cli-runner'; import { REPO_ROOT } from '@kbn/repo-info'; @@ -20,6 +20,7 @@ const MAX_PARALLELISM = availableParallelism(); const buildkiteQuickchecksFolder = join('.buildkite', 'scripts', 'steps', 'checks'); const quickChecksList = join(buildkiteQuickchecksFolder, 'quick_checks.json'); const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +const COLLECT_COMMITS_MARKER_FILE = join(REPO_ROOT, '.collect_commits_marker'); interface QuickCheck { script: string; @@ -57,6 +58,14 @@ let logger: ToolingLog; void run(async ({ log, flagsReader }) => { logger = log; + // Clean up any existing marker file from previous runs + if (existsSync(COLLECT_COMMITS_MARKER_FILE)) { + unlinkSync(COLLECT_COMMITS_MARKER_FILE); + } + + // Set environment variable so check scripts know where to write the marker file + process.env.COLLECT_COMMITS_MARKER_FILE = COLLECT_COMMITS_MARKER_FILE; + const checksToRun = collectScriptsToRun({ targetFile: flagsReader.string('file'), targetDir: flagsReader.string('dir'), @@ -81,12 +90,36 @@ void run(async ({ log, flagsReader }) => { logger.write('--- All checks finished.'); printResults(startTime, results); + // Check if any commits were made and push them in a single batch + // This allows multiple quick-check fixes to be committed and pushed together, + // avoiding multiple CI restarts when a PR has multiple offenses. + // File-changing checks run with parallelism=1 (sequentially), so commits happen + // one at a time without conflicts. + const commitsWereMade = existsSync(COLLECT_COMMITS_MARKER_FILE); + if (commitsWereMade) { + logger.write('--- Commits were made during checks. Pushing all changes now...'); + try { + await pushCommits(); + logger.write('--- Successfully pushed all commits.'); + // Clean up marker file + if (existsSync(COLLECT_COMMITS_MARKER_FILE)) { + unlinkSync(COLLECT_COMMITS_MARKER_FILE); + } + // Still exit with error to fail the current build, a new build should be started after the push + logger.write('--- Build will fail to trigger a new build with the fixes.'); + process.exitCode = 1; + } catch (error) { + logger.error(`--- Failed to push commits: ${error}`); + process.exitCode = 1; + } + } + const failedChecks = results.filter((check) => !check.success); if (failedChecks.length > 0) { logger.write(`--- ${failedChecks.length} quick check(s) failed. ❌`); logger.write(`See the script(s) marked with ❌ above for details.`); process.exitCode = 1; - } else { + } else if (!commitsWereMade) { logger.write('--- All checks passed. ✅'); return results; } @@ -188,7 +221,10 @@ async function runCheckAsync(script: string): Promise { return new Promise((resolve) => { validateScriptPath(script); - const scriptProcess = execFile('bash', [script]); + // Pass environment variables to child process, including COLLECT_COMMITS_MARKER_FILE + const scriptProcess = execFile('bash', [script], { + env: { ...process.env }, + }); let output = ''; const appendToOutput = (data: string | Buffer) => (output += data.toString()); @@ -264,3 +300,30 @@ function validateScriptPath(scriptPath: string) { function stripRoot(script: string) { return script.replace(REPO_ROOT, ''); } + +async function pushCommits(): Promise { + return new Promise((resolve, reject) => { + const pushProcess = execFile('git', ['push'], { + cwd: REPO_ROOT, + env: { ...process.env }, + }); + + let output = ''; + const appendToOutput = (data: string | Buffer) => (output += data.toString()); + + pushProcess.stdout?.on('data', appendToOutput); + pushProcess.stderr?.on('data', appendToOutput); + + pushProcess.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`git push failed with code ${code}: ${output}`)); + } + }); + + pushProcess.on('error', (error) => { + reject(error); + }); + }); +}