diff --git a/azdev/operations/command_change/custom.py b/azdev/operations/command_change/custom.py index 196230f5..fb835fa3 100644 --- a/azdev/operations/command_change/custom.py +++ b/azdev/operations/command_change/custom.py @@ -9,10 +9,10 @@ from azdev.utilities import (CMD_PROPERTY_ADD_BREAK_LIST, CMD_PROPERTY_REMOVE_BREAK_LIST, CMD_PROPERTY_UPDATE_BREAK_LIST, PARA_PROPERTY_REMOVE_BREAK_LIST, PARA_PROPERTY_ADD_BREAK_LIST, PARA_PROPERTY_UPDATE_BREAK_LIST) -from .util import extract_cmd_name, extract_cmd_property, ChangeType +from .util import extract_cmd_name, extract_cmd_property, extract_subgroup_name, ChangeType from .util import get_command_tree from .meta_changes import (CmdAdd, CmdRemove, CmdPropAdd, CmdPropRemove, CmdPropUpdate, ParaAdd, ParaRemove, - ParaPropAdd, ParaPropRemove, ParaPropUpdate) + ParaPropAdd, ParaPropRemove, ParaPropUpdate, SubgroupAdd, SubgroupRemove) logger = get_logger(__name__) @@ -25,7 +25,7 @@ class DiffExportFormat(Enum): class MetaChangeDetects: - EXPORTED_META_PROPERTY = ["rule_id", "is_break", "rule_message", "suggest_message", "cmd_name"] + EXPORTED_META_PROPERTY = ["rule_id", "is_break", "rule_message", "suggest_message", "cmd_name", "subgroup_name"] CHECKED_PARA_PROPERTY = ["name", "options", "required", "choices", "id_part", "nargs", "default", "desc", "aaz_type", "type", "aaz_default", "aaz_choices"] @@ -69,7 +69,14 @@ def __iter_dict_items(self, dict_items, diff_type): for dict_key in dict_items: has_cmd, cmd_name = extract_cmd_name(dict_key) if not has_cmd or not cmd_name: - print("extract cmd failed for " + dict_key) + has_subgroup, subgroup_name = extract_subgroup_name(dict_key) + if not has_subgroup or not subgroup_name: + continue + if diff_type == ChangeType.REMOVE: + diff_obj = SubgroupRemove(subgroup_name) + else: + diff_obj = SubgroupAdd(subgroup_name) + self.diff_objs.append(diff_obj) continue has_cmd_key, cmd_property = extract_cmd_property(dict_key, cmd_name) @@ -257,6 +264,55 @@ def check_deep_diffs(self): self.check_value_change() self.check_cmds_parameter_diff() + @staticmethod + def fill_subgroup_rules(obj, ret_mod, rule): + command_group_info = ret_mod + group_name_arr = obj.subgroup_name.split(" ") + start_level = 1 + while start_level < len(group_name_arr): + group_name = " ".join(group_name_arr[:start_level]) + if group_name not in command_group_info["sub_groups"]: + command_group_info["sub_groups"][group_name] = { + "name": group_name, + "commands": {}, + "sub_groups": {} + } + start_level += 1 + command_group_info = command_group_info["sub_groups"][group_name] + group_name = obj.subgroup_name + group_rules = [] + if group_name not in command_group_info["sub_groups"]: + command_group_info["sub_groups"] = {group_name: group_rules} + group_rules = command_group_info["sub_groups"][group_name] + group_rules.append(rule) + command_group_info["sub_groups"][group_name] = group_rules + + @staticmethod + def fill_cmd_rules(obj, ret_mod, rule): + command_tree = get_command_tree(obj.cmd_name) + command_group_info = ret_mod + while True: + if "is_group" not in command_tree: + break + if command_tree["is_group"]: + group_name = command_tree["group_name"] + if group_name not in command_group_info["sub_groups"]: + command_group_info["sub_groups"][group_name] = { + "name": group_name, + "commands": {}, + "sub_groups": {} + } + command_tree = command_tree["sub_info"] + command_group_info = command_group_info["sub_groups"][group_name] + else: + cmd_name = command_tree["cmd_name"] + command_rules = [] + if cmd_name in command_group_info["commands"]: + command_rules = command_group_info["commands"][cmd_name] + command_rules.append(rule) + command_group_info["commands"][cmd_name] = command_rules + break + def export_meta_changes(self, only_break, output_type="text"): ret_objs = [] ret_mod = { @@ -270,7 +326,8 @@ def export_meta_changes(self, only_break, output_type="text"): continue ret = {} for prop in self.EXPORTED_META_PROPERTY: - ret[prop] = getattr(obj, prop) + if hasattr(obj, prop): + ret[prop] = getattr(obj, prop) ret["rule_name"] = obj.__class__.__name__ if output_type == "dict": ret_objs.append(ret) @@ -278,28 +335,13 @@ def export_meta_changes(self, only_break, output_type="text"): ret_objs.append(str(obj)) if output_type != "tree": continue - command_tree = get_command_tree(obj.cmd_name) - command_group_info = ret_mod - while True: - if "is_group" not in command_tree: - break - if command_tree["is_group"]: - group_name = command_tree["group_name"] - if group_name not in command_group_info["sub_groups"]: - command_group_info["sub_groups"][group_name] = { - "name": group_name, - "commands": {}, - "sub_groups": {} - } - command_tree = command_tree["sub_info"] - command_group_info = command_group_info["sub_groups"][group_name] - else: - cmd_name = command_tree["cmd_name"] - command_rules = [] - if cmd_name in command_group_info["commands"]: - command_rules = command_group_info["commands"][cmd_name] - command_rules.append(ret) - command_group_info["commands"][cmd_name] = command_rules - break + if not hasattr(obj, "cmd_name") and not hasattr(obj, "subgroup_name"): + logger.info("unsupported rule ignored") + elif not hasattr(obj, "cmd_name") and hasattr(obj, "subgroup_name"): + self.fill_subgroup_rules(obj, ret_mod, ret) + elif not hasattr(obj, "subgroup_name") and hasattr(obj, "cmd_name"): + self.fill_cmd_rules(obj, ret_mod, ret) + else: + logger.info("unsupported rule ignored") return ret_objs if output_type in ["text", "dict"] else ret_mod diff --git a/azdev/operations/command_change/meta_changes.py b/azdev/operations/command_change/meta_changes.py index 7e5b7811..9c012595 100644 --- a/azdev/operations/command_change/meta_changes.py +++ b/azdev/operations/command_change/meta_changes.py @@ -23,6 +23,32 @@ def __str__(self): return " | ".join([str(a) for a in res]) +class SubgroupAdd(MetaChange): + def __init__(self, subgroup_name, is_break=False): + if not subgroup_name: + raise Exception("sub group name needed") + self.rule_id = "1011" + self.subgroup_name = subgroup_name + self.is_break = is_break + self.rule_message = get_change_rule_template(self.rule_id).format(self.subgroup_name) + self.suggest_message = get_change_suggest_template(self.rule_id).format(self.subgroup_name) \ + if self.is_break else "" + super().__init__(self.rule_id, is_break, self.rule_message, self.suggest_message) + + +class SubgroupRemove(MetaChange): + def __init__(self, subgroup_name, is_break=True): + if not subgroup_name: + raise Exception("sub group name needed") + self.rule_id = "1012" + self.subgroup_name = subgroup_name + self.is_break = is_break + self.rule_message = get_change_rule_template(self.rule_id).format(self.subgroup_name) + self.suggest_message = get_change_suggest_template(self.rule_id).format(self.subgroup_name) \ + if self.is_break else "" + super().__init__(self.rule_id, is_break, self.rule_message, self.suggest_message) + + class CmdAdd(MetaChange): def __init__(self, cmd_name, is_break=False): if not cmd_name: diff --git a/azdev/operations/command_change/util.py b/azdev/operations/command_change/util.py index 396eb88c..62797a07 100644 --- a/azdev/operations/command_change/util.py +++ b/azdev/operations/command_change/util.py @@ -13,6 +13,7 @@ logger = get_logger(__name__) +SUBGROUP_NAME_PATTERN = re.compile(r"\[\'sub_groups\'\]\[\'([a-zA-Z0-9\-\s]+)\'\]") CMD_NAME_PATTERN = re.compile(r"\[\'commands\'\]\[\'([a-zA-Z0-9\-\s]+)\'\]") CMD_PARAMETER_PROPERTY_PATTERN = re.compile(r"\[(.*?)\]") @@ -183,19 +184,24 @@ def gen_commands_meta(commands_meta, meta_output_path=None): f_out.write(jsbeautifier.beautify(json.dumps(module_info), options)) +def extract_subgroup_name(key): + subgroup_ame_res = re.findall(SUBGROUP_NAME_PATTERN, key) + if not subgroup_ame_res or len(subgroup_ame_res) == 0: + return False, None + return True, subgroup_ame_res[-1] + + def extract_cmd_name(key): - cmd_name_res = re.finditer(CMD_NAME_PATTERN, key) - if not cmd_name_res: + cmd_name_res = re.findall(CMD_NAME_PATTERN, key) + if not cmd_name_res or len(cmd_name_res) == 0: return False, None - for cmd_match in cmd_name_res: - cmd_name = cmd_match.group(1) - return True, cmd_name + return True, cmd_name_res[0] def extract_cmd_property(key, cmd_name): cmd_key_pattern = re.compile(cmd_name + r"\'\]\[\'([a-zA-Z0-9\-\_]+)\'\]") cmd_key_res = re.findall(cmd_key_pattern, key) - if not cmd_key_res: + if not cmd_key_res or len(cmd_key_res) == 0: return False, None return True, cmd_key_res[0] diff --git a/azdev/operations/tests/jsons/az_monitor_meta_before.json b/azdev/operations/tests/jsons/az_monitor_meta_before.json index dbb1da26..f52dcce3 100644 --- a/azdev/operations/tests/jsons/az_monitor_meta_before.json +++ b/azdev/operations/tests/jsons/az_monitor_meta_before.json @@ -262,7 +262,18 @@ }] } }, - "sub_groups": {} + "sub_groups": { + "monitor private-link-scope private-endpoint-connection cust": { + "name": "monitor private-link-scope private-endpoint-connection cust", + "commands": { + "monitor private-link-scope private-endpoint-connection cust show": { + "name": "monitor private-link-scope private-endpoint-connection cust show", + "parameters": [] + } + }, + "sub_groups": {} + } + } } } } diff --git a/azdev/operations/tests/test_break_change.py b/azdev/operations/tests/test_break_change.py index 8ef62eed..6919c297 100644 --- a/azdev/operations/tests/test_break_change.py +++ b/azdev/operations/tests/test_break_change.py @@ -9,7 +9,7 @@ import os from azdev.operations.command_change import export_command_meta, cmp_command_meta from azdev.operations.command_change.util import get_command_tree, extract_cmd_name, \ - extract_cmd_property, extract_para_info + extract_cmd_property, extract_para_info, extract_subgroup_name class MyTestCase(unittest.TestCase): @@ -41,6 +41,14 @@ def test_diff_dict_key_parse(self): para_res = extract_para_info(test_key) self.assertEqual(para_res[0], "0", "cmd parameter parse failed") + def test_diff_dict_key_for_subgroups(self): + test_key = "root['sub_groups']['monitor']['sub_groups']['monitor account']" + has_cmd, _ = extract_cmd_name(test_key) + self.assertFalse(has_cmd, "cmd parse error from diff dict key") + has_subgroup, subgroup_name = extract_subgroup_name(test_key) + self.assertTrue(has_subgroup, "sub group parse failed from diff dict key") + self.assertEqual(subgroup_name, "monitor account", "sub group name extract failed") + def test_diff_meta(self): if not os.path.exists("./jsons/az_monitor_meta_before.json") \ or not os.path.exists("./jsons/az_monitor_meta_after.json"): @@ -48,13 +56,17 @@ def test_diff_meta(self): result = cmp_command_meta(base_meta_file="./jsons/az_monitor_meta_before.json", diff_meta_file="./jsons/az_monitor_meta_after.json", output_type="text") - target_message = "please confirm cmd `monitor private-link-scope scoped-resource show` removed" - found = False - for line in result: - if line.find(target_message) > -1: - found = True - break - self.assertTrue(found, "target message not found") + target_message = [ + "please confirm cmd `monitor private-link-scope scoped-resource show` removed", + "sub group `monitor private-link-scope private-endpoint-connection cust` removed", + ] + for mes in target_message: + found = False + for line in result: + if line.find(mes) > -1: + found = True + break + self.assertTrue(found, "target message not found") if __name__ == '__main__': diff --git a/azdev/utilities/const.py b/azdev/utilities/const.py index df1718af..02cae108 100644 --- a/azdev/utilities/const.py +++ b/azdev/utilities/const.py @@ -35,6 +35,8 @@ "1008": "cmd `{0}` update parameter `{1}`: added property `{2}`", "1009": "cmd `{0}` update parameter `{1}`: removed property `{2}`", "1010": "cmd `{0}` update parameter `{1}`: updated property `{2}` from `{3}` to `{4}`", + "1011": "sub group `{0}` added", + "1012": "sub group `{0}` removed", } CHANGE_SUGGEST_MESSAGE_MAPPING = { @@ -49,4 +51,6 @@ "1008": "please remove property `{0}` for parameter `{1}` for cmd `{2}`", "1009": "please add back property `{0}` for parameter {1}` for cmd `{2}`", "1010": "please change property `{0}` from `{1}` to `{2}` for parameter `{3}` of cmd `{4}`", + "1011": "please confirm sub group `{0}` added", + "1012": "please confirm sub group `{0}` removed", }