From 4a6b76f6acc5c7d54c9dd1e96116ee9007fa93de Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Fri, 7 Aug 2026 13:59:36 +0200 Subject: [PATCH] Buildkite: Skip Kibana PR builds with no doc changes Co-authored-by: Cursor --- .buildkite/scripts/build_pr.sh | 19 +++++-- .buildkite/scripts/docs_paths.pl | 76 ++++++++++++++++++++++++++++ .buildkite/scripts/docs_paths.t | 39 ++++++++++++++ .buildkite/scripts/legacy_branches.t | 8 +++ 4 files changed, 139 insertions(+), 3 deletions(-) create mode 100755 .buildkite/scripts/docs_paths.pl create mode 100644 .buildkite/scripts/docs_paths.t diff --git a/.buildkite/scripts/build_pr.sh b/.buildkite/scripts/build_pr.sh index 50781309a672..b50c37e8af85 100755 --- a/.buildkite/scripts/build_pr.sh +++ b/.buildkite/scripts/build_pr.sh @@ -2,6 +2,9 @@ set -euo pipefail set +x +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DOCS_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + # This script should only be invoked by the Buildkite PR bot if [ -z ${GITHUB_PR_TARGET_BRANCH+set} ] || [ -z ${GITHUB_PR_NUMBER+set} ] || [ -z ${GITHUB_PR_BASE_REPO+set} ];then echo "One of the following env. variable GITHUB_PR_TARGET_BRANCH, GITHUB_PR_NUMBER, GITHUB_PR_BASE_REPO is missing - exiting." @@ -54,7 +57,7 @@ if [[ "${GITHUB_PR_BASE_REPO}" != 'docs' ]]; then # The helper exits non-zero only when conf.yaml itself couldn't be read (e.g. # the perl YAML module is missing), which short-circuits the condition below # so we fail open and build as today, rather than silently going green. - if legacy_branches=$(perl "$(dirname "$0")/legacy_branches.pl" "$GITHUB_PR_BASE_REPO") \ + if legacy_branches=$(perl "${SCRIPT_DIR}/legacy_branches.pl" "$GITHUB_PR_BASE_REPO") \ && ! grep -qxF "${GITHUB_PR_TARGET_BRANCH}" <<< "${legacy_branches}"; then echo "Target branch '${GITHUB_PR_TARGET_BRANCH}' is not a legacy AsciiDoc branch in conf.yaml for ${GITHUB_PR_BASE_REPO} — skipping build (reporting success)." exit 0 @@ -218,9 +221,19 @@ if [[ "${GITHUB_PR_BASE_REPO}" != 'docs' ]]; then docs_diff=$(git diff --stat "origin/$GITHUB_PR_TARGET_BRANCH"...HEAD -- ./docs/en ./docs/kr ./docs/jp) ;; - # All other repos will always build + # Repos without a specialized arm: derive doc paths from conf.yaml *) - docs_diff="always build" + git fetch origin "$GITHUB_PR_TARGET_BRANCH" + if docs_paths=$(perl "${SCRIPT_DIR}/docs_paths.pl" "$GITHUB_PR_BASE_REPO" "${DOCS_ROOT}/conf.yaml" 2>/dev/null); then + if [[ -z "$docs_paths" ]]; then + docs_diff="" + else + mapfile -t paths <<< "$docs_paths" + docs_diff=$(git diff --stat "origin/$GITHUB_PR_TARGET_BRANCH"...HEAD -- "${paths[@]}") + fi + else + docs_diff="always build" + fi ;; esac diff --git a/.buildkite/scripts/docs_paths.pl b/.buildkite/scripts/docs_paths.pl new file mode 100755 index 000000000000..bd96f20844c8 --- /dev/null +++ b/.buildkite/scripts/docs_paths.pl @@ -0,0 +1,76 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use YAML qw(LoadFile); + +# Usage: docs_paths.pl [conf.yaml] +# Prints one git-diff path per line from conf.yaml sources for the repo. +# +# Exit 0: conf.yaml was read successfully. Output is empty if the repo is not +# in conf.yaml or has no source paths. +# Exit 1: conf.yaml couldn't be loaded (e.g. missing YAML module, parse error) +# — caller should build as today (fail open), since we can't tell. + +my ( $github_repo, $conf_path ) = @ARGV; +$conf_path //= 'conf.yaml'; + +die "Usage: $0 [conf.yaml]\n" unless defined $github_repo; + +my $conf = eval { LoadFile($conf_path) }; +if ($@) { + warn "Failed to load $conf_path: $@\n"; + exit 1; +} + +# Sources in conf.yaml reference the conf key (e.g. "esf"), which for most repos +# is already the GitHub repo name but for some (e.g. "elastic-serverless-forwarder") +# differs from it. Try the GitHub name as a conf key directly first, then fall back +# to matching it against each repo's URL basename. +my $conf_key = exists $conf->{repos}{$github_repo} ? $github_repo : undef; +unless ( defined $conf_key ) { + while ( my ( $key, $url ) = each %{ $conf->{repos} } ) { + ( my $name = $url ) =~ s{.*/|\.git$}{}g; # URL -> repo name (strip path and .git) + if ( $name eq $github_repo ) { + $conf_key = $key; + last; + } + } +} + +unless ( defined $conf_key ) { + exit 0; # repo not in conf — no doc paths, caller should skip +} + +my %paths; +walk_entries( $conf->{contents}, $conf_key, \%paths ); + +print "$_\n" for sort keys %paths; +exit 0; + + +sub walk_entries { + my ( $entries, $conf_key, $paths ) = @_; + for my $entry (@$entries) { + if ( $entry->{sections} ) { + walk_entries( $entry->{sections}, $conf_key, $paths ); + } else { + collect_source_paths( $entry, $conf_key, $paths ); + } + } +} + +sub collect_source_paths { + my ( $book, $conf_key, $paths ) = @_; + for my $source ( @{ $book->{sources} // [] } ) { + next unless ( $source->{repo} // '' ) eq $conf_key; + my $path = normalize_path( $source->{path} ); + $paths->{$path} = 1 if defined $path && length $path; + } +} + +sub normalize_path { + my ($path) = @_; + return unless defined $path; + $path =~ s{^/}{}; # strip leading slash for git diff + return $path; +} diff --git a/.buildkite/scripts/docs_paths.t b/.buildkite/scripts/docs_paths.t new file mode 100644 index 000000000000..6a000a28fddc --- /dev/null +++ b/.buildkite/scripts/docs_paths.t @@ -0,0 +1,39 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use FindBin qw($RealBin); + +# Regression check for docs_paths.pl's repo-name resolution and path collection. +# Run from the repo root since docs_paths.pl defaults to ./conf.yaml. + +chdir "$RealBin/../.." or die "Can't chdir to repo root: $!"; + +my $conf = 'conf.yaml'; + +sub run { + my ($repo) = @_; + my $out = `perl .buildkite/scripts/docs_paths.pl @{[quotemeta $repo]} $conf 2>/dev/null`; + return ( $? >> 8, $out ); +} + +{ + my ( $exit, $out ) = run('kibana'); + is( $exit, 0, 'kibana: exits 0' ); + like( $out, qr{docs/}, 'kibana: includes docs/' ); + like( $out, qr{:\(glob\).*asciidoc}, 'kibana: includes a glob asciidoc pathspec' ); +} + +{ + my ( $exit, $out ) = run('elastic-serverless-forwarder'); + is( $exit, 0, 'elastic-serverless-forwarder: exits 0' ); + like( $out, qr{docs/en}, 'elastic-serverless-forwarder: resolves via URL basename to docs/en' ); +} + +{ + my ( $exit, $out ) = run('some-repo-not-in-conf-yaml'); + is( $exit, 0, 'unknown repo: exits 0' ); + is( $out, '', 'unknown repo: no doc paths' ); +} + +done_testing(); diff --git a/.buildkite/scripts/legacy_branches.t b/.buildkite/scripts/legacy_branches.t index 8905f5faa774..f143dd574bb6 100755 --- a/.buildkite/scripts/legacy_branches.t +++ b/.buildkite/scripts/legacy_branches.t @@ -24,6 +24,14 @@ for my $repo (qw(kibana-cn swiftype esf elastic-serverless-forwarder)) { isnt( $out, '', "$repo: has at least one legacy branch (conf key resolved)" ); } +{ + my ( $exit, $out ) = run('kibana'); + is( $exit, 0, 'kibana: exits 0' ); + isnt( $out, '', 'kibana: has at least one legacy branch' ); + like( $out, qr{^8\.19$}m, 'kibana: includes 8.19 legacy branch' ); + unlike( $out, qr{^9\.}m, 'kibana: has no 9.x legacy branches' ); +} + my ( $exit, $out ) = run('some-repo-not-in-conf-yaml'); is( $exit, 0, 'unknown repo: exits 0' ); is( $out, '', 'unknown repo: no legacy branches' );