Bazel CI and Dependabot Sync in kubernetes-client/java This Week
The kubernetes-client/java repo had a busy seven days, with 24 commits and 28 files touched. Almost all of it is build and CI infrastructure: a new auto sync workflow that keeps MODULE.bazel aligned when Dependabot bumps pom.xml, a cross OS Bazel repository cache, and a stack of small fixes to make the Bazel pipeline behave the same on Linux, macOS, and Windows. If you ship the Java client into production or maintain a fork, this is the kind of week that quietly removes a class of merge headaches.
A Dependabot to Bazel sync workflow
The project keeps two parallel dependency stores. Versions live in pom.xml (the source of truth) and Bazel reads them from MODULE.bazel and the locked maven_install.json. Until now, every Dependabot bump to pom.xml left the Bazel files stale, and someone had to manually rerun the sync script and bazel run @maven//:pin.
The new workflow dependabot-sync-bazel.yml does it automatically. It runs on pull_request_target for branches master and master-java8, filters to PRs opened by Dependabot from the same repo, then runs scripts/sync_bazel_dependencies.py and REPIN=1 bazel run @maven//:pin. If the diff touches MODULE.bazel or maven_install.json, the workflow commits the regen and pushes back to the PR head.
Notable bits:
- Trigger event is
pull_request_targetscoped to changes inpom.xml. The PR head must come from the same repository, which avoids the classic write token on fork foot gun. - The workflow needs
contents: writeandpull-requests: readonly. - Bazelisk is pinned to v1.24.1.
A companion workflow dependabot-sync-bazel-backfill.yml exists for catching open PRs that landed before the auto sync was in place. It enumerates open Dependabot PRs through the GitHub API and processes them in a loop. The latest patch on it switched the API call from a single 100 item page to a paginated loop, so repos with more than 100 open Dependabot PRs are handled cleanly:
open_prs_json='[]'
page=1
while :; do
page_json="$(curl -fsSL \
-u "x-access-token:${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"${api}/pulls?state=open&per_page=100&page=${page}")"
open_prs_json="$(jq -s 'add' \
<(printf '%s' "${open_prs_json}") \
<(printf '%s' "${page_json}"))"
if [[ "$(jq 'length' <<< "${page_json}")" -lt 100 ]]; then
break
fi
page=$((page + 1))
done
Not glamorous, but it removes a quiet warning that older versions of the workflow printed when they hit the page cap.
Cross OS repository cache and a split disk cache
The main Bazel CI now shares the Bazel repository cache across OS runners. The repository cache is where downloaded external JARs land, and there is no reason to fetch them again on each OS. The disk cache, which holds compiled action outputs, stays scoped to each OS because outputs are not portable.
In .github/workflows/bazel.yml this is now two cache steps instead of one:
- name: Restore Bazel repository cache (cross-OS)
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/bazel/cache/repos/v1
key: bazel-repos-java${{ matrix.java }}-${{ hashFiles('MODULE.bazel', '.bazelversion', 'maven_install.json') }}
restore-keys: |
bazel-repos-java${{ matrix.java }}-
bazel-repos-
enableCrossOsArchive: true
- name: Restore Bazel disk cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/bazel-disk-cache
key: bazel-${{ runner.os }}-java${{ matrix.java }}-${{ hashFiles('MODULE.bazel', '.bazelversion', 'maven_install.json') }}
For maintainers running similar matrices: enableCrossOsArchive: true is the lever. The same split was applied to the spring workflow so the spring matrix benefits too.
Windows portability and CodeQL going buildless
Two changes shipped to make the matrix actually pass on Windows. First, the Bazel build and test steps in bazel.yml were converted from one long line with shell continuations to folded YAML scalars (>-). The previous form leaned on backslash line continuations which bash on Windows does not handle the same way. The new form passes the whole command to bash as a single line, which every shell agrees on.
Second, the CodeQL workflow dropped the Bazel build step entirely and switched to build-mode: none. CodeQL for Java can now scan source without a build, which removes 25 lines of cache and bazel install plumbing from codeql-analysis.yml. CodeQL runs were previously spending time building the project just to satisfy the analyzer, and that is no longer needed.
Spring CI followed the rest of the project off Maven. The spring workflow was migrated from Maven to Bazel, then the cache key was aligned with the main CI naming, and finally its repository cache was split to use the cross OS share. The aim is one consistent Bazel pipeline for both core and spring modules.
Sync script polish
Most of the long tail of commits is refinements to scripts/sync_bazel_dependencies.py. The script extracts the managed dependency list from pom.xml, classifies Spring artifacts separately from core, resolves Maven property placeholders such as ${kubernetes-client.version}, and writes a generated block bracketed by # BEGIN: generated by scripts/sync_bazel_dependencies.py and # END: markers in MODULE.bazel.
The work over the week tightened:
- Property resolution: the recursive resolver now has a defined depth cap (
MAX_PROPERTY_RESOLUTION_DEPTH = 20) and a clearer failure path when a property cannot be resolved. - Validation errors: the messages now name the offending coordinate and the file path, which is what you want when CI fails on a Friday afternoon.
- Spring classification: the docstring spelling out what counts as a spring dependency was cleaned up, useful because the script writes a separate Bazel section for Spring artifacts on Java 17 and newer.
- Constants and helper docstrings: minor, but the file now reads as one piece rather than a junk drawer.
There is also a documented “generated dependency ordering” guarantee. The generated block is sorted deterministically, so running the script twice on the same pom.xml produces the same MODULE.bazel byte for byte. That is what makes the Dependabot sync workflow safe to commit automatically.
What to watch
A few things worth tracking if you maintain a fork or run CI of your own against this project:
- If you mirror the workflows, copy both
dependabot-sync-bazel.ymlanddependabot-sync-bazel-backfill.yml. The first handles new PRs, the second cleans the existing queue. - The
pull_request_targettrigger only runs the sync for PRs from the same repo. Forks still need the manual backfill or maintainer intervention. - The cross OS repository cache assumes Bazel’s repo cache layout stays compatible across runners. If a future Bazel version changes the on disk format, the shared cache becomes a miss rather than a corruption, but expect a brief slow week.
- CodeQL is now scanning Java with no build. Coverage should be similar, but any custom queries that relied on built artifacts need to be reviewed.