diff --git a/src/spring-cloud/azext_spring_cloud/_enterprise.py b/src/spring-cloud/azext_spring_cloud/_enterprise.py index 018d44192c3..cb6cc9e3d45 100644 --- a/src/spring-cloud/azext_spring_cloud/_enterprise.py +++ b/src/spring-cloud/azext_spring_cloud/_enterprise.py @@ -10,6 +10,7 @@ create_service_registry, create_gateway, create_api_portal) +from .buildpack_binding import create_default_buildpack_binding_for_application_insights from .custom import (app_get, _create_service) from azure.cli.core.commands.client_factory import get_subscription_id from azure.cli.core.util import sdk_no_wait @@ -58,14 +59,16 @@ def spring_cloud_create(cmd, client, resource_group, name, location=None, create_application_configuration_service(cmd, client, resource_group, name, enable_application_configuration_service), create_service_registry(cmd, client, resource_group, name, enable_service_registry), create_gateway(cmd, client, resource_group, name, enable_gateway), - create_api_portal(cmd, client, resource_group, name, enable_api_portal)] + create_api_portal(cmd, client, resource_group, name, enable_api_portal), + create_default_buildpack_binding_for_application_insights(cmd, client, resource_group, name, location, app_insights_key, + app_insights, sampling_rate, disable_app_insights) + ] pollers = [x for x in pollers if x] if not no_wait: _wait_till_end(cmd, *pollers) return resource - def app_create_enterprise(cmd, client, resource_group, service, name, assign_endpoint=None, cpu=None, diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index abaeddb08b7..232421467d8 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -694,45 +694,53 @@ helps['spring-cloud build-service buildpacks-binding'] = """ type: group - short-summary: (Enterprise Tier Only) Commands to manage Buildpacks Binding + short-summary: (Enterprise Tier Only) Commands to manage buildpack Binding """ -helps['spring-cloud build-service buildpacks-binding create'] = """ +helps['spring-cloud build-service builder buildpack-binding create'] = """ type: command - short-summary: Create a buildpacks binding. + short-summary: Create a buildpack binding. examples: - - name: Create a buildpacks binding without properties or secrets. - text: az spring-cloud build-service buildpacks-binding create --name first-binding --type ApplicationInsights - - name: Create a buildpacks binding with only secrets. - text: az spring-cloud build-service buildpacks-binding create --name first-binding --type ApplicationInsights --secrets k1=v1 k2=v2 - - name: Create a buildpacks binding with only properties. - text: az spring-cloud build-service buildpacks-binding create --name first-binding --type ApplicationInsights --properties a=b c=d - - name: Create a buildpacks binding with properties and secrets. - text: az spring-cloud build-service buildpacks-binding create --name first-binding --type ApplicationInsights --properties a=b c=d --secrets k1=v1 k2=v2 + - name: Create a buildpack binding without properties or secrets. + text: az spring-cloud build-service builder buildpack-binding create --name first-binding --builder-name first-builder --type ApplicationInsights + - name: Create a buildpack binding with only secrets. + text: az spring-cloud build-service builder buildpack-binding create --name first-binding --builder-name first-builder --type ApplicationInsights --secrets k1=v1 k2=v2 + - name: Create a buildpack binding with only properties. + text: az spring-cloud build-service builder buildpack-binding create --name first-binding --builder-name first-builder --type ApplicationInsights --properties a=b c=d + - name: Create a buildpack binding with properties and secrets. + text: az spring-cloud build-service builder buildpack-binding create --name first-binding --builder-name first-builder --type ApplicationInsights --properties a=b c=d --secrets k1=v1 k2=v2 """ -helps['spring-cloud build-service buildpacks-binding set'] = """ +helps['spring-cloud build-service builder buildpack-binding set'] = """ type: command - short-summary: Set a buildpacks binding. + short-summary: Set a buildpack binding. examples: - - name: Set a buildpacks binding with properties and secrets. - text: az spring-cloud build-service buildpacks-binding set --name first-binding --type ApplicationInsights --properties a=b c=d --secrets k1=v1 k2=v2 + - name: Set a buildpack binding with properties and secrets. + text: az spring-cloud build-service builder buildpack-binding set --name first-binding --builder-name first-builder --type ApplicationInsights --properties a=b c=d --secrets k1=v1 k2=v2 """ -helps['spring-cloud build-service buildpacks-binding show'] = """ +helps['spring-cloud build-service builder buildpack-binding show'] = """ type: command - short-summary: Show a buildpacks binding. The secrets will be masked. + short-summary: Show a buildpack binding. The secrets will be masked. examples: - - name: Show a buildpacks binding. - text: az spring-cloud build-service buildpacks-binding show --name first-binding + - name: Show a buildpack binding. + text: az spring-cloud build-service builder buildpack-binding show --name first-binding --builder-name first-builder """ -helps['spring-cloud build-service buildpacks-binding delete'] = """ +helps['spring-cloud build-service builder buildpack-binding list'] = """ type: command - short-summary: Delete a buildpacks binding. + short-summary: List all buildpack binding in a builder. The secrets will be masked. examples: - - name: Delete a buildpacks binding. - text: az spring-cloud build-service buildpacks-binding delete --name first-binding + - name: Show a buildpack binding. + text: az spring-cloud build-service builder buildpack-binding list --builder-name first-builder +""" + +helps['spring-cloud build-service builder buildpack-binding delete'] = """ + type: command + short-summary: Delete a buildpack binding. + examples: + - name: Delete a buildpack binding. + text: az spring-cloud build-service builder buildpack-binding delete --name first-binding --builder-name first-builder """ helps['spring-cloud gateway'] = """ diff --git a/src/spring-cloud/azext_spring_cloud/_params.py b/src/spring-cloud/azext_spring_cloud/_params.py index fba78cd28eb..ef1af285e78 100644 --- a/src/spring-cloud/azext_spring_cloud/_params.py +++ b/src/spring-cloud/azext_spring_cloud/_params.py @@ -15,9 +15,9 @@ validate_app_insights_parameters, validate_instance_count, validate_java_agent_parameters, validate_jar) from ._validators_enterprise import (validate_config_file_patterns, validate_cpu, validate_memory, - validate_buildpacks_binding_properties, - validate_buildpacks_binding_secrets, only_support_enterprise, - validate_buildpacks_binding_not_exist, validate_buildpacks_binding_exist, + validate_buildpack_binding_properties, + validate_buildpack_binding_secrets, only_support_enterprise, + validate_buildpack_binding_not_exist, validate_buildpack_binding_exist, validate_git_uri, validate_acs_patterns, validate_routes, validate_builder, validate_build_pool_size, validate_builder_resource, validate_builder_create, validate_builder_update) @@ -539,34 +539,55 @@ def prepare_logs_argument(c): with self.argument_context(scope) as c: c.argument('name', type=str, help="The builder name.") - for scope in ['spring-cloud build-service buildpacks-binding create', - 'spring-cloud build-service buildpacks-binding set']: + # BuildpackBinding in Builder + for scope in ['spring-cloud build-service builder buildpack-binding create']: with self.argument_context(scope) as c: c.argument('type', arg_type=get_enum_type(v20220101_preview_AppPlatformEnums.BindingType), - help='Required type for buildpacks binding.') + help='Required type for buildpack binding.') c.argument('properties', help='Non-sensitive properties for launchProperties. Format "key[=value]".', nargs='*', - validator=validate_buildpacks_binding_properties) + validator=validate_buildpack_binding_properties) c.argument('secrets', help='Sensitive properties for launchProperties. ' 'Once put, it will be encrypted and never return to user. ' 'Format "key[=value]".', nargs='*', - validator=validate_buildpacks_binding_secrets) + validator=validate_buildpack_binding_secrets) + c.argument('name', help='Name for buildpack binding.', validator=validate_buildpack_binding_not_exist) + c.argument('builder_name', help='The name for builder.', default="default") + c.argument('service', service_name_type, validator=only_support_enterprise) + - for scope in ['spring-cloud build-service buildpacks-binding create']: + for scope in ['spring-cloud build-service builder buildpack-binding set']: with self.argument_context(scope) as c: - c.argument('name', help='Name for buildpacks binding.', validator=validate_buildpacks_binding_not_exist) + c.argument('type', + arg_type=get_enum_type(v20220101_preview_AppPlatformEnums.BindingType), + help='Required type for buildpack binding.') + c.argument('properties', + help='Non-sensitive properties for launchProperties. Format "key[=value]".', + nargs='*', + validator=validate_buildpack_binding_properties) + c.argument('secrets', + help='Sensitive properties for launchProperties. ' + 'Once put, it will be encrypted and never return to user. ' + 'Format "key[=value]".', + nargs='*', + validator=validate_buildpack_binding_secrets) + c.argument('name', help='Name for buildpack binding.', validator=validate_buildpack_binding_exist) + c.argument('builder_name', help='The name for builder.', default="default") + c.argument('service', service_name_type, validator=only_support_enterprise) + - for scope in ['spring-cloud build-service buildpacks-binding set']: + for scope in ['spring-cloud build-service builder buildpack-binding show', + 'spring-cloud build-service builder buildpack-binding delete']: with self.argument_context(scope) as c: - c.argument('name', help='Name for buildpacks binding.', validator=validate_buildpacks_binding_exist) + c.argument('name', help='Name for buildpack binding.', validator=validate_buildpack_binding_exist) + c.argument('builder_name', help='The name for builder.', default="default") + c.argument('service', service_name_type, validator=only_support_enterprise) - for scope in ['spring-cloud build-service buildpacks-binding create', - 'spring-cloud build-service buildpacks-binding set', - 'spring-cloud build-service buildpacks-binding show', - 'spring-cloud build-service buildpacks-binding delete']: + for scope in ['spring-cloud build-service builder buildpack-binding list']: with self.argument_context(scope) as c: + c.argument('builder_name', help='The name for builder.', default="default") c.argument('service', service_name_type, validator=only_support_enterprise) diff --git a/src/spring-cloud/azext_spring_cloud/_validators_enterprise.py b/src/spring-cloud/azext_spring_cloud/_validators_enterprise.py index c5165561b4d..7eaf896f6b6 100644 --- a/src/spring-cloud/azext_spring_cloud/_validators_enterprise.py +++ b/src/spring-cloud/azext_spring_cloud/_validators_enterprise.py @@ -82,7 +82,7 @@ def _is_valid_app_and_profile_name(pattern): return len(parts) == 2 and _is_valid_app_name(parts[0]) and _is_valid_profile_name(parts[1]) -def validate_buildpacks_binding_properties(namespace): +def validate_buildpack_binding_properties(namespace): """ Extracts multiple space-separated properties in key[=value] format """ if isinstance(namespace.properties, list): properties_dict = {} @@ -91,7 +91,7 @@ def validate_buildpacks_binding_properties(namespace): namespace.properties = properties_dict -def validate_buildpacks_binding_secrets(namespace): +def validate_buildpack_binding_secrets(namespace): """ Extracts multiple space-separated secrets in key[=value] format """ if isinstance(namespace.secrets, list): secrets_dict = {} @@ -100,28 +100,29 @@ def validate_buildpacks_binding_secrets(namespace): namespace.secrets = secrets_dict -def validate_buildpacks_binding_not_exist(cmd, namespace): +def validate_buildpack_binding_not_exist(cmd, namespace): client = get_client(cmd) try: - binding_resource = client.buildpacks_binding.get(namespace.resource_group, + binding_resource = client.buildpack_binding.get(namespace.resource_group, namespace.service, DEFAULT_BUILD_SERVICE_NAME, + namespace.builder_name, namespace.name) if binding_resource is not None: - raise CLIError('Buildpacks Binding {} already exists ' + raise CLIError('buildpack Binding {} in builder {} already exists ' 'in resource group {}, service {}. You can edit it by set command.' - .format(namespace.name, namespace.resource_group, namespace.service)) + .format(namespace.name, namespace.resource_group, namespace.service, namespace.builder_name)) except ResourceNotFoundError: # Excepted case pass - -def validate_buildpacks_binding_exist(cmd, namespace): +def validate_buildpack_binding_exist(cmd, namespace): client = get_client(cmd) # If not exists exception will be raised - client.buildpacks_binding.get(namespace.resource_group, + client.buildpack_binding.get(namespace.resource_group, namespace.service, DEFAULT_BUILD_SERVICE_NAME, + namespace.builder_name, namespace.name) diff --git a/src/spring-cloud/azext_spring_cloud/buildpack_binding.py b/src/spring-cloud/azext_spring_cloud/buildpack_binding.py new file mode 100644 index 00000000000..f8f3138800f --- /dev/null +++ b/src/spring-cloud/azext_spring_cloud/buildpack_binding.py @@ -0,0 +1,173 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# pylint: disable=wrong-import-order +from .vendored_sdks.appplatform.v2022_01_01_preview import models +from azure.cli.core.util import sdk_no_wait +from ._utils import get_portal_uri +from .custom import (DEFAULT_BUILD_SERVICE_NAME) +from msrestazure.tools import parse_resource_id, is_valid_resource_id +from azure.cli.core.commands.client_factory import get_mgmt_service_client +from azure.cli.core.azclierror import InvalidArgumentValueError +from azure.mgmt.applicationinsights import ApplicationInsightsManagementClient +from azure.core.exceptions import ResourceNotFoundError +from knack.log import get_logger + +logger = get_logger(__name__) + +DEFAULT_BUILDER_NAME = "default" +DEFAULT_BINDING_NAME = "default" + +def create_or_update_buildpack_binding(cmd, client, resource_group, service, + name, type, builder_name=None, properties=None, secrets=None): + logger.warning('[1/1] Creating or updating to buildpack binding for builder "{}", (this operation can take a while to complete).'.format(builder_name)) + + binding_resource = _build_buildpack_binding_resource(type, properties, secrets) + return sdk_no_wait(False, client.buildpack_binding.begin_create_or_update, resource_group, + service, + DEFAULT_BUILD_SERVICE_NAME, + builder_name, + name, + binding_resource) + + +def buildpack_binding_show(cmd, client, resource_group, service, name, builder_name=None): + return client.buildpack_binding.get(resource_group, + service, + DEFAULT_BUILD_SERVICE_NAME, + builder_name, + name) + + +def buildpack_binding_list(cmd, client, resource_group, service, builder_name=None): + return client.buildpack_binding.list(resource_group, + service, + DEFAULT_BUILD_SERVICE_NAME, + builder_name) + + +def buildpack_binding_delete(cmd, client, resource_group, service, name, builder_name=None): + logger.warning('[1/1] Deleting buildpack binding for builder "{}", (this operation can take a while to complete).'.format(builder_name)) + + return sdk_no_wait(False, client.buildpack_binding.begin_delete, resource_group, + service, + DEFAULT_BUILD_SERVICE_NAME, + builder_name, + name) + + +def create_default_buildpack_binding_for_application_insights(cmd, client, resource_group, name, location, + app_insights_key, app_insights, sampling_rate, + disable_app_insights): + if not disable_app_insights: + logger.warning("Start configure Application Insights") + binding_resource = models.BuildpackBindingResource() + binding_resource.properties = _get_buildpack_binding_properties(cmd, resource_group, name, location, app_insights_key, app_insights, sampling_rate) + + if binding_resource.properties: + return client.buildpack_binding.begin_create_or_update(resource_group, name, + DEFAULT_BUILD_SERVICE_NAME, + DEFAULT_BUILDER_NAME, + DEFAULT_BINDING_NAME, + binding_resource) + +def _build_buildpack_binding_resource(binding_type, properties_dict, secrets_dict): + launch_properties = models.BuildpackBindingLaunchProperties(properties=properties_dict, + secrets=secrets_dict) + binding_properties = models.BuildpackBindingProperties(binding_type=binding_type, + launch_properties=launch_properties) + return models.BuildpackBindingResource(properties=binding_properties) + + +def _get_buildpack_binding_properties(cmd, resource_group, service_name, location, + app_insights_key, app_insights, sampling_rate): + + sampling_rate = sampling_rate or 10 + connection_string = app_insights_key or \ + _get_connection_string_from_app_insights(cmd, resource_group, app_insights) or \ + _create_app_insights_and_get_connection_string(cmd, resource_group, service_name, location) + + if not connection_string: + raise InvalidArgumentValueError('Error while trying to get the ConnectionString of Application Insights for the Azure Spring Cloud. ' + 'Please use the Azure Portal to create and configure the Application Insights, if needed.') + + launch_properties = models.BuildpackBindingLaunchProperties(properties={ + "connection-string": connection_string, + "sampling-percentage": sampling_rate, + }) + + return models.BuildpackBindingProperties(binding_type="ApplicationInsights", launch_properties=launch_properties) + +def _create_app_insights_and_get_connection_string(cmd, resource_group, service_name, location): + try: + created_app_insights = _try_create_application_insights(cmd, resource_group, service_name, location) + if created_app_insights: + return created_app_insights.connection_string + except Exception: # pylint: disable=broad-except + logger.warning( + 'Error while trying to create and configure an Application Insights for the Azure Spring Cloud. ' + 'Please use the Azure Portal to create and configure the Application Insights, if needed.') + return None + +def _get_connection_string_from_app_insights(cmd, resource_group, app_insights): + """Get connection string from: + 1) application insights name + 2) application insights resource id + """ + + if not app_insights: + return None + + if is_valid_resource_id(app_insights): + resource_id_dict = parse_resource_id(app_insights) + connection_string = _get_app_insights_connection_string( + cmd.cli_ctx, resource_id_dict['resource_group'], resource_id_dict['resource_name']) + else: + connection_string = _get_app_insights_connection_string(cmd.cli_ctx, resource_group, app_insights) + + if not connection_string: + logger.warning( + "Cannot find Connection string from application insights:{}".format(app_insights)) + + return connection_string + +def _get_app_insights_connection_string(cli_ctx, resource_group, name): + appinsights_client = get_mgmt_service_client(cli_ctx, ApplicationInsightsManagementClient) + appinsights = appinsights_client.components.get(resource_group, name) + if not appinsights or not appinsights.connection_string: + raise ResourceNotFoundError("App Insights {} under resource group {} was not found." + .format(name, resource_group)) + return appinsights.connection_string + +def _try_create_application_insights(cmd, resource_group, name, location): + creation_failed_warn = 'Unable to create the Application Insights for the Azure Spring Cloud. ' \ + 'Please use the Azure Portal to manually create and configure the Application Insights, ' \ + 'if needed.' + + ai_resource_group_name = resource_group + ai_name = name + ai_location = location + + app_insights_client = get_mgmt_service_client(cmd.cli_ctx, ApplicationInsightsManagementClient) + ai_properties = { + "name": ai_name, + "location": ai_location, + "kind": "web", + "properties": { + "Application_Type": "web" + } + } + appinsights = app_insights_client.components.create_or_update(ai_resource_group_name, ai_name, ai_properties) + if not appinsights or not appinsights.connection_string: + logger.warning(creation_failed_warn) + return None + + portal_url = get_portal_uri(cmd.cli_ctx) + # We make this success message as a warning to no interfere with regular JSON output in stdout + logger.warning('Application Insights \"%s\" was created for this Azure Spring Cloud. ' + 'You can visit %s/#resource%s/overview to view your ' + 'Application Insights component', appinsights.name, portal_url, appinsights.id) + + return appinsights diff --git a/src/spring-cloud/azext_spring_cloud/buildpacks_binding.py b/src/spring-cloud/azext_spring_cloud/buildpacks_binding.py deleted file mode 100644 index 3df525bee76..00000000000 --- a/src/spring-cloud/azext_spring_cloud/buildpacks_binding.py +++ /dev/null @@ -1,34 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -# pylint: disable=wrong-import-order -from ._enterprise import DEFAULT_BUILD_SERVICE_NAME -from .vendored_sdks.appplatform.v2022_01_01_preview import models - - -def create_or_update_buildpacks_binding(cmd, client, resource_group, service, - name, type, properties=None, secrets=None): - binding_resource = _build_buildpacks_binding_resource(type, properties, secrets) - return client.buildpacks_binding.create_or_update(resource_group, - service, - DEFAULT_BUILD_SERVICE_NAME, - name, - binding_resource) - - -def buildpacks_binding_show(cmd, client, resource_group, service, name): - return client.buildpacks_binding.get(resource_group, service, DEFAULT_BUILD_SERVICE_NAME, name) - - -def buildpacks_binding_delete(cmd, client, resource_group, service, name): - return client.buildpacks_binding.delete(resource_group, service, DEFAULT_BUILD_SERVICE_NAME, name) - - -def _build_buildpacks_binding_resource(binding_type, properties_dict, secrets_dict): - launch_properties = models.BuildpacksBindingLaunchProperties(properties=properties_dict, - secrets=secrets_dict) - binding_properties = models.BuildpacksBindingProperties(binding_type=binding_type, - launch_properties=launch_properties) - return models.BuildpacksBindingResource(properties=binding_properties) diff --git a/src/spring-cloud/azext_spring_cloud/commands.py b/src/spring-cloud/azext_spring_cloud/commands.py index a5268e6b33b..0b618bc6737 100644 --- a/src/spring-cloud/azext_spring_cloud/commands.py +++ b/src/spring-cloud/azext_spring_cloud/commands.py @@ -80,8 +80,8 @@ def load_command_table(self, _): client_factory=cf_spring_cloud_enterprise ) - buildpacks_binding_cmd_group = CliCommandType( - operations_tmpl="azext_spring_cloud.buildpacks_binding#{}", + buildpack_binding_cmd_group = CliCommandType( + operations_tmpl="azext_spring_cloud.buildpack_binding#{}", client_factory=cf_spring_cloud_enterprise ) @@ -298,14 +298,16 @@ def load_command_table(self, _): g.custom_command('show', 'builder_show') g.custom_command('delete', 'builder_delete') - with self.command_group('spring-cloud build-service buildpacks-binding', - custom_command_type=buildpacks_binding_cmd_group, - exception_handler=handle_asc_exception) as g: + + with self.command_group('spring-cloud build-service builder buildpack-binding', + custom_command_type=buildpack_binding_cmd_group, + exception_handler=handle_asc_exception, is_preview=True) as g: # create and set commands are differentiate by their parameter validators - g.custom_command('create', 'create_or_update_buildpacks_binding') - g.custom_command('set', 'create_or_update_buildpacks_binding') - g.custom_command('show', 'buildpacks_binding_show') - g.custom_command('delete', 'buildpacks_binding_delete') + g.custom_command('create', 'create_or_update_buildpack_binding') + g.custom_command('set', 'create_or_update_buildpack_binding') + g.custom_command('show', 'buildpack_binding_show') + g.custom_command('list', 'buildpack_binding_list') + g.custom_command('delete', 'buildpack_binding_delete') with self.command_group('spring-cloud', exception_handler=handle_asc_exception): pass