Skip to content

Commit 4693ff2

Browse files
authored
add version upgrade (#426)
* add new cmd `cal-next-version` for calculating new extension module versions
1 parent c1ceceb commit 4693ff2

12 files changed

Lines changed: 425 additions & 1 deletion

File tree

HISTORY.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
33
Release History
44
===============
5+
0.1.62
6+
++++++
7+
* `azdev extension cal-next-version`: New command to calculate valid version for next extension module release..
58

69
0.1.61
710
++++++

azdev/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# license information.
55
# -----------------------------------------------------------------------------
66

7-
__VERSION__ = '0.1.61'
7+
__VERSION__ = '0.1.62'

azdev/commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def operation_group(name):
6565
g.command('build', 'build_extensions')
6666
g.command('publish', 'publish_extensions')
6767
g.command('update-index', 'update_extension_index')
68+
g.command('cal-next-version', 'cal_next_version')
6869

6970
with CommandGroup(self, 'extension repo', operation_group('extensions')) as g:
7071
g.command('add', 'add_extension_repo')

azdev/help.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@
266266
short-summary: Update the extensions index.json from a built WHL file.
267267
"""
268268

269+
helps['extension cal-next-version'] = """
270+
short-summary: Calculate valid version for next extension module release.
271+
"""
269272

270273
helps['extension repo'] = """
271274
short-summary: Commands to manage extension repositories for development.

azdev/operations/constant.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,15 @@
220220
ORANGE_PCT = 60
221221
GREEN_PCT = 80
222222
BLUE_PCT = 100
223+
224+
VERSION_MAJOR_TAG = "major"
225+
VERSION_MINOR_TAG = "minor"
226+
VERSION_PATCH_TAG = "patch"
227+
VERSION_PRE_TAG = "pre"
228+
229+
VERSION_STABLE_TAG = "stable"
230+
VERSION_PREVIEW_TAG = "preview"
231+
232+
PREVIEW_INIT_SUFFIX = "b1"
233+
234+
CLI_EXTENSION_INDEX_URL = "https://azcliextensionsync.blob.core.windows.net/index1/index.json"

azdev/operations/extensions/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from azdev.utilities import (
1818
cmd, py_cmd, pip_cmd, display, get_ext_repo_paths, find_files, get_azure_config, get_azdev_config,
1919
require_azure_cli, heading, subheading, EXTENSION_PREFIX)
20+
from .version_upgrade import VersionUpgradeMod
2021

2122
logger = get_logger(__name__)
2223

@@ -328,3 +329,14 @@ def publish_extensions(extensions, storage_account, storage_account_key, storage
328329
if not update_index:
329330
logger.warning('You still need to update the index for your changes!')
330331
logger.warning(' az extension update-index <URL>')
332+
333+
334+
def cal_next_version(base_meta_file, diff_meta_file, current_version, is_preview=None, is_experimental=None,
335+
next_version_pre_tag=None, next_version_segment_tag=None):
336+
with open(base_meta_file, "r") as g:
337+
command_tree = json.load(g)
338+
module_name = command_tree["module_name"]
339+
version_op = VersionUpgradeMod(module_name, current_version, is_preview, is_experimental,
340+
base_meta_file, diff_meta_file, next_version_pre_tag, next_version_segment_tag)
341+
version_op.update_next_version()
342+
return version_op.format_outputs()
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"module_name": "ams",
3+
"name": "az",
4+
"commands": {},
5+
"sub_groups": {
6+
"ams": {
7+
"name": "ams",
8+
"commands": {},
9+
"sub_groups": {
10+
"ams asset": {
11+
"name": "ams asset",
12+
"commands": {
13+
"ams asset get-sas-urls": {
14+
"name": "ams asset get-sas-urls",
15+
"is_aaz": false,
16+
"parameters": [{
17+
"name": "resource_group_name",
18+
"options": ["--resource-group", "-g"],
19+
"required": true,
20+
"id_part": "resource_group"
21+
}, {
22+
"name": "account_name",
23+
"options": ["--account-name", "-a"],
24+
"required": true,
25+
"id_part": "name"
26+
}, {
27+
"name": "asset_name",
28+
"options": ["--name", "-n"],
29+
"required": true,
30+
"id_part": "child_name_1"
31+
}, {
32+
"name": "permissions",
33+
"options": ["--permissions"],
34+
"choices": ["Read", "ReadWrite", "ReadWriteDelete"],
35+
"default": "Read"
36+
}, {
37+
"name": "expiry_time",
38+
"options": ["--expiry"],
39+
"type": "custom_type",
40+
"default": "2023-07-10 18:47:05.586776"
41+
}]
42+
}
43+
},
44+
"sub_groups": {}
45+
}
46+
}
47+
}
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"module_name": "ams",
3+
"name": "az",
4+
"commands": {},
5+
"sub_groups": {
6+
"ams": {
7+
"name": "ams",
8+
"commands": {},
9+
"sub_groups": {
10+
"ams asset": {
11+
"name": "ams asset",
12+
"commands": {
13+
"ams asset get-sas-urls": {
14+
"name": "ams asset get-sas-urls",
15+
"is_aaz": false,
16+
"parameters": [{
17+
"name": "resource_group_name",
18+
"options": ["--resource-group", "-g"],
19+
"required": true,
20+
"id_part": "resource_group"
21+
}, {
22+
"name": "account_name",
23+
"options": ["--account-name", "-a"],
24+
"required": true,
25+
"id_part": "name"
26+
}, {
27+
"name": "asset_name",
28+
"options": ["--name", "-n"],
29+
"required": true,
30+
"id_part": "child_name_1"
31+
}, {
32+
"name": "permissions",
33+
"options": ["--permissions"],
34+
"choices": ["Read", "ReadWrite", "ReadWriteDelete"],
35+
"default": "Read"
36+
}, {
37+
"name": "expiry_time",
38+
"options": ["--expiry"],
39+
"type": "custom_type",
40+
"default": "2023-07-10 18:43:29.693229"
41+
}]
42+
}
43+
},
44+
"sub_groups": {}
45+
}
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)