Skip to content
Merged
32 changes: 30 additions & 2 deletions src/azure-cli/azure/cli/command_modules/appconfig/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
text: az appconfig kv delete -n MyAppConfiguration --key color --label MyLabel --yes
- name: Delete a key using connection string.
text: az appconfig kv delete --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --key color --label MyLabel
- name: Delete a key using your 'az login' credentials and App Configuration endpoint.
text: az appconfig kv delete --endpoint https://myappconfiguration.azconfig.io --key color --auth-mode login --yes
"""

helps['appconfig kv export'] = """
Expand All @@ -114,13 +116,15 @@
- name: Export all keys and feature flags with label test to a json file.
text: az appconfig kv export -n MyAppConfiguration --label test -d file --path D:/abc.json --format json
- name: Export all keys with null label to an App Service application.
text: az appconfig kv export -n MyAppConfiguration -d appservice --appservice-account MyAppService
text: az appconfig kv export -n MyAppConfiguration -d appservice --appservice-account MyAppService
- name: Export all keys with label test excluding feature flags to a json file.
text: az appconfig kv export -n MyAppConfiguration --label test -d file --path D:/abc.json --format json --skip-features
- name: Export all keys and feature flags with all labels to another App Configuration.
text: az appconfig kv export -n MyAppConfiguration -d appconfig --dest-name AnotherAppConfiguration --key * --label * --preserve-labels
- name: Export all keys and feature flags with all labels to another App Configuration and overwrite destination labels.
text: az appconfig kv export -n MyAppConfiguration -d appconfig --dest-name AnotherAppConfiguration --key * --label * --dest-label ExportedKeys
- name: Export all keys to another App Configuration using your 'az login' credentials.
text: az appconfig kv export -d appconfig --endpoint https://myappconfiguration.azconfig.io --auth-mode login --dest-endpoint https://anotherappconfiguration.azconfig.io --dest-auth-mode login --key * --label * --preserve-labels
"""

helps['appconfig kv import'] = """
Expand All @@ -139,6 +143,9 @@
text: az appconfig kv import -n MyAppConfiguration -s appconfig --src-name AnotherAppConfiguration --src-key * --src-label * --preserve-labels
- name: Import all keys and feature flags from a JSON file and apply JSON content type.
text: az appconfig kv import -n MyAppConfiguration -s file --path D:/abc.json --format json --separator . --content-type application/json
- name: Import all keys to another App Configuration using your 'az login' credentials.
text: az appconfig kv import -s appconfig --endpoint https://myappconfiguration.azconfig.io --auth-mode login --src-endpoint https://anotherappconfiguration.azconfig.io --src-auth-mode login --src-key * --src-label * --preserve-labels

"""

helps['appconfig kv list'] = """
Expand All @@ -155,6 +162,8 @@
text: az appconfig kv list -n MyAppConfiguration --key "KVRef_*" --resolve-keyvault --query "[*].{key:key, value:value}"
- name: List key-values with multiple labels.
text: az appconfig kv list --label test,prod,\\0 -n MyAppConfiguration
- name: List all key-values with all labels using your 'az login' credentials.
text: az appconfig kv list --endpoint https://myappconfiguration.azconfig.io --auth-mode login
"""

helps['appconfig kv lock'] = """
Expand Down Expand Up @@ -191,6 +200,8 @@
text: az appconfig kv set -n MyAppConfiguration --key options --value [1,2,3] --content-type application/activity+json;charset=utf-8
- name: Set a key with null value and JSON content type.
text: az appconfig kv set -n MyAppConfiguration --key foo --value null --content-type application/json
- name: Set a key-value using your 'az login' credentials.
text: az appconfig kv set --endpoint https://myappconfiguration.azconfig.io --key color --value red --auth-mode login
"""

helps['appconfig kv set-keyvault'] = """
Expand All @@ -211,6 +222,8 @@
text: az appconfig kv show -n MyAppConfiguration --key color --label MyLabel --datetime "2019-05-01T11:24:12Z"
- name: Show a key-value using connection string with label
text: az appconfig kv show --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --key color --label MyLabel
- name: Show a key-value using your 'az login' credentials.
text: az appconfig kv show --key color --auth-mode login --endpoint https://myappconfiguration.azconfig.io
"""

helps['appconfig kv unlock'] = """
Expand Down Expand Up @@ -289,6 +302,9 @@
- name: Set a feature flag with null label using connection string and set a description.
text:
az appconfig feature set --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --description "This is a colorful feature"
- name: Set a feature flag using your 'az login' credentials.
text:
az appconfig feature set --endpoint https://myappconfiguration.azconfig.io --feature color --label MyLabel --auth-mode login
"""

helps['appconfig feature delete'] = """
Expand All @@ -301,6 +317,9 @@
- name: Delete a feature using connection string.
text:
az appconfig feature delete --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --label MyLabel
- name: Delete a feature using App Configuration endpoint and your 'az login' credentials.
text:
az appconfig feature delete --endpoint https://myappconfiguration.azconfig.io --feature color --auth-mode login
"""

helps['appconfig feature show'] = """
Expand All @@ -313,6 +332,9 @@
- name: Show a feature flag using connection string and field filters
text:
az appconfig feature show --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --fields key locked conditions state
- name: Show a feature flag using App Configuration endpoint and your 'az login' credentials.
text:
az appconfig feature show --endpoint https://myappconfiguration.azconfig.io --feature color --auth-mode login
"""

helps['appconfig feature list'] = """
Expand Down Expand Up @@ -402,9 +424,12 @@
- name: Insert a filter at index 2 (zero-based index) for feature 'color' with label MyLabel and filter name 'MyFilter' with no parameters
text:
az appconfig feature filter add -n MyAppConfiguration --feature color --label MyLabel --filter-name MyFilter --index 2
- name: Add a filter with name 'MyFilter' using connection string.
- name: Add a filter with name 'MyFilter' using connection string.
text:
az appconfig feature filter add --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --filter-name MyFilter
- name: Add a filter with name 'MyFilter' using App Configuration endpoint and your 'az login' credentials.
text:
az appconfig feature filter add --endpoint=https://contoso.azconfig.io --feature color --filter-name MyFilter --auth-mode login
"""

helps['appconfig feature filter delete'] = """
Expand Down Expand Up @@ -444,4 +469,7 @@
- name: List 150 filters for feature flag 'color'
text:
az appconfig feature filter list --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --top 150
- name: List all filters for feature flag 'color' using your 'az login' credentials.
text:
az appconfig feature filter list --endpoint https://myappconfiguration.azconfig.io --feature color --all --auth-mode login
"""
15 changes: 14 additions & 1 deletion src/azure-cli/azure/cli/command_modules/appconfig/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
validate_feature_query_fields, validate_filter_parameters,
validate_separator, validate_secret_identifier,
validate_key, validate_feature,
validate_identity,
validate_identity, validate_auth_mode,
validate_resolve_keyvault)


Expand Down Expand Up @@ -72,6 +72,13 @@ def load_arguments(self, _):
c.argument('all_', options_list=['--all'], action='store_true', help="List all items.")
c.argument('fields', arg_type=fields_arg_type)
c.argument('sku', help='The sku of App Configuration', arg_type=get_enum_type(['Free', 'Standard']))
c.argument('endpoint', help='If auth mode is "login", provide endpoint URL of the App Configuration. The endpoint can be retrieved using "az appconfig show" command. You can configure the default endpoint using `az configure --defaults appconfig_endpoint=<endpoint>`', configured_default='appconfig_endpoint')
c.argument('auth_mode', arg_type=get_enum_type(['login', 'key']), configured_default='appconfig_auth_mode', validator=validate_auth_mode,
help='This parameter can be used for indicating how a data operation is to be authorized. ' +
'If the auth mode is "key", provide connection string or store name and your account access keys will be retrieved for authorization. ' +
'If the auth mode is "login", provide the store endpoint or store name and your "az login" credentials will be used for authorization. ' +
'You can configure the default auth mode using `az configure --defaults appconfig_auth_mode=<auth_mode>`. ' +
'For more information, see https://docs.microsoft.com/en-us/azure/azure-app-configuration/concept-enable-rbac')

with self.argument_context('appconfig create') as c:
c.argument('location', options_list=['--location', '-l'], arg_type=get_location_type(self.cli_ctx), validator=get_default_location_from_resource_group)
Expand Down Expand Up @@ -120,6 +127,9 @@ def load_arguments(self, _):
c.argument('src_key', help='If no key specified, import all keys by default. Support star sign as filters, for instance abc* means keys with abc as prefix. Key filtering not applicable for feature flags. By default, all feature flags with specified label will be imported.')
c.argument('src_label', help="Only keys with this label in source AppConfig will be imported. If no value specified, import keys with null label by default. Support star sign as filters, for instance * means all labels, abc* means labels with abc as prefix.")
c.argument('preserve_labels', arg_type=get_three_state_flag(), help="Flag to preserve labels from source AppConfig. This argument should NOT be specified along with --label.")
c.argument('src_endpoint', help='If --src-auth-mode is "login", provide endpoint URL of the source App Configuration.')
c.argument('src_auth_mode', arg_type=get_enum_type(['login', 'key']),
help='Auth mode for connecting to source App Configuration. For details, refer to "--auth-mode" argument.')

with self.argument_context('appconfig kv import', arg_group='AppService') as c:
c.argument('appservice_account', validator=validate_appservice_name_or_id, help='ARM ID for AppService OR the name of the AppService, assuming it is in the same subscription and resource group as the App Configuration. Required for AppService arguments')
Expand Down Expand Up @@ -147,6 +157,9 @@ def load_arguments(self, _):
c.argument('dest_connection_string', validator=validate_connection_string, help="Combination of access key and endpoint of the destination store.")
c.argument('dest_label', help="Exported KVs will be labeled with this destination label. If neither --dest-label nor --preserve-labels is specified, will assign null label.")
c.argument('preserve_labels', arg_type=get_three_state_flag(), help="Flag to preserve labels from source AppConfig. This argument should NOT be specified along with --dest-label.")
c.argument('dest_endpoint', help='If --dest-auth-mode is "login", provide endpoint URL of the destination App Configuration.')
c.argument('dest_auth_mode', arg_type=get_enum_type(['login', 'key']),
help='Auth mode for connecting to destination App Configuration. For details, refer to "--auth-mode" argument.')

with self.argument_context('appconfig kv export', arg_group='AppService') as c:
c.argument('appservice_account', validator=validate_appservice_name_or_id, help='ARM ID for AppService OR the name of the AppService, assuming it is in the same subscription and resource group as the App Configuration. Required for AppService arguments')
Expand Down
78 changes: 60 additions & 18 deletions src/azure-cli/azure/cli/command_modules/appconfig/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,48 @@
# --------------------------------------------------------------------------------------------

# pylint: disable=line-too-long
from knack.log import get_logger
from knack.prompting import NoTTYException, prompt_y_n
from knack.util import CLIError
from azure.appconfiguration import AzureAppConfigurationClient
from azure.mgmt.appconfiguration.models import ErrorException

from ._client_factory import cf_configstore
from ._constants import HttpHeaders

logger = get_logger(__name__)


def construct_connection_string(cmd, config_store_name):
connection_string_template = 'Endpoint={};Id={};Secret={}'
# If the logged in user/Service Principal does not have 'Reader' or 'Contributor' role
# assigned for the requested AppConfig, resolve_store_metadata will raise CLI error
resource_group_name, endpoint = resolve_store_metadata(cmd, config_store_name)

try:
resource_group_name, endpoint = resolve_resource_group(
cmd, config_store_name)
config_store_client = cf_configstore(cmd.cli_ctx)
access_keys = config_store_client.list_keys(
resource_group_name, config_store_name)
access_keys = config_store_client.list_keys(resource_group_name, config_store_name)
for entry in access_keys:
if not entry.read_only:
return connection_string_template.format(endpoint, entry.id, entry.value)
except Exception:
raise CLIError(
'Cannot find the App Configuration {}. Check if it exists in the subscription that logged in. '.format(config_store_name))
except ErrorException as ex:
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)))

raise CLIError('Cannot find a read write access key for the App Configuration {}'.format(
config_store_name))
raise CLIError('Cannot find a read write access key for the App Configuration {}'.format(config_store_name))


def resolve_resource_group(cmd, config_store_name):
config_store_client = cf_configstore(cmd.cli_ctx)
all_stores = config_store_client.list()
for store in all_stores:
if store.name.lower() == config_store_name.lower():
# Id has a fixed structure /subscriptions/subscriptionName/resourceGroups/groupName/providers/providerName/configurationStores/storeName"
return store.id.split('/')[4], store.endpoint
raise CLIError(
"App Configuration store: {} does not exist".format(config_store_name))
def resolve_store_metadata(cmd, config_store_name):
try:
config_store_client = cf_configstore(cmd.cli_ctx)
all_stores = config_store_client.list()
for store in all_stores:
if store.name.lower() == config_store_name.lower():
# Id has a fixed structure /subscriptions/subscriptionName/resourceGroups/groupName/providers/providerName/configurationStores/storeName"
return store.id.split('/')[4], store.endpoint
except ErrorException as ex:
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)))

raise CLIError("Failed to find the App Configuration store '{}'.".format(config_store_name))


def user_confirmation(message, yes=False):
Expand Down Expand Up @@ -118,3 +125,38 @@ def prep_null_label_for_url_encoding(label=None):
label = '"{0}"'.format(label)
label = ast.literal_eval(label)
return label


def get_appconfig_data_client(cmd, name, connection_string, auth_mode, endpoint):
azconfig_client = None
if auth_mode == "key":
connection_string = resolve_connection_string(cmd, name, connection_string)
try:
azconfig_client = AzureAppConfigurationClient.from_connection_string(connection_string=connection_string,
user_agent=HttpHeaders.USER_AGENT)
except ValueError as ex:
raise CLIError("Failed to initialize AzureAppConfigurationClient due to an exception: {}".format(str(ex)))

if auth_mode == "login":
if not endpoint:
try:
if name:
_, endpoint = resolve_store_metadata(cmd, name)
else:
raise CLIError("App Configuration endpoint or name should be provided if auth mode is 'login'.")
except Exception as ex:
raise CLIError(str(ex) + "\nYou may be able to resolve this issue by providing App Configuration endpoint instead of name.")

from azure.cli.core._profile import Profile
profile = Profile(cli_ctx=cmd.cli_ctx)
# Due to this bug in get_login_credentials: https://github.com/Azure/azure-cli/issues/15179,
# we need to manage the AAD scope by passing appconfig endpoint as resource
cred, _, _ = profile.get_login_credentials(resource=endpoint)
Comment on lines +152 to +154
Copy link
Member

Choose a reason for hiding this comment

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

#15184 has been merged. You may now safely remove resource=endpoint.

try:
azconfig_client = AzureAppConfigurationClient(credential=cred,
base_url=endpoint,
user_agent=HttpHeaders.USER_AGENT)
except (ValueError, TypeError) as ex:
raise CLIError("Failed to initialize AzureAppConfigurationClient due to an exception: {}".format(str(ex)))

return azconfig_client
Loading