Esempio n. 1
0
    def test_case_insensitive_enum_choices(self):
        from enum import Enum

        class TestEnum(Enum):  # pylint: disable=too-few-public-methods

            opt1 = "ALL_CAPS"
            opt2 = "camelCase"
            opt3 = "snake_case"

        def test_handler():
            pass

        cli = DummyCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'test command', test_handler)
        command.add_argument('opt',
                             '--opt',
                             required=True,
                             **enum_choice_list(TestEnum))
        cmd_table = {'test command': command}
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)

        args = parser.parse_args('test command --opt alL_cAps'.split())
        self.assertEqual(args.opt, 'ALL_CAPS')

        args = parser.parse_args('test command --opt CAMELCASE'.split())
        self.assertEqual(args.opt, 'camelCase')

        args = parser.parse_args('test command --opt sNake_CASE'.split())
        self.assertEqual(args.opt, 'snake_case')
Esempio n. 2
0
    def test_register_simple_commands(self):
        def test_handler1():
            pass

        def test_handler2():
            pass

        command = CliCommand('command the-name', test_handler1)
        command2 = CliCommand('sub-command the-second-name', test_handler2)
        cmd_table = {
            'command the-name': command,
            'sub-command the-second-name': command2
        }

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)
        args = parser.parse_args('command the-name'.split())
        self.assertIs(args.func, test_handler1)

        args = parser.parse_args('sub-command the-second-name'.split())
        self.assertIs(args.func, test_handler2)

        AzCliCommandParser.error = VerifyError(self, )
        parser.parse_args('sub-command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 3
0
    def test_register_simple_commands(self):
        def test_handler1():
            pass

        def test_handler2():
            pass

        cli = TestCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'command the-name', test_handler1)
        command2 = AzCliCommand(cli.loader, 'sub-command the-second-name', test_handler2)
        cmd_table = {'command the-name': command, 'sub-command the-second-name': command2}
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)
        args = parser.parse_args('command the-name'.split())
        self.assertIs(args.func, command)

        args = parser.parse_args('sub-command the-second-name'.split())
        self.assertIs(args.func, command2)

        AzCliCommandParser.error = VerifyError(self,)
        parser.parse_args('sub-command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 4
0
    def test_register_simple_commands(self):
        def test_handler1():
            pass

        def test_handler2():
            pass

        cli = DummyCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'command the-name', test_handler1)
        command2 = AzCliCommand(cli.loader, 'sub-command the-second-name',
                                test_handler2)
        cmd_table = {
            'command the-name': command,
            'sub-command the-second-name': command2
        }
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)
        args = parser.parse_args('command the-name'.split())
        self.assertIs(args.func, command)

        args = parser.parse_args('sub-command the-second-name'.split())
        self.assertIs(args.func, command2)

        AzCliCommandParser.error = VerifyError(self, )
        parser.parse_args('sub-command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 5
0
    def test_case_insensitive_enum_choices(self):
        from enum import Enum

        class TestEnum(Enum):  # pylint: disable=too-few-public-methods

            opt1 = "ALL_CAPS"
            opt2 = "camelCase"
            opt3 = "snake_case"

        def test_handler():
            pass

        command = CliCommand('test command', test_handler)
        command.add_argument('opt', '--opt', required=True, **enum_choice_list(TestEnum))
        cmd_table = {'test command': command}

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)

        args = parser.parse_args('test command --opt alL_cAps'.split())
        self.assertEqual(args.opt, 'ALL_CAPS')

        args = parser.parse_args('test command --opt CAMELCASE'.split())
        self.assertEqual(args.opt, 'camelCase')

        args = parser.parse_args('test command --opt sNake_CASE'.split())
        self.assertEqual(args.opt, 'snake_case')
Esempio n. 6
0
    def test_nargs_parameter(self):
        def test_handler():
            pass

        command = CliCommand('test command', test_handler)
        command.add_argument('req', '--req', required=True, nargs=2)
        cmd_table = {'test command': command}

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)

        args = parser.parse_args('test command --req yep nope'.split())
        self.assertIs(args.func, test_handler)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command -req yep'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 7
0
    def test_required_parameter(self):
        def test_handler(args):  # pylint: disable=unused-argument
            pass

        command = CliCommand('test command', test_handler)
        command.add_argument('req', '--req', required=True)
        cmd_table = {'test command': command}

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)

        args = parser.parse_args('test command --req yep'.split())
        self.assertIs(args.func, test_handler)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 8
0
    def test_required_parameter(self):
        def test_handler(args): # pylint: disable=unused-argument
            pass

        command = CliCommand('test command', test_handler)
        command.add_argument('req', '--req', required=True)
        cmd_table = {'test command': command}

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)

        args = parser.parse_args('test command --req yep'.split())
        self.assertIs(args.func, test_handler)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 9
0
    def test_nargs_parameter(self):
        def test_handler():
            pass

        command = CliCommand('test command', test_handler)
        command.add_argument('req', '--req', required=True, nargs=2)
        cmd_table = {'test command': command}

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)

        args = parser.parse_args('test command --req yep nope'.split())
        self.assertIs(args.func, command)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command -req yep'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 10
0
    def test_nargs_parameter(self):
        def test_handler():
            pass

        cli = TestCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'test command', test_handler)
        command.add_argument('req', '--req', required=True, nargs=2)
        cmd_table = {'test command': command}

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cmd_table)

        args = parser.parse_args('test command --req yep nope'.split())
        self.assertIs(args.func, command)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command -req yep'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 11
0
    def test_register_simple_commands(self):
        def test_handler1():
            pass

        def test_handler2():
            pass

        command = CliCommand('command the-name', test_handler1)
        command2 = CliCommand('sub-command the-second-name', test_handler2)
        cmd_table = {'command the-name': command, 'sub-command the-second-name': command2}

        parser = AzCliCommandParser()
        parser.load_command_table(cmd_table)
        args = parser.parse_args('command the-name'.split())
        self.assertIs(args.func, command)

        args = parser.parse_args('sub-command the-second-name'.split())
        self.assertIs(args.func, command2)

        AzCliCommandParser.error = VerifyError(self,)
        parser.parse_args('sub-command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 12
0
    def test_required_parameter(self):
        def test_handler(args):  # pylint: disable=unused-argument
            pass

        cli = DummyCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'test command', test_handler)
        command.add_argument('req', '--req', required=True)
        cmd_table = {'test command': command}
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)

        args = parser.parse_args('test command --req yep'.split())
        self.assertIs(args.func, command)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 13
0
    def test_required_parameter(self):
        def test_handler(args):  # pylint: disable=unused-argument
            pass

        cli = DummyCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'test command', test_handler)
        command.add_argument('req', '--req', required=True)
        cmd_table = {'test command': command}
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)

        args = parser.parse_args('test command --req yep'.split())
        self.assertIs(args.func, command)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 14
0
    def test_nargs_parameter(self):
        def test_handler():
            pass

        cli = TestCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'test command', test_handler)
        command.add_argument('req', '--req', required=True, nargs=2)
        cmd_table = {'test command': command}
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)

        args = parser.parse_args('test command --req yep nope'.split())
        self.assertIs(args.func, command)

        AzCliCommandParser.error = VerifyError(self)
        parser.parse_args('test command -req yep'.split())
        self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 15
0
    def test_nargs_parameter(self):
        def test_handler():
            pass

        cli = DummyCli()
        cli.loader = mock.MagicMock()
        cli.loader.cli_ctx = cli

        command = AzCliCommand(cli.loader, 'test command', test_handler)
        command.add_argument('req', '--req', required=True, nargs=2)
        cmd_table = {'test command': command}
        cli.commands_loader.command_table = cmd_table

        parser = AzCliCommandParser(cli)
        parser.load_command_table(cli.commands_loader)

        args = parser.parse_args('test command --req yep nope'.split())
        self.assertIs(args.func, command)

        with mock.patch('azure.cli.core.parser.AzCliCommandParser.error', new=VerifyError(self)):
            parser.parse_args('test command -req yep'.split())
            self.assertTrue(AzCliCommandParser.error.called)
Esempio n. 16
0
class Application(object):

    TRANSFORM_RESULT = 'Application.TransformResults'
    FILTER_RESULT = 'Application.FilterResults'
    GLOBAL_PARSER_CREATED = 'GlobalParser.Created'
    COMMAND_PARSER_LOADED = 'CommandParser.Loaded'
    COMMAND_PARSER_PARSED = 'CommandParser.Parsed'
    COMMAND_TABLE_LOADED = 'CommandTable.Loaded'

    def __init__(self, config=None):
        self._event_handlers = defaultdict(lambda: [])
        self.session = {
            'headers': {
                'x-ms-client-request-id': str(uuid.uuid1())
                },
            'command': 'unknown',
            'completer_active': ARGCOMPLETE_ENV_NAME in os.environ,
            'query_active': False
            }

        # Register presence of and handlers for global parameters
        self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments)
        self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments)

        # Let other extensions make their presence known
        azure.cli.core.extensions.register_extensions(self)

        self.global_parser = AzCliCommandParser(prog='az', add_help=False)
        global_group = self.global_parser.add_argument_group('global', 'Global Arguments')
        self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group)

        self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser])

        self.initialize(config or Configuration([]))

    def initialize(self, configuration):
        self.configuration = configuration

    def execute(self, unexpanded_argv):
        argv = Application._expand_file_prefixed_files(unexpanded_argv)
        command_table = self.configuration.get_command_table()
        self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table)
        self.parser.load_command_table(command_table)
        self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser)

        if len(argv) == 0:
            az_subparser = self.parser.subparsers[tuple()]
            _help.show_welcome(az_subparser)
            log_telemetry('welcome')
            return None

        if argv[0].lower() == 'help':
            argv[0] = '--help'

        args = self.parser.parse_args(argv)
        self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args)
        results = []
        for expanded_arg in _explode_list_args(args):
            self.session['command'] = expanded_arg.command
            try:
                _validate_arguments(expanded_arg)
            except CLIError:
                raise
            except: # pylint: disable=bare-except
                err = sys.exc_info()[1]
                getattr(expanded_arg, '_parser', self.parser).validation_error(str(err))

            # Consider - we are using any args that start with an underscore (_) as 'private'
            # arguments and remove them from the arguments that we pass to the actual function.
            # This does not feel quite right.
            params = dict([(key, value)
                           for key, value in expanded_arg.__dict__.items()
                           if not key.startswith('_')])
            params.pop('subcommand', None)
            params.pop('func', None)
            params.pop('command', None)
            log_telemetry(expanded_arg.command, log_type='pageview',
                          output_type=self.configuration.output_format,
                          parameters=[p for p in unexpanded_argv if p.startswith('-')])

            result = expanded_arg.func(params)
            result = todict(result)
            results.append(result)

        if len(results) == 1:
            results = results[0]

        event_data = {'result': results}
        self.raise_event(self.TRANSFORM_RESULT, event_data=event_data)
        self.raise_event(self.FILTER_RESULT, event_data=event_data)
        return CommandResultItem(event_data['result'],
                                 table_transformer=
                                 command_table[args.command].table_transformer,
                                 is_query_active=self.session['query_active'])

    def raise_event(self, name, **kwargs):
        '''Raise the event `name`.
        '''
        logger.info("Application event '%s' with event data %s", name, kwargs)
        for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list
            func(**kwargs)

    def register(self, name, handler):
        '''Register a callable that will be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        '''
        self._event_handlers[name].append(handler)
        logger.info("Registered application event handler '%s' at %s", name, handler)

    def remove(self, name, handler):
        '''Remove a callable that is registered to be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        '''
        self._event_handlers[name].remove(handler)
        logger.info("Removed application event handler '%s' at %s", name, handler)

    @staticmethod
    def _register_builtin_arguments(**kwargs):
        global_group = kwargs['global_group']
        global_group.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS)
        global_group.add_argument('--output', '-o', dest='_output_format',
                                  choices=['json', 'tsv', 'list', 'table', 'jsonc'],
                                  default=az_config.get('core', 'output', fallback='json'),
                                  help='Output format',
                                  type=str.lower)
        # The arguments for verbosity don't get parsed by argparse but we add it here for help.
        global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true',
                                  help='Increase logging verbosity. Use --debug for full debug logs.') #pylint: disable=line-too-long
        global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true',
                                  help='Increase logging verbosity to show all debug logs.')

    @staticmethod
    def _expand_file_prefixed_files(argv):
        return list(
            [Application._load_file(arg[1:]) if arg.startswith('@') else arg for arg in argv]
            )

    @staticmethod
    def _load_file(path):
        try:
            if path == '-':
                content = sys.stdin.read()
            else:
                with open(path, 'r') as input_file:
                    content = input_file.read()

            return content[0:-1] if content[-1] == '\n' else content
        except:
            raise CLIError('Failed to open file {}'.format(path))

    def _handle_builtin_arguments(self, **kwargs):
        args = kwargs['args']
        self.configuration.output_format = args._output_format #pylint: disable=protected-access
        del args._output_format
Esempio n. 17
0
class Application(object):

    TRANSFORM_RESULT = 'Application.TransformResults'
    FILTER_RESULT = 'Application.FilterResults'
    GLOBAL_PARSER_CREATED = 'GlobalParser.Created'
    COMMAND_PARSER_LOADED = 'CommandParser.Loaded'
    COMMAND_PARSER_PARSING = 'CommandParser.Parsing'
    COMMAND_PARSER_PARSED = 'CommandParser.Parsed'
    COMMAND_TABLE_LOADED = 'CommandTable.Loaded'
    COMMAND_TABLE_PARAMS_LOADED = 'CommandTableParams.Loaded'

    def __init__(self, configuration=None):
        self._event_handlers = defaultdict(lambda: [])
        self.session = {
            'headers': {
                'x-ms-client-request-id': str(uuid.uuid1())
            },
            'command': 'unknown',
            'completer_active': ARGCOMPLETE_ENV_NAME in os.environ,
            'query_active': False
        }

        # Register presence of and handlers for global parameters
        self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments)
        self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments)

        # Let other extensions make their presence known
        azure.cli.core.extensions.register_extensions(self)

        self.global_parser = AzCliCommandParser(prog='az', add_help=False)
        global_group = self.global_parser.add_argument_group('global', 'Global Arguments')
        self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group)

        self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser])
        self.configuration = configuration
        self.progress_controller = progress.ProgressHook()

    def get_progress_controller(self, det=False):
        self.progress_controller.init_progress(progress.get_progress_view(det))
        return self.progress_controller

    def initialize(self, configuration):
        self.configuration = configuration

    def execute(self, unexpanded_argv):  # pylint: disable=too-many-statements
        argv = Application._expand_file_prefixed_files(unexpanded_argv)
        command_table = self.configuration.get_command_table(argv)
        self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table)
        self.parser.load_command_table(command_table)
        self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser)

        if not argv:
            enable_autocomplete(self.parser)
            az_subparser = self.parser.subparsers[tuple()]
            _help.show_welcome(az_subparser)

            # TODO: Question, is this needed?
            telemetry.set_command_details('az')
            telemetry.set_success(summary='welcome')

            return None

        if argv[0].lower() == 'help':
            argv[0] = '--help'

        # Rudimentary parsing to get the command
        nouns = []
        for i, current in enumerate(argv):
            try:
                if current[0] == '-':
                    break
            except IndexError:
                pass
            argv[i] = current.lower()
            nouns.append(argv[i])

        command = ' '.join(nouns)

        if argv[-1] in ('--help', '-h') or command in command_table:
            self.configuration.load_params(command)
            self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table)
            self.parser.load_command_table(command_table)

        if self.session['completer_active']:
            enable_autocomplete(self.parser)

        self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv)
        args = self.parser.parse_args(argv)

        self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args)
        results = []
        for expanded_arg in _explode_list_args(args):
            self.session['command'] = expanded_arg.command
            try:
                _validate_arguments(expanded_arg)
            except CLIError:
                raise
            except:  # pylint: disable=bare-except
                err = sys.exc_info()[1]
                getattr(expanded_arg, '_parser', self.parser).validation_error(str(err))

            # Consider - we are using any args that start with an underscore (_) as 'private'
            # arguments and remove them from the arguments that we pass to the actual function.
            # This does not feel quite right.
            params = dict([(key, value)
                           for key, value in expanded_arg.__dict__.items()
                           if not key.startswith('_')])
            params.pop('subcommand', None)
            params.pop('func', None)
            params.pop('command', None)

            telemetry.set_command_details(expanded_arg.command,
                                          self.configuration.output_format,
                                          [p for p in unexpanded_argv if p.startswith('-')])

            result = expanded_arg.func(params)
            result = todict(result)
            results.append(result)

        if len(results) == 1:
            results = results[0]

        event_data = {'result': results}
        self.raise_event(self.TRANSFORM_RESULT, event_data=event_data)
        self.raise_event(self.FILTER_RESULT, event_data=event_data)

        return CommandResultItem(event_data['result'],
                                 table_transformer=command_table[args.command].table_transformer,
                                 is_query_active=self.session['query_active'])

    def raise_event(self, name, **kwargs):
        '''Raise the event `name`.
        '''
        data = truncate_text(str(kwargs), width=500)
        logger.debug("Application event '%s' with event data %s", name, data)
        for func in list(self._event_handlers[name]):  # Make copy in case handler modifies the list
            func(**kwargs)

    def register(self, name, handler):
        '''Register a callable that will be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        '''
        self._event_handlers[name].append(handler)
        logger.debug("Registered application event handler '%s' at %s", name, handler)

    def remove(self, name, handler):
        '''Remove a callable that is registered to be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        '''
        self._event_handlers[name].remove(handler)
        logger.debug("Removed application event handler '%s' at %s", name, handler)

    @staticmethod
    def _register_builtin_arguments(**kwargs):
        global_group = kwargs['global_group']
        global_group.add_argument('--output', '-o', dest='_output_format',
                                  choices=['json', 'tsv', 'table', 'jsonc','pandas'],
                                  default=az_config.get('core', 'output', fallback='json'),
                                  help='Output format',
                                  type=str.lower)
        # The arguments for verbosity don't get parsed by argparse but we add it here for help.
        global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true',
                                  help='Increase logging verbosity. Use --debug for full debug logs.')  # pylint: disable=line-too-long
        global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true',
                                  help='Increase logging verbosity to show all debug logs.')

    @staticmethod
    def _maybe_load_file(arg):
        ix = arg.find('@')
        if ix == -1:  # no @ found
            return arg

        poss_file = arg[ix + 1:]
        if not poss_file:  # if nothing after @ then it can't be a file
            return arg
        elif ix == 0:
            return Application._load_file(poss_file)

        # if @ not at the start it can't be a file
        return arg

    @staticmethod
    def _expand_file_prefix(arg):
        arg_split = arg.split('=', 1)
        try:
            return '='.join([arg_split[0], Application._maybe_load_file(arg_split[1])])
        except IndexError:
            return Application._maybe_load_file(arg_split[0])

    @staticmethod
    def _expand_file_prefixed_files(argv):
        return list([Application._expand_file_prefix(arg) for arg in argv])

    @staticmethod
    def _load_file(path):
        if path == '-':
            content = sys.stdin.read()
        else:
            content = read_file_content(os.path.expanduser(path),
                                        allow_binary=True)

        return content[0:-1] if content and content[-1] == '\n' else content

    def _handle_builtin_arguments(self, **kwargs):
        args = kwargs['args']
        self.configuration.output_format = args._output_format  # pylint: disable=protected-access
        del args._output_format
Esempio n. 18
0
class Application(object):

    TRANSFORM_RESULT = 'Application.TransformResults'
    FILTER_RESULT = 'Application.FilterResults'
    GLOBAL_PARSER_CREATED = 'GlobalParser.Created'
    COMMAND_PARSER_LOADED = 'CommandParser.Loaded'
    COMMAND_PARSER_PARSING = 'CommandParser.Parsing'
    COMMAND_PARSER_PARSED = 'CommandParser.Parsed'
    COMMAND_TABLE_LOADED = 'CommandTable.Loaded'
    COMMAND_TABLE_PARAMS_LOADED = 'CommandTableParams.Loaded'

    def __init__(self, configuration=None):
        self._event_handlers = defaultdict(lambda: [])
        self.session = {
            'headers': {},  # the x-ms-client-request-id is generated before a command is to execute
            'command': 'unknown',
            'completer_active': ARGCOMPLETE_ENV_NAME in os.environ,
            'query_active': False,
            'az_interactive_active': False
        }

        # Register presence of and handlers for global parameters
        self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments)
        self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments)

        # Let other extensions make their presence known
        azure.cli.core.extensions.register_extensions(self)

        self.global_parser = AzCliCommandParser(prog='az', add_help=False)
        global_group = self.global_parser.add_argument_group('global', 'Global Arguments')
        self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group)

        self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser])
        self.configuration = configuration
        self.progress_controller = progress.ProgressHook()

    def get_progress_controller(self, det=False):
        self.progress_controller.init_progress(progress.get_progress_view(det))
        return self.progress_controller

    def initialize(self, configuration):
        self.configuration = configuration

    def execute(self, unexpanded_argv):  # pylint: disable=too-many-statements
        self.refresh_request_id()

        argv = Application._expand_file_prefixed_files(unexpanded_argv)
        command_table = self.configuration.get_command_table(argv)
        self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table)
        self.parser.load_command_table(command_table)
        self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser)

        if not argv:
            enable_autocomplete(self.parser)
            az_subparser = self.parser.subparsers[tuple()]
            _help.show_welcome(az_subparser)

            # TODO: Question, is this needed?
            telemetry.set_command_details('az')
            telemetry.set_success(summary='welcome')

            return None

        if argv[0].lower() == 'help':
            argv[0] = '--help'

        # Rudimentary parsing to get the command
        nouns = []
        for i, current in enumerate(argv):
            try:
                if current[0] == '-':
                    break
            except IndexError:
                pass
            argv[i] = current.lower()
            nouns.append(argv[i])

        command = ' '.join(nouns)

        if argv[-1] in ('--help', '-h') or command in command_table:
            self.configuration.load_params(command)
            self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table)
            self.parser.load_command_table(command_table)

        if self.session['completer_active']:
            enable_autocomplete(self.parser)

        self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv)
        args = self.parser.parse_args(argv)

        self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args)
        results = []
        for expanded_arg in _explode_list_args(args):
            self.session['command'] = expanded_arg.command
            try:
                _validate_arguments(expanded_arg)
            except CLIError:
                raise
            except:  # pylint: disable=bare-except
                err = sys.exc_info()[1]
                getattr(expanded_arg, '_parser', self.parser).validation_error(str(err))

            # Consider - we are using any args that start with an underscore (_) as 'private'
            # arguments and remove them from the arguments that we pass to the actual function.
            # This does not feel quite right.
            params = dict([(key, value)
                           for key, value in expanded_arg.__dict__.items()
                           if not key.startswith('_')])
            params.pop('subcommand', None)
            params.pop('func', None)
            params.pop('command', None)

            telemetry.set_command_details(expanded_arg.command,
                                          self.configuration.output_format,
                                          [p for p in unexpanded_argv if p.startswith('-')])

            result = expanded_arg.func(params)
            result = todict(result)
            results.append(result)

        if len(results) == 1:
            results = results[0]

        event_data = {'result': results}
        self.raise_event(self.TRANSFORM_RESULT, event_data=event_data)
        self.raise_event(self.FILTER_RESULT, event_data=event_data)

        return CommandResultItem(event_data['result'],
                                 table_transformer=command_table[args.command].table_transformer,
                                 is_query_active=self.session['query_active'])

    def raise_event(self, name, **kwargs):
        '''Raise the event `name`.
        '''
        data = truncate_text(str(kwargs), width=500)
        logger.debug("Application event '%s' with event data %s", name, data)
        for func in list(self._event_handlers[name]):  # Make copy in case handler modifies the list
            func(**kwargs)

    def register(self, name, handler):
        '''Register a callable that will be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        '''
        self._event_handlers[name].append(handler)
        logger.debug("Registered application event handler '%s' at %s", name, handler)

    def remove(self, name, handler):
        '''Remove a callable that is registered to be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        '''
        self._event_handlers[name].remove(handler)
        logger.debug("Removed application event handler '%s' at %s", name, handler)

    def refresh_request_id(self):
        """Assign a new randome GUID as x-ms-client-request-id

        The method must be invoked before each command execution in order to ensure unique client side request ID is
        generated.
        """
        self.session['headers']['x-ms-client-request-id'] = str(uuid.uuid1())

    @staticmethod
    def _register_builtin_arguments(**kwargs):
        global_group = kwargs['global_group']
        global_group.add_argument('--output', '-o', dest='_output_format',
                                  choices=['json', 'tsv', 'table', 'jsonc'],
                                  default=az_config.get('core', 'output', fallback='json'),
                                  help='Output format',
                                  type=str.lower)
        # The arguments for verbosity don't get parsed by argparse but we add it here for help.
        global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true',
                                  help='Increase logging verbosity. Use --debug for full debug logs.')
        global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true',
                                  help='Increase logging verbosity to show all debug logs.')

    @staticmethod
    def _maybe_load_file(arg):
        ix = arg.find('@')
        if ix == -1:  # no @ found
            return arg

        poss_file = arg[ix + 1:]
        if not poss_file:  # if nothing after @ then it can't be a file
            return arg
        elif ix == 0:
            return Application._load_file(poss_file)

        # if @ not at the start it can't be a file
        return arg

    @staticmethod
    def _expand_file_prefix(arg):
        arg_split = arg.split('=', 1)
        try:
            return '='.join([arg_split[0], Application._maybe_load_file(arg_split[1])])
        except IndexError:
            return Application._maybe_load_file(arg_split[0])

    @staticmethod
    def _expand_file_prefixed_files(argv):
        return list([Application._expand_file_prefix(arg) for arg in argv])

    @staticmethod
    def _load_file(path):
        if path == '-':
            content = sys.stdin.read()
        else:
            content = read_file_content(os.path.expanduser(path),
                                        allow_binary=True)

        return content[0:-1] if content and content[-1] == '\n' else content

    def _handle_builtin_arguments(self, **kwargs):
        args = kwargs['args']
        self.configuration.output_format = args._output_format  # pylint: disable=protected-access
        del args._output_format
Esempio n. 19
0
class Application(object):

    TRANSFORM_RESULT = "Application.TransformResults"
    FILTER_RESULT = "Application.FilterResults"
    GLOBAL_PARSER_CREATED = "GlobalParser.Created"
    COMMAND_PARSER_LOADED = "CommandParser.Loaded"
    COMMAND_PARSER_PARSED = "CommandParser.Parsed"
    COMMAND_TABLE_LOADED = "CommandTable.Loaded"
    COMMAND_TABLE_PARAMS_LOADED = "CommandTableParams.Loaded"

    def __init__(self, config=None):
        self._event_handlers = defaultdict(lambda: [])
        self.session = {
            "headers": {"x-ms-client-request-id": str(uuid.uuid1())},
            "command": "unknown",
            "completer_active": ARGCOMPLETE_ENV_NAME in os.environ,
            "query_active": False,
        }

        # Register presence of and handlers for global parameters
        self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments)
        self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments)

        # Let other extensions make their presence known
        azure.cli.core.extensions.register_extensions(self)

        self.global_parser = AzCliCommandParser(prog="az", add_help=False)
        global_group = self.global_parser.add_argument_group("global", "Global Arguments")
        self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group)

        self.parser = AzCliCommandParser(prog="az", parents=[self.global_parser])

        self.initialize(config or Configuration([]))

    def initialize(self, configuration):
        self.configuration = configuration

    def execute(self, unexpanded_argv):
        argv = Application._expand_file_prefixed_files(unexpanded_argv)
        command_table = self.configuration.get_command_table()
        self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table)
        self.parser.load_command_table(command_table)
        self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser)

        if len(argv) == 0:
            enable_autocomplete(self.parser)
            az_subparser = self.parser.subparsers[tuple()]
            _help.show_welcome(az_subparser)
            log_telemetry("welcome")
            return None

        if argv[0].lower() == "help":
            argv[0] = "--help"

        # Rudimentary parsing to get the command
        nouns = []
        for noun in argv:
            if noun[0] == "-":
                break
            nouns.append(noun)
        command = " ".join(nouns)

        if argv[-1] in ("--help", "-h") or command in command_table:
            self.configuration.load_params(command)
            self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table)
            self.parser.load_command_table(command_table)

        if self.session["completer_active"]:
            enable_autocomplete(self.parser)

        args = self.parser.parse_args(argv)

        self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args)
        results = []
        for expanded_arg in _explode_list_args(args):
            self.session["command"] = expanded_arg.command
            try:
                _validate_arguments(expanded_arg)
            except CLIError:
                raise
            except:  # pylint: disable=bare-except
                err = sys.exc_info()[1]
                getattr(expanded_arg, "_parser", self.parser).validation_error(str(err))

            # Consider - we are using any args that start with an underscore (_) as 'private'
            # arguments and remove them from the arguments that we pass to the actual function.
            # This does not feel quite right.
            params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith("_")])
            params.pop("subcommand", None)
            params.pop("func", None)
            params.pop("command", None)
            log_telemetry(
                expanded_arg.command,
                log_type="pageview",
                output_type=self.configuration.output_format,
                parameters=[p for p in unexpanded_argv if p.startswith("-")],
            )

            result = expanded_arg.func(params)
            result = todict(result)
            results.append(result)

        if len(results) == 1:
            results = results[0]

        event_data = {"result": results}
        self.raise_event(self.TRANSFORM_RESULT, event_data=event_data)
        self.raise_event(self.FILTER_RESULT, event_data=event_data)
        return CommandResultItem(
            event_data["result"],
            table_transformer=command_table[args.command].table_transformer,
            is_query_active=self.session["query_active"],
        )

    def raise_event(self, name, **kwargs):
        """Raise the event `name`.
        """
        logger.info("Application event '%s' with event data %s", name, kwargs)
        for func in list(self._event_handlers[name]):  # Make copy in case handler modifies the list
            func(**kwargs)

    def register(self, name, handler):
        """Register a callable that will be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        """
        self._event_handlers[name].append(handler)
        logger.info("Registered application event handler '%s' at %s", name, handler)

    def remove(self, name, handler):
        """Remove a callable that is registered to be called when the
        event `name` is raised.

        param: name: The name of the event
        param: handler: Function that takes two parameters;
          name: name of the event raised
          event_data: `dict` with event specific data.
        """
        self._event_handlers[name].remove(handler)
        logger.info("Removed application event handler '%s' at %s", name, handler)

    @staticmethod
    def _register_builtin_arguments(**kwargs):
        global_group = kwargs["global_group"]
        global_group.add_argument(
            "--output",
            "-o",
            dest="_output_format",
            choices=["json", "tsv", "list", "table", "jsonc"],
            default=az_config.get("core", "output", fallback="json"),
            help="Output format",
            type=str.lower,
        )
        # The arguments for verbosity don't get parsed by argparse but we add it here for help.
        global_group.add_argument(
            "--verbose",
            dest="_log_verbosity_verbose",
            action="store_true",
            help="Increase logging verbosity. Use --debug for full debug logs.",
        )  # pylint: disable=line-too-long
        global_group.add_argument(
            "--debug",
            dest="_log_verbosity_debug",
            action="store_true",
            help="Increase logging verbosity to show all debug logs.",
        )

    @staticmethod
    def _expand_file_prefixed_files(argv):
        return list([Application._load_file(arg[1:]) if arg.startswith("@") else arg for arg in argv])

    @staticmethod
    def _load_file(path):
        try:
            if path == "-":
                content = sys.stdin.read()
            else:
                with open(path, "r") as input_file:
                    content = input_file.read()

            return content[0:-1] if content[-1] == "\n" else content
        except:
            raise CLIError("Failed to open file {}".format(path))

    def _handle_builtin_arguments(self, **kwargs):
        args = kwargs["args"]
        self.configuration.output_format = args._output_format  # pylint: disable=protected-access
        del args._output_format