@@ -108,7 +108,7 @@ def latest_github_tag(repo: str, stable_only: bool) -> str:
108108 return sorted (candidates , key = version_sort_key )[- 1 ]
109109
110110
111- def latest_ghcr_digest (image : str , tag : str = "latest" ) -> str :
111+ def ghcr_digest_for_tag (image : str , tag : str ) -> str :
112112 token_data = http_json (f"https://ghcr.io/token?scope=repository:{ image } :pull" )
113113 if not isinstance (token_data , dict ) or not token_data .get ("token" ):
114114 fail (f"Could not get GHCR token for { image } " )
@@ -133,14 +133,47 @@ def latest_ghcr_digest(image: str, tag: str = "latest") -> str:
133133 with urllib .request .urlopen (request , timeout = 30 ) as response :
134134 digest = response .headers .get ("docker-content-digest" , "" ).strip ()
135135 except urllib .error .HTTPError as exc :
136+ if exc .code == 404 :
137+ raise LookupError (f"Tag not found: { image } :{ tag } " ) from exc
136138 fail (f"HTTP error while requesting GHCR manifest for { image } :{ tag } : { exc .code } { exc .reason } " )
137139 except urllib .error .URLError as exc :
138140 fail (f"Network error while requesting GHCR manifest for { image } :{ tag } : { exc .reason } " )
139141 if not digest :
140- fail (f"Could not determine digest for GHCR image { image } :{ tag } " )
142+ raise LookupError (f"Digest header missing: { image } :{ tag } " )
141143 return digest
142144
143145
146+ def latest_ghcr_digest (image : str , latest_version : str , stable_only : bool ) -> str :
147+ # For stable-only tracking, resolve digest from the exact version tag instead of
148+ # floating tags like "latest", which may point at prerelease images.
149+ candidates : list [str ] = []
150+
151+ if latest_version :
152+ candidates .append (latest_version )
153+ if latest_version .startswith ("v" ):
154+ candidates .append (latest_version [1 :])
155+
156+ if stable_only :
157+ candidates .append ("stable" )
158+ else :
159+ candidates .append ("latest" )
160+
161+ seen : set [str ] = set ()
162+ for candidate in candidates :
163+ if not candidate or candidate in seen :
164+ continue
165+ seen .add (candidate )
166+ try :
167+ return ghcr_digest_for_tag (image , candidate )
168+ except LookupError :
169+ continue
170+
171+ fail (
172+ f"Could not determine digest for GHCR image { image } using candidates: "
173+ + ", " .join (candidates )
174+ )
175+
176+
144177def read_local_version (config : dict [str , object ]) -> str :
145178 version_source = str (config .get ("version_source" , "" )).strip ()
146179 version_key = str (config .get ("version_key" , "" )).strip ()
@@ -250,7 +283,11 @@ def main() -> None:
250283 current_version = read_local_version (upstream )
251284 current_digest = read_local_digest (upstream )
252285 latest_version = latest_github_tag (str (upstream .get ("repo" , "" )).strip (), stable_only )
253- latest_digest = latest_ghcr_digest (str (upstream .get ("image" , "" )).strip (), "latest" )
286+ latest_digest = latest_ghcr_digest (
287+ str (upstream .get ("image" , "" )).strip (),
288+ latest_version ,
289+ stable_only ,
290+ )
254291 version_update_available = latest_version != current_version
255292 digest_update_available = latest_digest != current_digest
256293 updates_available = version_update_available
0 commit comments