1# -------------------------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for license information. 4# -------------------------------------------------------------------------------------------- 5 6# pylint: disable=line-too-long 7from knack.log import get_logger 8from knack.util import CLIError 9from azure.appconfiguration import AzureAppConfigurationClient 10from azure.core.exceptions import HttpResponseError 11 12from ._client_factory import cf_configstore 13from ._constants import HttpHeaders 14 15logger = get_logger(__name__) 16 17 18def construct_connection_string(cmd, config_store_name): 19 connection_string_template = 'Endpoint={};Id={};Secret={}' 20 # If the logged in user/Service Principal does not have 'Reader' or 'Contributor' role 21 # assigned for the requested AppConfig, resolve_store_metadata will raise CLI error 22 resource_group_name, endpoint = resolve_store_metadata(cmd, config_store_name) 23 24 try: 25 config_store_client = cf_configstore(cmd.cli_ctx) 26 access_keys = config_store_client.list_keys(resource_group_name, config_store_name) 27 for entry in access_keys: 28 if not entry.read_only: 29 return connection_string_template.format(endpoint, entry.id, entry.value) 30 except HttpResponseError as ex: 31 raise CLIError('Failed to get access keys for the App Configuration "{}". Make sure that the account that logged in has sufficient permissions to access the App Configuration store.\n{}'.format(config_store_name, str(ex))) 32 33 raise CLIError('Cannot find a read write access key for the App Configuration {}'.format(config_store_name)) 34 35 36def resolve_store_metadata(cmd, config_store_name): 37 try: 38 config_store_client = cf_configstore(cmd.cli_ctx) 39 all_stores = config_store_client.list() 40 for store in all_stores: 41 if store.name.lower() == config_store_name.lower(): 42 # Id has a fixed structure /subscriptions/subscriptionName/resourceGroups/groupName/providers/providerName/configurationStores/storeName" 43 return store.id.split('/')[4], store.endpoint 44 except HttpResponseError as ex: 45 raise CLIError("Failed to get the list of App Configuration stores for the current user. Make sure that the account that logged in has sufficient permissions to access the App Configuration store.\n{}".format(str(ex))) 46 47 raise CLIError("Failed to find the App Configuration store '{}'.".format(config_store_name)) 48 49 50def resolve_connection_string(cmd, config_store_name=None, connection_string=None): 51 string = '' 52 error_message = '''You may have specified both store name and connection string, which is a conflict. 53Please specify exactly ONE (suggest connection string) in one of the following options:\n 541 pass in App Configuration store name as a parameter\n 552 pass in connection string as a parameter\n 563 preset App Configuration store name using 'az configure --defaults app_configuration_store=xxxx'\n 574 preset connection string using 'az configure --defaults appconfig_connection_string=xxxx'\n 585 preset connection in environment variable like set AZURE_APPCONFIG_CONNECTION_STRING=xxxx''' 59 60 if config_store_name: 61 string = construct_connection_string(cmd, config_store_name) 62 63 if connection_string: 64 if string and ';'.join(sorted(connection_string.split(';'))) != string: 65 raise CLIError(error_message) 66 string = connection_string 67 68 connection_string_env = cmd.cli_ctx.config.get( 69 'appconfig', 'connection_string', None) 70 71 if connection_string_env: 72 if not is_valid_connection_string(connection_string_env): 73 raise CLIError( 74 "The environment variable connection string is invalid. Correct format should be Endpoint=https://example.appconfig.io;Id=xxxxx;Secret=xxxx") 75 76 if string and ';'.join(sorted(connection_string_env.split(';'))) != string: 77 raise CLIError(error_message) 78 string = connection_string_env 79 80 if not string: 81 raise CLIError( 82 'Please specify config store name or connection string(suggested).') 83 return string 84 85 86def is_valid_connection_string(connection_string): 87 if connection_string is not None: 88 segments = connection_string.split(';') 89 if len(segments) != 3: 90 return False 91 92 segments.sort() 93 if segments[0].startswith('Endpoint=') and segments[1].startswith('Id=') and segments[2].startswith('Secret='): 94 return True 95 return False 96 97 98def get_store_name_from_connection_string(connection_string): 99 if is_valid_connection_string(connection_string): 100 segments = dict(seg.split("=", 1) for seg in connection_string.split(";")) 101 endpoint = segments.get("Endpoint") 102 if endpoint: 103 return endpoint.split("//")[1].split('.')[0] 104 return None 105 106 107def prep_label_filter_for_url_encoding(label=None): 108 if label is not None: 109 import ast 110 # ast library requires quotes around string 111 label = '"{0}"'.format(label) 112 label = ast.literal_eval(label) 113 return label 114 115 116def get_appconfig_data_client(cmd, name, connection_string, auth_mode, endpoint): 117 azconfig_client = None 118 if auth_mode == "key": 119 connection_string = resolve_connection_string(cmd, name, connection_string) 120 try: 121 azconfig_client = AzureAppConfigurationClient.from_connection_string(connection_string=connection_string, 122 user_agent=HttpHeaders.USER_AGENT) 123 except ValueError as ex: 124 raise CLIError("Failed to initialize AzureAppConfigurationClient due to an exception: {}".format(str(ex))) 125 126 if auth_mode == "login": 127 if not endpoint: 128 try: 129 if name: 130 _, endpoint = resolve_store_metadata(cmd, name) 131 else: 132 raise CLIError("App Configuration endpoint or name should be provided if auth mode is 'login'.") 133 except Exception as ex: 134 raise CLIError(str(ex) + "\nYou may be able to resolve this issue by providing App Configuration endpoint instead of name.") 135 136 from azure.cli.core._profile import Profile 137 profile = Profile(cli_ctx=cmd.cli_ctx) 138 cred, _, _ = profile.get_login_credentials() 139 try: 140 azconfig_client = AzureAppConfigurationClient(credential=cred, 141 base_url=endpoint, 142 user_agent=HttpHeaders.USER_AGENT) 143 except (ValueError, TypeError) as ex: 144 raise CLIError("Failed to initialize AzureAppConfigurationClient due to an exception: {}".format(str(ex))) 145 146 return azconfig_client 147