From 19892dac09efdfbe171fdf10cc3abd0739aa569f Mon Sep 17 00:00:00 2001 From: Dustin Wojciechowski Date: Mon, 24 Mar 2025 13:33:59 -0700 Subject: [PATCH 1/5] Updated to latest preview --- src/containerapp/azext_containerapp/_clients.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containerapp/azext_containerapp/_clients.py b/src/containerapp/azext_containerapp/_clients.py index e5022714801..1bc9f7aa792 100644 --- a/src/containerapp/azext_containerapp/_clients.py +++ b/src/containerapp/azext_containerapp/_clients.py @@ -26,7 +26,7 @@ logger = get_logger(__name__) -PREVIEW_API_VERSION = "2024-10-02-preview" +PREVIEW_API_VERSION = "2025-02-02-preview" POLLING_TIMEOUT = 1500 # how many seconds before exiting POLLING_SECONDS = 2 # how many seconds between requests POLLING_TIMEOUT_FOR_MANAGED_CERTIFICATE = 1500 # how many seconds before exiting From c9da7de870040a956c8f94c8e557c9c908cade9d Mon Sep 17 00:00:00 2001 From: Dustin Wojciechowski Date: Mon, 24 Mar 2025 14:52:52 -0700 Subject: [PATCH 2/5] Implementations for show and update --- src/containerapp/azext_containerapp/_help.py | 18 ++++++ .../azext_containerapp/_params.py | 11 ++++ .../azext_containerapp/commands.py | 4 ++ src/containerapp/azext_containerapp/custom.py | 63 +++++++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index deb74eb46be..8316123d8a4 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -2357,3 +2357,21 @@ text: | az containerapp env http-route-config delete -g MyResourceGroup -n MyEnvironment -r configname """ + +helps['containerapp env ingress show'] = """ + type: command + short-summary: Show the ingress settings for the environment. + examples: + - name: Show the ingress settings for the environment. + text: | + az containerapp env ingress show -g MyResourceGroup -n MyEnvironment +""" + +helps['containerapp env ingress update'] = """ + type: command + short-summary: Update the ingress settings for the environment. + examples: + - name: Update the ingress settings for the environment. + text: | + az containerapp env ingress update -g MyResourceGroup -n MyEnvironment -w WorkloadProfileName +""" diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 82271348eb2..8c4dbb29180 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -500,3 +500,14 @@ def load_arguments(self, _): with self.argument_context('containerapp revision set-mode') as c: c.argument('mode', arg_type=get_enum_type(['single', 'multiple', 'labels']), help="The active revisions mode for the container app.") c.argument('target_label', help="The label to apply to new revisions. Required for revision mode 'labels'.", is_preview=True) + + with self.argument_context('containerapp env ingress') as c: + c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('name', options_list=['--environment'], help="The name of the managed environment.") + c.argument('workload_profile_name', options_list=['--workload-profile-name'], help="The name of the workload profile.") + c.argument('min_replicas', options_list=['--min-replicas'], type=int, help="Minimum number of replicas to run.") + c.argument('max_replicas', options_list=['--max-replicas'], type=int, help="Maximum number of replicas to run.") + c.argument('termination_grace_period', options_list=['--termination-grace-period'], type=int, help="Time given during shutdown to finish requests before cancelling.") + c.argument('request_idle_timeout', options_list=['--request-idle-timeout'], type=int, help="Timeout (in minutes) for idle requests.") + c.argument('header_count_limit', options_list=['--header-count-limit'], type=int, help="Limit for header count.") + diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 3967b14a0b4..b88fd162ae1 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -291,3 +291,7 @@ def load_command_table(self, args): with self.command_group('containerapp revision label') as g: g.custom_command('add', 'add_revision_label') g.custom_command('remove', 'remove_revision_label') + + with self.command_group('containerapp env ingress', is_preview=True) as g: + g.custom_show_command('show', 'show_environment_ingress') + g.custom_command('update', 'update_environment_ingress') diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index bc23b9e284c..f4a44599d52 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -3709,3 +3709,66 @@ def remove_revision_label(cmd, resource_group_name, name, label, no_wait=False): return r['properties']['configuration']['ingress']['traffic'] except Exception as e: handle_raw_exception(e) + + +def show_environment_ingress(cmd, environment, resource_group_name): + _validate_subscription_registered(cmd, CONTAINER_APPS_RP) + + try: + env = ManagedEnvironmentPreviewClient.show(cmd, resource_group_name, environment) + if not env: + raise ResourceNotFoundError(f"The containerapp environment '{environment}' does not exist") + + ingress_config = safe_get(env, "properties", "ingressConfiguration") + if not ingress_config: + return {"message": "No ingress configuration found for this environment"} + + return ingress_config + except Exception as e: + handle_raw_exception(e) + + +def update_environment_ingress(cmd, environment, resource_group_name, workload_profile_name, min_replicas, max_replicas, termination_grace_period, request_idle_timeout, header_count_limit, no_wait=False): + _validate_subscription_registered(cmd, CONTAINER_APPS_RP) + + try: + env = ManagedEnvironmentPreviewClient.show(cmd, resource_group_name, environment) + if not env: + raise ResourceNotFoundError(f"The containerapp environment '{environment}' does not exist") + + env_patch = {} + ingress_config = {} + + # Only set values that were specified + if workload_profile_name is not None: + ingress_config["workloadProfileName"] = workload_profile_name + if min_replicas is not None: + ingress_config["minReplicas"] = min_replicas + if max_replicas is not None: + ingress_config["maxReplicas"] = max_replicas + if termination_grace_period is not None: + ingress_config["terminationGracePeriod"] = termination_grace_period + if request_idle_timeout is not None: + ingress_config["requestIdleTimeout"] = request_idle_timeout + if header_count_limit is not None: + ingress_config["headerCountLimit"] = header_count_limit + + # Only add ingressConfiguration to the patch if any values were specified + if ingress_config: + safe_set(env_patch, "properties", "ingressConfiguration", value=ingress_config) + else: + return {"message": "No changes specified for ingress configuration"} + + # Update the environment with the patched ingress configuration + result = ManagedEnvironmentPreviewClient.update( + cmd=cmd, + resource_group_name=resource_group_name, + name=environment, + managed_environment_envelope=env_patch, + no_wait=no_wait + ) + + return safe_get(result, "properties", "ingressConfiguration") + + except Exception as e: + handle_raw_exception(e) From 862a714c306127074e09daa0c0ede6b63d9854b2 Mon Sep 17 00:00:00 2001 From: Dustin Wojciechowski Date: Fri, 18 Apr 2025 10:09:52 -0700 Subject: [PATCH 3/5] Test created --- .../latest/test_containerapp_commands.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py index 95fd0cddf29..5776532fc9b 100644 --- a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py +++ b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py @@ -638,6 +638,55 @@ def test_containerapp_cors_policy(self, resource_group): JMESPathCheck('corsPolicy', None), ]) + @AllowLargeResponse(8192) + @ResourceGroupPreparer(location="westeurope") + def test_containerapp_env_ingress_commands(self, resource_group): + self.cmd('configure --defaults location={}'.format(TEST_LOCATION)) + + env_name = self.create_random_name(prefix='containerapp-env', length=24) + logs_workspace_name = self.create_random_name(prefix='containerapp-env', length=24) + + logs_workspace_id = self.cmd('monitor log-analytics workspace create -g {} -n {} -l eastus'.format(resource_group, logs_workspace_name)).get_output_in_json()["customerId"] + logs_workspace_key = self.cmd('monitor log-analytics workspace get-shared-keys -g {} -n {}'.format(resource_group, logs_workspace_name)).get_output_in_json()["primarySharedKey"] + + self.cmd('containerapp env create -g {} -n {} --logs-workspace-id {} --logs-workspace-key {}'.format(resource_group, env_name, logs_workspace_id, logs_workspace_key)) + + containerapp_env = self.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json() + + while containerapp_env["properties"]["provisioningState"].lower() == "waiting": + time.sleep(5) + containerapp_env = self.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json() + + ingress_config = self.cmd('containerapp env ingress show --environment {} -g {}'.format(env_name, resource_group), checks=[ + JMESPathCheck('properties', JMESPathCheckExists()), + ]).get_output_in_json() + + self.cmd('containerapp env ingress update --environment {} -g {} --workload-profile-name Consumption --min-replicas 2 --max-replicas 5'.format( + env_name, resource_group), checks=[ + JMESPathCheck('properties.ingressConfiguration.workloadProfileName', 'Consumption'), + JMESPathCheck('properties.ingressConfiguration.minReplicas', 2), + JMESPathCheck('properties.ingressConfiguration.maxReplicas', 5), + ]) + + self.cmd('containerapp env ingress show --environment {} -g {}'.format(env_name, resource_group), checks=[ + JMESPathCheck('properties.ingressConfiguration.workloadProfileName', 'Consumption'), + JMESPathCheck('properties.ingressConfiguration.minReplicas', 2), + JMESPathCheck('properties.ingressConfiguration.maxReplicas', 5), + ]) + + self.cmd('containerapp env ingress update --environment {} -g {} --termination-grace-period 45 --request-idle-timeout 180 --header-count-limit 40'.format( + env_name, resource_group), checks=[ + JMESPathCheck('properties.ingressConfiguration.workloadProfileName', 'Consumption'), + JMESPathCheck('properties.ingressConfiguration.minReplicas', 2), + JMESPathCheck('properties.ingressConfiguration.maxReplicas', 5), + JMESPathCheck('properties.ingressConfiguration.terminationGracePeriod', 45), + JMESPathCheck('properties.ingressConfiguration.requestIdleTimeout', 180), + JMESPathCheck('properties.ingressConfiguration.headerCountLimit', 40), + ]) + + # Clean up + self.cmd(f'containerapp env delete -g {resource_group} -n {env_name} --yes') + class ContainerappCustomDomainTests(ScenarioTest): def __init__(self, *arg, **kwargs): From ded2bbe581b8a007cfe3c5b6395ffb0fce86131c Mon Sep 17 00:00:00 2001 From: Dustin Wojciechowski Date: Fri, 18 Apr 2025 15:19:41 -0700 Subject: [PATCH 4/5] Added restore-defaults --- src/containerapp/azext_containerapp/_help.py | 9 +++++ .../azext_containerapp/commands.py | 1 + src/containerapp/azext_containerapp/custom.py | 34 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 8316123d8a4..c8817ac7a04 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -2375,3 +2375,12 @@ text: | az containerapp env ingress update -g MyResourceGroup -n MyEnvironment -w WorkloadProfileName """ + +helps['containerapp env ingress restore-defaults'] = """ + type: command + short-summary: Reset the ingress settings to default values. + examples: + - name: Reset the ingress settings for the environment to its default values + text: | + az containerapp env ingress restore-defaults -g MyResourceGroup -n MyEnvironment -w WorkloadProfileName +""" diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index b88fd162ae1..f1d9ab276a1 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -295,3 +295,4 @@ def load_command_table(self, args): with self.command_group('containerapp env ingress', is_preview=True) as g: g.custom_show_command('show', 'show_environment_ingress') g.custom_command('update', 'update_environment_ingress') + g.custom_command('restore-defaults', 'reset_environment_ingress_to_defaults') diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index f4a44599d52..76e93cff739 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -3772,3 +3772,37 @@ def update_environment_ingress(cmd, environment, resource_group_name, workload_p except Exception as e: handle_raw_exception(e) + +def reset_environment_ingress_to_defaults(cmd, environment, resource_group_name, workload_profile_name, no_wait=False): + """Reset environment ingress configuration to default values. + + :param cmd: Command context + :param environment: Name of the Container App environment + :param resource_group_name: Name of resource group + :param no_wait: Do not wait for the long-running operation to finish + :return: The updated ingress configuration + """ + _validate_subscription_registered(cmd, CONTAINER_APPS_RP) + + # Default values for environment ingress + default_min_replicas = 2 + default_max_replicas = 10 + default_termination_grace_period = 8 + default_request_idle_timeout = 4 + default_header_count_limit = 100 + + logger.warning("Resetting environment ingress configuration to default values") + + # Call existing update method with default values + return update_environment_ingress( + cmd=cmd, + environment=environment, + resource_group_name=resource_group_name, + workload_profile_name=workload_profile_name, + min_replicas=default_min_replicas, + max_replicas=default_max_replicas, + termination_grace_period=default_termination_grace_period, + request_idle_timeout=default_request_idle_timeout, + header_count_limit=default_header_count_limit, + no_wait=no_wait + ) From c77640113ab38bde93e08a91de6e9015520b3338 Mon Sep 17 00:00:00 2001 From: Dustin Wojciechowski Date: Fri, 18 Apr 2025 15:38:15 -0700 Subject: [PATCH 5/5] Added test for restore-defaults --- .../tests/latest/test_containerapp_commands.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py index 5776532fc9b..d005fa620b0 100644 --- a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py +++ b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_commands.py @@ -684,6 +684,24 @@ def test_containerapp_env_ingress_commands(self, resource_group): JMESPathCheck('properties.ingressConfiguration.headerCountLimit', 40), ]) + self.cmd('containerapp env ingress restore-defaults --environment {} -g {}'.format(env_name, resource_group), checks=[ + JMESPathCheck('properties.ingressConfiguration.workloadProfileName', 'Consumption'), + JMESPathCheck('properties.ingressConfiguration.minReplicas', 2), + JMESPathCheck('properties.ingressConfiguration.maxReplicas', 10), + JMESPathCheck('properties.ingressConfiguration.terminationGracePeriod', 8), + JMESPathCheck('properties.ingressConfiguration.requestIdleTimeout', 4), + JMESPathCheck('properties.ingressConfiguration.headerCountLimit', 100), + ]) + + self.cmd('containerapp env ingress show --environment {} -g {}'.format(env_name, resource_group), checks=[ + JMESPathCheck('properties.ingressConfiguration.workloadProfileName', 'Consumption'), + JMESPathCheck('properties.ingressConfiguration.minReplicas', 2), + JMESPathCheck('properties.ingressConfiguration.maxReplicas', 10), + JMESPathCheck('properties.ingressConfiguration.terminationGracePeriod', 480), + JMESPathCheck('properties.ingressConfiguration.requestIdleTimeout', 240), + JMESPathCheck('properties.ingressConfiguration.headerCountLimit', 100), + ]) + # Clean up self.cmd(f'containerapp env delete -g {resource_group} -n {env_name} --yes')