azdev 0.1.83__tar.gz → 0.1.85__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {azdev-0.1.83 → azdev-0.1.85}/HISTORY.rst +9 -0
- {azdev-0.1.83/azdev.egg-info → azdev-0.1.85}/PKG-INFO +10 -1
- {azdev-0.1.83 → azdev-0.1.85}/azdev/__init__.py +1 -1
- {azdev-0.1.83 → azdev-0.1.85}/azdev/help.py +1 -1
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/breaking_change/__init__.py +7 -2
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/cmdcov.py +2 -2
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/constant.py +18 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/linter.py +31 -1
- azdev-0.1.85/azdev/operations/linter/rules/command_group_rules.py +77 -0
- azdev-0.1.85/azdev/operations/linter/rules/command_rules.py +72 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rules/help_rules.py +3 -3
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rules/parameter_rules.py +35 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/util.py +43 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/setup.py +113 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/__init__.py +2 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/git_util.py +36 -0
- {azdev-0.1.83 → azdev-0.1.85/azdev.egg-info}/PKG-INFO +10 -1
- azdev-0.1.83/azdev/operations/linter/rules/command_group_rules.py +0 -42
- azdev-0.1.83/azdev/operations/linter/rules/command_rules.py +0 -37
- {azdev-0.1.83 → azdev-0.1.85}/LICENSE +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/MANIFEST.in +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/README.md +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/README.rst +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/__main__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/commands.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/completer.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/config/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/config/cli.flake8 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/config/cli_pylintrc +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/config/ext.flake8 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/config/ext_pylintrc +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/HISTORY.rst +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/README.rst +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/_client_factory.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/_help.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/_params.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/_validators.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/azext_metadata.json +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/blank__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/commands.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/custom.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/module__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/pkg_declare__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/setup.cfg +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/setup.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/mod_templates/test_service_scenario.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/breaking_change/markdown_template.jinja2 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/_macros.j2 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/component.css +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/component.js +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/favicon.ico +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/index.j2 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/index2.j2 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/cmdcov/module.j2 +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/code_gen.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/command_change/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/command_change/custom.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/command_change/util.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/extensions/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/extensions/util.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/extensions/version_upgrade.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/help/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/help/refdoc/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/help/refdoc/conf.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/legal.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/pylint_checkers/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/pylint_checkers/show_command.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rule_decorators.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rules/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rules/ci_exclusions.yml +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rules/command_coverage_rules.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/linter/rules/linter_exclusions.yml +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/performance.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/pypi.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/python_sdk.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/regex.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/resource.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/secret.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/statistics/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/statistics/util.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/style.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/testtool/__init__.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/testtool/incremental_strategy.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/testtool/profile_context.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/operations/testtool/pytest_runner.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/params.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/transformers.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/command.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/config.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/const.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/display.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/path.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/pypi.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/testing.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev/utilities/tools.py +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev.egg-info/SOURCES.txt +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev.egg-info/dependency_links.txt +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev.egg-info/entry_points.txt +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev.egg-info/requires.txt +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/azdev.egg-info/top_level.txt +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/pyproject.toml +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/setup.cfg +0 -0
- {azdev-0.1.83 → azdev-0.1.85}/setup.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Release History
|
|
4
4
|
===============
|
|
5
|
+
0.1.85
|
|
6
|
+
++++++
|
|
7
|
+
* `azdev setup`: Setup the upstream and enable .githooks for azure-cli and azure-cli-extensions repos
|
|
8
|
+
* `azdev linter`: Add `disallowed_html_tags` and `broken_site_link` detection in linter rule and set them as `Medium` for unblock CI pipeline temporarily
|
|
9
|
+
|
|
10
|
+
0.1.84
|
|
11
|
+
++++++
|
|
12
|
+
* `azdev generate-breaking-change-report`: Fix `azdev -h` error caused by global importing `azure.cli.core` in `breaking-change.py` module.
|
|
13
|
+
|
|
5
14
|
0.1.83
|
|
6
15
|
++++++
|
|
7
16
|
* `azdev generate-breaking-change-report`: Fix `azdev.operations.breaking_change` not included in `setup.py`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: azdev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.85
|
|
4
4
|
Summary: Microsoft Azure CLI Developer Tools
|
|
5
5
|
Home-page: https://github.com/Azure/azure-cli-dev-tools
|
|
6
6
|
Author: Microsoft Corporation
|
|
@@ -148,6 +148,15 @@ License
|
|
|
148
148
|
|
|
149
149
|
Release History
|
|
150
150
|
===============
|
|
151
|
+
0.1.85
|
|
152
|
+
++++++
|
|
153
|
+
* `azdev setup`: Setup the upstream and enable .githooks for azure-cli and azure-cli-extensions repos
|
|
154
|
+
* `azdev linter`: Add `disallowed_html_tags` and `broken_site_link` detection in linter rule and set them as `Medium` for unblock CI pipeline temporarily
|
|
155
|
+
|
|
156
|
+
0.1.84
|
|
157
|
+
++++++
|
|
158
|
+
* `azdev generate-breaking-change-report`: Fix `azdev -h` error caused by global importing `azure.cli.core` in `breaking-change.py` module.
|
|
159
|
+
|
|
151
160
|
0.1.83
|
|
152
161
|
++++++
|
|
153
162
|
* `azdev generate-breaking-change-report`: Fix `azdev.operations.breaking_change` not included in `setup.py`.
|
|
@@ -160,7 +160,7 @@ helps['linter'] = """
|
|
|
160
160
|
text: azdev linter --repo azure-cli --tgt upstream/master --src upstream/dev
|
|
161
161
|
"""
|
|
162
162
|
|
|
163
|
-
helps['scan'] = """
|
|
163
|
+
helps['scan'] = r"""
|
|
164
164
|
short-summary: Scan secrets for files or string
|
|
165
165
|
long-summary: Check built-in scanning rules at https://github.com/microsoft/security-utilities/blob/main/GeneratedRegexPatterns/PreciselyClassifiedSecurityKeys.json
|
|
166
166
|
examples:
|
|
@@ -9,8 +9,6 @@ from collections import defaultdict
|
|
|
9
9
|
from importlib import import_module
|
|
10
10
|
|
|
11
11
|
import packaging.version
|
|
12
|
-
from azure.cli.core.breaking_change import MergedStatusTag, UpcomingBreakingChangeTag, TargetVersion
|
|
13
|
-
from knack.deprecation import Deprecated
|
|
14
12
|
from knack.log import get_logger
|
|
15
13
|
|
|
16
14
|
from azdev.operations.statistics import _create_invoker_and_load_cmds # pylint: disable=protected-access
|
|
@@ -82,6 +80,9 @@ def _handle_custom_breaking_change(module, command, breaking_change):
|
|
|
82
80
|
|
|
83
81
|
|
|
84
82
|
def _handle_status_tag(module, command, status_tag):
|
|
83
|
+
from knack.deprecation import Deprecated
|
|
84
|
+
from azure.cli.core.breaking_change import MergedStatusTag, UpcomingBreakingChangeTag, TargetVersion
|
|
85
|
+
|
|
85
86
|
if isinstance(status_tag, MergedStatusTag):
|
|
86
87
|
for tag in status_tag.tags:
|
|
87
88
|
yield from _handle_status_tag(module, command, tag)
|
|
@@ -107,6 +108,8 @@ def _handle_command_deprecation(module, command, deprecate_info):
|
|
|
107
108
|
|
|
108
109
|
|
|
109
110
|
def _calc_target_of_arg_deprecation(arg_name, arg_settings):
|
|
111
|
+
from knack.deprecation import Deprecated
|
|
112
|
+
|
|
110
113
|
option_str_list = []
|
|
111
114
|
depr = arg_settings.get('deprecate_info')
|
|
112
115
|
for option in arg_settings.get('option_list', []):
|
|
@@ -128,6 +131,8 @@ def _handle_arg_deprecation(module, command, target, deprecation_info):
|
|
|
128
131
|
|
|
129
132
|
|
|
130
133
|
def _handle_options_deprecation(module, command, options):
|
|
134
|
+
from knack.deprecation import Deprecated
|
|
135
|
+
|
|
131
136
|
deprecate_option_map = defaultdict(lambda: [])
|
|
132
137
|
for option in options:
|
|
133
138
|
if isinstance(option, Deprecated):
|
|
@@ -239,8 +239,8 @@ class CmdcovManager:
|
|
|
239
239
|
self.command_test_coverage['Total'][0] += count
|
|
240
240
|
self.command_test_coverage['Total'][1] += len(self.all_untested_commands[module])
|
|
241
241
|
self.command_test_coverage['Total'][2] = f'''{self.command_test_coverage["Total"][0] /
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
(self.command_test_coverage["Total"][0] +
|
|
243
|
+
self.command_test_coverage["Total"][1]):.3%}'''
|
|
244
244
|
logger.warning(self.command_test_coverage)
|
|
245
245
|
return self.command_test_coverage
|
|
246
246
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
4
|
# license information.
|
|
5
5
|
# -----------------------------------------------------------------------------
|
|
6
|
+
# pylint: disable=line-too-long
|
|
6
7
|
|
|
7
8
|
ENCODING = 'utf-8'
|
|
8
9
|
|
|
@@ -79,6 +80,23 @@ EXCLUDE_MODULES = [
|
|
|
79
80
|
'util'
|
|
80
81
|
]
|
|
81
82
|
|
|
83
|
+
# refer to doc: https://review.learn.microsoft.com/en-us/help/platform/metadata-taxonomies/allowed-html?branch=main
|
|
84
|
+
ALLOWED_HTML_TAG = [
|
|
85
|
+
"a", "address", "article", "b", "blockquote", "br", "button", "br /",
|
|
86
|
+
"caption", "center", "cite", "code", "col", "colgroup",
|
|
87
|
+
"dd", "del", "details", "div", "dl", "dt", "em", "figcaption", "figure", "form",
|
|
88
|
+
"h1", "h2", "h3", "h4", "head", "hr",
|
|
89
|
+
"i", "iframe", "image", "img", "input", "ins", "kbd",
|
|
90
|
+
"label", "li", "nav", "nobr", "ol", "p", "pre", "rgn",
|
|
91
|
+
"s", "section", "source", "span", "strike", "strong", "sub", "summary", "sup",
|
|
92
|
+
"table", "tbody", "td", "tfoot", "th", "thead", "tr",
|
|
93
|
+
"u", "ul", "wbr"
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
DISALLOWED_HTML_TAG_RULE_LINK = "https://review.learn.microsoft.com/en-us/help/platform/validation-ref/disallowed-html-tag?branch=main"
|
|
97
|
+
|
|
98
|
+
BROKEN_LINK_RULE_LINK = "https://review.learn.microsoft.com/en-us/help/platform/validation-ref/other-site-link-broken?branch=main"
|
|
99
|
+
|
|
82
100
|
GLOBAL_EXCLUDE_COMMANDS = ['wait']
|
|
83
101
|
|
|
84
102
|
EXCLUDE_COMMANDS = {
|
|
@@ -20,7 +20,7 @@ from azdev.operations.regex import (
|
|
|
20
20
|
search_argument_context,
|
|
21
21
|
search_command,
|
|
22
22
|
search_command_group)
|
|
23
|
-
from azdev.utilities import diff_branches_detail
|
|
23
|
+
from azdev.utilities import diff_branches_detail, diff_branch_file_patch
|
|
24
24
|
from azdev.utilities.path import get_cli_repo_path, get_ext_repo_paths
|
|
25
25
|
from .util import share_element, exclude_commands, LinterError
|
|
26
26
|
|
|
@@ -63,6 +63,8 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
63
63
|
self.git_target = git_target
|
|
64
64
|
self.git_repo = git_repo
|
|
65
65
|
self.exclusions = exclusions
|
|
66
|
+
self.diffed_lines = set()
|
|
67
|
+
self._get_diffed_patches()
|
|
66
68
|
|
|
67
69
|
@property
|
|
68
70
|
def commands(self):
|
|
@@ -150,6 +152,17 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
150
152
|
def get_parameter_settings(self, command_name, parameter_name):
|
|
151
153
|
return self.get_command_metadata(command_name).arguments.get(parameter_name).type.settings
|
|
152
154
|
|
|
155
|
+
def get_parameter_help_info(self, command_name, parameter_name):
|
|
156
|
+
options = self.get_parameter_options(command_name, parameter_name)
|
|
157
|
+
command_help = self._loaded_help.get(command_name, None)
|
|
158
|
+
|
|
159
|
+
if not command_help:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
parameter_helps = command_help.parameters
|
|
163
|
+
param_help = next((param for param in parameter_helps if share_element(options, param.name.split())), None)
|
|
164
|
+
return param_help
|
|
165
|
+
|
|
153
166
|
def command_expired(self, command_name):
|
|
154
167
|
deprecate_info = self._command_loader.command_table[command_name].deprecate_info
|
|
155
168
|
if deprecate_info:
|
|
@@ -193,6 +206,10 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
193
206
|
return help_entry.short_summary or help_entry.long_summary
|
|
194
207
|
return help_entry
|
|
195
208
|
|
|
209
|
+
def get_loaded_help_entry(self, entry):
|
|
210
|
+
help_entry = self._loaded_help.get(entry, None)
|
|
211
|
+
return help_entry
|
|
212
|
+
|
|
196
213
|
def get_command_test_coverage(self):
|
|
197
214
|
diff_index = diff_branches_detail(repo=self.git_repo, target=self.git_target, source=self.git_source)
|
|
198
215
|
commands, _ = self._detect_new_command(diff_index)
|
|
@@ -354,6 +371,19 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
354
371
|
'Or add the parameter with missing_parameter_test_coverage rule in linter_exclusions.yml'])
|
|
355
372
|
return exec_state, violations
|
|
356
373
|
|
|
374
|
+
def _get_diffed_patches(self):
|
|
375
|
+
if not self.git_source or not self.git_target or not self.git_repo:
|
|
376
|
+
return
|
|
377
|
+
diff_patches = diff_branch_file_patch(repo=self.git_repo, target=self.git_target, source=self.git_source)
|
|
378
|
+
for change in diff_patches:
|
|
379
|
+
patch = change.diff.decode("utf-8")
|
|
380
|
+
added_lines = [line for line in patch.splitlines() if line.startswith('+') and not line.startswith('+++')]
|
|
381
|
+
self.diffed_lines |= set(added_lines)
|
|
382
|
+
if added_lines:
|
|
383
|
+
_logger.info("Changes in file '%s':", change.a_path)
|
|
384
|
+
for line in added_lines:
|
|
385
|
+
_logger.info(line)
|
|
386
|
+
|
|
357
387
|
|
|
358
388
|
# pylint: disable=too-many-instance-attributes
|
|
359
389
|
class LinterManager:
|
|
@@ -0,0 +1,77 @@
|
|
|
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=duplicate-code
|
|
7
|
+
|
|
8
|
+
from azdev.operations.constant import DISALLOWED_HTML_TAG_RULE_LINK
|
|
9
|
+
from ..rule_decorators import CommandGroupRule
|
|
10
|
+
from ..linter import RuleError, LinterSeverity
|
|
11
|
+
from ..util import has_illegal_html_tag, has_broken_site_links
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@CommandGroupRule(LinterSeverity.HIGH)
|
|
15
|
+
def missing_group_help(linter, command_group_name):
|
|
16
|
+
if not linter.get_command_group_help(command_group_name) and not linter.command_group_expired(command_group_name) \
|
|
17
|
+
and command_group_name != '':
|
|
18
|
+
raise RuleError('Missing help')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@CommandGroupRule(LinterSeverity.HIGH)
|
|
22
|
+
def expired_command_group(linter, command_group_name):
|
|
23
|
+
if linter.command_group_expired(command_group_name):
|
|
24
|
+
raise RuleError("Deprecated command group is expired and should be removed.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@CommandGroupRule(LinterSeverity.MEDIUM)
|
|
28
|
+
def require_wait_command_if_no_wait(linter, command_group_name):
|
|
29
|
+
# If any command within a command group or subgroup exposes the --no-wait parameter,
|
|
30
|
+
# the wait command should be exposed.
|
|
31
|
+
|
|
32
|
+
# find commands under this group. A command in this group has one more token than the group name.
|
|
33
|
+
group_command_names = [cmd for cmd in linter.commands if cmd.startswith(command_group_name) and
|
|
34
|
+
len(cmd.split()) == len(command_group_name.split()) + 1]
|
|
35
|
+
|
|
36
|
+
# if one of the commands in this group ends with wait we are good
|
|
37
|
+
for cmd in group_command_names:
|
|
38
|
+
cmds = cmd.split()
|
|
39
|
+
if cmds[-1].lower() == "wait":
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
# otherwise there is no wait command. If a command in this group has --no-wait, then error out.
|
|
43
|
+
for cmd in group_command_names:
|
|
44
|
+
if linter.get_command_metadata(cmd).supports_no_wait:
|
|
45
|
+
raise RuleError("Group does not have a 'wait' command, yet '{}' exposes '--no-wait'".format(cmd))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@CommandGroupRule(LinterSeverity.MEDIUM)
|
|
49
|
+
def disallowed_html_tag_from_command_group(linter, command_group_name):
|
|
50
|
+
if command_group_name == '' or not linter.get_loaded_help_entry(command_group_name):
|
|
51
|
+
return
|
|
52
|
+
help_entry = linter.get_loaded_help_entry(command_group_name)
|
|
53
|
+
if help_entry.short_summary and (disallowed_tags := has_illegal_html_tag(help_entry.short_summary,
|
|
54
|
+
linter.diffed_lines)):
|
|
55
|
+
raise RuleError("Disallowed html tags {} in short summary. "
|
|
56
|
+
"If the content is a placeholder, please remove <> or wrap it with backtick. "
|
|
57
|
+
"For more info please refer to: {}".format(disallowed_tags,
|
|
58
|
+
DISALLOWED_HTML_TAG_RULE_LINK))
|
|
59
|
+
if help_entry.long_summary and (disallowed_tags := has_illegal_html_tag(help_entry.long_summary,
|
|
60
|
+
linter.diffed_lines)):
|
|
61
|
+
raise RuleError("Disallowed html tags {} in long summary. "
|
|
62
|
+
"If content is a placeholder, please remove <> or wrap it with backtick. "
|
|
63
|
+
"For more info please refer to: {}".format(disallowed_tags,
|
|
64
|
+
DISALLOWED_HTML_TAG_RULE_LINK))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@CommandGroupRule(LinterSeverity.MEDIUM)
|
|
68
|
+
def broken_site_link_from_command_group(linter, command_group_name):
|
|
69
|
+
if command_group_name == '' or not linter.get_loaded_help_entry(command_group_name):
|
|
70
|
+
return
|
|
71
|
+
help_entry = linter.get_loaded_help_entry(command_group_name)
|
|
72
|
+
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary)):
|
|
73
|
+
raise RuleError("Broken links {} in short summary. "
|
|
74
|
+
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
75
|
+
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary)):
|
|
76
|
+
raise RuleError("Broken links {} in long summary. "
|
|
77
|
+
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
@@ -0,0 +1,72 @@
|
|
|
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=duplicate-code
|
|
7
|
+
|
|
8
|
+
from azdev.operations.constant import DISALLOWED_HTML_TAG_RULE_LINK
|
|
9
|
+
from ..rule_decorators import CommandRule
|
|
10
|
+
from ..linter import RuleError, LinterSeverity
|
|
11
|
+
from ..util import has_illegal_html_tag, has_broken_site_links
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@CommandRule(LinterSeverity.HIGH)
|
|
15
|
+
def missing_command_help(linter, command_name):
|
|
16
|
+
if not linter.get_command_help(command_name) and not linter.command_expired(command_name):
|
|
17
|
+
raise RuleError('Missing help')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@CommandRule(LinterSeverity.HIGH)
|
|
21
|
+
def no_ids_for_list_commands(linter, command_name):
|
|
22
|
+
if command_name.split()[-1] == 'list' and 'ids' in linter.get_command_parameters(command_name):
|
|
23
|
+
raise RuleError('List commands should not expose --ids argument')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@CommandRule(LinterSeverity.HIGH)
|
|
27
|
+
def expired_command(linter, command_name):
|
|
28
|
+
if linter.command_expired(command_name):
|
|
29
|
+
raise RuleError('Deprecated command is expired and should be removed.')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@CommandRule(LinterSeverity.LOW)
|
|
33
|
+
def group_delete_commands_should_confirm(linter, command_name):
|
|
34
|
+
# We cannot detect from cmd table etc whether a delete command deletes a collection, group or set of resources.
|
|
35
|
+
# so warn users for every delete command.
|
|
36
|
+
|
|
37
|
+
if command_name.split()[-1].lower() == "delete":
|
|
38
|
+
if 'yes' not in linter.get_command_parameters(command_name):
|
|
39
|
+
raise RuleError("If this command deletes a collection, or group of resources. "
|
|
40
|
+
"Please make sure to ask for confirmation.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@CommandRule(LinterSeverity.MEDIUM)
|
|
44
|
+
def disallowed_html_tag_from_command(linter, command_name):
|
|
45
|
+
if command_name == '' or not linter.get_loaded_help_entry(command_name):
|
|
46
|
+
return
|
|
47
|
+
help_entry = linter.get_loaded_help_entry(command_name)
|
|
48
|
+
if help_entry.short_summary and (disallowed_tags := has_illegal_html_tag(help_entry.short_summary,
|
|
49
|
+
linter.diffed_lines)):
|
|
50
|
+
raise RuleError("Disallowed html tags {} in short summary. "
|
|
51
|
+
"If the content is a placeholder, please remove <> or wrap it with backtick. "
|
|
52
|
+
"For more info please refer to: {}".format(disallowed_tags,
|
|
53
|
+
DISALLOWED_HTML_TAG_RULE_LINK))
|
|
54
|
+
if help_entry.long_summary and (disallowed_tags := has_illegal_html_tag(help_entry.long_summary,
|
|
55
|
+
linter.diffed_lines)):
|
|
56
|
+
raise RuleError("Disallowed html tags {} in long summary. "
|
|
57
|
+
"If content is a placeholder, please remove <> or wrap it with backtick. "
|
|
58
|
+
"For more info please refer to: {}".format(disallowed_tags,
|
|
59
|
+
DISALLOWED_HTML_TAG_RULE_LINK))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@CommandRule(LinterSeverity.MEDIUM)
|
|
63
|
+
def broken_site_link_from_command(linter, command_name):
|
|
64
|
+
if command_name == '' or not linter.get_loaded_help_entry(command_name):
|
|
65
|
+
return
|
|
66
|
+
help_entry = linter.get_loaded_help_entry(command_name)
|
|
67
|
+
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary)):
|
|
68
|
+
raise RuleError("Broken links {} in short summary. "
|
|
69
|
+
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
70
|
+
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary)):
|
|
71
|
+
raise RuleError("Broken links {} in long summary. "
|
|
72
|
+
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
@@ -20,10 +20,10 @@ from ..util import LinterError
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
# 'az' space then repeating runs of quoted tokens or non quoted characters
|
|
23
|
-
_az_pattern = 'az\s*' + '(([^\"\'])*|' + '((\"[^\"]*\"\s*)|(\'[^\']*\'\s*))' + ')'
|
|
23
|
+
_az_pattern = r'az\s*' + '(([^\"\'])*|' + r'((\"[^\"]*\"\s*)|(\'[^\']*\'\s*))' + ')'
|
|
24
24
|
# match the two types of command substitutions
|
|
25
|
-
_CMD_SUB_1 = re.compile("\$\(\s*" + "(" + _az_pattern + ")" + "\)")
|
|
26
|
-
_CMD_SUB_2 = re.compile("`\s*" + "(" + _az_pattern + ")" + "`")
|
|
25
|
+
_CMD_SUB_1 = re.compile(r"\$\(\s*" + "(" + _az_pattern + ")" + r"\)")
|
|
26
|
+
_CMD_SUB_2 = re.compile(r"`\s*" + "(" + _az_pattern + ")" + "`")
|
|
27
27
|
|
|
28
28
|
logger = get_logger(__name__)
|
|
29
29
|
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
from knack.deprecation import Deprecated
|
|
8
8
|
|
|
9
|
+
from azdev.operations.constant import DISALLOWED_HTML_TAG_RULE_LINK
|
|
9
10
|
from ..rule_decorators import ParameterRule
|
|
10
11
|
from ..linter import RuleError, LinterSeverity
|
|
12
|
+
from ..util import has_illegal_html_tag, has_broken_site_links
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@ParameterRule(LinterSeverity.HIGH)
|
|
@@ -170,3 +172,36 @@ def option_should_not_contain_under_score(linter, command_name, parameter_name):
|
|
|
170
172
|
return
|
|
171
173
|
if '_' in option:
|
|
172
174
|
raise RuleError("Argument's option {} contains '_' which should be '-' instead.".format(option))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@ParameterRule(LinterSeverity.MEDIUM)
|
|
178
|
+
def disallowed_html_tag_from_parameter(linter, command_name, parameter_name):
|
|
179
|
+
if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name):
|
|
180
|
+
return
|
|
181
|
+
help_entry = linter.get_parameter_help_info(command_name, parameter_name)
|
|
182
|
+
if help_entry.short_summary and (disallowed_tags := has_illegal_html_tag(help_entry.short_summary,
|
|
183
|
+
linter.diffed_lines)):
|
|
184
|
+
raise RuleError("Disallowed html tags {} in short summary. "
|
|
185
|
+
"If the content is a placeholder, please remove <> or wrap it with backtick. "
|
|
186
|
+
"For more info please refer to: {}".format(disallowed_tags,
|
|
187
|
+
DISALLOWED_HTML_TAG_RULE_LINK))
|
|
188
|
+
|
|
189
|
+
if help_entry.long_summary and (disallowed_tags := has_illegal_html_tag(help_entry.long_summary,
|
|
190
|
+
linter.diffed_lines)):
|
|
191
|
+
raise RuleError("Disallowed html tags {} in long summary. "
|
|
192
|
+
"If content is a placeholder, please remove <> or wrap it with backtick. "
|
|
193
|
+
"For more info please refer to: {}".format(disallowed_tags,
|
|
194
|
+
DISALLOWED_HTML_TAG_RULE_LINK))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@ParameterRule(LinterSeverity.MEDIUM)
|
|
198
|
+
def broken_site_link_from_parameter(linter, command_name, parameter_name):
|
|
199
|
+
if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name):
|
|
200
|
+
return
|
|
201
|
+
help_entry = linter.get_parameter_help_info(command_name, parameter_name)
|
|
202
|
+
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary)):
|
|
203
|
+
raise RuleError("Broken links {} in short summary. "
|
|
204
|
+
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
205
|
+
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary)):
|
|
206
|
+
raise RuleError("Broken links {} in long summary. "
|
|
207
|
+
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import copy
|
|
8
8
|
import re
|
|
9
|
+
import requests
|
|
9
10
|
|
|
10
11
|
from knack.log import get_logger
|
|
11
12
|
|
|
12
13
|
from azdev.utilities import get_name_index
|
|
14
|
+
from azdev.operations.constant import ALLOWED_HTML_TAG
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
logger = get_logger(__name__)
|
|
@@ -17,6 +19,12 @@ logger = get_logger(__name__)
|
|
|
17
19
|
|
|
18
20
|
_LOADER_CLS_RE = re.compile('.*azure/cli/command_modules/(?P<module>[^/]*)/__init__.*')
|
|
19
21
|
|
|
22
|
+
# add html tag extraction for <abd>, <lun1>, <edge zone>
|
|
23
|
+
# html tag search for <os_des>, <lun1_des> is enabled in cli ci but skipped in doc build cause internal issue
|
|
24
|
+
_HTML_TAG_RE = re.compile(r'<([^\n>]+)>')
|
|
25
|
+
|
|
26
|
+
_HTTP_LINK_RE = re.compile(r'(?<!`)(https?://[^\s`]+)(?!`)')
|
|
27
|
+
|
|
20
28
|
|
|
21
29
|
def filter_modules(command_loader, help_file_entries, modules=None, include_whl_extensions=False):
|
|
22
30
|
""" Modify the command table and help entries to only include certain modules/extensions.
|
|
@@ -112,3 +120,38 @@ class LinterError(Exception):
|
|
|
112
120
|
Exception thrown by linter for non rule violation reasons
|
|
113
121
|
"""
|
|
114
122
|
pass # pylint: disable=unnecessary-pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# pylint: disable=line-too-long
|
|
126
|
+
def has_illegal_html_tag(help_message, filtered_lines=None):
|
|
127
|
+
"""
|
|
128
|
+
Detect those content wrapped with <> but illegal html tag.
|
|
129
|
+
Refer to rule doc: https://review.learn.microsoft.com/en-us/help/platform/validation-ref/disallowed-html-tag?branch=main
|
|
130
|
+
"""
|
|
131
|
+
html_matches = re.findall(_HTML_TAG_RE, help_message)
|
|
132
|
+
unbackticked_matches = [match for match in html_matches if not re.search(r'`[^`]*' + re.escape('<' + match + '>') + r'[^`]*`', help_message)]
|
|
133
|
+
disallowed_html_tags = set(unbackticked_matches) - set(ALLOWED_HTML_TAG)
|
|
134
|
+
if filtered_lines:
|
|
135
|
+
disallowed_html_tags = [s for s in disallowed_html_tags if any(('<' + s + '>') in diff_line for diff_line in filtered_lines)]
|
|
136
|
+
return ['<' + s + '>' for s in disallowed_html_tags]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def has_broken_site_links(help_message, filtered_lines=None):
|
|
140
|
+
"""
|
|
141
|
+
Detect broken link in help message.
|
|
142
|
+
Refer to rule doc: https://review.learn.microsoft.com/en-us/help/platform/validation-ref/other-site-link-broken?branch=main
|
|
143
|
+
"""
|
|
144
|
+
urls = re.findall(_HTTP_LINK_RE, help_message)
|
|
145
|
+
invalid_urls = []
|
|
146
|
+
|
|
147
|
+
for url in urls:
|
|
148
|
+
url = re.sub(r'[.")\'\s]*$', '', url)
|
|
149
|
+
try:
|
|
150
|
+
response = requests.get(url, timeout=5)
|
|
151
|
+
if response.status_code != 200:
|
|
152
|
+
invalid_urls.append(url)
|
|
153
|
+
except requests.exceptions.RequestException:
|
|
154
|
+
invalid_urls.append(url)
|
|
155
|
+
if filtered_lines:
|
|
156
|
+
invalid_urls = [s for s in invalid_urls if any(s in diff_line for diff_line in filtered_lines)]
|
|
157
|
+
return invalid_urls
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# -----------------------------------------------------------------------------
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import subprocess
|
|
8
9
|
from shutil import copytree, rmtree
|
|
9
10
|
import time
|
|
10
11
|
|
|
@@ -256,6 +257,111 @@ def _interactive_setup():
|
|
|
256
257
|
raise CLIError('Installation aborted.')
|
|
257
258
|
|
|
258
259
|
|
|
260
|
+
def _setup_azure_cli_repo(cli_path):
|
|
261
|
+
if cli_path and cli_path != 'EDGE':
|
|
262
|
+
# Store original directory
|
|
263
|
+
original_dir = os.getcwd()
|
|
264
|
+
try:
|
|
265
|
+
# Change to CLI repo root directory
|
|
266
|
+
os.chdir(cli_path)
|
|
267
|
+
|
|
268
|
+
display(f"\nSetting up Azure CLI repo: {cli_path}\n")
|
|
269
|
+
# Change git hooks path
|
|
270
|
+
_change_git_hooks_path(cli_path)
|
|
271
|
+
|
|
272
|
+
# Check existing remotes
|
|
273
|
+
remotes = subprocess.check_output(['git', 'remote', '-v'], text=True)
|
|
274
|
+
|
|
275
|
+
# If upstream already exists, nothing to do
|
|
276
|
+
if 'upstream' in remotes:
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# Check origin remote URL
|
|
280
|
+
origin_url = None
|
|
281
|
+
for line in remotes.splitlines():
|
|
282
|
+
if line.startswith('origin') and '(fetch)' in line:
|
|
283
|
+
origin_url = line.split()[1]
|
|
284
|
+
break
|
|
285
|
+
|
|
286
|
+
# Only add upstream if origin is an azure-cli fork
|
|
287
|
+
if origin_url and origin_url.endswith('/azure-cli.git'):
|
|
288
|
+
upstream_url = 'https://github.com/Azure/azure-cli.git'
|
|
289
|
+
subprocess.check_call(['git', 'remote', 'add', 'upstream', upstream_url])
|
|
290
|
+
display(f"Added upstream remote: {upstream_url}")
|
|
291
|
+
# fetch the upstream/dev branch
|
|
292
|
+
subprocess.check_call(['git', 'fetch', 'upstream', 'dev'])
|
|
293
|
+
display(f"Fetched upstream/dev branch for CLI in {cli_path}")
|
|
294
|
+
except subprocess.CalledProcessError as e:
|
|
295
|
+
logger.warning("Failed to add upstream remote: %s", str(e))
|
|
296
|
+
finally:
|
|
297
|
+
# Always return to original directory
|
|
298
|
+
os.chdir(original_dir)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _setup_azure_cli_extension_repo(ext_repo_path):
|
|
302
|
+
if not ext_repo_path:
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
# Handle both single path and list of paths
|
|
307
|
+
repo_paths = ext_repo_path if isinstance(ext_repo_path, list) else [ext_repo_path]
|
|
308
|
+
|
|
309
|
+
# Iterate over all repository paths
|
|
310
|
+
for repo_path in repo_paths:
|
|
311
|
+
_setup_single_extension_repo(repo_path)
|
|
312
|
+
|
|
313
|
+
except subprocess.CalledProcessError as e:
|
|
314
|
+
logger.warning("Failed to add upstream remote for extensions: %s", str(e))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _setup_single_extension_repo(repo_path):
|
|
318
|
+
# Store original directory
|
|
319
|
+
original_dir = os.getcwd()
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
# Change to extension repo root directory
|
|
323
|
+
os.chdir(repo_path)
|
|
324
|
+
|
|
325
|
+
display(f"\nSetting up Azure CLI extension repo: {repo_path}\n")
|
|
326
|
+
# Change git hooks path
|
|
327
|
+
_change_git_hooks_path(repo_path)
|
|
328
|
+
|
|
329
|
+
# Check existing remotes
|
|
330
|
+
remotes = subprocess.check_output(['git', 'remote', '-v'], text=True)
|
|
331
|
+
|
|
332
|
+
# If upstream already exists, return
|
|
333
|
+
if 'upstream' in remotes:
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
# Check origin remote URL
|
|
337
|
+
origin_url = None
|
|
338
|
+
for line in remotes.splitlines():
|
|
339
|
+
if line.startswith('origin') and '(fetch)' in line:
|
|
340
|
+
origin_url = line.split()[1]
|
|
341
|
+
break
|
|
342
|
+
|
|
343
|
+
# Only add upstream if origin is an azure-cli-extensions fork
|
|
344
|
+
if origin_url and origin_url.endswith('/azure-cli-extensions.git'):
|
|
345
|
+
upstream_url = 'https://github.com/Azure/azure-cli-extensions.git'
|
|
346
|
+
subprocess.check_call(['git', 'remote', 'add', 'upstream', upstream_url])
|
|
347
|
+
display(f"Added upstream remote for extensions in {repo_path}: {upstream_url}")
|
|
348
|
+
# fetch the upstream/main branch
|
|
349
|
+
subprocess.check_call(['git', 'fetch', 'upstream', 'main'])
|
|
350
|
+
display(f"Fetched upstream/main branch for extensions in {repo_path}")
|
|
351
|
+
|
|
352
|
+
finally:
|
|
353
|
+
# Always return to original directory
|
|
354
|
+
os.chdir(original_dir)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _change_git_hooks_path(repo_path):
|
|
358
|
+
# if .githooks folder exists in the repo folder, change the git config to use the .githooks folder in the repo
|
|
359
|
+
githooks_path = os.path.join(repo_path, '.githooks')
|
|
360
|
+
if os.path.exists(githooks_path):
|
|
361
|
+
subprocess.check_call(['git', 'config', 'core.hooksPath', githooks_path])
|
|
362
|
+
display(f"Changed git hooks path to {githooks_path}")
|
|
363
|
+
|
|
364
|
+
|
|
259
365
|
def setup(cli_path=None, ext_repo_path=None, ext=None, deps=None):
|
|
260
366
|
|
|
261
367
|
require_virtual_env()
|
|
@@ -319,6 +425,13 @@ def setup(cli_path=None, ext_repo_path=None, ext=None, deps=None):
|
|
|
319
425
|
config.set_value('ext', 'repo_paths', dev_sources if dev_sources else '_NONE_')
|
|
320
426
|
config.set_value('cli', 'repo_path', cli_path if cli_path else '_NONE_')
|
|
321
427
|
|
|
428
|
+
# Add upstreams for CLI and extensions repos if they are forks
|
|
429
|
+
if cli_path:
|
|
430
|
+
_setup_azure_cli_repo(cli_path)
|
|
431
|
+
|
|
432
|
+
if ext_repo_path:
|
|
433
|
+
_setup_azure_cli_extension_repo(ext_repo_path)
|
|
434
|
+
|
|
322
435
|
# install packages
|
|
323
436
|
subheading('Installing packages')
|
|
324
437
|
|
|
@@ -35,6 +35,7 @@ from .display import (
|
|
|
35
35
|
from .git_util import (
|
|
36
36
|
diff_branches,
|
|
37
37
|
filter_by_git_diff,
|
|
38
|
+
diff_branch_file_patch,
|
|
38
39
|
diff_branches_detail
|
|
39
40
|
)
|
|
40
41
|
from .path import (
|
|
@@ -94,5 +95,6 @@ __all__ = [
|
|
|
94
95
|
'require_virtual_env',
|
|
95
96
|
'require_azure_cli',
|
|
96
97
|
'diff_branches_detail',
|
|
98
|
+
'diff_branch_file_patch',
|
|
97
99
|
'calc_selected_mod_names',
|
|
98
100
|
]
|
|
@@ -127,3 +127,39 @@ def diff_branches_detail(repo, target, source):
|
|
|
127
127
|
|
|
128
128
|
diff_index = target_commit.diff(source_commit)
|
|
129
129
|
return diff_index
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def diff_branch_file_patch(repo, target, source):
|
|
133
|
+
""" Returns compare results of files that have changed in a given repo between two branches.
|
|
134
|
+
Only focus on these files: _params.py, commands.py, test_*.py """
|
|
135
|
+
try:
|
|
136
|
+
import git # pylint: disable=unused-import,unused-variable
|
|
137
|
+
import git.exc as git_exc
|
|
138
|
+
import gitdb
|
|
139
|
+
except ImportError as ex:
|
|
140
|
+
raise CLIError(ex)
|
|
141
|
+
|
|
142
|
+
from git import Repo
|
|
143
|
+
try:
|
|
144
|
+
git_repo = Repo(repo)
|
|
145
|
+
except (git_exc.NoSuchPathError, git_exc.InvalidGitRepositoryError):
|
|
146
|
+
raise CLIError('invalid git repo: {}'.format(repo))
|
|
147
|
+
|
|
148
|
+
def get_commit(branch):
|
|
149
|
+
try:
|
|
150
|
+
return git_repo.commit(branch)
|
|
151
|
+
except gitdb.exc.BadName:
|
|
152
|
+
raise CLIError('usage error, invalid branch: {}'.format(branch))
|
|
153
|
+
|
|
154
|
+
if source:
|
|
155
|
+
source_commit = get_commit(source)
|
|
156
|
+
else:
|
|
157
|
+
source_commit = git_repo.head.commit
|
|
158
|
+
target_commit = get_commit(target)
|
|
159
|
+
|
|
160
|
+
logger.info('Filtering down to modules which have changed based on:')
|
|
161
|
+
logger.info('cd %s', repo)
|
|
162
|
+
logger.info('git --no-pager diff %s..%s --name-only -- .\n', target_commit, source_commit)
|
|
163
|
+
|
|
164
|
+
diff_index = target_commit.diff(source_commit, create_patch=True)
|
|
165
|
+
return diff_index
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: azdev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.85
|
|
4
4
|
Summary: Microsoft Azure CLI Developer Tools
|
|
5
5
|
Home-page: https://github.com/Azure/azure-cli-dev-tools
|
|
6
6
|
Author: Microsoft Corporation
|
|
@@ -148,6 +148,15 @@ License
|
|
|
148
148
|
|
|
149
149
|
Release History
|
|
150
150
|
===============
|
|
151
|
+
0.1.85
|
|
152
|
+
++++++
|
|
153
|
+
* `azdev setup`: Setup the upstream and enable .githooks for azure-cli and azure-cli-extensions repos
|
|
154
|
+
* `azdev linter`: Add `disallowed_html_tags` and `broken_site_link` detection in linter rule and set them as `Medium` for unblock CI pipeline temporarily
|
|
155
|
+
|
|
156
|
+
0.1.84
|
|
157
|
+
++++++
|
|
158
|
+
* `azdev generate-breaking-change-report`: Fix `azdev -h` error caused by global importing `azure.cli.core` in `breaking-change.py` module.
|
|
159
|
+
|
|
151
160
|
0.1.83
|
|
152
161
|
++++++
|
|
153
162
|
* `azdev generate-breaking-change-report`: Fix `azdev.operations.breaking_change` not included in `setup.py`.
|
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
from ..rule_decorators import CommandGroupRule
|
|
8
|
-
from ..linter import RuleError, LinterSeverity
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@CommandGroupRule(LinterSeverity.HIGH)
|
|
12
|
-
def missing_group_help(linter, command_group_name):
|
|
13
|
-
if not linter.get_command_group_help(command_group_name) and not linter.command_group_expired(command_group_name) \
|
|
14
|
-
and command_group_name != '':
|
|
15
|
-
raise RuleError('Missing help')
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@CommandGroupRule(LinterSeverity.HIGH)
|
|
19
|
-
def expired_command_group(linter, command_group_name):
|
|
20
|
-
if linter.command_group_expired(command_group_name):
|
|
21
|
-
raise RuleError("Deprecated command group is expired and should be removed.")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@CommandGroupRule(LinterSeverity.MEDIUM)
|
|
25
|
-
def require_wait_command_if_no_wait(linter, command_group_name):
|
|
26
|
-
# If any command within a command group or subgroup exposes the --no-wait parameter,
|
|
27
|
-
# the wait command should be exposed.
|
|
28
|
-
|
|
29
|
-
# find commands under this group. A command in this group has one more token than the group name.
|
|
30
|
-
group_command_names = [cmd for cmd in linter.commands if cmd.startswith(command_group_name) and
|
|
31
|
-
len(cmd.split()) == len(command_group_name.split()) + 1]
|
|
32
|
-
|
|
33
|
-
# if one of the commands in this group ends with wait we are good
|
|
34
|
-
for cmd in group_command_names:
|
|
35
|
-
cmds = cmd.split()
|
|
36
|
-
if cmds[-1].lower() == "wait":
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
# otherwise there is no wait command. If a command in this group has --no-wait, then error out.
|
|
40
|
-
for cmd in group_command_names:
|
|
41
|
-
if linter.get_command_metadata(cmd).supports_no_wait:
|
|
42
|
-
raise RuleError("Group does not have a 'wait' command, yet '{}' exposes '--no-wait'".format(cmd))
|
|
@@ -1,37 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
from ..rule_decorators import CommandRule
|
|
8
|
-
from ..linter import RuleError, LinterSeverity
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@CommandRule(LinterSeverity.HIGH)
|
|
12
|
-
def missing_command_help(linter, command_name):
|
|
13
|
-
if not linter.get_command_help(command_name) and not linter.command_expired(command_name):
|
|
14
|
-
raise RuleError('Missing help')
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@CommandRule(LinterSeverity.HIGH)
|
|
18
|
-
def no_ids_for_list_commands(linter, command_name):
|
|
19
|
-
if command_name.split()[-1] == 'list' and 'ids' in linter.get_command_parameters(command_name):
|
|
20
|
-
raise RuleError('List commands should not expose --ids argument')
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@CommandRule(LinterSeverity.HIGH)
|
|
24
|
-
def expired_command(linter, command_name):
|
|
25
|
-
if linter.command_expired(command_name):
|
|
26
|
-
raise RuleError('Deprecated command is expired and should be removed.')
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@CommandRule(LinterSeverity.LOW)
|
|
30
|
-
def group_delete_commands_should_confirm(linter, command_name):
|
|
31
|
-
# We cannot detect from cmd table etc whether a delete command deletes a collection, group or set of resources.
|
|
32
|
-
# so warn users for every delete command.
|
|
33
|
-
|
|
34
|
-
if command_name.split()[-1].lower() == "delete":
|
|
35
|
-
if 'yes' not in linter.get_command_parameters(command_name):
|
|
36
|
-
raise RuleError("If this command deletes a collection, or group of resources. "
|
|
37
|
-
"Please make sure to ask for confirmation.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|