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..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/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