From 4dc363f59fe1141527d153f1eb51b7fc78e72232 Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Thu, 11 Sep 2025 05:02:31 +0000 Subject: [PATCH 1/9] adding az containerapp functions commands --- src/containerapp/HISTORY.rst | 1 + .../azext_containerapp/_clients.py | 72 +++++++++++ src/containerapp/azext_containerapp/_help.py | 33 +++++ .../azext_containerapp/_params.py | 9 ++ .../azext_containerapp/commands.py | 4 + .../containerapp_functions_decorator.py | 113 ++++++++++++++++++ src/containerapp/azext_containerapp/custom.py | 41 ++++++- 7 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 src/containerapp/azext_containerapp/containerapp_functions_decorator.py diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index 9145c99d1a0..a75e35eab2f 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -4,6 +4,7 @@ Release History =============== upcoming ++++++ +* 'az containerapp functions get/list': New command group to list and show functions in container apps. * 'az containerapp update/up': Disallow changing `--revisions-mode` to Labels. * 'az containerapp session code-interpreter': Fix `--path` in examples * 'az containerapp sessionpool create/update': Support `--lifecycle-type` and `--max-alive-period` diff --git a/src/containerapp/azext_containerapp/_clients.py b/src/containerapp/azext_containerapp/_clients.py index 4424abf2336..8ff850599bd 100644 --- a/src/containerapp/azext_containerapp/_clients.py +++ b/src/containerapp/azext_containerapp/_clients.py @@ -303,6 +303,78 @@ def list(cls, cmd, resource_group_name, container_app_name): return policy_list +class ContainerAppFunctionsPreviewClient(): + api_version = PREVIEW_API_VERSION + + @classmethod + def list_functions_by_revision(cls, cmd, resource_group_name, container_app_name, revision_name): + """List all functions for a specific revision""" + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/revisions/{}/functions?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + container_app_name, + revision_name, + cls.api_version) + + r = send_raw_request(cmd.cli_ctx, "GET", request_url) + return r.json() + + @classmethod + def get_function_by_revision(cls, cmd, resource_group_name, container_app_name, revision_name, function_name): + """Get a specific function for a specific revision""" + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/revisions/{}/functions/{}?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + container_app_name, + revision_name, + function_name, + cls.api_version) + + r = send_raw_request(cmd.cli_ctx, "GET", request_url) + return r.json() + + @classmethod + def list_functions(cls, cmd, resource_group_name, container_app_name): + """List all functions for a container app (across all revisions)""" + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/functions?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + container_app_name, + cls.api_version) + + r = send_raw_request(cmd.cli_ctx, "GET", request_url) + return r.json() + + @classmethod + def get_function(cls, cmd, resource_group_name, container_app_name, function_name): + """Get a specific function for a container app""" + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/functions/{}?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + container_app_name, + function_name, + cls.api_version) + + r = send_raw_request(cmd.cli_ctx, "GET", request_url) + return r.json() + + class DaprComponentResiliencyPreviewClient(): api_version = PREVIEW_API_VERSION diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 2b34c35fcf5..018e132d946 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -179,6 +179,39 @@ az containerapp replica count -n my-containerapp -g MyResourceGroup """ +helps['containerapp functions'] = """ + type: group + short-summary: Commands to view functions in a container app. +""" + +helps['containerapp functions list'] = """ + type: command + short-summary: List all functions in a container app or a specific revision. + long-summary: | + When revision name is not provided, lists functions from the active revision. If there are multiple active revisions, the API will return an error requiring you to specify which revision. + examples: + - name: List all functions in a container app (single active revision mode) + text: | + az containerapp functions list -n my-containerapp -g MyResourceGroup + - name: List all functions for a specific revision + text: | + az containerapp functions list -n my-containerapp -g MyResourceGroup --revision-name MyRevision +""" + +helps['containerapp functions show'] = """ + type: command + short-summary: Show details of a specific function in a container app. + long-summary: | + When revision name is not provided, shows function from the active revision. If there are multiple active revisions, the API will return an error requiring you to specify which revision. + examples: + - name: Show details of a function in a container app (single active revision mode) + text: | + az containerapp functions show -n my-containerapp -g MyResourceGroup --function-name MyFunction + - name: Show details of a function for a specific revision + text: | + az containerapp functions show -n my-containerapp -g MyResourceGroup --function-name MyFunction --revision-name MyRevision +""" + # Environment Commands helps['containerapp env'] = """ type: group diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index d644ad5743c..b832df47829 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -529,3 +529,12 @@ def load_arguments(self, _): c.argument('termination_grace_period', options_list=['--termination-grace-period', '-t'], type=int, help="Time in seconds to drain requests during ingress shutdown. Default 500, minimum 0, maximum 3600.") c.argument('request_idle_timeout', options_list=['--request-idle-timeout'], type=int, help="Timeout in minutes for idle requests. Default 4, minimum 4, maximum 30.") c.argument('header_count_limit', options_list=['--header-count-limit'], type=int, help="Limit of http headers per request. Default 100, minimum 1.") + + with self.argument_context('containerapp functions list') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") + c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to list functions from. If not provided, lists functions from the active revision (only works if there's a single active revision).") + + with self.argument_context('containerapp functions show') as c: + c.argument('function_name', options_list=['--function-name', '-f'], help="The name of the function to show details for.") + c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to get the function from. If not provided, gets function from the active revision (only works if there's a single active revision).") diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 996219c6465..262a1c9784f 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -32,6 +32,10 @@ def load_command_table(self, args): g.custom_command('list', 'list_replicas') g.custom_command('count', 'count_replicas', is_preview=True) + with self.command_group('containerapp functions', is_preview=True) as g: + g.custom_command('list', 'list_containerapp_functions') + g.custom_show_command('show', 'show_containerapp_function') + with self.command_group('containerapp env') as g: g.custom_show_command('show', 'show_managed_environment') g.custom_command('list', 'list_managed_environments') diff --git a/src/containerapp/azext_containerapp/containerapp_functions_decorator.py b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py new file mode 100644 index 00000000000..4a33ff7e445 --- /dev/null +++ b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py @@ -0,0 +1,113 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long, broad-except, logging-format-interpolation, too-many-public-methods, too-many-boolean-expressions, logging-fstring-interpolation + +from knack.log import get_logger +from typing import Any, Dict + +from azure.cli.core.commands import AzCliCommand +from azure.cli.core.azclierror import (ValidationError, ResourceNotFoundError) +from azure.cli.command_modules.containerapp.base_resource import BaseResource + +from ._clients import ContainerAppFunctionsPreviewClient +from ._client_factory import handle_raw_exception + +logger = get_logger(__name__) + + +class ContainerAppFunctionsDecorator(BaseResource): + """Base decorator for Container App Functions operations""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def get_argument_container_app_name(self): + return self.get_param("container_app_name") + + def get_argument_revision_name(self): + return self.get_param("revision_name") + + def get_argument_function_name(self): + return self.get_param("function_name") + + def set_argument_container_app_name(self, container_app_name): + self.set_param("container_app_name", container_app_name) + + def set_argument_revision_name(self, revision_name): + self.set_param("revision_name", revision_name) + + def set_argument_function_name(self, function_name): + self.set_param("function_name", function_name) + + +class ContainerAppFunctionsListDecorator(ContainerAppFunctionsDecorator): + """Decorator for listing functions""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def list(self): + """List functions for a container app or revision""" + try: + revision_name = self.get_argument_revision_name() + container_app_name = self.get_argument_container_app_name() + resource_group_name = self.get_argument_resource_group_name() + + if revision_name: + # List functions for a specific revision + return self.client.list_functions_by_revision( + cmd=self.cmd, + resource_group_name=resource_group_name, + container_app_name=container_app_name, + revision_name=revision_name + ) + else: + # List functions for the entire container app + return self.client.list_functions( + cmd=self.cmd, + resource_group_name=resource_group_name, + container_app_name=container_app_name + ) + except Exception as e: + handle_raw_exception(e) + + +class ContainerAppFunctionsShowDecorator(ContainerAppFunctionsDecorator): + """Decorator for showing a specific function""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def show(self): + """Show details of a specific function""" + try: + revision_name = self.get_argument_revision_name() + container_app_name = self.get_argument_container_app_name() + function_name = self.get_argument_function_name() + resource_group_name = self.get_argument_resource_group_name() + + if not function_name: + raise ValidationError("Function name is required.") + + if revision_name: + # Get function for a specific revision + return self.client.get_function_by_revision( + cmd=self.cmd, + resource_group_name=resource_group_name, + container_app_name=container_app_name, + revision_name=revision_name, + function_name=function_name + ) + else: + # Get function for the entire container app + return self.client.get_function( + cmd=self.cmd, + resource_group_name=resource_group_name, + container_app_name=container_app_name, + function_name=function_name + ) + except Exception as e: + handle_raw_exception(e) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 622a2f0e972..a854424d8dc 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -93,6 +93,10 @@ from .containerapp_session_code_interpreter_decorator import SessionCodeInterpreterCommandsPreviewDecorator from .containerapp_job_registry_decorator import ContainerAppJobRegistryPreviewSetDecorator from .containerapp_env_maintenance_config_decorator import ContainerAppEnvMaintenanceConfigPreviewDecorator +from .containerapp_functions_decorator import ( + ContainerAppFunctionsListDecorator, + ContainerAppFunctionsShowDecorator +) from .dotnet_component_decorator import DotNetComponentDecorator from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception from ._clients import ( @@ -114,7 +118,8 @@ DotNetComponentPreviewClient, MaintenanceConfigPreviewClient, HttpRouteConfigPreviewClient, - LabelHistoryPreviewClient + LabelHistoryPreviewClient, + ContainerAppFunctionsPreviewClient ) from ._dev_service_utils import DevServiceUtils from ._models import ( @@ -3937,3 +3942,37 @@ def remove_environment_premium_ingress(cmd, name, resource_group_name, no_wait=F except Exception as e: handle_raw_exception(e) + + +# Container App Functions commands +def list_containerapp_functions(cmd, resource_group_name, name, revision_name=None): + """List functions for a container app or specific revision""" + containerapp_functions_list_decorator = ContainerAppFunctionsListDecorator( + cmd=cmd, + client=ContainerAppFunctionsPreviewClient, + raw_parameters={ + 'resource_group_name': resource_group_name, + 'container_app_name': name, + 'revision_name': revision_name + }, + models=CONTAINER_APPS_SDK_MODELS + ) + + return containerapp_functions_list_decorator.list() + + +def show_containerapp_function(cmd, resource_group_name, name, function_name, revision_name=None): + """Show details of a specific function for a container app or revision""" + containerapp_functions_show_decorator = ContainerAppFunctionsShowDecorator( + cmd=cmd, + client=ContainerAppFunctionsPreviewClient, + raw_parameters={ + 'resource_group_name': resource_group_name, + 'container_app_name': name, + 'function_name': function_name, + 'revision_name': revision_name + }, + models=CONTAINER_APPS_SDK_MODELS + ) + + return containerapp_functions_show_decorator.show() From a9393d88b104debe162ab6fac6b39afc66da4038 Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Thu, 11 Sep 2025 11:20:55 +0000 Subject: [PATCH 2/9] modification to help descriptions --- src/containerapp/azext_containerapp/_clients.py | 4 ++-- src/containerapp/azext_containerapp/_params.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/containerapp/azext_containerapp/_clients.py b/src/containerapp/azext_containerapp/_clients.py index 8ff850599bd..f6971abc712 100644 --- a/src/containerapp/azext_containerapp/_clients.py +++ b/src/containerapp/azext_containerapp/_clients.py @@ -343,7 +343,7 @@ def get_function_by_revision(cls, cmd, resource_group_name, container_app_name, @classmethod def list_functions(cls, cmd, resource_group_name, container_app_name): - """List all functions for a container app (across all revisions)""" + """List all functions for a container app in single revision mode""" management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager sub_id = get_subscription_id(cmd.cli_ctx) url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/functions?api-version={}" @@ -359,7 +359,7 @@ def list_functions(cls, cmd, resource_group_name, container_app_name): @classmethod def get_function(cls, cmd, resource_group_name, container_app_name, function_name): - """Get a specific function for a container app""" + """Get a specific function for a container app in single revision mode""" management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager sub_id = get_subscription_id(cmd.cli_ctx) url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/functions/{}?api-version={}" diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index b832df47829..f430ca5e77e 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -533,8 +533,11 @@ def load_arguments(self, _): with self.argument_context('containerapp functions list') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") + c.argument('function_name', options_list=['--function-name', '-f'], help="The name of the function to show details for.") c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to list functions from. If not provided, lists functions from the active revision (only works if there's a single active revision).") with self.argument_context('containerapp functions show') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") c.argument('function_name', options_list=['--function-name', '-f'], help="The name of the function to show details for.") c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to get the function from. If not provided, gets function from the active revision (only works if there's a single active revision).") From 94cf376ac996990dc083453a30f3bcdcba46116a Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Thu, 11 Sep 2025 11:26:04 +0000 Subject: [PATCH 3/9] correcting params.py file --- src/containerapp/azext_containerapp/_params.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index f430ca5e77e..ebace4509ed 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -533,7 +533,6 @@ def load_arguments(self, _): with self.argument_context('containerapp functions list') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") - c.argument('function_name', options_list=['--function-name', '-f'], help="The name of the function to show details for.") c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to list functions from. If not provided, lists functions from the active revision (only works if there's a single active revision).") with self.argument_context('containerapp functions show') as c: From 95e3345ee655da20b737507f380151d94f189e8b Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Fri, 12 Sep 2025 05:50:07 +0000 Subject: [PATCH 4/9] addressing comments --- src/containerapp/HISTORY.rst | 2 +- src/containerapp/azext_containerapp/_help.py | 12 ++++++------ src/containerapp/azext_containerapp/_params.py | 4 ++-- .../containerapp_functions_decorator.py | 12 ++++++++++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index a75e35eab2f..5ce2b3e5f5b 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -4,7 +4,7 @@ Release History =============== upcoming ++++++ -* 'az containerapp functions get/list': New command group to list and show functions in container apps. +* 'az containerapp functions show/list': New command group to list and show functions in container apps. * 'az containerapp update/up': Disallow changing `--revisions-mode` to Labels. * 'az containerapp session code-interpreter': Fix `--path` in examples * 'az containerapp sessionpool create/update': Support `--lifecycle-type` and `--max-alive-period` diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 018e132d946..977b16b25e5 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -186,11 +186,11 @@ helps['containerapp functions list'] = """ type: command - short-summary: List all functions in a container app or a specific revision. + short-summary: List all functions in a container app or a specific revision. (pass --revisionName parameter) long-summary: | - When revision name is not provided, lists functions from the active revision. If there are multiple active revisions, the API will return an error requiring you to specify which revision. + revisionName is required only if Container App active Revision Mode is setup in Multiple Revision Mode. (Default: Single Revision Mode) examples: - - name: List all functions in a container app (single active revision mode) + - name: List all functions in a container app. (single active revision mode) text: | az containerapp functions list -n my-containerapp -g MyResourceGroup - name: List all functions for a specific revision @@ -200,11 +200,11 @@ helps['containerapp functions show'] = """ type: command - short-summary: Show details of a specific function in a container app. + short-summary: Show details of a specific function in a container app or a specific revision within app. (pass --revisionName parameter) long-summary: | - When revision name is not provided, shows function from the active revision. If there are multiple active revisions, the API will return an error requiring you to specify which revision. + revisionName is required only if Container App active Revision Mode is setup in Multiple Revision Mode. (Default: Single Revision Mode) examples: - - name: Show details of a function in a container app (single active revision mode) + - name: Show details of a function in a container app. (single active revision mode) text: | az containerapp functions show -n my-containerapp -g MyResourceGroup --function-name MyFunction - name: Show details of a function for a specific revision diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index ebace4509ed..886885d7efc 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -533,10 +533,10 @@ def load_arguments(self, _): with self.argument_context('containerapp functions list') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") - c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to list functions from. If not provided, lists functions from the active revision (only works if there's a single active revision).") + c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to list functions from. It is required if container app is running in multiple active revision mode.") with self.argument_context('containerapp functions show') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") c.argument('function_name', options_list=['--function-name', '-f'], help="The name of the function to show details for.") - c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to get the function from. If not provided, gets function from the active revision (only works if there's a single active revision).") + c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to get the function from. It is required if container app is running in multiple active revision mode.") diff --git a/src/containerapp/azext_containerapp/containerapp_functions_decorator.py b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py index 4a33ff7e445..12d5c63e0df 100644 --- a/src/containerapp/azext_containerapp/containerapp_functions_decorator.py +++ b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py @@ -55,6 +55,12 @@ def list(self): revision_name = self.get_argument_revision_name() container_app_name = self.get_argument_container_app_name() resource_group_name = self.get_argument_resource_group_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not container_app_name: + raise ValidationError("Container app name is required.") if revision_name: # List functions for a specific revision @@ -88,6 +94,12 @@ def show(self): container_app_name = self.get_argument_container_app_name() function_name = self.get_argument_function_name() resource_group_name = self.get_argument_resource_group_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not container_app_name: + raise ValidationError("Container app name is required.") if not function_name: raise ValidationError("Function name is required.") From e42a0f6cf5699af4cae6987a08317e117cf0000c Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Fri, 12 Sep 2025 13:39:44 +0000 Subject: [PATCH 5/9] modifying help description --- src/containerapp/azext_containerapp/_help.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 977b16b25e5..e6ec088a0fe 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -189,6 +189,7 @@ short-summary: List all functions in a container app or a specific revision. (pass --revisionName parameter) long-summary: | revisionName is required only if Container App active Revision Mode is setup in Multiple Revision Mode. (Default: Single Revision Mode) + Run to check activerevisionmode: az containerapp show -n my-containerapp -g MyResourceGroup --query properties.configuration.activeRevisionsMode examples: - name: List all functions in a container app. (single active revision mode) text: | @@ -203,6 +204,7 @@ short-summary: Show details of a specific function in a container app or a specific revision within app. (pass --revisionName parameter) long-summary: | revisionName is required only if Container App active Revision Mode is setup in Multiple Revision Mode. (Default: Single Revision Mode) + Run to check activerevisionmode: az containerapp show -n my-containerapp -g MyResourceGroup --query properties.configuration.activeRevisionsMode examples: - name: Show details of a function in a container app. (single active revision mode) text: | From 88ab1f6e29958d6ed98d1d1bbc81065ae37ea1ac Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Sat, 13 Sep 2025 11:21:10 +0000 Subject: [PATCH 6/9] adding az containerapp function keys commands --- src/containerapp/HISTORY.rst | 4 + .../azext_containerapp/_clients.py | 76 ++++++++ src/containerapp/azext_containerapp/_help.py | 48 ++++- .../azext_containerapp/_params.py | 27 +++ .../azext_containerapp/commands.py | 6 + .../containerapp_function_keys_decorator.py | 179 ++++++++++++++++++ src/containerapp/azext_containerapp/custom.py | 57 +++++- 7 files changed, 395 insertions(+), 2 deletions(-) create mode 100644 src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index 9145c99d1a0..d77df9d9c09 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -4,6 +4,10 @@ Release History =============== upcoming ++++++ +* 'az containerapp function list-keys': List function keys for a specific function in a container app +* 'az containerapp function update-keys': Update specific function key for a specific function in a container app +* 'az containerapp function list-hostkeys': List host keys for a container app +* 'az containerapp function update-hostkeys': Update specific host key for a container app * 'az containerapp update/up': Disallow changing `--revisions-mode` to Labels. * 'az containerapp session code-interpreter': Fix `--path` in examples * 'az containerapp sessionpool create/update': Support `--lifecycle-type` and `--max-alive-period` diff --git a/src/containerapp/azext_containerapp/_clients.py b/src/containerapp/azext_containerapp/_clients.py index 4424abf2336..8db367852c4 100644 --- a/src/containerapp/azext_containerapp/_clients.py +++ b/src/containerapp/azext_containerapp/_clients.py @@ -1698,3 +1698,79 @@ def remove(cls, cmd, resource_group_name, environment_name): if r.status_code == 202: operation_url = r.headers.get(HEADER_LOCATION) poll_results(cmd, operation_url) + + class ContainerAppFunctionsPreviewClient: + api_version = PREVIEW_API_VERSION + + @classmethod + def list_function_keys(cls, cmd, resource_group_name, name, function_name): + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerapps/{}/functions/{}/listkeys?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + name, + function_name, + cls.api_version) + + r = send_raw_request(cmd.cli_ctx, "POST", request_url) + return r.json() + + @classmethod + def update_function_keys(cls, cmd, resource_group_name, name, function_name, key_name, key_value=None): + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerapps/{}/functions/{}/keys/{}?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + name, + function_name, + key_name, + cls.api_version) + + body = {} + if key_value: + body["value"] = key_value + + r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(body)) + return r.json() + + @classmethod + def list_host_keys(cls, cmd, resource_group_name, name): + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerapps/{}/host/default/listkeys?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + name, + cls.api_version) + + r = send_raw_request(cmd.cli_ctx, "POST", request_url) + return r.json() + + @classmethod + def update_host_keys(cls, cmd, resource_group_name, name, key_type, key_name, key_value=None): + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerapps/{}/host/default/{}/{}?api-version={}" + request_url = url_fmt.format( + management_hostname.strip('/'), + sub_id, + resource_group_name, + name, + key_type, + key_name, + cls.api_version) + + body = {} + if key_value: + body["value"] = key_value + + r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(body)) + return r.json() \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 2b34c35fcf5..7eb8120bfb7 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -166,7 +166,6 @@ az containerapp up -n my-containerapp --image my-app:v1.0 --kind functionapp """ - helps['containerapp replica count'] = """ type: command short-summary: Count of a container app's replica(s) @@ -179,6 +178,53 @@ az containerapp replica count -n my-containerapp -g MyResourceGroup """ +helps['containerapp function'] = """ + type: group + short-summary: Commands to manage function keys in a container app. +""" + +helps['containerapp function list-keys'] = """ + type: command + short-summary: List function keys for a specific function in a container app. + examples: + - name: List function keys for a specific function + text: | + az containerapp function list-keys -n my-containerapp -g MyResourceGroup --revision MyContainerappRevision --function-name MyFunctionName +""" + +helps['containerapp function update-keys'] = """ + type: command + short-summary: Update function keys for a specific function in a container app. + examples: + - name: Update a function key for a specific function + text: | + az containerapp function update-keys -n my-containerapp -g MyResourceGroup --revision MyContainerappRevision --function-name MyFunctionName --key-name MyKeyName --key-value MyKeyValue +""" + +helps['containerapp function list-hostkeys'] = """ + type: command + short-summary: List host keys for a container app. + examples: + - name: List host keys for a container app + text: | + az containerapp function list-hostkeys -n my-containerapp -g MyResourceGroup --revision MyContainerappRevision +""" + +helps['containerapp function update-hostkeys'] = """ + type: command + short-summary: Update host keys for a container app. + examples: + - name: Update a host key for a container app with function key type + text: | + az containerapp function update-hostkeys -n my-containerapp -g MyResourceGroup --revision MyContainerappRevision --key-name MyKeyName --key-value MyKeyValue --key-type functionKeys + - name: Update a host key for a container app with master key type + text: | + az containerapp function update-hostkeys -n my-containerapp -g MyResourceGroup --revision MyContainerappRevision --key-name MyKeyName --key-value MyKeyValue --key-type masterKey + - name: Update a host key for a container app with system key type + text: | + az containerapp function update-hostkeys -n my-containerapp -g MyResourceGroup --revision MyContainerappRevision --key-name MyKeyName --key-value MyKeyValue --key-type systemKeys +""" + # Environment Commands helps['containerapp env'] = """ type: group diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index d644ad5743c..00eb5a01308 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -529,3 +529,30 @@ def load_arguments(self, _): c.argument('termination_grace_period', options_list=['--termination-grace-period', '-t'], type=int, help="Time in seconds to drain requests during ingress shutdown. Default 500, minimum 0, maximum 3600.") c.argument('request_idle_timeout', options_list=['--request-idle-timeout'], type=int, help="Timeout in minutes for idle requests. Default 4, minimum 4, maximum 30.") c.argument('header_count_limit', options_list=['--header-count-limit'], type=int, help="Limit of http headers per request. Default 100, minimum 1.") + + with self.argument_context('containerapp function list-keys') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--name', '-n'], help="The name of the container app.") + c.argument('revision', options_list=['--revision'], help="The name of the container app revision.") + c.argument('function_name', options_list=['--function-name'], help="The name of the function.") + + with self.argument_context('containerapp function update-keys') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--name', '-n'], help="The name of the container app.") + c.argument('revision', options_list=['--revision'], help="The name of the container app revision.") + c.argument('function_name', options_list=['--function-name'], help="The name of the function.") + c.argument('key_name', options_list=['--key-name'], help="The name of the key to update.") + c.argument('key_value', options_list=['--key-value'], help="The value of the key to update.") + + with self.argument_context('containerapp function list-hostkeys') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--name', '-n'], help="The name of the container app.") + c.argument('revision', options_list=['--revision'], help="The name of the container app revision.") + + with self.argument_context('containerapp function update-hostkeys') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--name', '-n'], help="The name of the container app.") + c.argument('revision', options_list=['--revision'], help="The name of the container app revision.") + c.argument('key_name', options_list=['--key-name'], help="The name of the host key to update.") + c.argument('key_value', options_list=['--key-value'], help="The value of the host key to update.") + c.argument('key_type', options_list=['--key-type'], arg_type=get_enum_type(['functionKeys', 'masterKey', 'systemKeys']), help="The type of the host key.") \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 996219c6465..e3de3e20c95 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -297,3 +297,9 @@ def load_command_table(self, args): g.custom_command('add', 'add_environment_premium_ingress') g.custom_command('update', 'update_environment_premium_ingress') g.custom_command('remove', 'remove_environment_premium_ingress', confirmation=True) + + with self.command_group('containerapp function') as g: + g.custom_command('list-keys', 'list_containerapp_function_keys') + g.custom_command('update-keys', 'update_containerapp_function_keys') + g.custom_command('list-hostkeys', 'list_containerapp_function_hostkeys') + g.custom_command('update-hostkeys', 'update_containerapp_function_hostkeys') diff --git a/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py new file mode 100644 index 00000000000..77f1dd285e2 --- /dev/null +++ b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py @@ -0,0 +1,179 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long, broad-except, logging-format-interpolation +from knack.log import get_logger +from typing import Any, Dict + +from azure.cli.core.commands import AzCliCommand +from azure.cli.core.azclierror import ValidationError +from azure.cli.command_modules.containerapp.base_resource import BaseResource + +from ._clients import ContainerAppFunctionsPreviewClient +from ._client_factory import handle_raw_exception + +logger = get_logger(__name__) + + +class ContainerAppFunctionsDecorator(BaseResource): + """Base decorator for Container App Functions operations""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def get_argument_function_name(self): + return self.get_param("function_name") + + +class ContainerAppFunctionKeysListDecorator(ContainerAppFunctionsDecorator): + """Decorator for listing function keys""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def list_keys(self): + """List keys for a specific function""" + try: + function_name = self.get_argument_function_name() + resource_group_name = self.get_argument_resource_group_name() + name = self.get_argument_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not name: + raise ValidationError("Container app name is required.") + + if not function_name: + raise ValidationError("Function name is required.") + + return self.client.list_function_keys( + cmd=self.cmd, + resource_group_name=resource_group_name, + name=name, + function_name=function_name + ) + except Exception as e: + handle_raw_exception(e) + + +class ContainerAppFunctionKeysUpdateDecorator(ContainerAppFunctionsDecorator): + """Decorator for updating function keys""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def get_argument_key_name(self): + return self.get_param("key_name") + + def get_argument_key_value(self): + return self.get_param("key_value") + + def update_keys(self): + """Update keys for a specific function""" + try: + function_name = self.get_argument_function_name() + key_name = self.get_argument_key_name() + key_value = self.get_argument_key_value() + resource_group_name = self.get_argument_resource_group_name() + name = self.get_argument_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not name: + raise ValidationError("Container app name is required.") + + if not function_name: + raise ValidationError("Function name is required.") + + if not key_name: + raise ValidationError("Key name is required.") + + return self.client.update_function_keys( + cmd=self.cmd, + resource_group_name=resource_group_name, + name=name, + function_name=function_name, + key_name=key_name, + key_value=key_value + ) + except Exception as e: + handle_raw_exception(e) + + +class ContainerAppFunctionHostKeysListDecorator(ContainerAppFunctionsDecorator): + """Decorator for listing host keys""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def list_host_keys(self): + """List host keys for the container app function host""" + try: + resource_group_name = self.get_argument_resource_group_name() + name = self.get_argument_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not name: + raise ValidationError("Container app name is required.") + + return self.client.list_host_keys( + cmd=self.cmd, + resource_group_name=resource_group_name, + name=name + ) + except Exception as e: + handle_raw_exception(e) + + +class ContainerAppFunctionHostKeysUpdateDecorator(ContainerAppFunctionsDecorator): + """Decorator for updating host keys""" + + def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): + super().__init__(cmd, client, raw_parameters, models) + + def get_argument_key_type(self): + return self.get_param("key_type") + + def get_argument_key_name(self): + return self.get_param("key_name") + + def get_argument_key_value(self): + return self.get_param("key_value") + + def update_host_keys(self): + """Update host keys for the container app function host""" + try: + key_type = self.get_argument_key_type() + key_name = self.get_argument_key_name() + key_value = self.get_argument_key_value() + resource_group_name = self.get_argument_resource_group_name() + name = self.get_argument_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not name: + raise ValidationError("Container app name is required.") + + if not key_type: + raise ValidationError("Key type is required.") + + if not key_name: + raise ValidationError("Key name is required.") + + return self.client.update_host_keys( + cmd=self.cmd, + resource_group_name=resource_group_name, + name=name, + key_type=key_type, + key_name=key_name, + key_value=key_value + ) + except Exception as e: + handle_raw_exception(e) \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 622a2f0e972..6093f6f3f13 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -93,6 +93,12 @@ from .containerapp_session_code_interpreter_decorator import SessionCodeInterpreterCommandsPreviewDecorator from .containerapp_job_registry_decorator import ContainerAppJobRegistryPreviewSetDecorator from .containerapp_env_maintenance_config_decorator import ContainerAppEnvMaintenanceConfigPreviewDecorator +from .containerapp_function_keys_decorator import ( + ContainerAppFunctionKeysListDecorator, + ContainerAppFunctionKeysUpdateDecorator, + ContainerAppFunctionHostKeysListDecorator, + ContainerAppFunctionHostKeysUpdateDecorator +) from .dotnet_component_decorator import DotNetComponentDecorator from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception from ._clients import ( @@ -114,7 +120,8 @@ DotNetComponentPreviewClient, MaintenanceConfigPreviewClient, HttpRouteConfigPreviewClient, - LabelHistoryPreviewClient + LabelHistoryPreviewClient, + ContainerAppFunctionsPreviewClient ) from ._dev_service_utils import DevServiceUtils from ._models import ( @@ -3937,3 +3944,51 @@ def remove_environment_premium_ingress(cmd, name, resource_group_name, no_wait=F except Exception as e: handle_raw_exception(e) + +# Container App Function Key Commands +def list_containerapp_function_keys(cmd, name, resource_group_name, function_name, revision=None): + """List function keys for a specific function""" + raw_parameters = locals() + containerapp_function_decorator = ContainerAppFunctionKeysListDecorator( + cmd=cmd, + client=ContainerAppFunctionsPreviewClient, + raw_parameters=raw_parameters, + models=CONTAINER_APPS_SDK_MODELS + ) + return containerapp_function_decorator.list_keys() + + +def update_containerapp_function_keys(cmd, name, resource_group_name, function_name, key_name, key_value=None, revision=None): + """Update function keys for a specific function""" + raw_parameters = locals() + containerapp_function_decorator = ContainerAppFunctionKeysUpdateDecorator( + cmd=cmd, + client=ContainerAppFunctionsPreviewClient, + raw_parameters=raw_parameters, + models=CONTAINER_APPS_SDK_MODELS + ) + return containerapp_function_decorator.update_keys() + + +def list_containerapp_function_hostkeys(cmd, name, resource_group_name, revision=None): + """List host keys for the container app function host""" + raw_parameters = locals() + containerapp_function_decorator = ContainerAppFunctionHostKeysListDecorator( + cmd=cmd, + client=ContainerAppFunctionsPreviewClient, + raw_parameters=raw_parameters, + models=CONTAINER_APPS_SDK_MODELS + ) + return containerapp_function_decorator.list_host_keys() + + +def update_containerapp_function_hostkeys(cmd, name, resource_group_name, key_type, key_name, key_value=None, revision=None): + """Update host keys for the container app function host""" + raw_parameters = locals() + containerapp_function_decorator = ContainerAppFunctionHostKeysUpdateDecorator( + cmd=cmd, + client=ContainerAppFunctionsPreviewClient, + raw_parameters=raw_parameters, + models=CONTAINER_APPS_SDK_MODELS + ) + return containerapp_function_decorator.update_host_keys() \ No newline at end of file From 0986024dd0476c6512f7393f9a5ecdd14f7c6413 Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Sat, 13 Sep 2025 11:27:48 +0000 Subject: [PATCH 7/9] modifying the command to be az containerapp function instead of az containerapp functions --- src/containerapp/HISTORY.rst | 2 +- src/containerapp/azext_containerapp/_help.py | 14 +++++++------- src/containerapp/azext_containerapp/_params.py | 4 ++-- src/containerapp/azext_containerapp/commands.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index 5ce2b3e5f5b..be1b47b396b 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -4,7 +4,7 @@ Release History =============== upcoming ++++++ -* 'az containerapp functions show/list': New command group to list and show functions in container apps. +* 'az containerapp function show/list': New command group to list and show functions in container apps. * 'az containerapp update/up': Disallow changing `--revisions-mode` to Labels. * 'az containerapp session code-interpreter': Fix `--path` in examples * 'az containerapp sessionpool create/update': Support `--lifecycle-type` and `--max-alive-period` diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index e6ec088a0fe..65732e28ab5 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -179,12 +179,12 @@ az containerapp replica count -n my-containerapp -g MyResourceGroup """ -helps['containerapp functions'] = """ +helps['containerapp function'] = """ type: group short-summary: Commands to view functions in a container app. """ -helps['containerapp functions list'] = """ +helps['containerapp function list'] = """ type: command short-summary: List all functions in a container app or a specific revision. (pass --revisionName parameter) long-summary: | @@ -193,13 +193,13 @@ examples: - name: List all functions in a container app. (single active revision mode) text: | - az containerapp functions list -n my-containerapp -g MyResourceGroup + az containerapp function list -n my-containerapp -g MyResourceGroup - name: List all functions for a specific revision text: | - az containerapp functions list -n my-containerapp -g MyResourceGroup --revision-name MyRevision + az containerapp function list -n my-containerapp -g MyResourceGroup --revision-name MyRevision """ -helps['containerapp functions show'] = """ +helps['containerapp function show'] = """ type: command short-summary: Show details of a specific function in a container app or a specific revision within app. (pass --revisionName parameter) long-summary: | @@ -208,10 +208,10 @@ examples: - name: Show details of a function in a container app. (single active revision mode) text: | - az containerapp functions show -n my-containerapp -g MyResourceGroup --function-name MyFunction + az containerapp function show -n my-containerapp -g MyResourceGroup --function-name MyFunction - name: Show details of a function for a specific revision text: | - az containerapp functions show -n my-containerapp -g MyResourceGroup --function-name MyFunction --revision-name MyRevision + az containerapp function show -n my-containerapp -g MyResourceGroup --function-name MyFunction --revision-name MyRevision """ # Environment Commands diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 886885d7efc..cc861ed7b0c 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -530,12 +530,12 @@ def load_arguments(self, _): c.argument('request_idle_timeout', options_list=['--request-idle-timeout'], type=int, help="Timeout in minutes for idle requests. Default 4, minimum 4, maximum 30.") c.argument('header_count_limit', options_list=['--header-count-limit'], type=int, help="Limit of http headers per request. Default 100, minimum 1.") - with self.argument_context('containerapp functions list') as c: + with self.argument_context('containerapp function list') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") c.argument('revision_name', options_list=['--revision-name', '-r'], help="The name of the revision to list functions from. It is required if container app is running in multiple active revision mode.") - with self.argument_context('containerapp functions show') as c: + with self.argument_context('containerapp function show') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) c.argument('name', options_list=['--name', '-n'], help="The name of the Container App.") c.argument('function_name', options_list=['--function-name', '-f'], help="The name of the function to show details for.") diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 262a1c9784f..b917efb0e02 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -32,7 +32,7 @@ def load_command_table(self, args): g.custom_command('list', 'list_replicas') g.custom_command('count', 'count_replicas', is_preview=True) - with self.command_group('containerapp functions', is_preview=True) as g: + with self.command_group('containerapp function', is_preview=True) as g: g.custom_command('list', 'list_containerapp_functions') g.custom_show_command('show', 'show_containerapp_function') From b9c8a4b108be7cf1a4d84cceb37f434ca7c16730 Mon Sep 17 00:00:00 2001 From: khushishah513 Date: Mon, 15 Sep 2025 05:21:36 +0000 Subject: [PATCH 8/9] updating function keys decorator --- .../containerapp_function_keys_decorator.py | 122 +++++++++--------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py index 77f1dd285e2..8a09d2ec6c1 100644 --- a/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py +++ b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py @@ -17,8 +17,8 @@ logger = get_logger(__name__) -class ContainerAppFunctionsDecorator(BaseResource): - """Base decorator for Container App Functions operations""" +class ContainerAppFunctionKeysDecorator(BaseResource): + """Base decorator for Container App Function Keys operations""" def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): super().__init__(cmd, client, raw_parameters, models) @@ -26,8 +26,38 @@ def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: def get_argument_function_name(self): return self.get_param("function_name") + def get_argument_revision_name(self): + return self.get_param("revision_name") -class ContainerAppFunctionKeysListDecorator(ContainerAppFunctionsDecorator): + def validate_common_arguments(self): + """Validate common arguments required for all function operations""" + resource_group_name = self.get_argument_resource_group_name() + name = self.get_argument_name() + revision_name = self.get_argument_revision_name() + + if not resource_group_name: + raise ValidationError("Resource group name is required.") + + if not name: + raise ValidationError("Container app name is required.") + + if not revision_name: + raise ValidationError("Revision name is required.") + + return resource_group_name, name, revision_name + + def validate_function_arguments(self): + """Validate arguments required for function-specific operations""" + resource_group_name, name, revision_name = self.validate_common_arguments() + function_name = self.get_argument_function_name() + + if not function_name: + raise ValidationError("Function name is required.") + + return resource_group_name, name, revision_name, function_name + + +class ContainerAppFunctionKeysListDecorator(ContainerAppFunctionKeysDecorator): """Decorator for listing function keys""" def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): @@ -36,18 +66,7 @@ def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: def list_keys(self): """List keys for a specific function""" try: - function_name = self.get_argument_function_name() - resource_group_name = self.get_argument_resource_group_name() - name = self.get_argument_name() - - if not resource_group_name: - raise ValidationError("Resource group name is required.") - - if not name: - raise ValidationError("Container app name is required.") - - if not function_name: - raise ValidationError("Function name is required.") + resource_group_name, name, revision_name, function_name = self.validate_function_arguments() return self.client.list_function_keys( cmd=self.cmd, @@ -59,7 +78,7 @@ def list_keys(self): handle_raw_exception(e) -class ContainerAppFunctionKeysUpdateDecorator(ContainerAppFunctionsDecorator): +class ContainerAppFunctionKeysUpdateDecorator(ContainerAppFunctionKeysDecorator): """Decorator for updating function keys""" def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): @@ -71,26 +90,21 @@ def get_argument_key_name(self): def get_argument_key_value(self): return self.get_param("key_value") + def validate_update_arguments(self): + """Validate arguments required for updating function keys""" + resource_group_name, name, revision_name, function_name = self.validate_function_arguments() + key_name = self.get_argument_key_name() + + if not key_name: + raise ValidationError("Key name is required.") + + return resource_group_name, name, revision_name, function_name, key_name + def update_keys(self): """Update keys for a specific function""" try: - function_name = self.get_argument_function_name() - key_name = self.get_argument_key_name() + resource_group_name, name, revision_name, function_name, key_name = self.validate_update_arguments() key_value = self.get_argument_key_value() - resource_group_name = self.get_argument_resource_group_name() - name = self.get_argument_name() - - if not resource_group_name: - raise ValidationError("Resource group name is required.") - - if not name: - raise ValidationError("Container app name is required.") - - if not function_name: - raise ValidationError("Function name is required.") - - if not key_name: - raise ValidationError("Key name is required.") return self.client.update_function_keys( cmd=self.cmd, @@ -104,7 +118,7 @@ def update_keys(self): handle_raw_exception(e) -class ContainerAppFunctionHostKeysListDecorator(ContainerAppFunctionsDecorator): +class ContainerAppFunctionHostKeysListDecorator(ContainerAppFunctionKeysDecorator): """Decorator for listing host keys""" def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): @@ -113,14 +127,7 @@ def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: def list_host_keys(self): """List host keys for the container app function host""" try: - resource_group_name = self.get_argument_resource_group_name() - name = self.get_argument_name() - - if not resource_group_name: - raise ValidationError("Resource group name is required.") - - if not name: - raise ValidationError("Container app name is required.") + resource_group_name, name, revision_name = self.validate_common_arguments() return self.client.list_host_keys( cmd=self.cmd, @@ -131,7 +138,7 @@ def list_host_keys(self): handle_raw_exception(e) -class ContainerAppFunctionHostKeysUpdateDecorator(ContainerAppFunctionsDecorator): +class ContainerAppFunctionHostKeysUpdateDecorator(ContainerAppFunctionKeysDecorator): """Decorator for updating host keys""" def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str): @@ -146,26 +153,25 @@ def get_argument_key_name(self): def get_argument_key_value(self): return self.get_param("key_value") + def validate_host_key_arguments(self): + """Validate arguments required for updating host keys""" + resource_group_name, name, revision_name = self.validate_common_arguments() + key_type = self.get_argument_key_type() + key_name = self.get_argument_key_name() + + if not key_type: + raise ValidationError("Key type is required.") + + if not key_name: + raise ValidationError("Key name is required.") + + return resource_group_name, name, revision_name, key_type, key_name + def update_host_keys(self): """Update host keys for the container app function host""" try: - key_type = self.get_argument_key_type() - key_name = self.get_argument_key_name() + resource_group_name, name, revision_name, key_type, key_name = self.validate_host_key_arguments() key_value = self.get_argument_key_value() - resource_group_name = self.get_argument_resource_group_name() - name = self.get_argument_name() - - if not resource_group_name: - raise ValidationError("Resource group name is required.") - - if not name: - raise ValidationError("Container app name is required.") - - if not key_type: - raise ValidationError("Key type is required.") - - if not key_name: - raise ValidationError("Key name is required.") return self.client.update_host_keys( cmd=self.cmd, From d9bd529aef6b589e557278f107c98c709554d06b Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Fri, 19 Sep 2025 11:00:42 +0000 Subject: [PATCH 9/9] Adding command param in debug command --- src/containerapp/azext_containerapp/_ssh_utils.py | 9 +++++++-- src/containerapp/azext_containerapp/custom.py | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/containerapp/azext_containerapp/_ssh_utils.py b/src/containerapp/azext_containerapp/_ssh_utils.py index eae3ca9a776..233e4c2f4da 100644 --- a/src/containerapp/azext_containerapp/_ssh_utils.py +++ b/src/containerapp/azext_containerapp/_ssh_utils.py @@ -9,23 +9,28 @@ SSH_DEFAULT_ENCODING, read_ssh from azure.cli.core.commands.client_factory import get_subscription_id +import urllib from knack.log import get_logger logger = get_logger(__name__) class DebugWebSocketConnection(WebSocketConnection): - def __init__(self, cmd, resource_group_name, name, revision, replica, container): + def __init__(self, cmd, resource_group_name, name, revision, replica, container, command): super(DebugWebSocketConnection, self).__init__(cmd, resource_group_name, name, revision, replica, container, "") + self.command = urllib.parse.quote_plus(command) if command else None def _get_url(self, cmd, resource_group_name, name, revision, replica, container, startup_command): sub = get_subscription_id(cmd.cli_ctx) base_url = self._logstream_endpoint proxy_api_url = base_url[:base_url.index("/subscriptions/")].replace("https://", "") - return (f"wss://{proxy_api_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/containerApps/{name}" + debug_url = (f"wss://{proxy_api_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/containerApps/{name}" f"/revisions/{revision}/replicas/{replica}/debug" f"?targetContainer={container}") + if self.command: + debug_url += f"&command={self.command}" + return debug_url def read_debug_ssh(connection: WebSocketConnection, response_encodings): diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 06c526e877e..540f9e8d690 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -3591,7 +3591,7 @@ def list_maintenance_config(cmd, resource_group_name, env_name): return r -def containerapp_debug(cmd, resource_group_name, name, container=None, revision=None, replica=None): +def containerapp_debug(cmd, resource_group_name, name, container=None, revision=None, replica=None, command=None): logger.warning("Connecting...") conn = DebugWebSocketConnection( cmd=cmd, @@ -3599,7 +3599,8 @@ def containerapp_debug(cmd, resource_group_name, name, container=None, revision= name=name, revision=revision, replica=replica, - container=container + container=container, + command=command ) encodings = [SSH_DEFAULT_ENCODING, SSH_BACKUP_ENCODING]