Skip to content

Commit

Permalink
add version upgrade (#426)
Browse files Browse the repository at this point in the history
* add new cmd `cal-next-version` for calculating new extension module versions
  • Loading branch information
AllyW authored Jan 8, 2024
1 parent c1ceceb commit 4693ff2
Show file tree
Hide file tree
Showing 12 changed files with 425 additions and 1 deletion.
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Release History
===============
0.1.62
++++++
* `azdev extension cal-next-version`: New command to calculate valid version for next extension module release..

0.1.61
++++++
Expand Down
2 changes: 1 addition & 1 deletion azdev/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# license information.
# -----------------------------------------------------------------------------

__VERSION__ = '0.1.61'
__VERSION__ = '0.1.62'
1 change: 1 addition & 0 deletions azdev/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def operation_group(name):
g.command('build', 'build_extensions')
g.command('publish', 'publish_extensions')
g.command('update-index', 'update_extension_index')
g.command('cal-next-version', 'cal_next_version')

with CommandGroup(self, 'extension repo', operation_group('extensions')) as g:
g.command('add', 'add_extension_repo')
Expand Down
3 changes: 3 additions & 0 deletions azdev/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@
short-summary: Update the extensions index.json from a built WHL file.
"""

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

helps['extension repo'] = """
short-summary: Commands to manage extension repositories for development.
Expand Down
12 changes: 12 additions & 0 deletions azdev/operations/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,15 @@
ORANGE_PCT = 60
GREEN_PCT = 80
BLUE_PCT = 100

VERSION_MAJOR_TAG = "major"
VERSION_MINOR_TAG = "minor"
VERSION_PATCH_TAG = "patch"
VERSION_PRE_TAG = "pre"

VERSION_STABLE_TAG = "stable"
VERSION_PREVIEW_TAG = "preview"

PREVIEW_INIT_SUFFIX = "b1"

CLI_EXTENSION_INDEX_URL = "https://azcliextensionsync.blob.core.windows.net/index1/index.json"
12 changes: 12 additions & 0 deletions azdev/operations/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from azdev.utilities import (
cmd, py_cmd, pip_cmd, display, get_ext_repo_paths, find_files, get_azure_config, get_azdev_config,
require_azure_cli, heading, subheading, EXTENSION_PREFIX)
from .version_upgrade import VersionUpgradeMod

logger = get_logger(__name__)

Expand Down Expand Up @@ -328,3 +329,14 @@ def publish_extensions(extensions, storage_account, storage_account_key, storage
if not update_index:
logger.warning('You still need to update the index for your changes!')
logger.warning(' az extension update-index <URL>')


def cal_next_version(base_meta_file, diff_meta_file, current_version, is_preview=None, is_experimental=None,
next_version_pre_tag=None, next_version_segment_tag=None):
with open(base_meta_file, "r") as g:
command_tree = json.load(g)
module_name = command_tree["module_name"]
version_op = VersionUpgradeMod(module_name, current_version, is_preview, is_experimental,
base_meta_file, diff_meta_file, next_version_pre_tag, next_version_segment_tag)
version_op.update_next_version()
return version_op.format_outputs()
204 changes: 204 additions & 0 deletions azdev/operations/extensions/version_upgrade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# -----------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -----------------------------------------------------------------------------
# pylint: disable=line-too-long

# NOTE: The version uodate rules in this doc is complied with the doc below
# https://github.com/Azure/azure-cli/blob/release/doc/extensions/versioning_guidelines.md

from packaging.version import parse
from azure_cli_diff_tool.utils import DiffLevel
from azdev.operations.constant import (PREVIEW_INIT_SUFFIX, VERSION_MAJOR_TAG, VERSION_MINOR_TAG,
VERSION_PATCH_TAG, VERSION_STABLE_TAG, VERSION_PREVIEW_TAG, VERSION_PRE_TAG,
CLI_EXTENSION_INDEX_URL)


class ModuleVersion:

def __init__(self, parse_version):
self.major = parse_version.major
self.minor = parse_version.minor
self.patch = parse_version.micro
self.pre = parse_version.pre
self.pre_num = parse_version.pre and parse_version.pre[1]

def init_stable_version(self):
self.major = 1
self.minor = 0
self.patch = 0
self.pre = None
self.pre_num = 0

def init_preview_version(self):
self.major = 1
self.minor = 0
self.patch = 0
self.pre = "b"
self.pre_num = 1

def __str__(self):
version_arr = [self.major, ".", self.minor, ".", self.patch]
if self.pre:
version_arr.append(self.pre)
version_arr.append(self.pre_num)
return "".join([str(item) for item in version_arr])


# pylint: disable=too-many-instance-attributes
class VersionUpgradeMod:

def __init__(self, module_name, current_version, is_preview, is_experimental,
meta_diff_before, meta_diff_after, next_version_pre_tag=None, next_version_segment_tag=None):
self.module_name = module_name
try:
self.version = parse(current_version)
except Exception as e:
raise ValueError("Invalid version: {0} cause {1}".format(current_version, str(e)))
self.is_preview = bool(is_preview or is_experimental or (self.version.pre and self.version.pre in ["a", "b"]))
self.has_preview_tag = is_preview
self.has_exp_tag = is_experimental
self.version_raw = current_version
self.norm_versions()
self.base_meta_file = meta_diff_before
self.diff_meta_file = meta_diff_after
self.next_version_pre_tag = next_version_pre_tag and next_version_pre_tag.lower()
self.next_version_segment_tag = next_version_segment_tag and next_version_segment_tag.lower()
self.diffs = []
self.init_version_diffs()
self.init_version_pre_tag()
self.next_version = ModuleVersion(self.version)
self.last_stable_major = float('inf')
self.parse_last_stable_major()

def norm_versions(self):
if not self.is_preview:
return
version_pre = self.version.pre
if version_pre is None:
self.version_raw += PREVIEW_INIT_SUFFIX
try:
self.version = parse(self.version_raw)
except Exception as e:
raise ValueError("Invalid version: {0} cause {1}".format(self.version_raw, str(e)))

def init_version_diffs(self):
from azure_cli_diff_tool import meta_diff
meta_diffs = meta_diff(self.base_meta_file, self.diff_meta_file, output_type="dict")
if meta_diffs:
self.diffs = meta_diffs

def init_version_pre_tag(self):
"""
use next version pre tag if user inputs
otherwise, consistant with the preview tag
"""
if self.next_version_pre_tag is not None:
return

if self.is_preview:
self.next_version_pre_tag = VERSION_PREVIEW_TAG
else:
self.next_version_pre_tag = VERSION_STABLE_TAG

def update_next_version(self):
if self.next_version_pre_tag == VERSION_STABLE_TAG:
self.next_version.pre = None
self.next_version.pre_num = 0
elif self.next_version_pre_tag == VERSION_PREVIEW_TAG:
self.next_version.pre = "b"
self.next_version.pre_num = (self.version.pre and self.version.pre[1]) or 1
else:
raise ValueError("Unsupported pre tag: {0}".format(self.next_version_pre_tag))

if self.version.major < 1:
# for version starting with 0.x.x, norm them to first stable/preview version
if self.next_version_pre_tag == VERSION_STABLE_TAG:
self.next_version.init_stable_version()
else:
self.next_version.init_preview_version()
return

if self.next_version_segment_tag:
if self.next_version_segment_tag == VERSION_MAJOR_TAG:
self.next_version.major = self.version.major + 1
self.next_version.minor = 0
self.next_version.patch = 0
elif self.next_version_segment_tag == VERSION_MINOR_TAG:
self.next_version.minor = self.version.minor + 1
self.next_version.patch = 0
elif self.next_version_segment_tag == VERSION_PATCH_TAG:
self.next_version.patch = self.version.micro + 1
elif self.next_version_segment_tag == VERSION_PRE_TAG:
self.next_version.patch = (self.version.pre and self.version.pre[1] or 0) + 1
else:
raise ValueError("Unsupported segment tag: {0}".format(self.next_version_segment_tag))
return

self.update_version_from_differs()

def update_version_from_differs(self):
found_break = False
for item in self.diffs:
if item["diff_level"] == DiffLevel.BREAK.value:
found_break = True
break
if found_break:
if self.next_version_pre_tag == VERSION_PREVIEW_TAG and self.is_preview and self.last_stable_major < self.version.major:
# refer to rule: https://github.com/Azure/azure-cli/blob/release/doc/extensions/versioning_guidelines.md#notes-1
self.next_version.pre_num = self.version.pre[1] + 1
else:
self.next_version.major = self.version.major + 1
self.next_version.minor = 0
self.next_version.patch = 0
elif len(self.diffs) > 0:
if self.is_preview:
self.next_version.pre_num = self.version.pre[1] + 1
else:
self.next_version.minor = self.version.minor + 1
self.next_version.patch = 0
else:
if self.is_preview:
self.next_version.pre_num = self.version.pre[1] + 1
else:
self.next_version.patch = self.version.micro + 1

@staticmethod
def find_max_version(version_datas):
max_stable_major = -1
has_stable = False
for item in version_datas:
try:
version_parse = parse(item["metadata"]["version"])
except Exception as e:
raise ValueError(str(e))
if version_parse.pre is None and not item["metadata"].get("azext.isExperimental", False) \
and not item["metadata"].get("azext.isPreview", False):
max_stable_major = max(version_parse.major, max_stable_major)
has_stable = True
return has_stable, max_stable_major

def parse_last_stable_major(self):
import requests
try:
response = requests.get(CLI_EXTENSION_INDEX_URL)
extension_data = response.json()
if self.module_name not in extension_data["extensions"]:
return
has_stable, max_stable_major = self.find_max_version(
extension_data["extensions"][self.base_meta_file["module_name"]])
if has_stable:
self.last_stable_major = max_stable_major
except Exception as e:
raise ValueError(str(e))

def format_outputs(self):
has_preview_tag = bool(self.next_version.pre and (self.has_preview_tag or self.has_exp_tag))
result = {
"version": str(self.next_version),
"is_stable": self.next_version.pre is None,
"has_preview_tag": has_preview_tag,
"has_exp_tag": False
}
return result
49 changes: 49 additions & 0 deletions azdev/operations/tests/jsons/az_ams_meta_after.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"module_name": "ams",
"name": "az",
"commands": {},
"sub_groups": {
"ams": {
"name": "ams",
"commands": {},
"sub_groups": {
"ams asset": {
"name": "ams asset",
"commands": {
"ams asset get-sas-urls": {
"name": "ams asset get-sas-urls",
"is_aaz": false,
"parameters": [{
"name": "resource_group_name",
"options": ["--resource-group", "-g"],
"required": true,
"id_part": "resource_group"
}, {
"name": "account_name",
"options": ["--account-name", "-a"],
"required": true,
"id_part": "name"
}, {
"name": "asset_name",
"options": ["--name", "-n"],
"required": true,
"id_part": "child_name_1"
}, {
"name": "permissions",
"options": ["--permissions"],
"choices": ["Read", "ReadWrite", "ReadWriteDelete"],
"default": "Read"
}, {
"name": "expiry_time",
"options": ["--expiry"],
"type": "custom_type",
"default": "2023-07-10 18:47:05.586776"
}]
}
},
"sub_groups": {}
}
}
}
}
}
49 changes: 49 additions & 0 deletions azdev/operations/tests/jsons/az_ams_meta_before.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"module_name": "ams",
"name": "az",
"commands": {},
"sub_groups": {
"ams": {
"name": "ams",
"commands": {},
"sub_groups": {
"ams asset": {
"name": "ams asset",
"commands": {
"ams asset get-sas-urls": {
"name": "ams asset get-sas-urls",
"is_aaz": false,
"parameters": [{
"name": "resource_group_name",
"options": ["--resource-group", "-g"],
"required": true,
"id_part": "resource_group"
}, {
"name": "account_name",
"options": ["--account-name", "-a"],
"required": true,
"id_part": "name"
}, {
"name": "asset_name",
"options": ["--name", "-n"],
"required": true,
"id_part": "child_name_1"
}, {
"name": "permissions",
"options": ["--permissions"],
"choices": ["Read", "ReadWrite", "ReadWriteDelete"],
"default": "Read"
}, {
"name": "expiry_time",
"options": ["--expiry"],
"type": "custom_type",
"default": "2023-07-10 18:43:29.693229"
}]
}
},
"sub_groups": {}
}
}
}
}
}
Loading

0 comments on commit 4693ff2

Please sign in to comment.