diff --git a/README.md b/README.md index b80c1a41..5425a251 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,22 @@ The version number for the latest version can be detected in two ways: URL for the latest version, and the first match group is taken to be the version. (This follows the convention used by [`debian/watch`](https://wiki.debian.org/debian/watch) files.) + * If the application download is behind load-balanced URL that changes regularly + (e.g. `https://stable.dl2.example.com` and `dl.example.com`), + the regex needs to be adjusted to extract the version number in all cases. + Otherwise this project assumes a new version was published. + * You can use a non-capturing group for this use-case. For the use case above, use + the pattern below to allow downloads from: + * `https://dl.example.com/foo-v1.9.tar.gz` + * `https://stable.dl1.example.com/foo-v1.9.tar.gz` + * ... +```json +"x-checker-data": { + "type": "rotating-url", + "url": "http://example.com/last-version", + "pattern": "https://(?:dl|stable.dl\\d).example.com/foo-v([0-9.]+).tar.gz" +} +``` Some upstream vendors may add unwanted GET query parameters to the download URL, such as identifiers for counting unique downloads. diff --git a/src/checkers/urlchecker.py b/src/checkers/urlchecker.py index 767eb5af..6b383149 100644 --- a/src/checkers/urlchecker.py +++ b/src/checkers/urlchecker.py @@ -61,6 +61,26 @@ def extract_version(checker_data, url): return m.group(1) +def is_same_version(checker_data, current_url, new_version): + """ + Check if the new application version is the same with the current one. If the + version number could be extracted, those strings are compared with each other + to be resilient against load-balanced urls pointing to the same file. + """ + if new_version.version is None: + # No pattern given or failed parsing the new version, so check only + # if the url is different + return current_url == new_version.url + + current_version_string = extract_version(checker_data, current_url) + if current_version_string is None: + # If the pattern failed to apply to the old/current version, + # check again only the url. Otherwise we would compare None values + return current_url == new_version.url + + return current_version_string == new_version.version + + class URLChecker(Checker): PRIORITY = 99 CHECKER_DATA_TYPE = "rotating-url" @@ -138,9 +158,10 @@ async def check(self, external_data: ExternalBase): if not is_rotating: new_version = new_version._replace(url=url) # pylint: disable=no-member + same_version = is_same_version( + external_data.checker_data, external_data.current_version.url, new_version + ) external_data.set_new_version( new_version, - is_update=( - is_rotating and external_data.current_version.url != new_version.url - ), + is_update=is_rotating and not same_version, ) diff --git a/src/lib/externaldata.py b/src/lib/externaldata.py index 4d069746..825d11fa 100644 --- a/src/lib/externaldata.py +++ b/src/lib/externaldata.py @@ -319,10 +319,8 @@ def json(self) -> t.Dict[str, t.Any]: def matches(self, other: ExternalFile): for i in (self, other): assert i.checksum is None or isinstance(i.checksum, MultiDigest), i.checksum - return ( - self.url == other.url - and self.checksum == other.checksum - and (self.size is None or other.size is None or self.size == other.size) + return self.checksum == other.checksum and ( + self.size is None or other.size is None or self.size == other.size ) def is_same_version(self, other: ExternalFile):