def test_prompt_choice_list_question_no_help_string(self, _): a_list = ['red', 'blue', 'yellow', 'green'] with mock.patch('logging.Logger.warning') as mock_log_warn: with self.assertRaises(StopIteration): with mock.patch('knack.prompting._input', side_effect=['?']): prompt_choice_list('What is your favourite color?', a_list) mock_log_warn.assert_called_once_with('Valid values are %s', mock.ANY)
def init_environment(cmd, prompt=True, management_cluster_name=None, resource_group_name=None, location=None): check_prereqs(cmd, install=True) # Create a management cluster if needed use_new_cluster = False pre_prompt = None try: find_management_cluster_retry(cmd) cluster_name = find_cluster_in_current_context() if prompt and not prompt_y_n( f"Do you want to use {cluster_name} as the management cluster?" ): use_new_cluster = True else: return True except ResourceNotFoundError as err: error_msg = err.error_msg if management_cluster_components_missing_matching_expressions( error_msg): choices = [ "Create a new management cluster", "Use default kuberenetes cluster found and install CAPI required components", "Exit" ] msg = "The default kubernetes cluster found is missing required components for a management cluster.\ \nDo you want to:" index_choice = 0 if prompt: index_choice = prompt_choice_list(msg, choices) if index_choice == 0: use_new_cluster = True elif index_choice != 1: return False else: raise UnclassifiedUserFault(err) from err except subprocess.CalledProcessError: pre_prompt = """ No Kubernetes cluster was found using the default configuration. Cluster API needs a "management cluster" to run its components. Learn more from the Cluster API Book: https://cluster-api.sigs.k8s.io/user/concepts.html """ use_new_cluster = True if use_new_cluster and not create_new_management_cluster( cmd, management_cluster_name, resource_group_name, location, pre_prompt_text=pre_prompt, prompt=prompt): return False _create_azure_identity_secret(cmd) _install_capi_provider_components(cmd) return True
def _create_organization(self): self.logger.info( "Starting process to create a new Azure DevOps organization") regions = self.adbp.list_regions() region_names = sorted( [region.display_name for region in regions.value]) self.logger.info( "The region for an Azure DevOps organization is where the organization will be located. " "Try locate it near your other resources and your location") choice_index = prompt_choice_list( 'Please select a region for the new organization: ', region_names) region = [ region for region in regions.value if region.display_name == region_names[choice_index] ][0] while True: organization_name = prompt( "Please enter a name for your new organization: ") new_organization = self.adbp.create_organization( organization_name, region.name) if new_organization.valid is False: self.logger.warning(new_organization.message) self.logger.warning("Note: all names must be globally unique") else: break self.organization_name = new_organization.name
def _create_organization(self): self.logger.info( "Starting process to create a new Azure DevOps organization") regions = self.adbp.list_regions() region_names = sorted( [region.display_name for region in regions.value]) self.logger.info("The region for an Azure DevOps organization is where the organization will be located. Try locate it near your other resources and your location") # pylint: disable=line-too-long choice_index = prompt_choice_list( 'Please select a region for the new organization: ', region_names) region = [ region for region in regions.value if region.display_name == region_names[choice_index] ][0] while True: organization_name = prompt( "Please enter the name of the new organization: ") new_organization = self.adbp.create_organization( organization_name, region.name) if new_organization.valid is False: self.logger.warning(new_organization.message) self.logger.warning("Note: any name must be globally unique") else: break url = "https://dev.azure.com/" + new_organization.name + "/" self.logger.info( "Finished creating the new organization. Click the link to see your new organization: %s", url) self.organization_name = new_organization.name
def resolve_port_or_expose_list(ports, name): if len(ports) > 1: message = f"You have more than one {name} mapping defined in your docker-compose file." message += " Which port would you like to use? " choice_index = prompt_choice_list(message, ports) return ports[choice_index]
def _handle_global_configuration(config): # print location of global configuration print(MSG_GLOBAL_SETTINGS_LOCATION.format(config.config_path)) # set up the config parsers file_config = config.config_parser config_exists = file_config.read([config.config_path]) should_modify_global_config = False if config_exists: # print current config and prompt to allow global config modification _print_cur_configuration(file_config) should_modify_global_config = prompt_y_n(MSG_PROMPT_MANAGE_GLOBAL, default='n') answers['modify_global_prompt'] = should_modify_global_config if not config_exists or should_modify_global_config: # no config exists yet so configure global config or user wants to modify global config output_index = prompt_choice_list(MSG_PROMPT_GLOBAL_OUTPUT, OUTPUT_LIST, default=get_default_from_config(config.config_parser, 'core', 'output', OUTPUT_LIST)) answers['output_type_prompt'] = output_index answers['output_type_options'] = str(OUTPUT_LIST) enable_file_logging = prompt_y_n(MSG_PROMPT_FILE_LOGGING, default='n') allow_telemetry = prompt_y_n(MSG_PROMPT_TELEMETRY, default='y') answers['telemetry_prompt'] = allow_telemetry # save the global config try: config.config_parser.add_section('core') except configparser.DuplicateSectionError: pass try: config.config_parser.add_section('logging') except configparser.DuplicateSectionError: pass config.set_value('core', 'output', OUTPUT_LIST[output_index]['name']) config.set_value('core', 'collect_telemetry', 'yes' if allow_telemetry else 'no') config.set_value('logging', 'enable_log_file', 'yes' if enable_file_logging else 'no')
def _handle_global_configuration(): # print location of global configuration print(MSG_GLOBAL_SETTINGS_LOCATION.format(GLOBAL_CONFIG_PATH)) # set up the config parsers file_config = get_config_parser() config_exists = file_config.read([GLOBAL_CONFIG_PATH]) global_config = get_config_parser() global_config.read(GLOBAL_CONFIG_PATH) should_modify_global_config = False if config_exists: # print current config and prompt to allow global config modification print(MSG_HEADING_CURRENT_CONFIG_INFO) print_current_configuration(file_config) should_modify_global_config = prompt_y_n(MSG_PROMPT_MANAGE_GLOBAL, default='n') answers['modify_global_prompt'] = should_modify_global_config if not config_exists or should_modify_global_config: # no config exists yet so configure global config or user wants to modify global config output_index = prompt_choice_list(MSG_PROMPT_GLOBAL_OUTPUT, OUTPUT_LIST, default=get_default_from_config( global_config, 'core', 'output', OUTPUT_LIST)) answers['output_type_prompt'] = output_index answers['output_type_options'] = str(OUTPUT_LIST) try: from vsts.cli.code.common.git_alias import setup_git_aliases, are_git_aliases_setup if not are_git_aliases_setup(): setup_aliases = prompt_y_n(MSG_PROMPT_GIT_ALIAS, default='y') if setup_aliases: answers['git_aliases'] = True setup_git_aliases() else: answers['git_aliases'] = False else: answers['git_aliases'] = True except (ModuleNotFoundError, CLIError): logging.debug( 'Skipping git alias configuration, because module was not found.' ) enable_file_logging = prompt_y_n(MSG_PROMPT_FILE_LOGGING, default='n') allow_telemetry = prompt_y_n(MSG_PROMPT_TELEMETRY, default='y') answers['telemetry_prompt'] = allow_telemetry # save the global config try: global_config.add_section('core') except configparser.DuplicateSectionError: pass try: global_config.add_section('logging') except configparser.DuplicateSectionError: pass global_config.set('core', 'output', OUTPUT_LIST[output_index]['name']) global_config.set('core', 'collect_telemetry', 'yes' if allow_telemetry else 'no') global_config.set('logging', 'enable_log_file', 'yes' if enable_file_logging else 'no') set_global_config(global_config)
def init_environment(cmd, prompt=True): check_prereqs(cmd, install=True) # Create a management cluster if needed try: find_management_cluster_retry(cmd) except ResourceNotFoundError as err: if str(err) == "No Cluster API installation found": _install_capz_components(cmd) except subprocess.CalledProcessError: if prompt: choices = ["kind - a local Docker container-based cluster", "AKS - a managed cluster in the Azure cloud", "exit - don't create a management cluster"] prompt = """ No Kubernetes cluster was found using the default configuration. Cluster API needs a "management cluster" to run its components. Learn more from the Cluster API Book: https://cluster-api.sigs.k8s.io/user/concepts.html Where do you want to create a management cluster? """ choice_index = prompt_choice_list(prompt, choices) else: choice_index = 0 cluster_name = "capi-manager" if choice_index == 0: check_kind(cmd, install=not prompt) begin_msg = 'Creating local management cluster "{}" with kind'.format(cluster_name) end_msg = '✓ Created local management cluster "{}"'.format(cluster_name) with Spinner(cmd, begin_msg, end_msg): command = ["kind", "create", "cluster", "--name", cluster_name] try: # if --verbose, don't capture stderr stderr = None if is_verbose() else subprocess.STDOUT output = subprocess.check_output(command, universal_newlines=True, stderr=stderr) logger.info("%s returned:\n%s", " ".join(command), output) except subprocess.CalledProcessError as err: raise UnclassifiedUserFault("Couldn't create kind management cluster") from err elif choice_index == 1: with Spinner(cmd, "Creating Azure resource group", "✓ Created Azure resource group"): command = ["az", "group", "create", "-l", "southcentralus", "--name", cluster_name] try: output = subprocess.check_output(command, universal_newlines=True) logger.info("%s returned:\n%s", " ".join(command), output) except subprocess.CalledProcessError as err: raise UnclassifiedUserFault("Couldn't create Azure resource group") from err with Spinner(cmd, "Creating Azure management cluster with AKS", "✓ Created AKS management cluster"): command = ["az", "aks", "create", "-g", cluster_name, "--name", cluster_name] try: output = subprocess.check_output(command, universal_newlines=True) logger.info("%s returned:\n%s", " ".join(command), output) except subprocess.CalledProcessError as err: raise UnclassifiedUserFault("Couldn't create AKS management cluster") from err else: return _install_capz_components(cmd)
def check_scenario(self): if self.repository_name: self.scenario = 'AZURE_DEVOPS' elif self.github_pat or self.github_repository: self.scenario = 'GITHUB_INTEGRATION' else: choice_index = prompt_choice_list( 'Please choose Azure function source code location: ', SUPPORTED_SOURCECODE_LOCATIONS) self.scenario = SUPPORTED_SCENARIOS[choice_index] return self.scenario
def test_prompt_choice_list_question_with_help_string(self, _): a_list = ['red', 'blue', 'yellow', 'green'] with mock.patch('knack.prompting._input', side_effect=['?', '1']): with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout: actual_result = prompt_choice_list( 'What is your favourite color?', a_list, help_string='Your real favourite.') self.assertEqual(0, actual_result) self.assertIn('Your real favourite.', mock_stdout.getvalue())
def _select_project(self): projects = self.adbp.list_projects(self.organization_name) if projects.count > 0: project_names = sorted([project.name for project in projects.value]) choice_index = prompt_choice_list('Please select your project: ', project_names) project = [project for project in projects.value if project.name == project_names[choice_index]][0] self.project_name = project.name else: self.logger.warning("There are no existing projects in this organization. " "You need to create a new project.") self._create_project()
def _select_organization(self): organizations = self.adbp.list_organizations() organization_names = sorted([organization.accountName for organization in organizations.value]) if not organization_names: self.logger.warning("There are no existing organizations, you need to create a new organization.") self._create_organization() self.created_organization = True else: choice_index = prompt_choice_list('Please choose the organization: ', organization_names) organization_match = [organization for organization in organizations.value if organization.accountName == organization_names[choice_index]][0] self.organization_name = organization_match.accountName
def select_version(): supported_versions = ['v1.0', 'beta'] profile = read_profile() selected = prompt_choice_list('Select graph version', supported_versions) selected_version = supported_versions[selected] profile.update({'version': selected_version}) write_profile( profile, error_msg='An error occured while setting the selected version') print(f'Version {selected_version} selected')
def _handle_global_configuration(config, cloud_forbid_telemetry): # print location of global configuration print(MSG_GLOBAL_SETTINGS_LOCATION.format(config.config_path)) # set up the config parsers file_config = configparser.ConfigParser() config_exists = file_config.read([config.config_path]) should_modify_global_config = False if config_exists: # print current config and prompt to allow global config modification _print_cur_configuration(file_config) should_modify_global_config = prompt_y_n(MSG_PROMPT_MANAGE_GLOBAL, default='n') answers['modify_global_prompt'] = should_modify_global_config if not config_exists or should_modify_global_config: # no config exists yet so configure global config or user wants to modify global config with ConfiguredDefaultSetter(config, False): output_index = prompt_choice_list(MSG_PROMPT_GLOBAL_OUTPUT, OUTPUT_LIST, default=get_default_from_config( config, 'core', 'output', OUTPUT_LIST)) answers['output_type_prompt'] = output_index answers['output_type_options'] = str(OUTPUT_LIST) enable_file_logging = prompt_y_n(MSG_PROMPT_FILE_LOGGING, default='n') if cloud_forbid_telemetry: allow_telemetry = False else: allow_telemetry = prompt_y_n(MSG_PROMPT_TELEMETRY, default='y') answers['telemetry_prompt'] = allow_telemetry cache_ttl = None while not cache_ttl: try: cache_ttl = prompt( MSG_PROMPT_CACHE_TTL) or DEFAULT_CACHE_TTL # ensure valid int by casting cache_value = int(cache_ttl) if cache_value < 1: raise ValueError except ValueError: logger.error('TTL must be a positive integer') cache_ttl = None # save the global config config.set_value('core', 'output', OUTPUT_LIST[output_index]['name']) config.set_value('core', 'collect_telemetry', 'yes' if allow_telemetry else 'no') config.set_value('core', 'cache_ttl', cache_ttl) config.set_value('logging', 'enable_log_file', 'yes' if enable_file_logging else 'no')
def process_git_exists(self): self.logger.warning("There is a local git file.") if self.local_git is None: self.logger.warning("1. Chosing the add remote will create a new remote to the build repository but otherwise preserve your local git") # pylint: disable=line-too-long self.logger.warning("2. Chosing the add new repository will delete your local git file and create a new one with a reference to the build repository.") # pylint: disable=line-too-long self.logger.warning("3. Choosing the use existing will use the repository you are referencing to do the build. Only choose use existing if your local git file is referencing an azure repository that you can create a build with.") # pylint: disable=line-too-long command_options = ['AddRemote', 'AddNewRepository', 'UseExisting'] choice_index = prompt_choice_list( 'Please choose the action you would like to take: ', command_options) command = command_options[choice_index] else: command = self.local_git if command == 'AddNewRepository': self.logger.info("Removing your old, adding a new repository.") # https://docs.python.org/3/library/os.html#os.name (if os.name is nt it is windows) if os.name == 'nt': os.system("rmdir /s /q .git") else: os.system("rm -rf .git") self.process_git_doesnt_exist() elif command == 'AddRemote': self.process_remote() else: # default is to try and use the existing azure repo repository_type = self.find_type_repository() self.logger.info( "We have detected that you have a %s type of repository", repository_type) if repository_type == 'azure repos': # Figure out what the repository information is for their current azure repos account lines = (check_output('git remote show origin'.split()) ).decode('utf-8').split('\n') for line in lines: if re.search('Push', line): m = re.search('http.*', line) url = m.group(0) segs = url.split('/') organization_name = segs[2].split('.')[0] project_name = segs[3] repository_name = segs[5] # We don't need to push to it as it is all currently there self.organization_name = organization_name self.project_name = project_name self.repository_name = repository_name else: self.logger.critical("We don't support any other repositories except for azure repos. We cannot setup a build with these repositories.") # pylint: disable=line-too-long exit(1)
def _config_env_public_azure(cli_ctx, _): from adal.adal_error import AdalError from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.cli.core._profile import Profile from azure.cli.core.profiles import ResourceType # Determine if user logged in try: list( get_mgmt_service_client( cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES).resources.list()) except CLIError: # Not logged in login_successful = False while not login_successful: method_index = prompt_choice_list(MSG_PROMPT_LOGIN, LOGIN_METHOD_LIST) answers['login_index'] = method_index answers['login_options'] = str(LOGIN_METHOD_LIST) profile = Profile(cli_ctx=cli_ctx) interactive = False username = None password = None service_principal = None tenant = None if method_index == 0: # device auth interactive = True elif method_index == 1: # username and password username = prompt('Username: '******'Password: '******'Service principal: ') tenant = prompt('Tenant: ') password = prompt_pass(msg='Client secret: ') elif method_index == 3: # skip return try: profile.find_subscriptions_on_login(interactive, username, password, service_principal, tenant) login_successful = True logger.warning('Login successful!') except AdalError as err: logger.error('Login error!') logger.error(err)
def _config_env_public_azure(cli_ctx, _): from adal.adal_error import AdalError from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.mgmt.resource import ResourceManagementClient from azure.cli.core._profile import Profile # Determine if user logged in try: list(get_mgmt_service_client(cli_ctx, ResourceManagementClient).resources.list()) except CLIError: # Not logged in login_successful = False while not login_successful: method_index = prompt_choice_list(MSG_PROMPT_LOGIN, LOGIN_METHOD_LIST) answers['login_index'] = method_index answers['login_options'] = str(LOGIN_METHOD_LIST) profile = Profile(cli_ctx=cli_ctx) interactive = False username = None password = None service_principal = None tenant = None if method_index == 0: # device auth interactive = True elif method_index == 1: # username and password username = prompt('Username: '******'Password: '******'Service principal: ') tenant = prompt('Tenant: ') password = prompt_pass(msg='Client secret: ') elif method_index == 3: # skip return try: profile.find_subscriptions_on_login( interactive, username, password, service_principal, tenant) login_successful = True logger.warning('Login successful!') except AdalError as err: logger.error('Login error!') logger.error(err)
def test_prompt_choice_list_with_name_desc(self, _): a_list = [{ 'name': 'red', 'desc': ' A desc.' }, { 'name': 'blue', 'desc': ' A desc.' }, { 'name': 'yellow', 'desc': ' A desc.' }, { 'name': 'green', 'desc': ' A desc.' }] with mock.patch('knack.prompting._input', return_value='2'): actual_result = prompt_choice_list('What is your favourite color?', a_list) self.assertEqual(1, actual_result)
def _select_functionapp(self): self.logger.info("Retrieving functionapp names.") functionapps = list_function_app(self.cmd) functionapp_names = sorted([functionapp.name for functionapp in functionapps]) if not functionapp_names: raise CLIError("You do not have any existing function apps associated with this account subscription.{ls}" "1. Please make sure you are logged into the right azure account by " "running 'az account show' and checking the user.{ls}" "2. If you are logged in as the right account please check the subscription you are using. " "Run 'az account show' and view the name.{ls}" " If you need to set the subscription run " "'az account set --subscription <subscription name or id>'{ls}" "3. If you do not have a function app please create one".format(ls=os.linesep)) choice_index = prompt_choice_list('Please select the target function app: ', functionapp_names) functionapp = [functionapp for functionapp in functionapps if functionapp.name == functionapp_names[choice_index]][0] self.logger.info("Selected functionapp %s", functionapp.name) return functionapp
def select_cloud(): supported_clouds = cloud_manager.get_clouds() formatted = [] for cloud_name in supported_clouds: cloud = supported_clouds[cloud_name] formatted.append({ 'name': cloud.get('name'), 'desc': f"""Graph Endpoint: {cloud.get('graph_endpoint')} - Azure AD Endpoint: {cloud.get('azure_ad_endpoint')} """ }) selected = prompt_choice_list('Select a cloud \n', formatted) name = formatted[selected].get('name') cloud_manager.set_current_cloud(name) print(f'{name} cloud selected')
def get_environment(flow_rp): """ Prompt for environment if not provided. """ environments_val = flow_rp.get_environments() environments_list = environments_val[_VALUE] environments = { env[_PROPERTIES][_DISPLAY_NAME]: env[_NAME] for env in environments_list } environment_keys = list(environments.keys()) sid = prompt_choice_list('Please select an environment:', environment_keys) environment = environments[environment_keys[sid]] print('Environment selected: {}'.format(environment_keys[sid])) return environment
def get_connector_id(powerapps_rp, environment): """ Select connector id if not provided. """ connectors_val = powerapps_rp.get_all_connectors(environment) connectors_list = connectors_val[_VALUE] custom_connectors = filter(lambda conn: conn[_PROPERTIES][_IS_CUSTOM_API], connectors_list) connectors = { conn[_PROPERTIES][_DISPLAY_NAME] + ' - ' + conn[_PROPERTIES][_CREATED_BY][_DISPLAY_NAME]: conn[_NAME] for conn in custom_connectors } connectors_keys = list(connectors.keys()) sid = prompt_choice_list('Please select a connector:', connectors_keys) connector_id = connectors[connectors_keys[sid]] print('Connector selected: {}'.format(connectors_keys[sid])) return connector_id
def create_new_management_cluster(cmd, cluster_name=None, resource_group_name=None, location=None, pre_prompt_text=None, prompt=True): choices = [ "azure - a management cluster in the Azure cloud", "local - a local Docker container-based management cluster", "exit - don't create a management cluster" ] default_cluster_name = "capi-manager" if prompt: prompt_text = pre_prompt_text if pre_prompt_text else "" prompt_text += """ Where do you want to create a management cluster? """ choice_index = prompt_choice_list(prompt_text, choices) if choice_index != 2 and not cluster_name: cluster_name = get_cluster_name_by_user_prompt( default_cluster_name) else: if not cluster_name: cluster_name = default_cluster_name choice_index = 0 if choice_index == 0: if not create_aks_management_cluster( cmd, cluster_name, resource_group_name, location, yes=not prompt): return False elif choice_index == 1: check_kind(cmd, install=not prompt) begin_msg = f'Creating local management cluster "{cluster_name}" with kind' end_msg = f'✓ Created local management cluster "{cluster_name}"' command = ["kind", "create", "cluster", "--name", cluster_name] try_command_with_spinner(cmd, command, begin_msg, end_msg, "Couldn't create kind management cluster") else: return False return True
def _select_functionapp(self): self.logger.info("Retrieving functionapp names.") functionapps = list_function_app(self.cmd) functionapp_names = sorted( [functionapp.name for functionapp in functionapps]) if len(functionapp_names) < 1: self.logger.critical( "You do not have any existing functionapps associated with this account subscription." ) self.logger.critical("1. Please make sure you are logged into the right azure account by running `az account show` and checking the user.") # pylint: disable=line-too-long self.logger.critical("2. If you are logged in as the right account please check the subscription you are using. Run `az account show` and view the name.") # pylint: disable=line-too-long self.logger.critical(" If you need to set the subscription run `az account set --subscription \"{SUBSCRIPTION_NAME}\"`") # pylint: disable=line-too-long self.logger.critical( "3. If you do not have a functionapp please create one in the portal." ) exit(1) choice_index = prompt_choice_list('Please choose the functionapp: ', functionapp_names) functionapp = [ functionapp for functionapp in functionapps if functionapp.name == functionapp_names[choice_index] ][0] self.logger.info("Selected functionapp %s", functionapp.name) return functionapp
def init_environment(cmd): check_preqreqs(cmd, install=True) # Create a management cluster if needed try: find_management_cluster_retry(cmd.cli_ctx) except ResourceNotFoundError as err: if str(err) == "No CAPZ installation found": _install_capz_components() except subprocess.CalledProcessError: providers = [ 'AKS - a managed cluster in Azure', 'kind - a local docker-based cluster', "exit - don't create a management cluster" ] prompt = """\ No Kubernetes cluster was found using the default configuration. Cluster API needs a "management cluster" to run its components. Learn more from the Cluster API Book: https://cluster-api.sigs.k8s.io/user/concepts.html Where should we create a management cluster? """ choice_index = prompt_choice_list(prompt, providers) random_id = ''.join( random.choices(string.ascii_lowercase + string.digits, k=6)) cluster_name = "capi-manager-" + random_id if choice_index == 0: logger.info("AKS management cluster") cmd = [ "az", "group", "create", "-l", "southcentralus", "--name", cluster_name ] try: output = subprocess.check_output(cmd, universal_newlines=True) logger.info("%s returned:\n%s", " ".join(cmd), output) except subprocess.CalledProcessError as err: raise UnclassifiedUserFault(err) cmd = [ "az", "aks", "create", "-g", cluster_name, "--name", cluster_name ] try: output = subprocess.check_output(cmd, universal_newlines=True) logger.info("%s returned:\n%s", " ".join(cmd), output) except subprocess.CalledProcessError as err: raise UnclassifiedUserFault(err) elif choice_index == 1: logger.info("kind management cluster") # Install kind kind_path = "kind" if not which("kind"): kind_path = install_kind(cmd) cmd = [kind_path, "create", "cluster", "--name", cluster_name] try: output = subprocess.check_output(cmd, universal_newlines=True) logger.info("%s returned:\n%s", " ".join(cmd), output) except subprocess.CalledProcessError as err: raise UnclassifiedUserFault(err) else: return _install_capz_components()
def _check_value(self, action, value): # pylint: disable=too-many-statements, too-many-locals, too-many-branches # Override to customize the error message when a argument is not among the available choices # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: # pylint: disable=too-many-nested-blocks # self.cli_ctx is None when self.prog is beyond 'az', such as 'az iot'. # use cli_ctx from cli_help which is not lost. cli_ctx = self.cli_ctx or (self.cli_help.cli_ctx if self.cli_help else None) caused_by_extension_not_installed = False command_name_inferred = self.prog error_msg = None if not self.command_source: candidates = [] args = self.prog.split() + self._raw_arguments use_dynamic_install = self._get_extension_use_dynamic_install_config( ) if use_dynamic_install != 'no': # Check if the command is from an extension from azure.cli.core.util import roughly_parse_command command_str = roughly_parse_command(args[1:]) ext_name = self._search_in_extension_commands(command_str) # The input command matches the prefix of one or more extension commands if isinstance(ext_name, list): if len(ext_name) > 1: from knack.prompting import prompt_choice_list, NoTTYException try: prompt_msg = "The command requires the latest version of one of the following " \ "extensions. You need to pick one to install:" choice_idx = prompt_choice_list( prompt_msg, ext_name) ext_name = ext_name[choice_idx] use_dynamic_install = 'yes_without_prompt' except NoTTYException: error_msg = "{}{}\nUnable to prompt for selection as no tty available. Please " \ "update or install the extension with 'az extension add --upgrade -n " \ "<extension-name>'.".format(prompt_msg, ext_name) logger.error(error_msg) telemetry.set_user_fault(error_msg) self.exit(2) else: ext_name = ext_name[0] if ext_name: caused_by_extension_not_installed = True telemetry.set_command_details( command_str, parameters=AzCliCommandInvoker. _extract_parameter_names(args), # pylint: disable=protected-access extension_name=ext_name) run_after_extension_installed = self._get_extension_run_after_dynamic_install_config( ) prompt_info = "" if use_dynamic_install == 'yes_without_prompt': logger.warning( 'The command requires the extension %s. ' 'It will be installed first.', ext_name) go_on = True else: from knack.prompting import prompt_y_n, NoTTYException prompt_msg = 'The command requires the extension {}. ' \ 'Do you want to install it now?'.format(ext_name) if run_after_extension_installed: prompt_msg = '{} The command will continue to run after the extension is installed.' \ .format(prompt_msg) NO_PROMPT_CONFIG_MSG = "Run 'az config set extension.use_dynamic_install=" \ "yes_without_prompt' to allow installing extensions without prompt." try: go_on = prompt_y_n(prompt_msg, default='y') if go_on: prompt_info = " with prompt" logger.warning(NO_PROMPT_CONFIG_MSG) except NoTTYException: error_msg = "The command requires the extension {}. " \ "Unable to prompt for extension install confirmation as no tty " \ "available. {}".format(ext_name, NO_PROMPT_CONFIG_MSG) go_on = False if go_on: from azure.cli.core.extension.operations import add_extension add_extension(cli_ctx=cli_ctx, extension_name=ext_name, upgrade=True) if run_after_extension_installed: import subprocess import platform exit_code = subprocess.call( args, shell=platform.system() == 'Windows') error_msg = ( "Extension {} dynamically installed{} and commands will be " "rerun automatically.").format( ext_name, prompt_info) telemetry.set_user_fault(error_msg) self.exit(exit_code) else: with CommandLoggerContext(logger): error_msg = 'Extension {} installed{}. Please rerun your command.' \ .format(ext_name, prompt_info) logger.error(error_msg) telemetry.set_user_fault(error_msg) self.exit(2) else: error_msg = "The command requires the latest version of extension {ext_name}. " \ "To install, run 'az extension add --upgrade -n {ext_name}'.".format( ext_name=ext_name) if not error_msg else error_msg if not error_msg: # parser has no `command_source`, value is part of command itself error_msg = "'{value}' is misspelled or not recognized by the system.".format( value=value) az_error = CommandNotFoundError(error_msg) if not caused_by_extension_not_installed: candidates = difflib.get_close_matches(value, action.choices, cutoff=0.7) if candidates: # use the most likely candidate to replace the misspelled command args_inferred = [ item if item != value else candidates[0] for item in args ] command_name_inferred = ' '.join(args_inferred).split( '-')[0] else: # `command_source` indicates command values have been parsed, value is an argument parameter = action.option_strings[ 0] if action.option_strings else action.dest error_msg = "{prog}: '{value}' is not a valid value for '{param}'.".format( prog=self.prog, value=value, param=parameter) candidates = difflib.get_close_matches(value, action.choices, cutoff=0.7) az_error = InvalidArgumentValueError(error_msg) command_arguments = self._get_failure_recovery_arguments(action) if candidates: az_error.set_recommendation("Did you mean '{}' ?".format( candidates[0])) # recommend a command for user if not caused_by_extension_not_installed: recommender = CommandRecommender(*command_arguments, error_msg, cli_ctx) recommender.set_help_examples( self.get_examples(command_name_inferred)) recommended_command = recommender.recommend_a_command() if recommended_command: az_error.set_recommendation( "Try this: '{}'".format(recommended_command)) # remind user to check extensions if we can not find a command to recommend if isinstance(az_error, CommandNotFoundError) \ and not az_error.recommendations and self.prog == 'az' \ and use_dynamic_install == 'no': az_error.set_recommendation(EXTENSION_REFERENCE) az_error.set_recommendation( OVERVIEW_REFERENCE.format(command=self.prog)) az_error.print_error() az_error.send_telemetry() self.exit(2)
def bind_webapp_postgres(cmd, resource_group, appname, server, database, name=None, client_id=None, client_secret=None, username=None, password=None, authtype='Secret', permission=None): try: if authtype == 'Secret': if not username: username = prompt('Username: '******'Password: '******'{0}_{1}_{2}_{3}_{4}'.format(appname, server, database, int(time.time()), random.randint(10000, 99999)) subscription = get_subscription_id(cmd.cli_ctx) scope = '/subscriptions/{0}/resourceGroups/{1}'.format( subscription, resource_group) source = '{0}/providers/Microsoft.Web/sites/{1}'.format(scope, appname) target = _get_target_id(scope, postgres=server, database=database) succeeded = False for i in range(3): try: result = _bind(cmd, subscription, resource_group, name, source, target, authtype, permission, client_id, client_secret, username, password) except Exception as e: s = str(e) if s.find('\"UnauthorizedResourceAccess\"' ) and authtype == 'Secret': print('Admin username or password error, retry left: {0}'. format(3 - i)) choice_list = [ 're-input the password for the admin user \"{0}\"'. format(username), 'Change the password for the admin user \"{0}\"'. format(username) ] choice = prompt_choice_list( 'Select re-input the admin password or change the admin password', choice_list) if choice == 0: password = prompt_pass(msg='Password: '******'New password: ') _update_postgres_server(target, password) continue else: succeeded = True print(json.dumps(result, indent=2)) break if not succeeded: result = _bind(cmd, subscription, resource_group, name, source, target, authtype, permission, client_id, client_secret, username, password) print(json.dumps(result, indent=2)) except Exception as e: print(e) logger.error(e) sys.exit(1)
def _check_value_in_extensions(cli_ctx, parser, args, no_prompt): # pylint: disable=too-many-statements, too-many-locals """Check if the command args can be found in extension commands. Exit command if the error is caused by an extension not installed. Otherwise return. """ # Check if the command is from an extension from azure.cli.core.util import roughly_parse_command from azure.cli.core.azclierror import NoTTYError exit_code = 2 command_str = roughly_parse_command(args[1:]) allow_prefix_match = args[-1] == '-h' or args[-1] == '--help' ext_name = _search_in_extension_commands(cli_ctx, command_str, allow_prefix_match=allow_prefix_match) # ext_name is a list if the input command matches the prefix of one or more extension commands, # for instance: `az blueprint` when running `az blueprint -h` # ext_name is a str if the input command matches a complete command of an extension, # for instance: `az blueprint create` if isinstance(ext_name, list): if len(ext_name) > 1: from knack.prompting import prompt_choice_list, NoTTYException prompt_msg = "The command requires the latest version of one of the following " \ "extensions. You need to pick one to install:" try: choice_idx = prompt_choice_list(prompt_msg, ext_name) ext_name = ext_name[choice_idx] no_prompt = True except NoTTYException: tty_err_msg = "{}{}\nUnable to prompt for selection as no tty available. Please update or " \ "install the extension with 'az extension add --upgrade -n <extension-name>'." \ .format(prompt_msg, ext_name) az_error = NoTTYError(tty_err_msg) az_error.print_error() az_error.send_telemetry() parser.exit(exit_code) else: ext_name = ext_name[0] if not ext_name: return # If a valid command has parser error, it may be caused by CLI running on a profile that is # not 'latest' and the command is not supported in that profile. If this command exists in an extension, # CLI will try to download the extension and rerun the command. But the parser will fail again and try to # install the extension and rerun the command infinitely. So we need to check if the latest version of the # extension is already installed and return if yes as the error is not caused by extension not installed. from azure.cli.core.extension import get_extension, ExtensionNotInstalledException from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError try: ext = get_extension(ext_name) except ExtensionNotInstalledException: pass else: try: resolve_from_index(ext_name, cur_version=ext.version, cli_ctx=cli_ctx) except NoExtensionCandidatesError: return telemetry.set_command_details(command_str, parameters=AzCliCommandInvoker._extract_parameter_names(args), # pylint: disable=protected-access extension_name=ext_name) run_after_extension_installed = _get_extension_run_after_dynamic_install_config(cli_ctx) prompt_info = "" if no_prompt: logger.warning('The command requires the extension %s. It will be installed first.', ext_name) install_ext = True else: # yes_prompt from knack.prompting import prompt_y_n, NoTTYException prompt_msg = 'The command requires the extension {}. Do you want to install it now?'.format(ext_name) if run_after_extension_installed: prompt_msg = '{} The command will continue to run after the extension is installed.' \ .format(prompt_msg) NO_PROMPT_CONFIG_MSG = "Run 'az config set extension.use_dynamic_install=" \ "yes_without_prompt' to allow installing extensions without prompt." try: install_ext = prompt_y_n(prompt_msg, default='y') if install_ext: prompt_info = " with prompt" logger.warning(NO_PROMPT_CONFIG_MSG) except NoTTYException: tty_err_msg = "The command requires the extension {}. " \ "Unable to prompt for extension install confirmation as no tty " \ "available. {}".format(ext_name, NO_PROMPT_CONFIG_MSG) az_error = NoTTYError(tty_err_msg) az_error.print_error() az_error.send_telemetry() parser.exit(exit_code) print_error = True if install_ext: from azure.cli.core.extension.operations import add_extension add_extension(cli_ctx=cli_ctx, extension_name=ext_name, upgrade=True) if run_after_extension_installed: import subprocess import platform exit_code = subprocess.call(args, shell=platform.system() == 'Windows') # In this case, error msg is for telemetry recording purpose only. # From UX perspective, the command will rerun in subprocess. Whether it succeeds or fails, # mesages will be shown from the subprocess and this process should not print more message to # interrupt that. print_error = False error_msg = ("Extension {} dynamically installed{} and commands will be " "rerun automatically.").format(ext_name, prompt_info) else: error_msg = 'Extension {} installed{}. Please rerun your command.' \ .format(ext_name, prompt_info) else: error_msg = "The command requires the latest version of extension {ext_name}. " \ "To install, run 'az extension add --upgrade -n {ext_name}'.".format( ext_name=ext_name) az_error = CommandNotFoundError(error_msg) if print_error: az_error.print_error() az_error.send_telemetry() parser.exit(exit_code)
def test_prompt_choice_list(self, _): a_list = ['red', 'blue', 'yellow', 'green'] with mock.patch('knack.prompting._input', return_value='3'): actual_result = prompt_choice_list('What is your favourite color?', a_list) self.assertEqual(2, actual_result)
def _prompt_for_parameters(missing_parameters, fail_on_no_tty=True): # pylint: disable=too-many-statements prompt_list = missing_parameters.keys() if isinstance(missing_parameters, OrderedDict) \ else sorted(missing_parameters) result = OrderedDict() no_tty = False for param_name in prompt_list: param = missing_parameters[param_name] param_type = param.get('type', 'string') description = 'Missing description' metadata = param.get('metadata', None) if metadata is not None: description = metadata.get('description', description) allowed_values = param.get('allowedValues', None) prompt_str = "Please provide {} value for '{}' (? for help): ".format( param_type, param_name) while True: if allowed_values is not None: try: ix = prompt_choice_list(prompt_str, allowed_values, help_string=description) result[param_name] = allowed_values[ix] except NoTTYException: result[param_name] = None no_tty = True break elif param_type == 'securestring': try: value = prompt_pass(prompt_str, help_string=description) except NoTTYException: value = None no_tty = True result[param_name] = value break elif param_type == 'int': try: int_value = prompt_int(prompt_str, help_string=description) result[param_name] = int_value except NoTTYException: result[param_name] = 0 no_tty = True break elif param_type == 'bool': try: value = prompt_t_f(prompt_str, help_string=description) result[param_name] = value except NoTTYException: result[param_name] = False no_tty = True break elif param_type in ['object', 'array']: try: value = prompt(prompt_str, help_string=description) except NoTTYException: value = '' no_tty = True if value == '': value = {} if param_type == 'object' else [] else: try: value = shell_safe_json_parse(value) except Exception as ex: # pylint: disable=broad-except logger.error(ex) continue result[param_name] = value break else: try: result[param_name] = prompt(prompt_str, help_string=description) except NoTTYException: result[param_name] = None no_tty = True break if no_tty and fail_on_no_tty: raise NoTTYException return result
def _prompt_for_parameters(missing_parameters, fail_on_no_tty=True): # pylint: disable=too-many-statements prompt_list = missing_parameters.keys() if isinstance(missing_parameters, OrderedDict) \ else sorted(missing_parameters) result = OrderedDict() no_tty = False for param_name in prompt_list: param = missing_parameters[param_name] param_type = param.get('type', 'string') description = 'Missing description' metadata = param.get('metadata', None) if metadata is not None: description = metadata.get('description', description) allowed_values = param.get('allowedValues', None) prompt_str = "Please provide {} value for '{}' (? for help): ".format(param_type, param_name) while True: if allowed_values is not None: try: ix = prompt_choice_list(prompt_str, allowed_values, help_string=description) result[param_name] = allowed_values[ix] except NoTTYException: result[param_name] = None no_tty = True break elif param_type == 'securestring': try: value = prompt_pass(prompt_str, help_string=description) except NoTTYException: value = None no_tty = True result[param_name] = value break elif param_type == 'int': try: int_value = prompt_int(prompt_str, help_string=description) result[param_name] = int_value except NoTTYException: result[param_name] = 0 no_tty = True break elif param_type == 'bool': try: value = prompt_t_f(prompt_str, help_string=description) result[param_name] = value except NoTTYException: result[param_name] = False no_tty = True break elif param_type in ['object', 'array']: try: value = prompt(prompt_str, help_string=description) except NoTTYException: value = '' no_tty = True if value == '': value = {} if param_type == 'object' else [] else: try: value = shell_safe_json_parse(value) except Exception as ex: # pylint: disable=broad-except logger.error(ex) continue result[param_name] = value break else: try: result[param_name] = prompt(prompt_str, help_string=description) except NoTTYException: result[param_name] = None no_tty = True break if no_tty and fail_on_no_tty: raise NoTTYException return result