Пример #1
0
    def test_load_service_data(self, *args):
        # local aliases for brevity
        builtin = FakeFileSystem.builtin_full_path
        customer = FakeFileSystem.customer_full_path

        correct = [['service-1',
                    builtin('service-1', 'service-1-2.yaml')],
                   ['service-4',
                    builtin('service-4', 'service-4-2.yaml')],
                   ['service-2',
                    customer('service-2', 'service-2-1.yaml')],
                   ['service-3',
                    customer('service-3', 'service-3-2.yaml')],
                   ['service-5',
                    customer('service-5', 'service-5-1.yaml')],
                   ['alias-1',
                    builtin('service-1', 'service-1-2.yaml')]]

        with patch.object(YAMLFileLoader, 'load_alias') as mock_load_alias:
            mock_load_alias.side_effect = \
                lambda path: 'alias-1' if 'service-1' in path else None
            loader = Loader()
            for service in correct:
                with patch.object(YAMLFileLoader,
                                  'load_file') as mock_load_file:
                    mock_load_file.side_effect = lambda path: path
                    self.assertEqual(service[1],
                                     loader.load_service_data(service[0]))
                    self.assertEqual(1, mock_load_file.call_count)

        for not_found in ['service-6', 'service-foo']:
            with patch.object(YAMLFileLoader, 'load_file') as mock_load_file:
                with self.assertRaises(ValidationError):
                    loader.load_service_data(not_found)
                self.assertEqual(0, mock_load_file.call_count)
Пример #2
0
    def test_load_json(self, *args):
        cases = [
            [
                lambda path: path == Loader.BUILTIN_DATA_PATH,
                Loader.BUILTIN_DATA_PATH,
                "Loader should use builtin data path."
            ],
            [
                lambda path: path == Loader.CUSTOMER_DATA_PATH,
                Loader.CUSTOMER_DATA_PATH,
                "Loader should use customer data path."
            ],
            [
                lambda path: True, Loader.CUSTOMER_DATA_PATH,
                "Loader should prefer file at customer data path over built-in path."
            ]
        ]

        for case in cases:
            loader = Loader()
            with patch('os.path.isdir') as mock_isdir:
                mock_isdir.side_effect = case[0]
                with patch.object(JSONFileLoader,
                                  'load_file') as mock_load_file:
                    mock_load_file.side_effect = lambda path: path
                    data = loader.load_json('foo.json')
                    self.assertEquals(1, mock_load_file.call_count)
                    self.assertEquals(os.path.join(case[1], 'foo.json'), data,
                                      case[2])
Пример #3
0
    def test_list_available_services(self, *args):
        loader = Loader()
        services = loader.list_available_services()

        self.assertEqual(6, len(services))
        self.assertEqual(len(set(services)), len(services))
        self.assertEqual(
            0,
            len(
                set(services).symmetric_difference([
                    'alias-1', 'service-1', 'service-2', 'service-3',
                    'service-4', 'service-5'
                ])))
Пример #4
0
 def __init__(self):
     self._loader = Loader()
     self._endpoint_creator = EndpointCreator(EndpointResolver())
     self._user_agent_header = self._build_user_agent_header()
     self._response_parser_factory = ResponseParserFactory()
     self._cli_data = self._loader.load_json('cli.json')
     self._retryhandler = self._create_default_retryhandler()
     self._available_services = self._loader.list_available_services()
     self._command_table = self._build_command_table()
     self._argument_table = self._build_argument_table()
     self._client_creator = ClientCreator(self._loader, Context(),
                                          self._endpoint_creator,
                                          self._user_agent_header,
                                          self._response_parser_factory,
                                          self._retryhandler)
Пример #5
0
 def test_load_service_data_repeatable_cached(self, *args):
     with patch.object(YAMLFileLoader, 'load_file'):
         loader = Loader()
     # warm up with both args and kwargs, ensure results are identical
     with patch.object(YAMLFileLoader, 'load_file') as mock_load_file:
         mock_load_file.side_effect = lambda path: path
         data = loader.load_service_data('service-1')
         self.assertEqual(
             data, loader.load_service_data(service_name='service-1'))
         self.assertEqual(2, mock_load_file.call_count)
     # ensure results are repeatable and are coming from cache (not calls to load_file)
     with patch.object(YAMLFileLoader, 'load_file') as mock_load_file:
         mock_load_file.side_effect = lambda path: path
         self.assertEqual(data, loader.load_service_data('service-1'))
         self.assertEqual(
             data, loader.load_service_data(service_name='service-1'))
         self.assertEqual(0, mock_load_file.call_count)
Пример #6
0
class CLIDriver(object):
    def __init__(self):
        self._loader = Loader()
        self._endpoint_creator = EndpointCreator(EndpointResolver())
        self._user_agent_header = self._build_user_agent_header()
        self._response_parser_factory = ResponseParserFactory()
        self._cli_data = self._loader.load_json('cli.json')
        self._retryhandler = self._create_default_retryhandler()
        self._available_services = self._loader.list_available_services()
        self._command_table = self._build_command_table()
        self._argument_table = self._build_argument_table()
        self._client_creator = ClientCreator(self._loader, Context(),
                                             self._endpoint_creator,
                                             self._user_agent_header,
                                             self._response_parser_factory,
                                             self._retryhandler)

    def main(self, args=None):
        if args is None:
            args = sys.argv[1:]
        parser = self._create_parser()
        command_table = self._get_command_table()
        if len(args) == 0 or (len(args) == 1 and args[0] == '--help'):
            args = ['help']
        parsed_args, remaining = parser.parse_known_args(args)
        try:
            self._handle_top_level_args(parsed_args)
            self._warn_for_old_python()
            return command_table[parsed_args.command](self._client_creator,
                                                      remaining, parsed_args)
        except Exception as e:
            LOG.debug("Exception caught in main()", exc_info=True)
            sys.stderr.write("\n")
            sys.stderr.write("%s\n" % six.text_type(e))
            return 255

    def _get_loader(self):
        return self._loader

    def _get_cli_data(self):
        return self._cli_data

    def _get_command_table(self):
        return self._command_table

    def _build_user_agent_header(self):
        return 'CDPCLI/%s Python/%s %s/%s' % (VERSION, platform.python_version(
        ), platform.system(), platform.release())

    def _build_command_table(self):
        commands = OrderedDict()
        services = self._get_available_services()
        for service_name in services:
            commands[service_name] = ServiceCommand(self, service_name)
        ConfigureCommand.add_command(commands)
        return commands

    def _get_argument_table(self):
        return self._argument_table

    def _build_argument_table(self):
        argument_table = OrderedDict()
        cli_data = self._get_cli_data()
        cli_arguments = cli_data.get('options', None)
        for option in cli_arguments:
            option_params = copy_kwargs(cli_arguments[option])
            cli_argument = self._create_cli_argument(option, option_params)
            cli_argument.add_to_arg_table(argument_table)
        return argument_table

    def _get_available_services(self):
        return self._available_services

    def get_service_model(self, service_name):
        service_data = self._loader.load_service_data(service_name)
        return ServiceModel(service_data, service_name=service_name)

    def _create_help_command(self):
        cli_data = self._get_cli_data()

        # We filter service aliases out of the service list at the bottom of the
        # top level help.
        commands = OrderedDict()
        for service_name, command in self._get_command_table().items():
            if not self._loader.is_service_alias(service_name):
                commands[service_name] = command

        return ProviderHelpCommand(commands, self._get_argument_table(),
                                   cli_data.get('description', None),
                                   cli_data.get('synopsis', None),
                                   cli_data.get('help_usage', None))

    def _create_parser(self):
        command_table = self._get_command_table()
        command_table['help'] = self._create_help_command()
        cli_data = self._get_cli_data()
        parser = MainArgParser(command_table, VERSION,
                               cli_data.get('description', None),
                               self._get_argument_table())
        return parser

    def _create_cli_argument(self, option_name, option_params):
        return CustomArgument(option_name,
                              help_text=option_params.get('help', ''),
                              dest=option_params.get('dest'),
                              default=option_params.get('default'),
                              action=option_params.get('action'),
                              required=option_params.get('required'),
                              choices=option_params.get('choices'),
                              hidden=option_params.get('hidden', False))

    def _handle_top_level_args(self, args):
        if args.profile:
            self._client_creator.context.set_config_variable(
                'profile', args.profile)
        if args.auth_config:
            self._client_creator.context.set_config_variable(
                'auth_config', args.auth_config)
        if args.debug:
            self._setup_logger(logging.DEBUG)
            LOG.debug("CLI version: %s", self._user_agent_header)
            LOG.debug("Arguments entered to CLI: %s", sys.argv[1:])
        else:
            self._setup_logger(logging.WARNING)

    def _setup_logger(self, log_level):
        ROOT_LOGGER.setLevel(logging.DEBUG)
        ch = logging.StreamHandler()
        ch.setLevel(log_level)
        formatter = logging.Formatter(LOG_FORMAT)
        ch.setFormatter(formatter)
        ROOT_LOGGER.addHandler(ch)

    def _create_default_retryhandler(self):
        # We create one retryhandler based on the __default__ configuration in
        # the _retry.json (in the 'data' directory). This retryhandler is used
        # by all services.
        config = self._load_retry_config()
        if not config:
            return
        LOG.info("Using retry config: %s" % config)
        return create_retry_handler(config)

    def _load_retry_config(self):
        original_config = self._loader.load_json('_retry.json')
        retry_config = build_retry_config(
            original_config['retry'], original_config.get('definitions', {}))
        return retry_config

    def _warn_for_old_python(self):
        if sys.version_info[0] < 3 or \
                (sys.version_info[0] == 3 and sys.version_info[1] < 5):
            LOG.warn(
                'You are running the CDP CLI under Python %s. The CDP CLI '
                'will require Python 3.5 or higher starting in '
                'January 2021. '
                'Please upgrade now!', sys.version)
Пример #7
0
    def __init__(self, debug=False, tls_verify=False, strict_errors=False, tls_warnings=False, client_endpoint=None,
                 cdp_credentials=None, error_handler=None, warning_handler=None, scrub_inputs=True, cp_region='default',
                 agent_header=None):
        # Init Params
        self.debug = debug
        self.tls_verify = tls_verify
        self.strict_errors = strict_errors
        self.tls_warnings = tls_warnings
        self.client_endpoint = client_endpoint
        self.cdp_credentials = cdp_credentials
        self.scrub_inputs = scrub_inputs
        self.cp_region = cp_region
        self.agent_header = agent_header if agent_header is not None else 'CDPY'

        # Setup
        self.throw_error = error_handler if error_handler else self._default_throw_error
        self.throw_warning = warning_handler if warning_handler else self._default_throw_warning
        self._clients = {}
        self.DEFAULT_PAGE_SIZE = 100

        _loader = Loader()
        _user_agent = self._make_user_agent_header()

        self._client_creator = ClientCreator(
            _loader,
            Context(),
            EndpointCreator(EndpointResolver()),
            _user_agent,
            ResponseParserFactory(),
            create_retry_handler(self._load_retry_config(_loader)))

        # Logging
        _log_format = '%(asctime)s - %(threadName)s - %(name)s - %(levelname)s - %(message)s'
        if debug:
            self._setup_logger(logging.DEBUG, _log_format)
            self.logger.debug("CDP SDK version: %s", _user_agent)
        else:
            self._setup_logger(logging.ERROR, _log_format)

        if self.tls_warnings is False:
            urllib3.disable_warnings(InsecureRequestWarning)

        # Warnings
        def _warning_format(message, category, filename, lineno, line=None):
            return ' %s:%s: %s:%s' % (filename, lineno, category.__name__, message)

        warnings.formatwarning = _warning_format

        # State listings
        # https://github.com/hortonworks/cloudbreak/blob/master/cluster-api/src/main/java/com/sequenceiq/
        #   cloudbreak/cluster/status/ClusterStatus.java#L8-L18
        # https://github.com/hortonworks/cloudbreak/blob/master/core-api/src/main/java/com/sequenceiq/
        #   cloudbreak/api/endpoint/v4/common/Status.java#L14-L53
        self.CREATION_STATES = [
            'REQUESTED',
            'EXTERNAL_DATABASE_CREATION_IN_PROGRESS',
            'STACK_CREATION_IN_PROGRESS',
            'CREATION_INITIATED',
            'FREEIPA_CREATION_IN_PROGRESS',
            'STARTING',
            'ENABLING',  # DF
            'provision:started',  # ML
            'installation:started'  # ML
        ]

        self.TERMINATION_STATES = [
            'EXTERNAL_DATABASE_DELETION_IN_PROGRESS',
            'STACK_DELETION_IN_PROGRESS',
            'FREEIPA_DELETE_IN_PROGRESS',
            'STOPPING',
            'deprovision:started',  # ML
            'DISABLING'  # DF
        ]

        self.STARTED_STATES = [
            'EXTERNAL_DATABASE_START_IN_PROGRESS',
            'AVAILABLE',
            'START_IN_PROGRESS',
            'RUNNING',
            'installation:finished',  # ML
            'Running',  # DW
            'GOOD_HEALTH',  # DF
            'ClusterCreationCompleted' #DE
        ]

        self.STOPPED_STATES = [
            'EXTERNAL_DATABASE_STOP_IN_PROGRESS',
            'STOP_IN_PROGRESS',
            'STOPPED',
            'ENV_STOPPED',
            'Stopped', # DW
            'NOT_ENABLED',  # DF
            'ClusterDeletionCompleted', 'AppDeleted' # DE

        ]

        self.FAILED_STATES = [
            'PROVISIONING_FAILED',
            'CREATE_FAILED',
            'REJECTED',
            'FAILED',
            'TIMEDOUT',
            'DELETE_FAILED',
            'Error',  # DW
            'installation:failed',  # ML
            'provision:failed',  # ML
            'deprovision:failed',  # ML
            'BAD_HEALTH',  # DF
            # DE service (all intermediate failure states, until CDE exposes a higher-level summary state)
            'ClusterChartInstallationFailed', 'ClusterDNSCreationFailed', 'ClusterDNSDeletionFailed',
            'ClusterIngressCreationFailed', 'ClusterProvisioningFailed', 'DBProvisioningFailed',
            'FSMountTargetsCreationFailed', 'FSProvisioningFailed', 'ClusterTLSCertCreationFailed',
            'ClusterServiceMeshProvisioningFailed', 'ClusterMonitoringConfigurationFailed',
            'ClusterChartDeletionFailed', 'ClusterDeletionFailed', 'ClusterNamespaceDeletionFailed',
            'DBDeletionFailed', 'FSMountTargetsDeletionFailed', 'FSDeletionFailed',
            'ClusterTLSCertDeletionFailed', 'ClusterServiceMeshDeletionFailed',
            'ClusterAccessGroupCreationFailed', 'ClusterAccessGroupDeletionFailed',
            'ClusterUserSyncCheckFailed', 'ClusterCreationFailed', 'ClusterDeleteFromDBFailed',
            'ClusterMaintenanceFailed', 'ClusterTLSCertRenewalFailed',
             # DE virtual cluster
             'AppInstallationFailed', 'AppDeletionFailed'
        ]

        self.REMOVABLE_STATES = [
            'AVAILABLE', 'UPDATE_FAILED', 'CREATE_FAILED', 'ENABLE_SECURITY_FAILED', 'DELETE_FAILED',
            'DELETE_COMPLETED', 'DELETED_ON_PROVIDER_SIDE', 'STOPPED', 'START_FAILED', 'STOP_FAILED',
            'installation:failed', 'deprovision:failed', 'installation:finished', 'modify:finished',  # ML
            'Error', 'Running', 'Stopped', 'Deleting',  # DW
            'GOOD_HEALTH', 'CONCERNING_HEALTH', 'BAD_HEALTH',  # DF
            'ClusterCreationCompleted', 'AppInstalled', 'ClusterProvisioningFailed'  #DE
        ]

        # common regex patterns
        self.DATAHUB_NAME_PATTERN = re.compile(r'[^a-z0-9-]')
        self.DATALAKE_NAME_PATTERN = re.compile(r'[^a-z0-9-]')
        self.ENV_NAME_PATTERN = re.compile(r'(^[^a-z0-9]|[^a-z0-9-]|^.{,4}$|^.{29,}$)')
        self.CREDENTIAL_NAME_PATTERN = re.compile(r'[^a-z0-9-]')
        self.OPERATION_REGEX = re.compile(r'operation ([0-9a-zA-Z-]{36}) running')

        # Workload services with special credential and endpoint handling
        self.WORKLOAD_SERVICES = ['dfworkload']

        # substrings to check for in different CRNs
        self.CRN_STRINGS = {
            'generic': ['crn:'],
            'env': [':environments:', ':environment:'],
            'df': [':df:', ':service:'],
            'flow': [':df:', ':flow:'],
            'readyflow': [':df:', 'readyFlow'],
            'deployment': [':df:', ':deployment:']
        }
Пример #8
0
 def test_load_service_data_no_dirs(self, *args):
     with patch.object(YAMLFileLoader, 'load_file') as mock_load_file:
         loader = Loader()
         with self.assertRaises(ValidationError):
             loader.load_service_data('service-1')
         self.assertEquals(0, mock_load_file.call_count)
Пример #9
0
 def test_load_json_no_dirs(self, *args):
     with patch.object(JSONFileLoader, 'load_file') as mock_load_file:
         loader = Loader()
         with self.assertRaises(DataNotFoundError):
             loader.load_json('foo.json')
         self.assertEquals(0, mock_load_file.call_count)
Пример #10
0
 def test_missing_builtin_service_alias(self, *args):
     '''Throws an exception because built-in service-1 is missing'''
     Loader()
Пример #11
0
    def __init__(self,
                 debug=False,
                 tls_verify=False,
                 strict_errors=False,
                 tls_warnings=False,
                 client_endpoint=None,
                 cdp_credentials=None,
                 error_handler=None,
                 warning_handler=None,
                 scrub_inputs=True):
        # Init Params
        self.debug = debug
        self.tls_verify = tls_verify
        self.strict_errors = strict_errors
        self.tls_warnings = tls_warnings
        self.client_endpoint = client_endpoint
        self.cdp_credentials = cdp_credentials
        self.scrub_inputs = scrub_inputs

        # Setup
        self.throw_error = error_handler if error_handler else self._default_throw_error
        self.throw_warning = warning_handler if warning_handler else self._default_throw_warning
        self._clients = {}
        self._PAGE_SIZE = 50

        _loader = Loader()
        _user_agent = self._make_user_agent_header()
        self._client_creator = ClientCreator(
            _loader, Context(), EndpointCreator(EndpointResolver()),
            _user_agent, ResponseParserFactory(),
            create_retry_handler(self._load_retry_config(_loader)))

        # Logging
        _log_format = '%(asctime)s - %(threadName)s - %(name)s - %(levelname)s - %(message)s'
        if debug:
            self._setup_logger(logging.DEBUG, _log_format)
            self.logger.debug("CDP SDK version: %s", _user_agent)
        else:
            self._setup_logger(logging.ERROR, _log_format)

        if self.tls_warnings is False:
            urllib3.disable_warnings(InsecureRequestWarning)

        # Warnings
        def _warning_format(message, category, filename, lineno, line=None):
            return ' %s:%s: %s:%s' % (filename, lineno, category.__name__,
                                      message)

        warnings.formatwarning = _warning_format

        # State listings
        # https://github.com/hortonworks/cloudbreak/blob/master/cluster-api/src/main/java/com/sequenceiq/
        #   cloudbreak/cluster/status/ClusterStatus.java#L8-L18
        # https://github.com/hortonworks/cloudbreak/blob/master/core-api/src/main/java/com/sequenceiq/
        #   cloudbreak/api/endpoint/v4/common/Status.java#L14-L53
        self.CREATION_STATES = [
            'REQUESTED', 'EXTERNAL_DATABASE_CREATION_IN_PROGRESS',
            'STACK_CREATION_IN_PROGRESS', 'CREATION_INITIATED',
            'FREEIPA_CREATION_IN_PROGRESS', 'STARTING'
        ]

        self.TERMINATION_STATES = [
            'EXTERNAL_DATABASE_DELETION_IN_PROGRESS',
            'STACK_DELETION_IN_PROGRESS', 'FREEIPA_DELETE_IN_PROGRESS',
            'STOPPING'
        ]

        self.STARTED_STATES = [
            'EXTERNAL_DATABASE_START_IN_PROGRESS', 'AVAILABLE',
            'START_IN_PROGRESS', 'RUNNING', 'Running'
        ]

        self.STOPPED_STATES = [
            'EXTERNAL_DATABASE_STOP_IN_PROGRESS', 'STOP_IN_PROGRESS',
            'STOPPED', 'ENV_STOPPED'
        ]

        self.FAILED_STATES = [
            'PROVISIONING_FAILED', 'CREATE_FAILED', 'REJECTED', 'FAILED',
            'TIMEDOUT', 'DELETE_FAILED', 'Error'
        ]

        self.REMOVABLE_STATES = [
            'AVAILABLE', 'UPDATE_FAILED', 'CREATE_FAILED',
            'ENABLE_SECURITY_FAILED', 'DELETE_FAILED', 'DELETE_COMPLETED',
            'DELETED_ON_PROVIDER_SIDE', 'STOPPED', 'START_FAILED',
            'STOP_FAILED', 'Error', 'Running'
        ]

        # common regex patterns
        self.DATAHUB_NAME_PATTERN = re.compile(r'[^a-z0-9-]')
        self.DATALAKE_NAME_PATTERN = re.compile(r'[^a-z0-9-]')
        self.ENV_NAME_PATTERN = re.compile(
            r'(^[^a-z0-9]|[^a-z0-9-]|^.{,4}$|^.{29,}$)')
        self.CREDENTIAL_NAME_PATTERN = re.compile(r'[^a-z0-9-]')
        self.OPERATION_REGEX = re.compile(
            r'operation ([0-9a-zA-Z-]{36}) running')
Пример #12
0
class CLIDriver(object):
    def __init__(self):
        self._loader = Loader()
        self._endpoint_creator = EndpointCreator(EndpointResolver())
        self._user_agent_header = self._build_user_agent_header()
        self._response_parser_factory = ResponseParserFactory()
        self._cli_data = self._loader.load_json('cli.json')
        self._retryhandler = self._create_default_retryhandler()
        self._available_services = self._loader.list_available_services()
        self._command_table = self._build_command_table()
        self._argument_table = self._build_argument_table()
        self._context = Context()
        self._client_creator = ClientCreator(self._loader, self._context,
                                             self._endpoint_creator,
                                             self._user_agent_header,
                                             self._response_parser_factory,
                                             self._retryhandler)

    def main(self, args=None):
        if args is None:
            args = sys.argv[1:]
        parser = self._create_parser()
        command_table = self._get_command_table()
        if len(args) == 0 or (len(args) == 1 and args[0] == '--help'):
            args = ['help']
        parsed_args, remaining = parser.parse_known_args(args)
        try:
            self._handle_top_level_args(parsed_args)
            self._filter_command_table_for_form_factor(parsed_args)
            self._warn_for_old_python()
            self._warn_for_non_public_release()
            return command_table[parsed_args.command](self._client_creator,
                                                      remaining, parsed_args)
        except Exception as e:
            LOG.debug("Exception caught in main()", exc_info=True)
            sys.stderr.write("\n")
            sys.stderr.write("%s\n" % six.text_type(e))
            return 255

    def _get_loader(self):
        return self._loader

    def _get_cli_data(self):
        return self._cli_data

    def _get_command_table(self):
        return self._command_table

    def _build_user_agent_header(self):
        return 'CDPCLI/%s Python/%s %s/%s' % (VERSION, platform.python_version(
        ), platform.system(), platform.release())

    def _build_command_table(self):
        commands = OrderedDict()
        services = self._get_available_services()
        for service_name in services:
            commands[service_name] = ServiceCommand(self, service_name)
        ConfigureCommand.add_command(commands)
        LoginCommand.add_command(commands)
        LogoutCommand.add_command(commands)
        RefdocCommand.add_command(commands)
        return commands

    def _filter_command_table_for_form_factor(self, parsed_args):
        """
        Replaces services and operations in the command table that do not apply
        to the current form factor with stubs that error out when called.
        """

        # Find the form factor based on:
        # 1. the form factor explicitly specified by --form-factor, or else
        # 2. the configured form factor, or else
        # 3. the explicit endpoint URL, or else
        # 4. the configured CDP endpoint URL.
        if parsed_args.form_factor:
            form_factor = parsed_args.form_factor
        else:
            try:
                form_factor = self._context.get_scoped_config().get(
                    'form_factor', None)
            except ProfileNotFound:
                form_factor = None
            valid_form_factors = [dt.value for dt in list(DeploymentType)]
            if form_factor and form_factor not in valid_form_factors:
                raise InvalidConfiguredFormFactor(
                    form_factor=form_factor,
                    valid_form_factors=valid_form_factors)
            if not form_factor:
                endpoint_url = parsed_args.endpoint_url
                if not endpoint_url:
                    try:
                        endpoint_url = self._context.get_scoped_config(). \
                            get(EndpointResolver.CDP_ENDPOINT_URL_KEY_NAME, None)
                    except ProfileNotFound:
                        endpoint_url = None
                form_factor = \
                    ClassifyDeployment(endpoint_url).get_deployment_type().value
        LOG.debug("Current form factor is {}".format(form_factor))

        for command in list(self._command_table.keys()):
            try:
                # If the service does not apply to the current form factor,
                # filter it out.
                service_model = self._command_table[command].service_model
                service_form_factors = service_model.form_factors
                if form_factor not in service_form_factors:
                    self._command_table[command] = \
                        FilteredServiceCommand(self, command, form_factor,
                                               service_form_factors)
                else:
                    for operation_name in service_model.operation_names:
                        # If the operation does not apply to the current form
                        # factor, filter it out.
                        operation_model = service_model.operation_model(
                            operation_name)
                        operation_form_factors = operation_model.form_factors
                        if not operation_form_factors:
                            operation_form_factors = service_form_factors
                        if form_factor not in operation_form_factors:
                            self._command_table[command]. \
                                filter_operation(operation_name, form_factor,
                                                 operation_form_factors)

            except AttributeError:
                # not a service model, so available in all form factors
                pass

    def _get_argument_table(self):
        return self._argument_table

    def _build_argument_table(self):
        argument_table = OrderedDict()
        cli_data = self._get_cli_data()
        cli_arguments = cli_data.get('options', None)
        for option in cli_arguments:
            option_params = copy_kwargs(cli_arguments[option])
            cli_argument = self._create_cli_argument(option, option_params)
            cli_argument.add_to_arg_table(argument_table)
        return argument_table

    def _get_available_services(self):
        return self._available_services

    def get_service_model(self, service_name):
        service_data = self._loader.load_service_data(service_name)
        return ServiceModel(service_data, service_name=service_name)

    def _create_help_command(self):
        cli_data = self._get_cli_data()

        # We filter service aliases out of the service list at the bottom of the
        # top level help.
        commands = OrderedDict()
        for service_name, command in self._get_command_table().items():
            if not self._loader.is_service_alias(service_name):
                commands[service_name] = command

        return ProviderHelpCommand(commands, self._get_argument_table(),
                                   cli_data.get('description', None),
                                   cli_data.get('synopsis', None),
                                   cli_data.get('help_usage', None))

    def _create_parser(self):
        command_table = self._get_command_table()
        command_table['help'] = self._create_help_command()
        cli_data = self._get_cli_data()
        parser = MainArgParser(command_table, VERSION,
                               cli_data.get('description', None),
                               self._get_argument_table())
        return parser

    def _create_cli_argument(self, option_name, option_params):
        return CustomArgument(option_name,
                              help_text=option_params.get('help', ''),
                              dest=option_params.get('dest'),
                              default=option_params.get('default'),
                              action=option_params.get('action'),
                              required=option_params.get('required'),
                              choices=option_params.get('choices'),
                              cli_type_name=option_params.get('type'),
                              hidden=option_params.get('hidden', False))

    def _handle_top_level_args(self, args):
        if args.profile:
            self._client_creator.context.set_config_variable(
                'profile', args.profile)
        if args.auth_config:
            self._client_creator.context.set_config_variable(
                'auth_config', args.auth_config)
        if args.debug:
            self._setup_logger(logging.DEBUG)
            LOG.debug("CLI version: %s", self._user_agent_header)
            LOG.debug("Arguments entered to CLI: %s", sys.argv[1:])
        else:
            self._setup_logger(logging.WARNING)

        if args.force_ipv4:
            # Based on SO /a/46972341
            LOG.debug("Forcing IPv4 connections only")

            def _allowed_gai_family():
                return socket.AF_INET

            urllib3_connection.allowed_gai_family = _allowed_gai_family

    def _setup_logger(self, log_level):
        ROOT_LOGGER.setLevel(logging.DEBUG)
        ch = logging.StreamHandler()
        ch.setLevel(log_level)
        formatter = logging.Formatter(LOG_FORMAT)
        ch.setFormatter(formatter)
        ROOT_LOGGER.addHandler(ch)

    def _create_default_retryhandler(self):
        # We create one retryhandler based on the __default__ configuration in
        # the _retry.json (in the 'data' directory). This retryhandler is used
        # by all services.
        config = self._load_retry_config()
        if not config:
            return
        LOG.info("Using retry config: %s" % config)
        return create_retry_handler(config)

    def _load_retry_config(self):
        original_config = self._loader.load_json('_retry.json')
        retry_config = build_retry_config(
            original_config['retry'], original_config.get('definitions', {}))
        return retry_config

    def _warn_for_old_python(self):
        if sys.version_info[0] < 3 or \
                (sys.version_info[0] == 3 and sys.version_info[1] < 6):
            LOG.warn(
                'You are running the CDP CLI under Python %s. The CDP CLI '
                'now requires Python 3.6 or higher. Please upgrade now to '
                'avoid CLI errors.', sys.version)

    def _warn_for_non_public_release(self):
        if RELEASE != 'PUBLIC':
            if RELEASE == 'INTERNAL':
                article = 'an'
            else:
                article = 'a'
            LOG.warn('You are running {0} {1} release of the CDP CLI, which '
                     'has different capabilities from the standard public '
                     'release. Find the public release at: '
                     'https://pypi.org/project/cdpcli/'.format(
                         article, RELEASE))