From 5af521818ad1b9bba2b2afb348cabd79fd7e82fb Mon Sep 17 00:00:00 2001 From: Daniel Braun Date: Wed, 24 May 2023 14:38:52 +0000 Subject: [PATCH] fix: fallback to github api to find release tag, in case latest git tag has no release --- .../gh_release/gh_release_installer.py | 32 +++++++++---- .../gh_release/resolvers/asset_resolver.py | 16 +++++-- .../gh_release/resolvers/release_resolver.py | 48 +++++++++++++++---- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/nanolayer/installers/gh_release/gh_release_installer.py b/nanolayer/installers/gh_release/gh_release_installer.py index 2bf08ec9..28125bec 100644 --- a/nanolayer/installers/gh_release/gh_release_installer.py +++ b/nanolayer/installers/gh_release/gh_release_installer.py @@ -162,14 +162,30 @@ def install( asked_version=version, repo=repo, release_tag_regex=release_tag_regex ) - # will raise an exception if more or less than a single asset can meet the requirments - resolved_asset = AssetResolver.resolve( - repo=repo, - release_version=release_version, - asset_regex=asset_regex, - arch=arch, - binary_names=binary_names, - ) + try: + # will raise an exception if more or less than a single asset can meet the requirments + resolved_asset = AssetResolver.resolve( + repo=repo, + release_version=release_version, + asset_regex=asset_regex, + arch=arch, + binary_names=binary_names, + ) + except AssetResolver.NoReleaseError as e: + release_version = ReleaseResolver.resolve( + asked_version=version, + repo=repo, + release_tag_regex=release_tag_regex, + use_github_api=True, + ) + # will raise an exception if more or less than a single asset can meet the requirments + resolved_asset = AssetResolver.resolve( + repo=repo, + release_version=release_version, + asset_regex=asset_regex, + arch=arch, + binary_names=binary_names, + ) logger.warning("resolved asset: %s", resolved_asset.name) diff --git a/nanolayer/installers/gh_release/resolvers/asset_resolver.py b/nanolayer/installers/gh_release/resolvers/asset_resolver.py index d39db3c4..7ccfc929 100644 --- a/nanolayer/installers/gh_release/resolvers/asset_resolver.py +++ b/nanolayer/installers/gh_release/resolvers/asset_resolver.py @@ -5,6 +5,7 @@ from copy import deepcopy from enum import Enum from typing import Any, Dict, List, Optional +from urllib.error import HTTPError from pydantic import BaseModel, Extra @@ -72,6 +73,9 @@ class PlatformType(Enum): class AssetResolverError(Exception): pass + class NoReleaseError(Exception): + pass + class ReleaseAsset(BaseModel): class Config: extra = Extra.ignore @@ -119,9 +123,15 @@ def __call__(self, asset: "AssetResolver.ReleaseAsset") -> bool: @classmethod def _get_release_dict(cls, repo: str, tag: str) -> Dict[str, Any]: - response = urllib.request.urlopen( - f"https://api.github.com/repos/{repo}/releases/tags/{tag}" - ) # nosec + try: + response = urllib.request.urlopen( + f"https://api.github.com/repos/{repo}/releases/tags/{tag}" + ) # nosec + except HTTPError as e: + if e.code == 404: + raise cls.NoReleaseError( + f"no release exists for repo:{repo} and tag: {tag}" + ) from e return json.loads(response.read()) @classmethod diff --git a/nanolayer/installers/gh_release/resolvers/release_resolver.py b/nanolayer/installers/gh_release/resolvers/release_resolver.py index 5554a53a..ff5505a5 100644 --- a/nanolayer/installers/gh_release/resolvers/release_resolver.py +++ b/nanolayer/installers/gh_release/resolvers/release_resolver.py @@ -1,4 +1,6 @@ +import json import re +import urllib from typing import Any, Dict, List, Optional import invoke @@ -11,6 +13,15 @@ class ReleaseResolverError(Exception): GIT_VERSION_TAG_REGEX = r"(?:tags\/)([0-9A-Za-z\-\_|.]+)\\?$" + @classmethod + def _filter_tags_by_regex(cls, tags: List[str], regex: str) -> List[str]: + return list( + filter( + lambda match: re.match(regex, match) is not None, + tags, + ) + ) + @classmethod def get_version_tags( cls, repo: str, release_tag_regex: Optional[str] = None @@ -27,29 +38,46 @@ def get_version_tags( stringified_matches = [match[0] for match in matches if len(match) == 1] if release_tag_regex is not None: - stringified_matches = list( - filter( - lambda match: re.match(release_tag_regex, match) is not None, - stringified_matches, - ) + stringified_matches = cls._filter_tags_by_regex( + stringified_matches, release_tag_regex ) return stringified_matches return [] @classmethod - def get_latest_stable_version( + def get_latest_git_version_tag( cls, repo: str, release_tag_regex: Optional[str] = None - ) -> List[str]: + ) -> str: all_version_tags = cls.get_version_tags(repo, release_tag_regex) return natsorted(all_version_tags)[-1] + @classmethod + def get_latest_release_tag( + cls, repo: str, release_tag_regex: Optional[str] = None + ) -> str: + response = urllib.request.urlopen( + f"https://api.github.com/repos/{repo}/releases" + ) # nosec + release_dicts = json.loads(response.read()) + release_tags = [release_dict["tag_name"] for release_dict in release_dicts] + if release_tag_regex is not None: + release_tags = cls._filter_tags_by_regex(release_tags, release_tag_regex) + return natsorted(release_tags)[-1] + @classmethod def resolve( - cls, asked_version: str, repo: str, release_tag_regex: Optional[str] = None - ) -> Dict[str, Any]: + cls, + asked_version: str, + repo: str, + release_tag_regex: Optional[str] = None, + use_github_api: bool = False, + ) -> str: if asked_version == "latest": - tag = cls.get_latest_stable_version(repo, release_tag_regex) + if use_github_api: + return cls.get_latest_release_tag(repo, release_tag_regex) + return cls.get_latest_git_version_tag(repo, release_tag_regex) + else: versions = cls.get_version_tags(repo, release_tag_regex) if asked_version in versions: