Skip to content

Commit c961836

Browse files
committed
feat(renovate): add coverage check for Bazel http_archive deps
Adds a CI lint that fails any PR introducing or modifying an `http_archive(...)` block in `deps/repos.MODULE.bazel` without a corresponding `customManager` entry in `renovate.json`. Without this check, native deps (openssl, krb5, libxml2, …) drift silently behind upstream — Renovate's `bazel-module` manager only covers `bazel_dep(...)` declarations from the BCR registry, not the `http_archive(...)` calls used for natives. None of the 29 such deps in the repo are tracked today. Pieces: - `tasks/renovate.py::check_bazel_coverage` — invoke task that diffs the set of `http_archive(name=...)` entries in the bazel module file against `customManagers[].depNameTemplate` in `renovate.json`, minus an explicit allowlist. Writes a markdown report to `$GITHUB_STEP_SUMMARY` on failure. - `deps/.renovate-untracked.json` — typed allowlist with non-empty rationale strings. Currently lists all 29 deps as pending tracking; follow-up PRs will move each entry from this file into a customManager in `renovate.json`. - `.github/workflows/validate-renovate-deps.yml` — runs the task on PRs touching `deps/`, `renovate.json`, or the task itself. Run locally with `dda inv -- renovate.check-bazel-coverage`.
1 parent 79de445 commit c961836

4 files changed

Lines changed: 167 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: "Validate Renovate coverage of Bazel native deps"
3+
on:
4+
pull_request:
5+
paths:
6+
- "deps/**"
7+
- "renovate.json"
8+
- "tasks/renovate.py"
9+
- ".github/workflows/validate-renovate-deps.yml"
10+
11+
permissions: {}
12+
13+
jobs:
14+
check_coverage:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
18+
- name: Install dda
19+
uses: ./.github/actions/install-dda
20+
with:
21+
features: legacy-tasks
22+
- name: Verify every http_archive in deps/ has Renovate coverage
23+
run: dda inv -- renovate.check-bazel-coverage

deps/.renovate-untracked.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"_comment": "Bazel http_archive deps in deps/repos.MODULE.bazel that intentionally have no Renovate customManager. Every entry MUST include a non-empty rationale. The check is enforced by tasks/check_renovate_bazel_coverage.py and the validate-renovate-deps.yml workflow. As deps move from this allowlist to a customManager in renovate.json, remove the entry here in the same PR.",
3+
"intentionally_untracked": {
4+
"xz": "Pending tracking in Phase 2a (github-releases batch).",
5+
"zlib": "Pending tracking in Phase 2a (github-releases batch).",
6+
"openssl": "Pending tracking in Phase 2a (github-releases batch). Blocked by openssl drift cleanup (AIX 3.5.5, MSI 3.5).",
7+
"libffi": "Pending tracking in Phase 2a (github-releases batch).",
8+
"pcre2": "Pending tracking in Phase 2a (github-releases batch).",
9+
"util-linux": "Pending tracking in Phase 2a (github-releases batch).",
10+
"libseccomp": "Pending tracking in Phase 2a (github-releases batch).",
11+
"patchelf": "Pending tracking in Phase 2a (github-releases batch).",
12+
"nghttp2": "Pending tracking in Phase 2a (github-releases batch).",
13+
"popt": "Pending tracking in Phase 2a (github-releases batch).",
14+
"libyaml": "Pending tracking in Phase 2a (github-releases batch).",
15+
"zstd": "Pending tracking in Phase 2a (github-releases batch).",
16+
"lua": "Pending tracking in Phase 2a (github-releases batch).",
17+
"xmlsec": "Pending tracking in Phase 2a (github-releases batch).",
18+
"krb5": "Pending tracking in Phase 2b (tricky-tag batch — tag krb5-X.Y.Z-final differs from tarball).",
19+
"rpm": "Pending tracking in Phase 2b (tricky-tag batch — strip_prefix is rpm-rpm-X.Y.Z-release).",
20+
"systemd": "Pending tracking in Phase 2b (tricky-tag batch — single-integer versioning, loose).",
21+
"libsepol": "Pending tracking in Phase 2b (SELinux monorepo).",
22+
"libselinux": "Pending tracking in Phase 2b (SELinux monorepo).",
23+
"openssl_fips": "Pending tracking in Phase 2b. Will be added with `enabled: false` — pinned to 3.0.9 for FIPS 140-2 validation, must NOT auto-update.",
24+
"libxml2": "Pending tracking in Phase 2c (GitLab-tags batch).",
25+
"libxslt": "Pending tracking in Phase 2c (GitLab-tags batch).",
26+
"dbus": "Pending tracking in Phase 2c (GitLab-tags batch).",
27+
"sqlite3": "Hard: version is split across a tuple (sqlite_ver = (\"3\", \"53\", \"00\")) and embeds a release-year directory in the URL. Needs a refactor to a single literal before a Renovate regex can target it cleanly. Phase 3.",
28+
"bzip2": "Hard: sourceware.org tarballs; no native Renovate datasource. Needs a customDatasource. Phase 3.",
29+
"attr": "Hard: hosted on Savannah (savannah.nongnu.org); no native Renovate datasource. Needs a customDatasource. Phase 3.",
30+
"gpg-error": "Hard: hosted on gnupg.org FTP; no native Renovate datasource. Needs a customDatasource. Phase 3.",
31+
"gcrypt": "Hard: hosted on gnupg.org FTP; no native Renovate datasource. Needs a customDatasource. Phase 3.",
32+
"unixodbc": "Hard: hosted on unixodbc.org; no native Renovate datasource. Needs a customDatasource. Phase 3."
33+
}
34+
}

tasks/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
python_version,
7070
quality_gates,
7171
release,
72+
renovate,
7273
rtloader,
7374
sbomgen,
7475
schema,
@@ -240,6 +241,7 @@
240241
ns.add_collection(setup)
241242
ns.add_collection(systray)
242243
ns.add_collection(release)
244+
ns.add_collection(renovate)
243245
ns.add_collection(rtloader)
244246
ns.add_collection(system_probe)
245247
ns.add_collection(process_agent)

tasks/renovate.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Renovate maintenance tasks.
3+
4+
So far this module exposes a single check that verifies every native dep in
5+
``deps/repos.MODULE.bazel`` is either tracked by a Renovate ``customManager`` in
6+
``renovate.json`` or listed in ``deps/.renovate-untracked.json`` with a
7+
rationale. It runs in CI via ``.github/workflows/validate-renovate-deps.yml``.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import json
13+
import os
14+
import re
15+
from pathlib import Path
16+
17+
from invoke import task
18+
from invoke.context import Context
19+
from invoke.exceptions import Exit
20+
21+
REPO_ROOT = Path(__file__).resolve().parent.parent
22+
MODULE_FILE = REPO_ROOT / "deps" / "repos.MODULE.bazel"
23+
RENOVATE_FILE = REPO_ROOT / "renovate.json"
24+
ALLOWLIST_FILE = REPO_ROOT / "deps" / ".renovate-untracked.json"
25+
26+
HTTP_ARCHIVE_NAME_RE = re.compile(r'http_archive\s*\(\s*name\s*=\s*"([^"]+)"')
27+
28+
29+
def _parse_module_bazel(path: Path) -> set[str]:
30+
return set(HTTP_ARCHIVE_NAME_RE.findall(path.read_text()))
31+
32+
33+
def _parse_renovate_json(path: Path) -> set[str]:
34+
raw = path.read_text()
35+
# renovate.json is JSON5 (trailing commas allowed); strip them so json.loads accepts.
36+
stripped = re.sub(r",(\s*[}\]])", r"\1", raw)
37+
data = json.loads(stripped)
38+
return {cm["depNameTemplate"] for cm in data.get("customManagers", []) if "depNameTemplate" in cm}
39+
40+
41+
def _parse_allowlist(path: Path) -> dict[str, str]:
42+
if not path.exists():
43+
return {}
44+
data = json.loads(path.read_text())
45+
entries = data.get("intentionally_untracked", {})
46+
bad = [k for k, v in entries.items() if not (isinstance(v, str) and v.strip())]
47+
if bad:
48+
raise Exit(
49+
f"Allowlist {path} has empty rationale for: {', '.join(bad)}. "
50+
"Every entry must include a non-empty justification string."
51+
)
52+
return entries
53+
54+
55+
def _emit_failure_report(untracked: set[str], allowlist: dict[str, str]) -> str:
56+
lines = [
57+
"## ❌ Renovate coverage check failed",
58+
"",
59+
f"The following deps in `{MODULE_FILE.relative_to(REPO_ROOT)}` have no "
60+
f"matching `customManager` in `renovate.json`:",
61+
"",
62+
"| dep | suggested fix |",
63+
"|---|---|",
64+
]
65+
for dep in sorted(untracked):
66+
lines.append(
67+
f"| `{dep}` | Add a `customManagers` entry with "
68+
f'`depNameTemplate: "{dep}"`, or add to '
69+
"`deps/.renovate-untracked.json` with a rationale. |"
70+
)
71+
lines += [
72+
"",
73+
"See `renovate.json` for existing patterns (linux-images, windows-images, ...).",
74+
"",
75+
f"Currently allowlisted ({len(allowlist)}): "
76+
+ (", ".join(f"`{k}`" for k in sorted(allowlist)) if allowlist else "_none_"),
77+
]
78+
return "\n".join(lines)
79+
80+
81+
@task
82+
def check_bazel_coverage(_: Context) -> None:
83+
"""
84+
Fail if any http_archive in deps/repos.MODULE.bazel lacks a Renovate customManager.
85+
86+
A dep is considered covered when either:
87+
* its name appears as `depNameTemplate` in one of `renovate.json`'s customManagers, or
88+
* it is listed in `deps/.renovate-untracked.json` with a non-empty rationale.
89+
90+
Writes a markdown report to ``$GITHUB_STEP_SUMMARY`` when running in GitHub Actions.
91+
"""
92+
bazel_names = _parse_module_bazel(MODULE_FILE)
93+
tracked_names = _parse_renovate_json(RENOVATE_FILE)
94+
allowlist = _parse_allowlist(ALLOWLIST_FILE)
95+
96+
untracked = bazel_names - tracked_names - set(allowlist)
97+
if untracked:
98+
report = _emit_failure_report(untracked, allowlist)
99+
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
100+
if summary_path:
101+
Path(summary_path).write_text(report + "\n", encoding="utf-8")
102+
raise Exit(report, code=1)
103+
104+
print(
105+
f"OK: {len(bazel_names)} http_archive deps, "
106+
f"{len(bazel_names) - len(allowlist)} tracked by Renovate, "
107+
f"{len(allowlist)} intentionally untracked."
108+
)

0 commit comments

Comments
 (0)