Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Linter rule for service_name.json #3186

Merged
merged 14 commits into from
Apr 19, 2021
126 changes: 126 additions & 0 deletions scripts/ci/service_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
"""
Check format of service_name.json. Command and AzureServiceName are required. Others are optional.
Each highest level command group should have reference in service_name.json.
"""
import json

from azure.cli.core import MainCommandsLoader, AzCli
from azure.cli.core._help import AzCliHelp, CliCommandHelpFile
from azure.cli.core.commands import AzCliCommandInvoker, ExtensionCommandSource
from azure.cli.core.parser import AzCliCommandParser
from knack.help import GroupHelpFile


def get_extension_help_files(cli_ctx):

# 1. Create invoker and load command table and arguments. Remember to turn off applicability check.
invoker = cli_ctx.invocation_cls(cli_ctx=cli_ctx, commands_loader_cls=cli_ctx.commands_loader_cls,
parser_cls=cli_ctx.parser_cls, help_cls=cli_ctx.help_cls)
cli_ctx.invocation = invoker

invoker.commands_loader.skip_applicability = True
cmd_table = invoker.commands_loader.load_command_table(None)

# turn off applicability check for all loaders
for loaders in invoker.commands_loader.cmd_to_loader_map.values():
for loader in loaders:
loader.skip_applicability = True

# filter the command table to only get commands from extensions
cmd_table = {k: v for k, v in cmd_table.items() if isinstance(v.command_source, ExtensionCommandSource)}
invoker.commands_loader.command_table = cmd_table
print('FOUND {} command(s) from the extension.'.format(len(cmd_table)))

for cmd_name in cmd_table:
invoker.commands_loader.load_arguments(cmd_name)

invoker.parser.load_command_table(invoker.commands_loader)

# 2. Now load applicable help files
parser_keys = []
parser_values = []
sub_parser_keys = []
sub_parser_values = []
_store_parsers(invoker.parser, parser_keys, parser_values, sub_parser_keys, sub_parser_values)
for cmd, parser in zip(parser_keys, parser_values):
if cmd not in sub_parser_keys:
sub_parser_keys.append(cmd)
sub_parser_values.append(parser)
help_ctx = cli_ctx.help_cls(cli_ctx=cli_ctx)
help_files = []
for cmd, parser in zip(sub_parser_keys, sub_parser_values):
try:
help_file = GroupHelpFile(help_ctx, cmd, parser) if _is_group(parser) \
else CliCommandHelpFile(help_ctx, cmd, parser)
help_file.load(parser)
help_files.append(help_file)
except Exception as ex:
print("Skipped '{}' due to '{}'".format(cmd, ex))
help_files = sorted(help_files, key=lambda x: x.command)
return help_files


def _store_parsers(parser, parser_keys, parser_values, sub_parser_keys, sub_parser_values):
for s in parser.subparsers.values():
parser_keys.append(_get_parser_name(s))
parser_values.append(s)
if _is_group(s):
for c in s.choices.values():
sub_parser_keys.append(_get_parser_name(c))
sub_parser_values.append(c)
_store_parsers(c, parser_keys, parser_values, sub_parser_keys, sub_parser_values)
Comment on lines +69 to +77
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuse the same function in azure-cli-core?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuse code from azure-cli-extensions



def _get_parser_name(s):
return (s._prog_prefix if hasattr(s, '_prog_prefix') else s.prog)[3:]


def _is_group(parser):
return getattr(parser, '_subparsers', None) is not None \
or getattr(parser, 'choices', None) is not None


def check():
az_cli = AzCli(cli_name='az',
commands_loader_cls=MainCommandsLoader,
invocation_cls=AzCliCommandInvoker,
parser_cls=AzCliCommandParser,
help_cls=AzCliHelp)
# with patch('getpass.getuser', return_value='your_system_user_login_name'):
# create_invoker_and_load_cmds_and_args(az_cli)
help_files = get_extension_help_files(az_cli)
high_command_set = set()
qwordy marked this conversation as resolved.
Show resolved Hide resolved
for help_file in help_files:
if help_file.command:
high_command_set.add(help_file.command.split()[0])
print(high_command_set)

# Load and check service_name.json
with open('src/service_name.json') as f:
service_names = json.load(f)
# print(service_names)
service_name_map = {}
for service_name in service_names:
command = service_name['Command']
service = service_name['AzureServiceName']
if not command.startswith('az '):
raise Exception('{} not starts with az'.format(command))
if not service:
raise Exception('AzureServiceName of {} is empty!'.format(command))
service_name_map[command[3:]] = service
print(service_name_map)

# Check existence in service_name.json
for high_command in high_command_set:
if high_command not in service_name_map:
raise Exception('No entry of {} in service_name.json'.format(high_command))
qwordy marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == "__main__":
check()
9 changes: 9 additions & 0 deletions scripts/ci/verify_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from subprocess import check_output, check_call
from pkg_resources import parse_version

import service_name


def separator_line():
print('-' * 100)
Expand Down Expand Up @@ -160,6 +162,9 @@ def linter_on_external_extension(index_json):
azdev_extension = AzdevExtensionHelper(name)
azdev_extension.linter()

print('Checking service name for external extensions')
service_name.check()

az_extension.remove()


Expand All @@ -181,6 +186,10 @@ def linter_on_internal_extension(modified_files):
azdev_extension = AzdevExtensionHelper(name)
azdev_extension.add_from_code()
azdev_extension.linter()

print('Checking service name for internal extensions')
service_name.check()

azdev_extension.remove()


Expand Down
Loading