azdev/operations/linter/rules/parameter_rules.py (151 lines of code) (raw):

# ----------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for # license information. # ----------------------------------------------------------------------------- from knack.deprecation import Deprecated from azdev.operations.constant import DISALLOWED_HTML_TAG_RULE_LINK from ..rule_decorators import ParameterRule from ..linter import RuleError, LinterSeverity from ..util import has_illegal_html_tag, has_broken_site_links @ParameterRule(LinterSeverity.HIGH) def missing_parameter_help(linter, command_name, parameter_name): if not linter.get_parameter_help(command_name, parameter_name) and not linter.command_expired(command_name): raise RuleError('Missing help') @ParameterRule(LinterSeverity.HIGH) def expired_parameter(linter, command_name, parameter_name): if linter.parameter_expired(command_name, parameter_name): raise RuleError('Deprecated parameter is expired and should be removed.') @ParameterRule(LinterSeverity.HIGH) def expired_option(linter, command_name, parameter_name): expired_options = linter.option_expired(command_name, parameter_name) if expired_options: raise RuleError("Deprecated options '{}' are expired and should be removed.".format(', '.join(expired_options))) @ParameterRule(LinterSeverity.HIGH) def bad_short_option(linter, command_name, parameter_name): bad_options = [] for option in linter.get_parameter_options(command_name, parameter_name): if isinstance(option, Deprecated): # we don't care if deprecated options are "bad options" since this is the # mechanism by which we get rid of them continue if not option.startswith('--') and len(option) != 2: bad_options.append(option) if bad_options: raise RuleError('Found multi-character short options: {}. Use a single character or ' 'convert to a long-option.'.format(' | '.join(bad_options))) @ParameterRule(LinterSeverity.MEDIUM) def parameter_should_not_end_in_resource_group(linter, command_name, parameter_name): options_list = linter.get_parameter_options(command_name, parameter_name) bad_options = [] for opt in options_list: if isinstance(opt, Deprecated): # we don't care if deprecated options are "bad options" since this is the # mechanism by which we get rid of them continue bad_opts = [opt.endswith('resource-group'), opt.endswith('resourcegroup'), opt.endswith("resource-group-name")] if any(bad_opts) and opt != "--resource-group": bad_options.append(opt) if bad_options: bad_options_str = ' | '.join(bad_options) raise RuleError("A command should only have '--resource-group' as its resource group parameter. " "However options '{}' in command '{}' end with 'resource-group' or similar." .format(bad_options_str, command_name)) @ParameterRule(LinterSeverity.HIGH) def no_positional_parameters(linter, command_name, parameter_name): options_list = linter.get_parameter_options(command_name, parameter_name) if not options_list: raise RuleError("CLI commands should have optional parameters instead of positional parameters. " "However parameter '{}' in command '{}' is a positional." .format(parameter_name, command_name)) @ParameterRule(LinterSeverity.HIGH) def no_parameter_defaults_for_update_commands(linter, command_name, parameter_name): is_update_command = command_name.split()[-1].lower() == "update" default_val = linter.get_parameter_settings(command_name, parameter_name).get('default') if is_update_command and default_val: raise RuleError("Update commands should not have parameters with default values. '{}' in '{}' has a " "default value of '{}'".format(parameter_name, command_name, default_val)) @ParameterRule(LinterSeverity.MEDIUM) def no_required_location_param(linter, command_name, parameter_name): # Location parameters should typically not be required. # If there is a resource group, one can default to the its location. has_resource_group = "resource_group_name" in linter.get_command_parameters(command_name) is_location_param = (parameter_name.lower() == "location" or parameter_name.endswith("location")) if has_resource_group and is_location_param: parameter = linter.get_parameter_settings(command_name, parameter_name) is_required = parameter.get('required') if is_required: location_params = linter.get_parameter_options(command_name, parameter_name) location_params = location_params or "'{}'".format(parameter_name) raise RuleError("Location parameters should not be required. However, {} in '{}' should is required. " "Please make it optional and default to the location of the resource group." .format(location_params, command_name)) @ParameterRule(LinterSeverity.LOW) def id_params_only_for_guid(linter, command_name, parameter_name): # Check if the parameter is an id param, except for '--ids'. If so, attempt to figure out if # it is a resource id parametere. This check can lead to false positives, which is why it is a low severity check. # Its aim is to guide reviewers and developers. def _help_contains_queries(help_str, queries): a_query_is_in_a_str = next((True for query in queries if query.lower() in help_str.lower()), False) return a_query_is_in_a_str options_list = linter.get_parameter_options(command_name, parameter_name) or [] queries = ["resource id", "arm id"] is_id_param = False # first find out if an option ends with id. for opt in options_list: if isinstance(opt, Deprecated): return id_opts = [opt.endswith('-id'), opt.endswith('-ids')] if any(id_opts) and opt != "--ids": is_id_param = True # if an option is an id param, check if the help text makes reference to 'resource id' etc. This could lead to fa if is_id_param: param_help = linter.get_parameter_help(command_name, parameter_name) if param_help and _help_contains_queries(param_help, queries): raise RuleError("An option {} ends with '-id'. Arguments ending with '-id' " "must be guids/uuids and not resource ids.".format(options_list)) @ParameterRule(LinterSeverity.HIGH) def option_length_too_long(linter, command_name, parameter_name): min_length = None length_threshold = 22 # only 5% argument's length exceed this value. options_list = linter.get_parameter_options(command_name, parameter_name) or [] for option in options_list: if isinstance(option, Deprecated) or option.startswith('--__'): return option_len = len(option) if option.startswith('--'): option_len -= 2 elif option.startswith('-'): option_len -= 1 min_length = min(min_length, option_len) if min_length else option_len if min_length and min_length > length_threshold: raise RuleError("The lengths of all options {} are longer than threshold {}. " "Argument {} must have a short abbreviation.".format(options_list, length_threshold, parameter_name)) @ParameterRule(LinterSeverity.HIGH) def option_should_not_contain_under_score(linter, command_name, parameter_name): options_list = linter.get_parameter_options(command_name, parameter_name) or [] for option in options_list: if isinstance(option, Deprecated) or option.startswith('--__'): return if '_' in option: raise RuleError("Argument's option {} contains '_' which should be '-' instead.".format(option)) @ParameterRule(LinterSeverity.HIGH) def disallowed_html_tag_from_parameter(linter, command_name, parameter_name): if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name): return help_entry = linter.get_parameter_help_info(command_name, parameter_name) if help_entry.short_summary and (disallowed_tags := has_illegal_html_tag(help_entry.short_summary, linter.diffed_lines)): raise RuleError("Disallowed html tags {} in short summary. " "If the content is a placeholder, please remove <> or wrap it with backtick. " "For example: 1) <Name>-res.yaml -> `<Name>-res.yaml`; " "2) http://<URL>:<PORT> -> `http://<URL>:<PORT>`. " "For more info please refer to: {}".format(disallowed_tags, DISALLOWED_HTML_TAG_RULE_LINK)) if help_entry.long_summary and (disallowed_tags := has_illegal_html_tag(help_entry.long_summary, linter.diffed_lines)): raise RuleError("Disallowed html tags {} in long summary. " "If content is a placeholder, please remove <> or wrap it with backtick. " "For example: 1) <Name>-res.yaml -> `<Name>-res.yaml`; " "2) http://<URL>:<PORT> -> `http://<URL>:<PORT>`. " "For more info please refer to: {}".format(disallowed_tags, DISALLOWED_HTML_TAG_RULE_LINK)) @ParameterRule(LinterSeverity.MEDIUM) def broken_site_link_from_parameter(linter, command_name, parameter_name): if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name): return help_entry = linter.get_parameter_help_info(command_name, parameter_name) if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary, linter.diffed_lines)): raise RuleError("Broken links {} in short summary. " "If link is an example, please wrap it with backtick. ".format(broken_links)) if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary, linter.diffed_lines)): raise RuleError("Broken links {} in long summary. " "If link is an example, please wrap it with backtick. ".format(broken_links))