Skip to content

Commit 107fb34

Browse files
authored
{Containerapp} Add Maintenance Config Support (#8190)
1 parent 88009c6 commit 107fb34

File tree

10 files changed

+8996
-1
lines changed

10 files changed

+8996
-1
lines changed

src/containerapp/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ upcoming
77
* 'az containerapp create': Fix Role assignment error when the default Azure Container Registry could not be found
88
* Upgrade api-version to 2024-10-02-preview
99
* 'az containerapp create/update': `--yaml` support property pollingInterval and cooldownPeriod
10+
* 'az containerapp env maintenance-config add/update/list/remove': Support environment maintenance config management
1011
* 'az containerapp sessionpool create': Support managed identity when create session pool with --mi-system-assigned --mi-user-assigned
1112

1213
1.0.0b4

src/containerapp/azext_containerapp/_clients.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
HEADER_AZURE_ASYNC_OPERATION = "azure-asyncoperation"
3636
HEADER_LOCATION = "location"
3737
SESSION_RESOURCE = "https://dynamicsessions.io"
38+
MAINTENANCE_CONFIG_DEFAULT_NAME = "default"
3839

3940

4041
class GitHubActionPreviewClient(GitHubActionClient):
@@ -1396,3 +1397,74 @@ def list(cls, cmd, resource_group_name, environment_name):
13961397
dotNet_component_list.append(component)
13971398

13981399
return dotNet_component_list
1400+
1401+
1402+
class MaintenanceConfigPreviewClient():
1403+
api_version = PREVIEW_API_VERSION
1404+
maintenance_config_name = MAINTENANCE_CONFIG_DEFAULT_NAME
1405+
1406+
@classmethod
1407+
def list(cls, cmd, resource_group_name, environment_name):
1408+
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
1409+
sub_id = get_subscription_id(cmd.cli_ctx)
1410+
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/microsoft.app/managedenvironments/{}/maintenanceConfigurations/{}?api-version={}"
1411+
request_url = url_fmt.format(
1412+
management_hostname.strip('/'),
1413+
sub_id,
1414+
resource_group_name,
1415+
environment_name,
1416+
cls.maintenance_config_name,
1417+
cls.api_version)
1418+
1419+
r = send_raw_request(cmd.cli_ctx, "GET", request_url)
1420+
1421+
return r.json()
1422+
1423+
@classmethod
1424+
def create_or_update(cls, cmd, resource_group_name, environment_name, maintenance_config_envelope):
1425+
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
1426+
sub_id = get_subscription_id(cmd.cli_ctx)
1427+
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/microsoft.app/managedenvironments/{}/maintenanceConfigurations/{}?api-version={}"
1428+
request_url = url_fmt.format(
1429+
management_hostname.strip('/'),
1430+
sub_id,
1431+
resource_group_name,
1432+
environment_name,
1433+
cls.maintenance_config_name,
1434+
cls.api_version)
1435+
1436+
r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(maintenance_config_envelope))
1437+
1438+
if r.status_code == 201:
1439+
operation_url = r.headers.get(HEADER_AZURE_ASYNC_OPERATION)
1440+
poll_status(cmd, operation_url)
1441+
r = send_raw_request(cmd.cli_ctx, "GET", request_url)
1442+
if r.status_code == 202:
1443+
operation_url = r.headers.get(HEADER_LOCATION)
1444+
response = poll_results(cmd, operation_url)
1445+
if response is None:
1446+
raise ResourceNotFoundError("Could not find the maintenance config")
1447+
else:
1448+
return response
1449+
1450+
return r.json()
1451+
1452+
@classmethod
1453+
def remove(cls, cmd, resource_group_name, environment_name):
1454+
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
1455+
sub_id = get_subscription_id(cmd.cli_ctx)
1456+
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/microsoft.app/managedenvironments/{}/maintenanceConfigurations/{}?api-version={}"
1457+
request_url = url_fmt.format(
1458+
management_hostname.strip('/'),
1459+
sub_id,
1460+
resource_group_name,
1461+
environment_name,
1462+
cls.maintenance_config_name,
1463+
cls.api_version)
1464+
1465+
r = send_raw_request(cmd.cli_ctx, "DELETE", request_url)
1466+
1467+
if r.status_code in [200, 201, 202, 204]:
1468+
if r.status_code == 202:
1469+
operation_url = r.headers.get(HEADER_LOCATION)
1470+
poll_results(cmd, operation_url)

src/containerapp/azext_containerapp/_help.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,3 +2145,47 @@
21452145
az containerapp job registry set -n my-containerapp-job -g MyResourceGroup \\
21462146
--server MyContainerappJobRegistry.azurecr.io --identity system-environment
21472147
"""
2148+
2149+
# Maintenance Config Commands
2150+
helps['containerapp env maintenance-config'] = """
2151+
type: group
2152+
short-summary: Commands to manage Planned Maintenance for Container Apps
2153+
"""
2154+
2155+
helps['containerapp env maintenance-config add'] = """
2156+
type: command
2157+
short-summary: Add Planned Maintenance to a Container App Environment
2158+
examples:
2159+
- name: Configure a Container App Environment to use a Planned Maintenance
2160+
text: |
2161+
az containerapp env maintenance-config add --environment myEnv -g MyResourceGroup \\
2162+
--duration 10 --start-hour-utc 11 --weekday Sunday
2163+
"""
2164+
2165+
helps['containerapp env maintenance-config update'] = """
2166+
type: command
2167+
short-summary: Update Planned Maintenance in a Container App Environment
2168+
examples:
2169+
- name: Update the Planned Maintenance in a Container App Environment
2170+
text: |
2171+
az containerapp env maintenance-config update --environment myEnv -g MyResourceGroup \\
2172+
--duration 8 --start-hour-utc 12 --weekday Thursday
2173+
"""
2174+
2175+
helps['containerapp env maintenance-config list'] = """
2176+
type: command
2177+
short-summary: List Planned Maintenance in a Container App Environment
2178+
examples:
2179+
- name: List Planned Maintenance
2180+
text: |
2181+
az containerapp env maintenance-config list --environment myEnv -g MyResourceGroup
2182+
"""
2183+
2184+
helps['containerapp env maintenance-config remove'] = """
2185+
type: command
2186+
short-summary: Remove Planned Maintenance in a Container App Environment
2187+
examples:
2188+
- name: Remove Planned Maintenance
2189+
text: |
2190+
az containerapp env maintenance-config remove --environment myEnv -g MyResourceGroup
2191+
"""

src/containerapp/azext_containerapp/_models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,19 @@
293293
"tags": None
294294
}
295295

296+
MaintenanceConfiguration = {
297+
"name": "default",
298+
"properties": {
299+
"scheduledEntries": [
300+
{
301+
"weekDay": None,
302+
"startHourUtc": None,
303+
"durationHours": None
304+
}
305+
]
306+
}
307+
}
308+
296309
SessionPool = {
297310
"location": None,
298311
"properties": {

src/containerapp/azext_containerapp/_params.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,12 @@ def load_arguments(self, _):
370370
c.argument('max_replicas', type=int, help="Maximum number of replicas to run for the Java component.")
371371
c.argument('route_yaml', options_list=['--route-yaml', '--yaml'], help="Path to a .yaml file with the configuration of a Spring Cloud Gateway route. For an example, see https://aka.ms/gateway-for-spring-routes-yaml")
372372

373+
with self.argument_context('containerapp env maintenance-config') as c:
374+
c.argument('weekday', options_list=['--weekday', '-w'], arg_type=get_enum_type(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]), help="The weekday to schedule the maintenance configuration.")
375+
c.argument('start_hour_utc', options_list=['--start-hour-utc', '-s'], type=int, help="The hour to start the maintenance configuration. Valid value from 0 to 23.")
376+
c.argument('duration', options_list=['--duration', '-d'], type=int, help="The duration in hours of the maintenance configuration. Minimum value: 8. Maximum value: 24")
377+
c.argument('env_name', options_list=['--environment'], help="The environment name.")
378+
373379
with self.argument_context('containerapp job logs show') as c:
374380
c.argument('follow', help="Print logs in real time if present.", arg_type=get_three_state_flag())
375381
c.argument('tail', help="The number of past logs to print (0-300)", type=int, default=20)

src/containerapp/azext_containerapp/commands.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,9 @@ def load_command_table(self, args):
262262
g.custom_command('set', 'create_or_update_java_logger', supports_no_wait=True)
263263
g.custom_command('delete', 'delete_java_logger', supports_no_wait=True)
264264
g.custom_show_command('show', 'show_java_logger')
265+
266+
with self.command_group('containerapp env maintenance-config', is_preview=True) as g:
267+
g.custom_command('add', 'add_maintenance_config')
268+
g.custom_command('update', 'update_maintenance_config')
269+
g.custom_command('remove', 'remove_maintenance_config', confirmation=True)
270+
g.custom_show_command('list', 'list_maintenance_config')
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for license information.
5+
# --------------------------------------------------------------------------------------------
6+
# pylint: disable=line-too-long, broad-except, logging-format-interpolation
7+
8+
from copy import deepcopy
9+
from knack.log import get_logger
10+
from typing import Any, Dict
11+
12+
from azure.cli.core.azclierror import (ValidationError)
13+
from azure.cli.core.commands import AzCliCommand
14+
from azure.cli.command_modules.containerapp.base_resource import BaseResource
15+
16+
from ._models import MaintenanceConfiguration as MaintenanceConfigurationModel
17+
from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception
18+
19+
logger = get_logger(__name__)
20+
21+
22+
class ContainerappEnvMaintenanceConfigDecorator(BaseResource):
23+
def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str):
24+
super().__init__(cmd, client, raw_parameters, models)
25+
self.maintenance_config_def = deepcopy(MaintenanceConfigurationModel)
26+
self.existing_maintenance_config_def = None
27+
28+
def get_argument_environment_name(self):
29+
return self.get_param('env_name')
30+
31+
def get_argument_resource_group_name(self):
32+
return self.get_param('resource_group_name')
33+
34+
def get_argument_weekday(self):
35+
return self.get_param('weekday')
36+
37+
def get_argument_start_hour_utc(self):
38+
return self.get_param('start_hour_utc')
39+
40+
def get_argument_duration(self):
41+
return self.get_param('duration')
42+
43+
44+
class ContainerAppEnvMaintenanceConfigPreviewDecorator(ContainerappEnvMaintenanceConfigDecorator):
45+
def validate_arguments(self):
46+
if self.get_argument_start_hour_utc() is not None:
47+
if not (0 <= int(self.get_argument_start_hour_utc()) <= 23):
48+
raise ValidationError("Start hour must be an integer from 0 to 23")
49+
50+
if self.get_argument_duration() is not None:
51+
if not (8 <= int(self.get_argument_duration()) <= 24):
52+
raise ValidationError("Duration must be an integer from 8 to 24")
53+
54+
if self.get_argument_weekday() is not None:
55+
if self.get_argument_weekday().lower() not in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]:
56+
raise ValidationError("Weekday must be a day of the week")
57+
58+
def construct_payload(self, forUpdate=False):
59+
if forUpdate:
60+
self.existing_maintenance_config_def = self.client.list(
61+
cmd=self.cmd,
62+
resource_group_name=self.get_argument_resource_group_name(),
63+
environment_name=self.get_argument_environment_name())
64+
65+
self.maintenance_config_def = deepcopy(self.existing_maintenance_config_def)
66+
67+
if self.get_argument_start_hour_utc() is not None:
68+
self.maintenance_config_def["properties"]["scheduledEntries"][0]["startHourUtc"] = self.get_argument_start_hour_utc()
69+
if self.get_argument_duration() is not None:
70+
self.maintenance_config_def["properties"]["scheduledEntries"][0]["durationHours"] = self.get_argument_duration()
71+
if self.get_argument_weekday() is not None:
72+
self.maintenance_config_def["properties"]["scheduledEntries"][0]["weekDay"] = self.get_argument_weekday()
73+
74+
def create_or_update(self):
75+
try:
76+
return self.client.create_or_update(
77+
cmd=self.cmd,
78+
resource_group_name=self.get_argument_resource_group_name(),
79+
environment_name=self.get_argument_environment_name(),
80+
maintenance_config_envelope=self.maintenance_config_def)
81+
except Exception as e:
82+
handle_raw_exception(e)
83+
84+
def remove(self):
85+
try:
86+
return self.client.remove(
87+
cmd=self.cmd,
88+
resource_group_name=self.get_argument_resource_group_name(),
89+
environment_name=self.get_argument_environment_name())
90+
except Exception as e:
91+
handle_raw_exception(e)
92+
93+
def list(self):
94+
try:
95+
return self.client.list(
96+
cmd=self.cmd,
97+
resource_group_name=self.get_argument_resource_group_name(),
98+
environment_name=self.get_argument_environment_name())
99+
except Exception as e:
100+
handle_non_404_status_code_exception(e)
101+
return ""

src/containerapp/azext_containerapp/custom.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
from .containerapp_sessionpool_decorator import SessionPoolPreviewDecorator, SessionPoolCreateDecorator, SessionPoolUpdateDecorator
8585
from .containerapp_session_code_interpreter_decorator import SessionCodeInterpreterCommandsPreviewDecorator
8686
from .containerapp_job_registry_decorator import ContainerAppJobRegistryPreviewSetDecorator
87+
from .containerapp_env_maintenance_config_decorator import ContainerAppEnvMaintenanceConfigPreviewDecorator
8788
from .dotnet_component_decorator import DotNetComponentDecorator
8889
from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception
8990
from ._clients import (
@@ -102,7 +103,8 @@
102103
JavaComponentPreviewClient,
103104
SessionPoolPreviewClient,
104105
SessionCodeInterpreterPreviewClient,
105-
DotNetComponentPreviewClient
106+
DotNetComponentPreviewClient,
107+
MaintenanceConfigPreviewClient
106108
)
107109
from ._dev_service_utils import DevServiceUtils
108110
from ._models import (
@@ -3262,3 +3264,57 @@ def set_registry_job(cmd, name, resource_group_name, server, username=None, pass
32623264
containerapp_job_registry_set_decorator.construct_payload()
32633265
r = containerapp_job_registry_set_decorator.set()
32643266
return r
3267+
3268+
3269+
# maintenance config
3270+
def add_maintenance_config(cmd, resource_group_name, env_name, duration, start_hour_utc, weekday):
3271+
raw_parameters = locals()
3272+
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
3273+
cmd=cmd,
3274+
client=MaintenanceConfigPreviewClient,
3275+
raw_parameters=raw_parameters,
3276+
models=CONTAINER_APPS_SDK_MODELS
3277+
)
3278+
maintenance_config_decorator.construct_payload()
3279+
maintenance_config_decorator.validate_arguments()
3280+
r = maintenance_config_decorator.create_or_update()
3281+
return r
3282+
3283+
3284+
def update_maintenance_config(cmd, resource_group_name, env_name, duration=None, start_hour_utc=None, weekday=None):
3285+
raw_parameters = locals()
3286+
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
3287+
cmd=cmd,
3288+
client=MaintenanceConfigPreviewClient,
3289+
raw_parameters=raw_parameters,
3290+
models=CONTAINER_APPS_SDK_MODELS
3291+
)
3292+
forUpdate = True
3293+
maintenance_config_decorator.construct_payload(forUpdate)
3294+
maintenance_config_decorator.validate_arguments()
3295+
r = maintenance_config_decorator.create_or_update()
3296+
return r
3297+
3298+
3299+
def remove_maintenance_config(cmd, resource_group_name, env_name):
3300+
raw_parameters = locals()
3301+
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
3302+
cmd=cmd,
3303+
client=MaintenanceConfigPreviewClient,
3304+
raw_parameters=raw_parameters,
3305+
models=CONTAINER_APPS_SDK_MODELS
3306+
)
3307+
r = maintenance_config_decorator.remove()
3308+
return r
3309+
3310+
3311+
def list_maintenance_config(cmd, resource_group_name, env_name):
3312+
raw_parameters = locals()
3313+
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
3314+
cmd=cmd,
3315+
client=MaintenanceConfigPreviewClient,
3316+
raw_parameters=raw_parameters,
3317+
models=CONTAINER_APPS_SDK_MODELS
3318+
)
3319+
r = maintenance_config_decorator.list()
3320+
return r

0 commit comments

Comments
 (0)