|
| 1 | +# ----------------------------------------------------------------------------- |
| 2 | +# Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | +# Licensed under the MIT License. See License.txt in the project root for |
| 4 | +# license information. |
| 5 | +# ----------------------------------------------------------------------------- |
| 6 | +# pylint: disable=line-too-long |
| 7 | + |
| 8 | +# NOTE: The version uodate rules in this doc is complied with the doc below |
| 9 | +# https://github.com/Azure/azure-cli/blob/release/doc/extensions/versioning_guidelines.md |
| 10 | + |
| 11 | +from packaging.version import parse |
| 12 | +from azure_cli_diff_tool.utils import DiffLevel |
| 13 | +from azdev.operations.constant import (PREVIEW_INIT_SUFFIX, VERSION_MAJOR_TAG, VERSION_MINOR_TAG, |
| 14 | + VERSION_PATCH_TAG, VERSION_STABLE_TAG, VERSION_PREVIEW_TAG, VERSION_PRE_TAG, |
| 15 | + CLI_EXTENSION_INDEX_URL) |
| 16 | + |
| 17 | + |
| 18 | +class ModuleVersion: |
| 19 | + |
| 20 | + def __init__(self, parse_version): |
| 21 | + self.major = parse_version.major |
| 22 | + self.minor = parse_version.minor |
| 23 | + self.patch = parse_version.micro |
| 24 | + self.pre = parse_version.pre |
| 25 | + self.pre_num = parse_version.pre and parse_version.pre[1] |
| 26 | + |
| 27 | + def init_stable_version(self): |
| 28 | + self.major = 1 |
| 29 | + self.minor = 0 |
| 30 | + self.patch = 0 |
| 31 | + self.pre = None |
| 32 | + self.pre_num = 0 |
| 33 | + |
| 34 | + def init_preview_version(self): |
| 35 | + self.major = 1 |
| 36 | + self.minor = 0 |
| 37 | + self.patch = 0 |
| 38 | + self.pre = "b" |
| 39 | + self.pre_num = 1 |
| 40 | + |
| 41 | + def __str__(self): |
| 42 | + version_arr = [self.major, ".", self.minor, ".", self.patch] |
| 43 | + if self.pre: |
| 44 | + version_arr.append(self.pre) |
| 45 | + version_arr.append(self.pre_num) |
| 46 | + return "".join([str(item) for item in version_arr]) |
| 47 | + |
| 48 | + |
| 49 | +# pylint: disable=too-many-instance-attributes |
| 50 | +class VersionUpgradeMod: |
| 51 | + |
| 52 | + def __init__(self, module_name, current_version, is_preview, is_experimental, |
| 53 | + meta_diff_before, meta_diff_after, next_version_pre_tag=None, next_version_segment_tag=None): |
| 54 | + self.module_name = module_name |
| 55 | + try: |
| 56 | + self.version = parse(current_version) |
| 57 | + except Exception as e: |
| 58 | + raise ValueError("Invalid version: {0} cause {1}".format(current_version, str(e))) |
| 59 | + self.is_preview = bool(is_preview or is_experimental or (self.version.pre and self.version.pre in ["a", "b"])) |
| 60 | + self.has_preview_tag = is_preview |
| 61 | + self.has_exp_tag = is_experimental |
| 62 | + self.version_raw = current_version |
| 63 | + self.norm_versions() |
| 64 | + self.base_meta_file = meta_diff_before |
| 65 | + self.diff_meta_file = meta_diff_after |
| 66 | + self.next_version_pre_tag = next_version_pre_tag and next_version_pre_tag.lower() |
| 67 | + self.next_version_segment_tag = next_version_segment_tag and next_version_segment_tag.lower() |
| 68 | + self.diffs = [] |
| 69 | + self.init_version_diffs() |
| 70 | + self.init_version_pre_tag() |
| 71 | + self.next_version = ModuleVersion(self.version) |
| 72 | + self.last_stable_major = float('inf') |
| 73 | + self.parse_last_stable_major() |
| 74 | + |
| 75 | + def norm_versions(self): |
| 76 | + if not self.is_preview: |
| 77 | + return |
| 78 | + version_pre = self.version.pre |
| 79 | + if version_pre is None: |
| 80 | + self.version_raw += PREVIEW_INIT_SUFFIX |
| 81 | + try: |
| 82 | + self.version = parse(self.version_raw) |
| 83 | + except Exception as e: |
| 84 | + raise ValueError("Invalid version: {0} cause {1}".format(self.version_raw, str(e))) |
| 85 | + |
| 86 | + def init_version_diffs(self): |
| 87 | + from azure_cli_diff_tool import meta_diff |
| 88 | + meta_diffs = meta_diff(self.base_meta_file, self.diff_meta_file, output_type="dict") |
| 89 | + if meta_diffs: |
| 90 | + self.diffs = meta_diffs |
| 91 | + |
| 92 | + def init_version_pre_tag(self): |
| 93 | + """ |
| 94 | + use next version pre tag if user inputs |
| 95 | + otherwise, consistant with the preview tag |
| 96 | + """ |
| 97 | + if self.next_version_pre_tag is not None: |
| 98 | + return |
| 99 | + |
| 100 | + if self.is_preview: |
| 101 | + self.next_version_pre_tag = VERSION_PREVIEW_TAG |
| 102 | + else: |
| 103 | + self.next_version_pre_tag = VERSION_STABLE_TAG |
| 104 | + |
| 105 | + def update_next_version(self): |
| 106 | + if self.next_version_pre_tag == VERSION_STABLE_TAG: |
| 107 | + self.next_version.pre = None |
| 108 | + self.next_version.pre_num = 0 |
| 109 | + elif self.next_version_pre_tag == VERSION_PREVIEW_TAG: |
| 110 | + self.next_version.pre = "b" |
| 111 | + self.next_version.pre_num = (self.version.pre and self.version.pre[1]) or 1 |
| 112 | + else: |
| 113 | + raise ValueError("Unsupported pre tag: {0}".format(self.next_version_pre_tag)) |
| 114 | + |
| 115 | + if self.version.major < 1: |
| 116 | + # for version starting with 0.x.x, norm them to first stable/preview version |
| 117 | + if self.next_version_pre_tag == VERSION_STABLE_TAG: |
| 118 | + self.next_version.init_stable_version() |
| 119 | + else: |
| 120 | + self.next_version.init_preview_version() |
| 121 | + return |
| 122 | + |
| 123 | + if self.next_version_segment_tag: |
| 124 | + if self.next_version_segment_tag == VERSION_MAJOR_TAG: |
| 125 | + self.next_version.major = self.version.major + 1 |
| 126 | + self.next_version.minor = 0 |
| 127 | + self.next_version.patch = 0 |
| 128 | + elif self.next_version_segment_tag == VERSION_MINOR_TAG: |
| 129 | + self.next_version.minor = self.version.minor + 1 |
| 130 | + self.next_version.patch = 0 |
| 131 | + elif self.next_version_segment_tag == VERSION_PATCH_TAG: |
| 132 | + self.next_version.patch = self.version.micro + 1 |
| 133 | + elif self.next_version_segment_tag == VERSION_PRE_TAG: |
| 134 | + self.next_version.patch = (self.version.pre and self.version.pre[1] or 0) + 1 |
| 135 | + else: |
| 136 | + raise ValueError("Unsupported segment tag: {0}".format(self.next_version_segment_tag)) |
| 137 | + return |
| 138 | + |
| 139 | + self.update_version_from_differs() |
| 140 | + |
| 141 | + def update_version_from_differs(self): |
| 142 | + found_break = False |
| 143 | + for item in self.diffs: |
| 144 | + if item["diff_level"] == DiffLevel.BREAK.value: |
| 145 | + found_break = True |
| 146 | + break |
| 147 | + if found_break: |
| 148 | + if self.next_version_pre_tag == VERSION_PREVIEW_TAG and self.is_preview and self.last_stable_major < self.version.major: |
| 149 | + # refer to rule: https://github.com/Azure/azure-cli/blob/release/doc/extensions/versioning_guidelines.md#notes-1 |
| 150 | + self.next_version.pre_num = self.version.pre[1] + 1 |
| 151 | + else: |
| 152 | + self.next_version.major = self.version.major + 1 |
| 153 | + self.next_version.minor = 0 |
| 154 | + self.next_version.patch = 0 |
| 155 | + elif len(self.diffs) > 0: |
| 156 | + if self.is_preview: |
| 157 | + self.next_version.pre_num = self.version.pre[1] + 1 |
| 158 | + else: |
| 159 | + self.next_version.minor = self.version.minor + 1 |
| 160 | + self.next_version.patch = 0 |
| 161 | + else: |
| 162 | + if self.is_preview: |
| 163 | + self.next_version.pre_num = self.version.pre[1] + 1 |
| 164 | + else: |
| 165 | + self.next_version.patch = self.version.micro + 1 |
| 166 | + |
| 167 | + @staticmethod |
| 168 | + def find_max_version(version_datas): |
| 169 | + max_stable_major = -1 |
| 170 | + has_stable = False |
| 171 | + for item in version_datas: |
| 172 | + try: |
| 173 | + version_parse = parse(item["metadata"]["version"]) |
| 174 | + except Exception as e: |
| 175 | + raise ValueError(str(e)) |
| 176 | + if version_parse.pre is None and not item["metadata"].get("azext.isExperimental", False) \ |
| 177 | + and not item["metadata"].get("azext.isPreview", False): |
| 178 | + max_stable_major = max(version_parse.major, max_stable_major) |
| 179 | + has_stable = True |
| 180 | + return has_stable, max_stable_major |
| 181 | + |
| 182 | + def parse_last_stable_major(self): |
| 183 | + import requests |
| 184 | + try: |
| 185 | + response = requests.get(CLI_EXTENSION_INDEX_URL) |
| 186 | + extension_data = response.json() |
| 187 | + if self.module_name not in extension_data["extensions"]: |
| 188 | + return |
| 189 | + has_stable, max_stable_major = self.find_max_version( |
| 190 | + extension_data["extensions"][self.base_meta_file["module_name"]]) |
| 191 | + if has_stable: |
| 192 | + self.last_stable_major = max_stable_major |
| 193 | + except Exception as e: |
| 194 | + raise ValueError(str(e)) |
| 195 | + |
| 196 | + def format_outputs(self): |
| 197 | + has_preview_tag = bool(self.next_version.pre and (self.has_preview_tag or self.has_exp_tag)) |
| 198 | + result = { |
| 199 | + "version": str(self.next_version), |
| 200 | + "is_stable": self.next_version.pre is None, |
| 201 | + "has_preview_tag": has_preview_tag, |
| 202 | + "has_exp_tag": False |
| 203 | + } |
| 204 | + return result |
0 commit comments