From d0251dae94e2cb71c81d5ef49ec269393569930d Mon Sep 17 00:00:00 2001 From: ninpan-ms <71061174+ninpan-ms@users.noreply.github.com> Date: Thu, 12 Aug 2021 14:28:40 +0800 Subject: [PATCH] Add ACS related commands (#18) --- src/spring-cloud/azext_spring_cloud/_help.py | 55 +++++ .../azext_spring_cloud/_params.py | 27 +++ .../application_configuration_service.py | 205 ++++++++++++++++++ .../azext_spring_cloud/commands.py | 21 ++ 4 files changed, 308 insertions(+) create mode 100644 src/spring-cloud/azext_spring_cloud/application_configuration_service.py diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index 93efd356177..03711b986ae 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -487,3 +487,58 @@ - name: Unbind an app to Service Registry. text: az spring-cloud service-registry unbind --app MyApp -s MyService -g MyResourceGroup """ + +helps['spring-cloud application-configuration-service show'] = """ + type: command + short-summary: Show provisioning status, runtime status and settings of Application Configuration Service. +""" + +helps['spring-cloud application-configuration-service clear'] = """ + type: command + short-summary: Reset all Application Configuration Service settings. +""" + +helps['spring-cloud application-configuration-service git repo add'] = """ + type: command + short-summary: Add an item of git property to Application Configuration Service settings. + examples: + - name: Add an item of git property. + text: az spring-cloud application-configuration-service git repo add -s MyService -g MyResourceGroup --name MyName --patterns MyPattern --uri https://MyURI --label master +""" + +helps['spring-cloud application-configuration-service git repo update'] = """ + type: command + short-summary: Update an existing item of git property to Application Configuration Service settings. + examples: + - name: Update an item of git property. + text: az spring-cloud application-configuration-service git repo update -s MyService -g MyResourceGroup --name MyName --patterns MyPattern +""" + +helps['spring-cloud application-configuration-service git repo remove'] = """ + type: command + short-summary: Delete an existing item of git property to Application Configuration Service settings. + examples: + - name: Delete an item of git property. + text: az spring-cloud application-configuration-service git repo remove -s MyService -g MyResourceGroup --name MyName +""" + +helps['spring-cloud application-configuration-service git repo list'] = """ + type: command + short-summary: List all git settings of Application Configuration Service. +""" + +helps['spring-cloud application-configuration-service bind'] = """ + type: command + short-summary: Bind app to Application Configuration Service. + examples: + - name: Bind an app to Application Configuration Service. + text: az spring-cloud application-configuration-service bind --app MyApp -s MyService -g MyResourceGroup +""" + +helps['spring-cloud application-configuration-service unbind'] = """ + type: command + short-summary: Unbind app to Application Configuration Service. + examples: + - name: Unbind an app to Application Configuration Service. + text: az spring-cloud application-configuration-service unbind --app MyApp -s MyService -g MyResourceGroup +""" diff --git a/src/spring-cloud/azext_spring_cloud/_params.py b/src/spring-cloud/azext_spring_cloud/_params.py index 4fbcbbb79e4..dbde10e4e04 100644 --- a/src/spring-cloud/azext_spring_cloud/_params.py +++ b/src/spring-cloud/azext_spring_cloud/_params.py @@ -288,3 +288,30 @@ def prepare_logs_argument(c): with self.argument_context('spring-cloud service-registry unbind') as c: c.argument('app', app_name_type, help='Name of app.', validator=validate_app_name) + + with self.argument_context('spring-cloud application-configuration-service bind') as c: + c.argument('app', app_name_type, help='Name of app.', validator=validate_app_name) + + with self.argument_context('spring-cloud application-configuration-service unbind') as c: + c.argument('app', app_name_type, help='Name of app.', validator=validate_app_name) + + for scope in ['spring-cloud application-configuration-service git repo add', + 'spring-cloud application-configuration-service git repo update']: + with self.argument_context(scope) as c: + c.argument('patterns', + help="Required patterns used to search in Git repositories. For each pattern, use format like {application} or {application}/{profile} instead of {application}-{profile}.yml, and separate them by comma."), + c.argument('uri', help="Required Git URI."), + c.argument('label', help="Required branch name to search in the Git repository."), + c.argument('search_paths', help='search_paths of the added config, use , as delimiter for multiple paths.') + c.argument('username', help='Username of the added config.') + c.argument('password', help='Password of the added config.') + c.argument('host_key', help='Host key of the added config.') + c.argument('host_key_algorithm', help='Host key algorithm of the added config.') + c.argument('private_key', help='Private_key of the added config.') + c.argument('strict_host_key_checking', help='Strict_host_key_checking of the added config.') + + for scope in ['spring-cloud application-configuration-service git repo add', + 'spring-cloud application-configuration-service git repo update', + 'spring-cloud application-configuration-service git repo remove']: + with self.argument_context(scope) as c: + c.argument('name', help="Required unique name to label each item of git configs.") diff --git a/src/spring-cloud/azext_spring_cloud/application_configuration_service.py b/src/spring-cloud/azext_spring_cloud/application_configuration_service.py new file mode 100644 index 00000000000..cbaf3021512 --- /dev/null +++ b/src/spring-cloud/azext_spring_cloud/application_configuration_service.py @@ -0,0 +1,205 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# pylint: disable=unused-argument, logging-format-interpolation, protected-access, wrong-import-order, too-many-lines +import json + +from ._enterprise import app_get_enterprise +from ._util_enterprise import (is_enterprise_tier, get_client) +from .vendored_sdks.appplatform.v2022_05_01_preview import models as models +from azure.cli.core.commands import cached_put +from azure.cli.core.util import sdk_no_wait +from knack.log import get_logger +from knack.util import CLIError + +APPLICATION_CONFIGURATION_SERVICE_NAME = "ApplicationConfigurationService" + +logger = get_logger(__name__) + +def application_configuration_service_show(cmd, client, service, resource_group): + return client.configuration_services.get(resource_group, service) + + +def application_configuration_service_clear(cmd, client, service, resource_group): + properties = models.ConfigurationServiceGitProperty() + acs_resource = models.ConfigurationServiceResource(properties=properties) + return client.configuration_services.begin_create_or_update(resource_group, service, acs_resource) + + +def application_configuration_service_git_add(cmd, client, service, resource_group, + name, patterns, uri, label, + search_paths=None, + username=None, + password=None, + host_key=None, + host_key_algorithm=None, + private_key=None, + strict_host_key_checking=None, + no_wait=False): + repo = models.ConfigurationServiceGitRepository(name=name, patterns=patterns, uri=uri, label=label) + repo = _replace_repo_with_input(repo, patterns, uri, label, search_paths, username, password, host_key, host_key_algorithm, private_key, strict_host_key_checking) + + acs_resource = _get_or_default_acs_resource(client, resource_group, service) + repos = acs_resource.properties.settings.git_property.repositories + if next((r for r in repos if r.name == name), None) is not None: + raise CLIError("Repo '{}' already exists.".format(name)) + repos.append(repo) + acs_resource.properties.settings.git_property.repositories = repos + + _validate_acs_settings(client, resource_group, service, acs_resource.properties.settings) + + logger.warning("[2/2] Adding item to Application Configuration Service settings, (this operation can take a while to complete)") + return sdk_no_wait(no_wait, client.configuration_services.begin_create_or_update, resource_group, service, acs_resource) + + +def application_configuration_service_git_update(cmd, client, service, resource_group, name, + patterns=None, + uri=None, + label=None, + search_paths=None, + username=None, + password=None, + host_key=None, + host_key_algorithm=None, + private_key=None, + strict_host_key_checking=None, + no_wait=False): + acs_resource = _get_or_default_acs_resource(client, resource_group, service) + + repo = _get_existing_repo(acs_resource.properties.settings.git_property.repositories, name) + repo = _replace_repo_with_input(repo, patterns, uri, label, search_paths, username, password, host_key, host_key_algorithm, private_key, strict_host_key_checking) + + _validate_acs_settings(client, resource_group, service, acs_resource.properties.settings) + + logger.warning("[2/2] Updating item of Application Configuration Service settings, (this operation can take a while to complete)") + return sdk_no_wait(no_wait, client.configuration_services.begin_create_or_update, resource_group, service, acs_resource) + + +def application_configuration_service_git_remove(cmd, client, service, resource_group, name, no_wait=False): + acs_resource = _get_or_default_acs_resource(acs_resource) + + repo = _get_existing_repo(acs_resource.properties.settings.git_property.repositories, name) + acs_resource.properties.settings.git_property.repositories.remove(repo) + + _validate_acs_settings(client, resource_group, service, acs_resource.properties.settings) + + logger.warning("[2/2] Removing item of Application Configuration Service settings, (this operation can take a while to complete)") + return sdk_no_wait(no_wait, client.configuration_services.begin_create_or_update, resource_group, service, acs_resource) + + +def application_configuration_service_git_list(cmd, client, service, resource_group): + acs_resource = client.configuration_services.get(resource_group, service) + acs_settings = acs_resource.properties.settings + + if not acs_settings or not acs_settings.git_property or not acs_settings.git_property.repositories: + raise CLIError("Repos not found.") + + return acs_settings.git_property.repositories + + +def application_configuration_service_bind(cmd, client, service, resource_group, app): + _acs_bind_or_unbind_app(cmd, client, service, resource_group, app, True) + + +def application_configuration_service_unbind(cmd, client, service, resource_group, app): + _acs_bind_or_unbind_app(cmd, client, service, resource_group, app, False) + + +def _acs_bind_or_unbind_app(cmd, client, service, resource_group, app_name, enabled): + app = client.apps.get(resource_group, service, app_name) + app.properties.addon_configs = _get_app_addon_configs_with_acs(app.properties.addon_configs) + + if app.properties.addon_configs[APPLICATION_CONFIGURATION_SERVICE_NAME].enabled == enabled: + logger.warning('App "{}" has been {}binded'.format(app_name, '' if enabled else 'un')) + return + + app.properties.addon_configs[APPLICATION_CONFIGURATION_SERVICE_NAME].enabled = enabled + return client.apps.begin_update(resource_group, service, app_name, app) + + +def _get_app_addon_configs_with_acs(addon_configs): + if addon_configs is None: + addon_configs = {} + if addon_configs.get(APPLICATION_CONFIGURATION_SERVICE_NAME) is None: + addon_configs[APPLICATION_CONFIGURATION_SERVICE_NAME] = models.AddonProfile() + return addon_configs + + +def _replace_repo_with_input(repo, patterns, uri, label, search_paths, username, password, host_key, host_key_algorithm, private_key, strict_host_key_checking): + if patterns: + patterns = patterns.split(",") + if search_paths: + search_paths = search_paths.split(",") + + repo.patterns = patterns or repo.patterns + repo.uri = uri or repo.uri + repo.label = label or repo.label + repo.search_paths = search_paths or repo.search_paths + repo.username = username or repo.username + repo.password = password or repo.password + repo.host_key = host_key or repo.host_key + repo.host_key_algorithm = host_key_algorithm or repo.host_key_algorithm + repo.private_key = private_key or repo.private_key + repo.strict_host_key_checking = strict_host_key_checking or repo.strict_host_key_checking + return repo + + +def _get_existing_repo(repos, name): + repo = next((r for r in repos if r.name == name), None) + if not repo: + raise CLIError("Repo '{}' not found.".format(name)) + return repo + + +def _get_or_default_acs_resource(client, resource_group, service): + acs_resource = client.configuration_services.get(resource_group, service) + if acs_resource is None: + acs_resource = models.ConfigurationServiceResource() + acs_resource.properties = _get_acs_properties(acs_resource.properties) + return acs_resource + + +def _get_acs_properties(properties): + if properties is None: + properties = models.ConfigurationServiceProperties() + properties.settings = _get_acs_settings(properties.settings) + return properties + + +def _get_acs_settings(acs_settings): + if acs_settings is None: + acs_settings = models.ConfigurationServiceSettings() + acs_settings.git_property = _get_acs_git_property(acs_settings.git_property) + return acs_settings + + +def _get_acs_git_property(git_property): + if git_property is None: + git_property = models.ConfigurationServiceGitProperty() + git_property.repositories = _get_acs_repos(git_property.repositories) + return git_property + + +def _get_acs_repos(repos): + return repos or [] + + +def _validate_acs_settings(client, resource_group, service, acs_settings): + logger.warning("[1/2] Validating Application Configuration Service settings") + + if acs_settings is None or acs_settings.git_property is None: + return + + try: + result = sdk_no_wait(False, client.configuration_services.begin_validate, resource_group, service, acs_settings).result() + except Exception as err: # pylint: disable=broad-except + raise CLIError("{0}. You may raise a support ticket if needed by the following link: https://docs.microsoft.com/azure/spring-cloud/spring-cloud-faq?pivots=programming-language-java#how-can-i-provide-feedback-and-report-issues".format(err)) + + if result is not None and result.git_property_validation_result is not None: + git_result = result.git_property_validation_result + if not git_result.is_valid: + validation_result = git_result.git_repos_validation_result + filter_result = [{'name':x.name, 'messages':x.messages} for x in validation_result if len(x.messages) > 0] + raise CLIError("Application Configuration Service settings contain errors.\n{}".format(json.dumps(filter_result, indent=2))) diff --git a/src/spring-cloud/azext_spring_cloud/commands.py b/src/spring-cloud/azext_spring_cloud/commands.py index e3ac7e5971a..db5bc5bdb78 100644 --- a/src/spring-cloud/azext_spring_cloud/commands.py +++ b/src/spring-cloud/azext_spring_cloud/commands.py @@ -37,6 +37,11 @@ def load_command_table(self, _): client_factory=cf_spring_cloud_enterprise ) + application_configuration_service_cmd_group = CliCommandType( + operations_tmpl='azext_spring_cloud.application_configuration_service#{}', + client_factory=cf_spring_cloud_enterprise + ) + with self.command_group('spring-cloud', client_factory=cf_app_services, exception_handler=handle_asc_exception) as g: g.custom_command('create', 'spring_cloud_create', supports_no_wait=True, client_factory=cf_spring_cloud) @@ -150,5 +155,21 @@ def load_command_table(self, _): g.custom_command('bind', 'service_registry_bind') g.custom_command('unbind', 'service_registry_unbind') + with self.command_group('spring-cloud application-configuration-service', + custom_command_type=application_configuration_service_cmd_group, + exception_handler=handle_asc_exception) as g: + g.custom_command('clear', 'application_configuration_service_clear') + g.custom_command('show', 'application_configuration_service_show') + g.custom_command('bind', 'application_configuration_service_bind') + g.custom_command('unbind', 'application_configuration_service_unbind') + + with self.command_group('spring-cloud application-configuration-service git repo', + custom_command_type=application_configuration_service_cmd_group, + exception_handler=handle_asc_exception) as g: + g.custom_command('add', 'application_configuration_service_git_add') + g.custom_command('update', 'application_configuration_service_git_update') + g.custom_command('remove', 'application_configuration_service_git_remove') + g.custom_command('list', 'application_configuration_service_git_list') + with self.command_group('spring-cloud', exception_handler=handle_asc_exception): pass