| 1 | import os |
| 2 | import re |
| 3 | import requests |
| 4 | from itertools import groupby |
| 5 | from github import Github |
| 6 | from github.GithubException import GithubException |
| 7 | |
| 8 | repos_URL = { |
| 9 | "stable": "netdata/netdata", |
| 10 | "nightly": "netdata/netdata-nightlies" |
| 11 | } |
| 12 | |
| 13 | GH_TOKEN = os.getenv("GH_TOKEN") |
| 14 | if GH_TOKEN is None or GH_TOKEN != "": |
| 15 | print("Token is not defined or empty, continuing with limitation on requests per sec towards Github API") |
| 16 | |
| 17 | |
| 18 | def identify_channel(_version): |
| 19 | nightly_pattern = r'v(\d+)\.(\d+)\.(\d+)-(\d+)-nightly' |
| 20 | stable_pattern = r'v(\d+)\.(\d+)\.(\d+)' |
| 21 | if re.match(nightly_pattern, _version): |
| 22 | _channel = "nightly" |
| 23 | _pattern = nightly_pattern |
| 24 | elif re.match(stable_pattern, _version): |
| 25 | _channel = "stable" |
| 26 | _pattern = stable_pattern |
| 27 | else: |
| 28 | print("Invalid version format.") |
| 29 | return None |
| 30 | return _channel, _pattern |
| 31 | |
| 32 | |
| 33 | def padded_version(item): |
| 34 | key_value = '10000' |
| 35 | for value in item[1:]: |
| 36 | key_value += f'{value:05}' |
| 37 | return int(key_value) |
| 38 | |
| 39 | |
| 40 | def extract_version(title): |
| 41 | if identify_channel(title): |
| 42 | _, _pattern = identify_channel(title) |
| 43 | try: |
| 44 | match = re.match(_pattern, title) |
| 45 | if match: |
| 46 | return tuple(map(int, match.groups())) |
| 47 | except Exception as e: |
| 48 | print(f"Unexpected error: {e}") |
| 49 | return None |
| 50 | |
| 51 | |
| 52 | def get_release_path_and_filename(_version): |
| 53 | nightly_pattern = r'v(\d+)\.(\d+)\.(\d+)-(\d+)-nightly' |
| 54 | stable_pattern = r'v(\d+)\.(\d+)\.(\d+)' |
| 55 | if match := re.match(nightly_pattern, _version): |
| 56 | msb = match.group(1) |
| 57 | _path = "nightly" |
| 58 | _filename = f"v{msb}" |
| 59 | elif match := re.match(stable_pattern, _version): |
| 60 | msb = match.group(1) |
| 61 | _path = "stable" |
| 62 | _filename = f"v{msb}" |
| 63 | else: |
| 64 | print("Invalid version format.") |
| 65 | exit(1) |
| 66 | return (_path, _filename) |
| 67 | |
| 68 | |
| 69 | def compare_version_with_remote(version): |
| 70 | """ |
| 71 | If the version = fun (version) you need to update the version in the |
| 72 | remote. If the version remote doesn't exist, returns the version |
| 73 | :param channel: any version of the agent |
| 74 | :return: the greater from version and version remote. |
| 75 | """ |
| 76 | |
| 77 | prefix = "https://packages.netdata.cloud/releases" |
| 78 | path, filename = get_release_path_and_filename(version) |
| 79 | |
| 80 | remote_url = f"{prefix}/{path}/{filename}" |
| 81 | response = requests.get(remote_url) |
| 82 | |
| 83 | if response.status_code == 200: |
| 84 | version_remote = response.text.rstrip() |
| 85 | |
| 86 | version_components = extract_version(version) |
| 87 | remote_version_components = extract_version(version_remote) |
| 88 | |
| 89 | absolute_version = padded_version(version_components) |
| 90 | absolute_remote_version = padded_version(remote_version_components) |
| 91 | |
| 92 | if absolute_version > absolute_remote_version: |
| 93 | print(f"Version in the remote: {version_remote}, is older than the current: {version}, I need to update") |
| 94 | return (version) |
| 95 | else: |
| 96 | print(f"Version in the remote: {version_remote}, is newer than the current: {version}, no action needed") |
| 97 | return (None) |
| 98 | else: |
| 99 | # Remote version not found |
| 100 | print(f"Version in the remote not found, updating the predefined latest path with the version: {version}") |
| 101 | return (version) |
| 102 | |
| 103 | |
| 104 | def sort_and_grouby_major_agents_of_channel(channel): |
| 105 | """ |
| 106 | Fetches the GH API and read either netdata/netdata or netdata/netdata-nightlies repo. It fetches all of their |
| 107 | releases implements a grouping by their major release number. |
| 108 | Every k,v in this dictionary is in the form; "vX": [descending ordered list of Agents in this major release]. |
| 109 | :param channel: "nightly" or "stable" |
| 110 | :return: None or dict() with the Agents grouped by major version # (vX) |
| 111 | """ |
| 112 | try: |
| 113 | G = Github(GH_TOKEN) |
| 114 | repo = G.get_repo(repos_URL[channel]) |
| 115 | releases = repo.get_releases() |
| 116 | except GithubException as e: |
| 117 | print(f"GitHub API request failed: {e}") |
| 118 | return None |
| 119 | |
| 120 | except Exception as e: |
| 121 | print(f"An unexpected error occurred: {e}") |
| 122 | return None |
| 123 | |
| 124 | extracted_titles = [extract_version(item.title) for item in releases if |
| 125 | extract_version(item.title) is not None] |
| 126 | # Necessary sorting for implement the group by |
| 127 | extracted_titles.sort(key=lambda x: x[0]) |
| 128 | # Group titles by major version |
| 129 | grouped_by_major = {major: list(group) for major, group in groupby(extracted_titles, key=lambda x: x[0])} |
| 130 | sorted_grouped_by_major = {} |
| 131 | for key, values in grouped_by_major.items(): |
| 132 | sorted_values = sorted(values, key=padded_version, reverse=True) |
| 133 | sorted_grouped_by_major[key] = sorted_values |
| 134 | # Transform them in the correct form |
| 135 | if channel == "stable": |
| 136 | result_dict = {f"v{key}": [f"v{a}.{b}.{c}" for a, b, c in values] for key, values in |
| 137 | sorted_grouped_by_major.items()} |
| 138 | else: |
| 139 | result_dict = {f"v{key}": [f"v{a}.{b}.{c}-{d}-nightly" for a, b, c, d in values] for key, values in |
| 140 | sorted_grouped_by_major.items()} |
| 141 | return result_dict |