Skip to content

Commit

Permalink
{Containerapp} Add Maintenance Config Support (Azure#8190)
Browse files Browse the repository at this point in the history
  • Loading branch information
p-bouchon authored Nov 12, 2024
1 parent 88009c6 commit 107fb34
Show file tree
Hide file tree
Showing 10 changed files with 8,996 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ upcoming
* 'az containerapp create': Fix Role assignment error when the default Azure Container Registry could not be found
* Upgrade api-version to 2024-10-02-preview
* 'az containerapp create/update': `--yaml` support property pollingInterval and cooldownPeriod
* 'az containerapp env maintenance-config add/update/list/remove': Support environment maintenance config management
* 'az containerapp sessionpool create': Support managed identity when create session pool with --mi-system-assigned --mi-user-assigned

1.0.0b4
Expand Down
72 changes: 72 additions & 0 deletions src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
HEADER_AZURE_ASYNC_OPERATION = "azure-asyncoperation"
HEADER_LOCATION = "location"
SESSION_RESOURCE = "https://dynamicsessions.io"
MAINTENANCE_CONFIG_DEFAULT_NAME = "default"


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

return dotNet_component_list


class MaintenanceConfigPreviewClient():
api_version = PREVIEW_API_VERSION
maintenance_config_name = MAINTENANCE_CONFIG_DEFAULT_NAME

@classmethod
def list(cls, cmd, resource_group_name, environment_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/managedenvironments/{}/maintenanceConfigurations/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
cls.maintenance_config_name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "GET", request_url)

return r.json()

@classmethod
def create_or_update(cls, cmd, resource_group_name, environment_name, maintenance_config_envelope):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/microsoft.app/managedenvironments/{}/maintenanceConfigurations/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
cls.maintenance_config_name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(maintenance_config_envelope))

if r.status_code == 201:
operation_url = r.headers.get(HEADER_AZURE_ASYNC_OPERATION)
poll_status(cmd, operation_url)
r = send_raw_request(cmd.cli_ctx, "GET", request_url)
if r.status_code == 202:
operation_url = r.headers.get(HEADER_LOCATION)
response = poll_results(cmd, operation_url)
if response is None:
raise ResourceNotFoundError("Could not find the maintenance config")
else:
return response

return r.json()

@classmethod
def remove(cls, cmd, resource_group_name, environment_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/managedenvironments/{}/maintenanceConfigurations/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
environment_name,
cls.maintenance_config_name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "DELETE", request_url)

if r.status_code in [200, 201, 202, 204]:
if r.status_code == 202:
operation_url = r.headers.get(HEADER_LOCATION)
poll_results(cmd, operation_url)
44 changes: 44 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2145,3 +2145,47 @@
az containerapp job registry set -n my-containerapp-job -g MyResourceGroup \\
--server MyContainerappJobRegistry.azurecr.io --identity system-environment
"""

# Maintenance Config Commands
helps['containerapp env maintenance-config'] = """
type: group
short-summary: Commands to manage Planned Maintenance for Container Apps
"""

helps['containerapp env maintenance-config add'] = """
type: command
short-summary: Add Planned Maintenance to a Container App Environment
examples:
- name: Configure a Container App Environment to use a Planned Maintenance
text: |
az containerapp env maintenance-config add --environment myEnv -g MyResourceGroup \\
--duration 10 --start-hour-utc 11 --weekday Sunday
"""

helps['containerapp env maintenance-config update'] = """
type: command
short-summary: Update Planned Maintenance in a Container App Environment
examples:
- name: Update the Planned Maintenance in a Container App Environment
text: |
az containerapp env maintenance-config update --environment myEnv -g MyResourceGroup \\
--duration 8 --start-hour-utc 12 --weekday Thursday
"""

helps['containerapp env maintenance-config list'] = """
type: command
short-summary: List Planned Maintenance in a Container App Environment
examples:
- name: List Planned Maintenance
text: |
az containerapp env maintenance-config list --environment myEnv -g MyResourceGroup
"""

helps['containerapp env maintenance-config remove'] = """
type: command
short-summary: Remove Planned Maintenance in a Container App Environment
examples:
- name: Remove Planned Maintenance
text: |
az containerapp env maintenance-config remove --environment myEnv -g MyResourceGroup
"""
13 changes: 13 additions & 0 deletions src/containerapp/azext_containerapp/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,19 @@
"tags": None
}

MaintenanceConfiguration = {
"name": "default",
"properties": {
"scheduledEntries": [
{
"weekDay": None,
"startHourUtc": None,
"durationHours": None
}
]
}
}

SessionPool = {
"location": None,
"properties": {
Expand Down
6 changes: 6 additions & 0 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ def load_arguments(self, _):
c.argument('max_replicas', type=int, help="Maximum number of replicas to run for the Java component.")
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")

with self.argument_context('containerapp env maintenance-config') as c:
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.")
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.")
c.argument('duration', options_list=['--duration', '-d'], type=int, help="The duration in hours of the maintenance configuration. Minimum value: 8. Maximum value: 24")
c.argument('env_name', options_list=['--environment'], help="The environment name.")

with self.argument_context('containerapp job logs show') as c:
c.argument('follow', help="Print logs in real time if present.", arg_type=get_three_state_flag())
c.argument('tail', help="The number of past logs to print (0-300)", type=int, default=20)
Expand Down
6 changes: 6 additions & 0 deletions src/containerapp/azext_containerapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,9 @@ def load_command_table(self, args):
g.custom_command('set', 'create_or_update_java_logger', supports_no_wait=True)
g.custom_command('delete', 'delete_java_logger', supports_no_wait=True)
g.custom_show_command('show', 'show_java_logger')

with self.command_group('containerapp env maintenance-config', is_preview=True) as g:
g.custom_command('add', 'add_maintenance_config')
g.custom_command('update', 'update_maintenance_config')
g.custom_command('remove', 'remove_maintenance_config', confirmation=True)
g.custom_show_command('list', 'list_maintenance_config')
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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 copy import deepcopy
from knack.log import get_logger
from typing import Any, Dict

from azure.cli.core.azclierror import (ValidationError)
from azure.cli.core.commands import AzCliCommand
from azure.cli.command_modules.containerapp.base_resource import BaseResource

from ._models import MaintenanceConfiguration as MaintenanceConfigurationModel
from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception

logger = get_logger(__name__)


class ContainerappEnvMaintenanceConfigDecorator(BaseResource):
def __init__(self, cmd: AzCliCommand, client: Any, raw_parameters: Dict, models: str):
super().__init__(cmd, client, raw_parameters, models)
self.maintenance_config_def = deepcopy(MaintenanceConfigurationModel)
self.existing_maintenance_config_def = None

def get_argument_environment_name(self):
return self.get_param('env_name')

def get_argument_resource_group_name(self):
return self.get_param('resource_group_name')

def get_argument_weekday(self):
return self.get_param('weekday')

def get_argument_start_hour_utc(self):
return self.get_param('start_hour_utc')

def get_argument_duration(self):
return self.get_param('duration')


class ContainerAppEnvMaintenanceConfigPreviewDecorator(ContainerappEnvMaintenanceConfigDecorator):
def validate_arguments(self):
if self.get_argument_start_hour_utc() is not None:
if not (0 <= int(self.get_argument_start_hour_utc()) <= 23):
raise ValidationError("Start hour must be an integer from 0 to 23")

if self.get_argument_duration() is not None:
if not (8 <= int(self.get_argument_duration()) <= 24):
raise ValidationError("Duration must be an integer from 8 to 24")

if self.get_argument_weekday() is not None:
if self.get_argument_weekday().lower() not in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]:
raise ValidationError("Weekday must be a day of the week")

def construct_payload(self, forUpdate=False):
if forUpdate:
self.existing_maintenance_config_def = self.client.list(
cmd=self.cmd,
resource_group_name=self.get_argument_resource_group_name(),
environment_name=self.get_argument_environment_name())

self.maintenance_config_def = deepcopy(self.existing_maintenance_config_def)

if self.get_argument_start_hour_utc() is not None:
self.maintenance_config_def["properties"]["scheduledEntries"][0]["startHourUtc"] = self.get_argument_start_hour_utc()
if self.get_argument_duration() is not None:
self.maintenance_config_def["properties"]["scheduledEntries"][0]["durationHours"] = self.get_argument_duration()
if self.get_argument_weekday() is not None:
self.maintenance_config_def["properties"]["scheduledEntries"][0]["weekDay"] = self.get_argument_weekday()

def create_or_update(self):
try:
return self.client.create_or_update(
cmd=self.cmd,
resource_group_name=self.get_argument_resource_group_name(),
environment_name=self.get_argument_environment_name(),
maintenance_config_envelope=self.maintenance_config_def)
except Exception as e:
handle_raw_exception(e)

def remove(self):
try:
return self.client.remove(
cmd=self.cmd,
resource_group_name=self.get_argument_resource_group_name(),
environment_name=self.get_argument_environment_name())
except Exception as e:
handle_raw_exception(e)

def list(self):
try:
return self.client.list(
cmd=self.cmd,
resource_group_name=self.get_argument_resource_group_name(),
environment_name=self.get_argument_environment_name())
except Exception as e:
handle_non_404_status_code_exception(e)
return ""
58 changes: 57 additions & 1 deletion src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
from .containerapp_sessionpool_decorator import SessionPoolPreviewDecorator, SessionPoolCreateDecorator, SessionPoolUpdateDecorator
from .containerapp_session_code_interpreter_decorator import SessionCodeInterpreterCommandsPreviewDecorator
from .containerapp_job_registry_decorator import ContainerAppJobRegistryPreviewSetDecorator
from .containerapp_env_maintenance_config_decorator import ContainerAppEnvMaintenanceConfigPreviewDecorator
from .dotnet_component_decorator import DotNetComponentDecorator
from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception
from ._clients import (
Expand All @@ -102,7 +103,8 @@
JavaComponentPreviewClient,
SessionPoolPreviewClient,
SessionCodeInterpreterPreviewClient,
DotNetComponentPreviewClient
DotNetComponentPreviewClient,
MaintenanceConfigPreviewClient
)
from ._dev_service_utils import DevServiceUtils
from ._models import (
Expand Down Expand Up @@ -3262,3 +3264,57 @@ def set_registry_job(cmd, name, resource_group_name, server, username=None, pass
containerapp_job_registry_set_decorator.construct_payload()
r = containerapp_job_registry_set_decorator.set()
return r


# maintenance config
def add_maintenance_config(cmd, resource_group_name, env_name, duration, start_hour_utc, weekday):
raw_parameters = locals()
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
cmd=cmd,
client=MaintenanceConfigPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
maintenance_config_decorator.construct_payload()
maintenance_config_decorator.validate_arguments()
r = maintenance_config_decorator.create_or_update()
return r


def update_maintenance_config(cmd, resource_group_name, env_name, duration=None, start_hour_utc=None, weekday=None):
raw_parameters = locals()
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
cmd=cmd,
client=MaintenanceConfigPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
forUpdate = True
maintenance_config_decorator.construct_payload(forUpdate)
maintenance_config_decorator.validate_arguments()
r = maintenance_config_decorator.create_or_update()
return r


def remove_maintenance_config(cmd, resource_group_name, env_name):
raw_parameters = locals()
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
cmd=cmd,
client=MaintenanceConfigPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
r = maintenance_config_decorator.remove()
return r


def list_maintenance_config(cmd, resource_group_name, env_name):
raw_parameters = locals()
maintenance_config_decorator = ContainerAppEnvMaintenanceConfigPreviewDecorator(
cmd=cmd,
client=MaintenanceConfigPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
r = maintenance_config_decorator.list()
return r
Loading

0 comments on commit 107fb34

Please sign in to comment.