diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index bddf68a4143..00501b117a6 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -4,6 +4,11 @@ Release History =============== upcoming ++++++ +* 'az containerapp function show/list': New command group to list and show functions in container apps. +* '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..dbc74a11af8 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 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={}" + 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 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={}" + 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 @@ -1698,3 +1770,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 a8a362998d0..69b0089723e 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,83 @@ az containerapp replica count -n my-containerapp -g MyResourceGroup """ +helps['containerapp function'] = """ + type: group + short-summary: Commands related to Azure Function on Azure Container Apps. +""" + +helps['containerapp function list'] = """ + type: command + 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: | + az containerapp function list -n my-containerapp -g MyResourceGroup + - name: List all functions for a specific revision + text: | + az containerapp function list -n my-containerapp -g MyResourceGroup --revision-name MyRevision +""" + +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: | + 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: | + 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 function show -n my-containerapp -g MyResourceGroup --function-name MyFunction --revision-name MyRevision +""" + +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 98e644937fa..2afa3a04ae1 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -530,3 +530,33 @@ 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') 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.") + + with self.argument_context('containerapp function list') as c: + 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 function 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. It is required if container app is running in multiple active revision mode.") + + with self.argument_context('containerapp function list-keys') as c: + 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('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('revision', options_list=['--revision'], help="The name of the container app revision.") + + with self.argument_context('containerapp function update-hostkeys') as c: + 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.") 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/commands.py b/src/containerapp/azext_containerapp/commands.py index 996219c6465..a90ad7daaf6 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 function', 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') @@ -297,3 +301,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..8a09d2ec6c1 --- /dev/null +++ b/src/containerapp/azext_containerapp/containerapp_function_keys_decorator.py @@ -0,0 +1,185 @@ +# 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 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) + + def get_argument_function_name(self): + return self.get_param("function_name") + + def get_argument_revision_name(self): + return self.get_param("revision_name") + + 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): + super().__init__(cmd, client, raw_parameters, models) + + def list_keys(self): + """List keys for a specific function""" + try: + resource_group_name, name, revision_name, function_name = self.validate_function_arguments() + + 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(ContainerAppFunctionKeysDecorator): + """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 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: + resource_group_name, name, revision_name, function_name, key_name = self.validate_update_arguments() + key_value = self.get_argument_key_value() + + 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(ContainerAppFunctionKeysDecorator): + """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, name, revision_name = self.validate_common_arguments() + + 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(ContainerAppFunctionKeysDecorator): + """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 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: + resource_group_name, name, revision_name, key_type, key_name = self.validate_host_key_arguments() + key_value = self.get_argument_key_value() + + 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/containerapp_functions_decorator.py b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py new file mode 100644 index 00000000000..12d5c63e0df --- /dev/null +++ b/src/containerapp/azext_containerapp/containerapp_functions_decorator.py @@ -0,0 +1,125 @@ +# 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 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 + 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 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.") + + 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 55521eb2163..540f9e8d690 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -93,6 +93,16 @@ 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 .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 +124,8 @@ DotNetComponentPreviewClient, MaintenanceConfigPreviewClient, HttpRouteConfigPreviewClient, - LabelHistoryPreviewClient + LabelHistoryPreviewClient, + ContainerAppFunctionsPreviewClient ) from ._dev_service_utils import DevServiceUtils from ._models import ( @@ -3580,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, @@ -3588,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] @@ -3939,3 +3951,86 @@ 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() + +# 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() +