Skip to content

Commit 175a9d2

Browse files
FScholPerlurtzCopilot
authored
fix: renovate configuration and lockfile updates (#171)
Fix how lock is handled when updates come from renovate Frank Scholter Peres <frank.scholter_peres@mercedes-benz.com>, Mercedes-Benz Tech Innovation GmbH [Provider Information](https://github.com/mercedes-benz/foss/blob/master/PROVIDER_INFORMATION.md) Fixes #170 --------- Signed-off-by: Frank Scholter Peres(MBTI) <145544737+FScholPer@users.noreply.github.com> Co-authored-by: lurtz <727209+lurtz@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 10e3580 commit 175a9d2

3 files changed

Lines changed: 156 additions & 5 deletions

File tree

‎.github/renovate.json5‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,23 @@
3030
"semanticCommits": "enabled",
3131
"commitMessagePrefix": "chore(deps):",
3232
"prConcurrentLimit": 5,
33-
"prCreation": "auto",
33+
"prCreation": "immediate",
3434
"automerge": false,
35+
"postUpgradeTasks": {
36+
"commands": [
37+
"tools/internal/refresh_lockfile_digests.py"
38+
],
39+
"fileFilters": ["**/*"],
40+
"executionMode": "update"
41+
},
3542
"customManagers": [
3643
{
3744
"customType": "regex",
3845
"fileMatch": [
3946
"^tools/lockfiles/apm\\.lock\\.json$"
4047
],
4148
"matchStrings": [
42-
"\"version\":\\s*\"(?<currentValue>[^\"]+)\"",
43-
"\"url\":\\s*\"https://github\\.com/microsoft/apm/releases/download/v(?<currentValue>[^/]+)/[^\"]+\",\\s*\"sha256\":\\s*\"(?<currentDigest>[^\"]+)\""
49+
"\"version\":\\s*\"(?<currentValue>[^\"]+)\""
4450
],
4551
"datasourceTemplate": "github-releases",
4652
"depNameTemplate": "microsoft/apm",
@@ -52,8 +58,7 @@
5258
"^tools/lockfiles/opencode\\.lock\\.json$"
5359
],
5460
"matchStrings": [
55-
"\"version\":\\s*\"(?<currentValue>[^\"]+)\"",
56-
"\"url\":\\s*\"https://github\\.com/anomalyco/opencode/releases/download/v(?<currentValue>[^/]+)/[^\"]+\",\\s*\"sha256\":\\s*\"(?<currentDigest>[^\"]+)\""
61+
"\"version\":\\s*\"(?<currentValue>[^\"]+)\""
5762
],
5863
"datasourceTemplate": "github-releases",
5964
"depNameTemplate": "anomalyco/opencode",

‎.github/workflows/daily.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ jobs:
4848
configurationFile: .github/renovate.json5
4949
env:
5050
LOG_LEVEL: debug
51+
# Allow the post-upgrade task that refreshes lockfile URLs and sha256
52+
# digests (allowedCommands is a global-only option, so it must be set
53+
# here rather than in renovate.json5).
54+
RENOVATE_ALLOWED_COMMANDS: '["tools/internal/refresh_lockfile_digests.py"]'
5155
validate-renovate-config:
5256
name: Validate Renovate config
5357
runs-on: ubuntu-latest
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env python3
2+
# *******************************************************************************
3+
# Copyright (c) 2026 Contributors to the Eclipse Foundation
4+
#
5+
# See the NOTICE file(s) distributed with this work for additional
6+
# information regarding copyright ownership.
7+
#
8+
# This program and the accompanying materials are made available under the
9+
# terms of the Apache License Version 2.0 which is available at
10+
# https://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# SPDX-License-Identifier: Apache-2.0
13+
# *******************************************************************************
14+
"""Refresh URLs and sha256 digests in tool lockfiles after a version bump.
15+
16+
Intended to run as a Renovate ``postUpgradeTask``: Renovate rewrites the
17+
``version`` field (and the version embedded in each binary URL) via its
18+
``custom.regex`` manager, but it cannot compute the new sha256 digests. This
19+
script reads the (possibly new) version from each lockfile, fetches the
20+
corresponding GitHub release, and rewrites every binary's ``url`` and
21+
``sha256`` from the release's asset list.
22+
23+
Usage:
24+
python3 tools/internal/refresh_lockfile_digests.py [LOCKFILE ...]
25+
26+
With no arguments, refreshes all ``tools/lockfiles/*.lock.json`` files that
27+
contain a ``binaries`` list. Exits non-zero if any lockfile cannot be
28+
refreshed (e.g. the release or an expected asset is missing).
29+
"""
30+
31+
import json
32+
import os
33+
import sys
34+
import urllib.request
35+
36+
GITHUB_API = "https://api.github.com"
37+
38+
39+
def _api_get(url, token=None):
40+
req = urllib.request.Request(
41+
url,
42+
headers={
43+
"Accept": "application/vnd.github+json",
44+
"User-Agent": "score-devcontainer-lockfile-refresh",
45+
**({"Authorization": f"Bearer {token}"} if token else {}),
46+
},
47+
)
48+
with urllib.request.urlopen(req) as resp:
49+
return json.load(resp)
50+
51+
52+
def _asset_name(url):
53+
"""Extract the asset filename from a GitHub release download URL."""
54+
return url.rsplit("/", 1)[-1]
55+
56+
57+
def refresh_lockfile(path, token=None):
58+
"""Refresh one lockfile in place. Returns a list of changed binary names."""
59+
with open(path, encoding="utf-8") as f:
60+
data = json.load(f)
61+
62+
changed = []
63+
for tool_name, tool in data.items():
64+
if not isinstance(tool, dict) or "binaries" not in tool:
65+
continue
66+
version = tool.get("version")
67+
if not version:
68+
continue
69+
70+
# Derive the GitHub repo from the first binary URL:
71+
# https://github.com/<owner>/<repo>/releases/download/v<ver>/<asset>
72+
first_url = tool["binaries"][0].get("url", "")
73+
parts = first_url.split("/")
74+
if len(parts) < 8 or parts[2] != "github.com" or parts[5] != "releases":
75+
print(
76+
f" ! {tool_name}: cannot derive repo from {first_url!r}",
77+
file=sys.stderr,
78+
)
79+
continue
80+
owner, repo = parts[3], parts[4]
81+
82+
release = _api_get(
83+
f"{GITHUB_API}/repos/{owner}/{repo}/releases/tags/v{version}", token
84+
)
85+
digests = {a["name"]: a.get("digest", "") for a in release.get("assets", [])}
86+
87+
for binary in tool["binaries"]:
88+
asset = _asset_name(binary.get("url", ""))
89+
digest = digests.get(asset)
90+
if not digest:
91+
print(
92+
f" ! {tool_name}: no digest for asset {asset!r} in v{version}",
93+
file=sys.stderr,
94+
)
95+
continue
96+
sha = digest.split(":", 1)[1] if digest.startswith("sha256:") else digest
97+
# Rebuild the URL from the version so the file is consistent
98+
# whether or not Renovate already updated the URL.
99+
new_url = f"https://github.com/{owner}/{repo}/releases/download/v{version}/{asset}"
100+
if binary.get("sha256") != sha or binary.get("url") != new_url:
101+
binary["url"] = new_url
102+
binary["sha256"] = sha
103+
changed.append(f"{tool_name}/{asset}")
104+
105+
if changed:
106+
with open(path, "w", encoding="utf-8") as f:
107+
json.dump(data, f, indent=2)
108+
f.write("\n")
109+
return changed
110+
111+
112+
def main(argv):
113+
token = os.environ.get("RENOVATE_TOKEN") or os.environ.get("GITHUB_TOKEN")
114+
if not argv[1:]:
115+
lockdir = os.path.join(os.path.dirname(__file__), "..", "lockfiles")
116+
lockdir = os.path.abspath(lockdir)
117+
argv = [argv[0]] + [
118+
os.path.join(lockdir, name)
119+
for name in sorted(os.listdir(lockdir))
120+
if name.endswith(".lock.json")
121+
]
122+
123+
failed = False
124+
for path in argv[1:]:
125+
print(f"Refreshing {path}")
126+
try:
127+
changed = refresh_lockfile(path, token)
128+
except Exception as exc:
129+
print(f" ! failed: {exc}", file=sys.stderr)
130+
failed = True
131+
continue
132+
if changed:
133+
for c in changed:
134+
print(f" ~ {c}")
135+
else:
136+
print(" = up to date")
137+
138+
return 1 if failed else 0
139+
140+
141+
if __name__ == "__main__":
142+
sys.exit(main(sys.argv))

0 commit comments

Comments
 (0)