diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index 42756751195..b2ed32f8928 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -4,6 +4,8 @@ Release History =============== upcoming ++++++ +* 'az containerapp function invocations': Update application insights query +* 'az containerapp function keys': Update minimum replica check 1.3.0b1 ++++++ diff --git a/src/containerapp/azext_containerapp/_utils.py b/src/containerapp/azext_containerapp/_utils.py index 0ee41dbe4a8..ae7db89fe88 100644 --- a/src/containerapp/azext_containerapp/_utils.py +++ b/src/containerapp/azext_containerapp/_utils.py @@ -835,6 +835,17 @@ def create_acrpull_role_assignment_if_needed(cmd, registry_server, registry_iden time.sleep(5) +def get_min_replicas_from_revision(cmd, resource_group_name, container_app_name, revision_name): + revision_def = ContainerAppClient.show_revision( + cmd=cmd, + resource_group_name=resource_group_name, + container_app_name=container_app_name, + name=revision_name + ) + min_replicas = safe_get(revision_def, "properties", "template", "scale", "minReplicas", default=None) + return min_replicas + + def get_random_replica(cmd, resource_group_name, container_app_name, revision_name): logger.debug(f"Getting random replica for container app: name='{container_app_name}', resource_group='{resource_group_name}', revision='{revision_name}'") @@ -851,6 +862,18 @@ def get_random_replica(cmd, resource_group_name, container_app_name, revision_na if not replicas: logger.debug(f"No replicas found for revision '{revision_name}' - unable to proceed") + logger.debug(f"checking min replica count for revision='{revision_name}'") + + min_replicas = get_min_replicas_from_revision( + cmd, + resource_group_name, + container_app_name, + revision_name + ) + if min_replicas is None or min_replicas == 0: + logger.debug(f"The revision '{revision_name}' has minReplicas set to 0.") + raise CLIError(f"The revision '{revision_name}' has minReplicas set to 0. Ensure that there is at least one replica. To update minimum replica: Run 'az containerapp update --name {container_app_name} --resource-group {resource_group_name} --min-replica 1'") + raise CLIError(f"No replicas found for revision '{revision_name}' of container app '{container_app_name}'.") # Filter replicas by running state diff --git a/src/containerapp/azext_containerapp/_validators.py b/src/containerapp/azext_containerapp/_validators.py index 23d54230fe3..22b88f40266 100644 --- a/src/containerapp/azext_containerapp/_validators.py +++ b/src/containerapp/azext_containerapp/_validators.py @@ -318,7 +318,7 @@ def validate_revision_and_get_name(cmd, resource_group_name, container_app_name, if not provided_revision_name: logger.debug("No revision name provided for multiple revision mode container app") raise ValidationError("Revision name is required when active revision mode is not 'single'.") - return provided_revision_name + return provided_revision_name, active_revision_mode if not provided_revision_name: logger.debug("No revision name provided - attempting to determine latest revision") revision_name = safe_get(containerapp_def, "properties", "latestRevisionName") @@ -331,9 +331,9 @@ def validate_revision_and_get_name(cmd, resource_group_name, container_app_name, if not revision_name or revision_name is None: logger.debug("Could not determine any revision name from container app properties") raise ValidationError("Could not determine the latest revision name. Please provide --revision.") - return revision_name + return revision_name, active_revision_mode logger.debug("Using provided revision name: '%s'", provided_revision_name) - return provided_revision_name + return provided_revision_name, active_revision_mode def validate_functionapp_kind(cmd, resource_group_name, container_app_name): diff --git a/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py index a37a25b173f..460671d85ad 100644 --- a/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py +++ b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py @@ -58,7 +58,7 @@ def validate_common_arguments(self): ) # Validate revision and get the appropriate revision name - revision_name = validate_revision_and_get_name( + revision_name, _ = validate_revision_and_get_name( cmd=self.cmd, resource_group_name=resource_group_name, container_app_name=name, diff --git a/src/containerapp/azext_containerapp/containerapp_functions_decorator.py b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py index 4ac187936b4..9ef1070703b 100644 --- a/src/containerapp/azext_containerapp/containerapp_functions_decorator.py +++ b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py @@ -72,7 +72,7 @@ def validate_common_arguments(self): ) # Validate revision and get the appropriate revision name - revision_name = validate_revision_and_get_name( + revision_name, _ = validate_revision_and_get_name( cmd=self.cmd, resource_group_name=resource_group_name, container_app_name=name, @@ -170,6 +170,10 @@ class ContainerAppFunctionInvocationsDecorator(ContainerAppFunctionsDecorator): APP_INSIGHTS_API_VERSION = "2018-04-20" + def __init__(self, cmd, client, raw_parameters, models): + super().__init__(cmd, client, raw_parameters, models) + self.active_revision_mode = None + def validate_arguments(self): """Validate arguments required for function invocation operations""" validate_basic_arguments( @@ -185,14 +189,17 @@ def validate_arguments(self): ) revision_name = self.get_argument_revision_name() - revision_name = validate_revision_and_get_name( + revision_name, active_revision_mode = validate_revision_and_get_name( cmd=self.cmd, resource_group_name=self.get_argument_resource_group_name(), container_app_name=self.get_argument_container_app_name(), provided_revision_name=revision_name ) + # Update the revision name with the validated value self.set_argument_revision_name(revision_name) + # Store active revision mode for use in query building + self.active_revision_mode = active_revision_mode self.validate_function_name_requirement() def _get_app_insights_id(self, resource_group_name, container_app_name, revision_name): @@ -267,12 +274,14 @@ def get_summary(self): # Fetch the app insights resource app id app_id = self._get_app_insights_id(resource_group_name, container_app_name, revision_name) - # Use application insights query to get function invocations summary + # Set revision_name to empty string for single mode, keep it for multiple mode + revision_name = "" if self.active_revision_mode.lower() == "single" else revision_name + invocation_summary_query = ( f"requests | extend functionNameFromCustomDimension = tostring(customDimensions['faas.name']) " f"| where timestamp >= ago({timespan}) " f"| where cloud_RoleName =~ '{container_app_name}' " - f"| where cloud_RoleInstance contains '{revision_name}' " + f"| where isempty(\"{revision_name}\") or cloud_RoleInstance contains '{revision_name}' " f"| where operation_Name =~ '{function_name}' or functionNameFromCustomDimension =~ '{function_name}' " f"| summarize SuccessCount = coalesce(countif(success == true), 0), ErrorCount = coalesce(countif(success == false), 0)" ) @@ -299,14 +308,16 @@ def get_traces(self): # Fetch the app insights resource app id app_id = self._get_app_insights_id(resource_group_name, container_app_name, revision_name) - # Use application insights query to get function invocations traces + # Set revision_name to empty string for single mode, keep it for multiple mode + revision_name = "" if self.active_revision_mode.lower() == "single" else revision_name + invocation_traces_query = ( f"requests | extend functionNameFromCustomDimension = tostring(customDimensions['faas.name']) " f"| project timestamp, id, operation_Name, success, resultCode, duration, operation_Id, functionNameFromCustomDimension, " f"cloud_RoleName, cloud_RoleInstance, invocationId=coalesce(tostring(customDimensions['InvocationId']), tostring(customDimensions['faas.invocation_id'])) " f"| where timestamp > ago({timespan}) " f"| where cloud_RoleName =~ '{container_app_name}' " - f"| where cloud_RoleInstance contains '{revision_name}' " + f"| where isempty(\"{revision_name}\") or cloud_RoleInstance contains '{revision_name}' " f"| where operation_Name =~ '{function_name}' or functionNameFromCustomDimension =~ '{function_name}' " f"| order by timestamp desc | take {limit} " f"| project timestamp, success, resultCode, durationInMilliSeconds=duration, invocationId, operationId=operation_Id, operationName=operation_Name, functionNameFromCustomDimension "