-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[containerapp] az containerapp create/update: Support --customized-keys and clientType in --bind for dev service
#6939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
ae8860e
4626893
6a8fb7b
b6f3ebf
cbb40e7
aedbcb9
592ce76
0f43bc2
b173a18
84e3b39
d7eee6b
cdfbc67
82e3796
e9c09d1
d2794a8
6d8fe37
1570179
69689e6
2c06fe8
dbc7040
99a1d3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,7 @@ | |
|
|
||
|
|
||
| def process_service(cmd, resource_list, service_name, arg_dict, subscription_id, resource_group_name, name, | ||
| binding_name, service_connector_def_list, service_bindings_def_list): | ||
| binding_name, service_connector_def_list, service_bindings_def_list, customized_keys=None): | ||
| # Check if the service exists in the list of dict | ||
| for service in resource_list: | ||
| if service["name"] == service_name: | ||
|
|
@@ -74,11 +74,14 @@ def process_service(cmd, resource_list, service_name, arg_dict, subscription_id, | |
|
|
||
| if service_type is None or service_type not in DEV_SERVICE_LIST: | ||
| raise ResourceNotFoundError(f"The service '{service_name}' does not exist") | ||
|
|
||
| service_bindings_def_list.append({ | ||
| service_bind = { | ||
| "serviceId": containerapp_def["id"], | ||
| "name": binding_name | ||
| }) | ||
| "name": binding_name, | ||
| "clientType": arg_dict.get("clientType") | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it is called by update command? If yes, when --client-type not provided, will it is cleared
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if customized_keys: | ||
| service_bind["customizedKeys"] = customized_keys | ||
| service_bindings_def_list.append(service_bind) | ||
|
|
||
| else: | ||
| raise ValidationError("Service not supported") | ||
|
|
@@ -140,7 +143,7 @@ def check_unique_bindings(cmd, service_connectors_def_list, service_bindings_def | |
| return True | ||
|
|
||
|
|
||
| def parse_service_bindings(cmd, service_bindings_list, resource_group_name, name): | ||
| def parse_service_bindings(cmd, service_bindings_list, resource_group_name, name, customized_keys=None): | ||
| # Make it return both managed and dev bindings | ||
| service_bindings_def_list = [] | ||
| service_connector_def_list = [] | ||
|
|
@@ -194,7 +197,7 @@ def parse_service_bindings(cmd, service_bindings_list, resource_group_name, name | |
|
|
||
| # Will work for both create and update | ||
| process_service(cmd, resource_list, service_name, arg_dict, subscription_id, resource_group_name, | ||
| name, binding_name, service_connector_def_list, service_bindings_def_list) | ||
| name, binding_name, service_connector_def_list, service_bindings_def_list, customized_keys) | ||
|
|
||
| return service_connector_def_list, service_bindings_def_list | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # -------------------------------------------------------------------------------------------- | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| import argparse | ||
| from collections import defaultdict | ||
| from azure.cli.core.azclierror import ValidationError | ||
|
|
||
|
|
||
| class AddCustomizedKeys(argparse.Action): | ||
| def __call__(self, parser, namespace, values, option_string=None): | ||
| action = self.get_action(values, option_string) | ||
| namespace.customized_keys = action | ||
|
|
||
| def get_action(self, values, option_string): # pylint: disable=no-self-use | ||
| try: | ||
| properties = defaultdict(list) | ||
| for (k, v) in (x.split('=', 1) for x in values): | ||
| properties[k] = v | ||
| properties = dict(properties) | ||
| return properties | ||
| except ValueError: | ||
| raise ValidationError('Usage error: {} [DesiredKey=DefaultKey ...]'.format(option_string)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,8 @@ | |
| ValidationError, | ||
| ArgumentUsageError, | ||
| ResourceNotFoundError, | ||
| MutuallyExclusiveArgumentError) | ||
| MutuallyExclusiveArgumentError, | ||
| InvalidArgumentValueError) | ||
| from azure.cli.command_modules.containerapp.containerapp_decorator import BaseContainerAppDecorator, ContainerAppCreateDecorator | ||
| from azure.cli.command_modules.containerapp._github_oauth import cache_github_token | ||
| from azure.cli.command_modules.containerapp._utils import (store_as_secret_and_return_secret_ref, parse_env_var_flags, | ||
|
|
@@ -536,8 +537,20 @@ def set_up_update_containerapp_yaml(self, name, file_name): | |
| tags = yaml_containerapp.get('tags') | ||
| del yaml_containerapp['tags'] | ||
|
|
||
| service_binds = safe_get(yaml_containerapp, "properties", "template", "serviceBinds") | ||
|
|
||
Greedygre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Greedygre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| customized_keys_dict = {} | ||
| if service_binds: | ||
| for bind in service_binds: | ||
| customized_keys_dict[bind["name"]] = bind["customizedKeys"] | ||
|
|
||
| self.new_containerapp = _convert_object_from_snake_to_camel_case(_object_to_dict(self.new_containerapp)) | ||
| self.new_containerapp['tags'] = tags | ||
| # Containerapp object to dictionary will lose "properties" level | ||
| service_binds = safe_get(self.new_containerapp, "properties", "template", "serviceBinds") or safe_get(self.new_containerapp, "template", "serviceBinds") | ||
| if service_binds: | ||
| for bind in service_binds: | ||
| bind["customizedKeys"] = customized_keys_dict.get(bind["name"]) | ||
|
|
||
| # After deserializing, some properties may need to be moved under the "properties" attribute. Need this since we're not using SDK | ||
| self.new_containerapp = process_loaded_yaml(self.new_containerapp) | ||
|
|
@@ -584,6 +597,9 @@ def get_argument_service_type(self): | |
| def get_argument_service_bindings(self): | ||
| return self.get_param("service_bindings") | ||
|
|
||
| def get_argument_customized_keys(self): | ||
| return self.get_param("customized_keys") | ||
|
|
||
| def get_argument_service_connectors_def_list(self): | ||
| return self.get_param("service_connectors_def_list") | ||
|
|
||
|
|
@@ -601,6 +617,8 @@ def construct_payload(self): | |
| def validate_arguments(self): | ||
| super().validate_arguments() | ||
| validate_create(self.get_argument_registry_identity(), self.get_argument_registry_pass(), self.get_argument_registry_user(), self.get_argument_registry_server(), self.get_argument_no_wait(), self.get_argument_source(), self.get_argument_artifact(), self.get_argument_repo(), self.get_argument_yaml(), self.get_argument_environment_type()) | ||
| if self.get_argument_service_bindings() and len(self.get_argument_service_bindings()) > 1 and self.get_argument_customized_keys(): | ||
| raise InvalidArgumentValueError("--bind have multiple values, but --customized-keys only can be set when --bind has single value.") | ||
Greedygre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def set_up_source(self): | ||
| from ._up_utils import (_validate_source_artifact_args) | ||
|
|
@@ -768,14 +786,111 @@ def set_up_service_binds(self): | |
| service_connectors_def_list, service_bindings_def_list = parse_service_bindings(self.cmd, | ||
| self.get_argument_service_bindings(), | ||
| self.get_argument_resource_group_name(), | ||
| self.get_argument_name()) | ||
| self.get_argument_name(), | ||
| self.get_argument_customized_keys()) | ||
| self.set_argument_service_connectors_def_list(service_connectors_def_list) | ||
| unique_bindings = check_unique_bindings(self.cmd, service_connectors_def_list, service_bindings_def_list, | ||
| self.get_argument_resource_group_name(), self.get_argument_name()) | ||
| if not unique_bindings: | ||
| raise ValidationError("Binding names across managed and dev services should be unique.") | ||
| safe_set(self.containerapp_def, "properties", "template", "serviceBinds", value=service_bindings_def_list) | ||
|
|
||
| def set_up_create_containerapp_yaml(self, name, file_name): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you support update with --yaml
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we support. |
||
| if self.get_argument_image() or self.get_argument_min_replicas() or self.get_argument_max_replicas() or self.get_argument_target_port() or self.get_argument_ingress() or \ | ||
| self.get_argument_revisions_mode() or self.get_argument_secrets() or self.get_argument_env_vars() or self.get_argument_cpu() or self.get_argument_memory() or self.get_argument_registry_server() or \ | ||
| self.get_argument_registry_user() or self.get_argument_registry_pass() or self.get_argument_dapr_enabled() or self.get_argument_dapr_app_port() or self.get_argument_dapr_app_id() or \ | ||
| self.get_argument_startup_command() or self.get_argument_args() or self.get_argument_tags(): | ||
| not self.get_argument_disable_warnings() and logger.warning( | ||
| 'Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead') | ||
|
|
||
| yaml_containerapp = process_loaded_yaml(load_yaml_file(file_name)) | ||
|
|
||
| if not yaml_containerapp.get('name'): | ||
| yaml_containerapp['name'] = name | ||
| elif yaml_containerapp.get('name').lower() != name.lower(): | ||
| logger.warning( | ||
| 'The app name provided in the --yaml file "{}" does not match the one provided in the --name flag "{}". The one provided in the --yaml file will be used.'.format( | ||
| yaml_containerapp.get('name'), name)) | ||
| name = yaml_containerapp.get('name') | ||
|
|
||
| if not yaml_containerapp.get('type'): | ||
| yaml_containerapp['type'] = 'Microsoft.App/containerApps' | ||
| elif yaml_containerapp.get('type').lower() != "microsoft.app/containerapps": | ||
| raise ValidationError('Containerapp type must be \"Microsoft.App/ContainerApps\"') | ||
|
|
||
| # Deserialize the yaml into a ContainerApp object. Need this since we're not using SDK | ||
| try: | ||
| deserializer = create_deserializer(self.models) | ||
|
|
||
| self.containerapp_def = deserializer('ContainerApp', yaml_containerapp) | ||
| except DeserializationError as ex: | ||
| raise ValidationError( | ||
| 'Invalid YAML provided. Please see https://aka.ms/azure-container-apps-yaml for a valid containerapps YAML spec.') from ex | ||
|
|
||
| # Remove tags before converting from snake case to camel case, then re-add tags. We don't want to change the case of the tags. Need this since we're not using SDK | ||
| tags = None | ||
| if yaml_containerapp.get('tags'): | ||
| tags = yaml_containerapp.get('tags') | ||
| del yaml_containerapp['tags'] | ||
|
|
||
| service_binds = safe_get(yaml_containerapp, "properties", "template", "serviceBinds") | ||
| customized_keys_dict = {} | ||
| if service_binds: | ||
| for bind in service_binds: | ||
| customized_keys_dict[bind["name"]] = bind["customizedKeys"] | ||
|
|
||
| self.containerapp_def = _convert_object_from_snake_to_camel_case(_object_to_dict(self.containerapp_def)) | ||
| self.containerapp_def['tags'] = tags | ||
| # Containerapp object to dictionary will lose "properties" level | ||
| service_binds = safe_get(self.containerapp_def, "properties", "template", "serviceBinds") or safe_get(self.containerapp_def, "template", "serviceBinds") | ||
| if service_binds: | ||
| for bind in service_binds: | ||
| bind["customizedKeys"] = customized_keys_dict.get(bind["name"]) | ||
|
|
||
| # After deserializing, some properties may need to be moved under the "properties" attribute. Need this since we're not using SDK | ||
| self.containerapp_def = process_loaded_yaml(self.containerapp_def) | ||
|
|
||
| # Remove "additionalProperties" and read-only attributes that are introduced in the deserialization. Need this since we're not using SDK | ||
| _remove_additional_attributes(self.containerapp_def) | ||
| _remove_readonly_attributes(self.containerapp_def) | ||
|
|
||
| # Remove extra workloadProfileName introduced in deserialization | ||
| if "workloadProfileName" in self.containerapp_def: | ||
| del self.containerapp_def["workloadProfileName"] | ||
|
|
||
| # Validate managed environment | ||
| env_id = self.containerapp_def["properties"]['environmentId'] | ||
| env_info = None | ||
| if self.get_argument_managed_env(): | ||
| if not self.get_argument_disable_warnings() and env_id is not None and env_id != self.get_argument_managed_env(): | ||
| logger.warning('The environmentId was passed along with --yaml. The value entered with --environment will be ignored, and the configuration defined in the yaml will be used instead') | ||
| if env_id is None: | ||
| env_id = self.get_argument_managed_env() | ||
| safe_set(self.containerapp_def, "properties", "environmentId", value=env_id) | ||
|
|
||
| if not self.containerapp_def["properties"].get('environmentId'): | ||
| raise RequiredArgumentMissingError( | ||
| 'environmentId is required. This can be retrieved using the `az containerapp env show -g MyResourceGroup -n MyContainerappEnvironment --query id` command. Please see https://aka.ms/azure-container-apps-yaml for a valid containerapps YAML spec.') | ||
|
|
||
| if is_valid_resource_id(env_id): | ||
| parsed_managed_env = parse_resource_id(env_id) | ||
| env_name = parsed_managed_env['name'] | ||
| env_rg = parsed_managed_env['resource_group'] | ||
| else: | ||
| raise ValidationError('Invalid environmentId specified. Environment not found') | ||
|
|
||
| try: | ||
| env_info = self.get_environment_client().show(cmd=self.cmd, resource_group_name=env_rg, name=env_name) | ||
| except Exception as e: | ||
| handle_non_404_status_code_exception(e) | ||
|
|
||
| if not env_info: | ||
| raise ValidationError("The environment '{}' in resource group '{}' was not found".format(env_name, env_rg)) | ||
|
|
||
| # Validate location | ||
| if not self.containerapp_def.get('location'): | ||
| self.containerapp_def['location'] = env_info['location'] | ||
|
|
||
| def get_environment_client(self): | ||
| if self.get_argument_yaml(): | ||
| env = safe_get(self.containerapp_def, "properties", "environmentId") | ||
|
|
@@ -848,6 +963,9 @@ class ContainerAppPreviewUpdateDecorator(ContainerAppUpdateDecorator): | |
| def get_argument_service_bindings(self): | ||
| return self.get_param("service_bindings") | ||
|
|
||
| def get_argument_customized_keys(self): | ||
| return self.get_param("customized_keys") | ||
|
|
||
| def get_argument_service_connectors_def_list(self): | ||
| return self.get_param("service_connectors_def_list") | ||
|
|
||
|
|
@@ -866,6 +984,12 @@ def set_argument_source(self, source): | |
| def get_argument_artifact(self): | ||
| return self.get_param("artifact") | ||
|
|
||
| def validate_arguments(self): | ||
| super().validate_arguments() | ||
| if self.get_argument_service_bindings() and len(self.get_argument_service_bindings()) > 1 and self.get_argument_customized_keys(): | ||
| raise InvalidArgumentValueError( | ||
| "--bind have multiple values, but --customized-keys only can be set when --bind has single value.") | ||
|
|
||
| def construct_payload(self): | ||
| super().construct_payload() | ||
| self.set_up_service_bindings() | ||
|
|
@@ -978,7 +1102,7 @@ def set_up_service_bindings(self): | |
| if self.get_argument_service_bindings() is not None: | ||
| linker_client = get_linker_client(self.cmd) | ||
|
|
||
| service_connectors_def_list, service_bindings_def_list = parse_service_bindings(self.cmd, self.get_argument_service_bindings(), self.get_argument_resource_group_name(), self.get_argument_name()) | ||
| service_connectors_def_list, service_bindings_def_list = parse_service_bindings(self.cmd, self.get_argument_service_bindings(), self.get_argument_resource_group_name(), self.get_argument_name(), self.get_argument_customized_keys()) | ||
| self.set_argument_service_connectors_def_list(service_connectors_def_list) | ||
| service_bindings_used_map = {update_item["name"]: False for update_item in service_bindings_def_list} | ||
|
|
||
|
|
@@ -991,6 +1115,10 @@ def set_up_service_bindings(self): | |
| for update_item in service_bindings_def_list: | ||
| if update_item["name"] in item.values(): | ||
| item["serviceId"] = update_item["serviceId"] | ||
| if update_item.get("clientType"): | ||
| item["clientType"] = update_item.get("clientType") | ||
| if update_item.get("customizedKeys"): | ||
| item["customizedKeys"] = update_item.get("customizedKeys") | ||
| service_bindings_used_map[update_item["name"]] = True | ||
|
|
||
| for update_item in service_bindings_def_list: | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.