Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Maintenance Config Support #8190

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ upcoming
* 'az containerapp containerapp create/up': `--registry-server` and `--source` use managed identity for image pull by default
* 'az containerapp containerapp create': `--registry-server` use managed identity for image pull by default. `--no-wait` will not take effect with system registry identity.
* 'az containerapp env java-component gateway-for-spring': Support create/update/show/delete Gateway for spring.
* 'az containerapp env maintenance-config': Support Add, Update, Show, Delete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you get ack from PMs with maintenance-config change since doc has different values

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version 1.0.0b4 has been released, please sync main branch and move the description to upcoming


1.0.0b3
++++++
Expand Down
96 changes: 96 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 @@ -1382,3 +1383,98 @@ def list(cls, cmd, resource_group_name, environment_name):
dotNet_component_list.append(component)

return dotNet_component_list


class MaintenanceConfigPreviewClient():
api_version = "2024-10-02-preview"
maintenance_config_name = MAINTENANCE_CONFIG_DEFAULT_NAME

@classmethod
def show(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 add(cls, cmd, resource_group_name, environment_name, maintenance_config_envelope, no_wait=False):
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 no_wait:
return r.json()
elif r.status_code == 201:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operation_url = r.headers.get(HEADER_AZURE_ASYNC_OPERATION)
poll_status(cmd, operation_url)
r = send_raw_request(cmd.cli_ctx, "GET", request_url)

return r.json()

@classmethod
def update(cls, cmd, resource_group_name, environment_name, maintenance_config_envelope, no_wait=False):
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 no_wait:
return
elif 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 delete(cls, cmd, resource_group_name, environment_name, no_wait=False):
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 no_wait:
return # API doesn't return JSON (it returns no content)
elif r.status_code in [200, 201, 202, 204]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

201 polling is not handled and also invalid for delete operations. Refer https://github.com/cloud-and-ai-microsoft/resource-provider-contract/blob/master/v1.0/async-api-reference.md#delete-resource-asynchronously

For this api, we only return 200. Still future proofing is good.

if r.status_code == 202:
operation_url = r.headers.get(HEADER_LOCATION)
poll_results(cmd, operation_url)
39 changes: 39 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2131,3 +2131,42 @@
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 add'] = """
type: command
short-summary: Add a Maintenance Configuration to a Container App Environment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May need to update description based PM final decision. Example:

Suggested change
short-summary: Add a Maintenance Configuration to a Container App Environment
short-summary: Add a Scheduled Maintenance to a Container App Environment

examples:
- name: Configure an Env to use a maintenance config
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: To have full name for descriptions. PM can contribute here

Suggested change
- name: Configure an Env to use a maintenance config
- name: Configure a Container App Environment to use a maintenance configuration

text: |
az containerapp env maintenance-config add --env-name myEnv -g MyResourceGroup \\
--duration 10 --start-hour-utc 11 --weekday Sunday
"""

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

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

helps['containerapp env maintenance-config delete'] = """
type: command
short-summary: Delete a Maintenance Configuration in a Container App Environment
examples:
- name: Delete a Maintenance Config
text: |
az containerapp env maintenance-config add --env-name 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
5 changes: 5 additions & 0 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ 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'], help="The hour to start the maintenance configuration.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
c.argument('start_hour_utc', options_list=['--start-hour-utc', '-s'], help="The hour to start the maintenance configuration.")
c.argument('start_hour_utc', options_list=['--start-hour-utc', '-s'], help="The hour to start the maintenance configuration. Valid value from 0 to 23")

c.argument('duration', options_list=['--duration', '-d'], help="The duration in hours of the maintenance configuration.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since, you have it in validation

Suggested change
c.argument('duration', options_list=['--duration', '-d'], help="The duration in hours of the maintenance configuration.")
c.argument('duration', options_list=['--duration', '-d'], help="The duration in hours of the maintenance configuration. Minimum value: 8")


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', supports_no_wait=True)
g.custom_command('update', 'update_maintenance_config', supports_no_wait=True)
g.custom_command('delete', 'delete_maintenance_config', supports_no_wait=True)
g.custom_command('show', 'show_maintenance_config')
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 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 azure.cli.core.azclierror import (ValidationError)
from copy import deepcopy
from knack.log import get_logger
from typing import Any, Dict

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 MaintenanceConfigDecorator(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 MaintenanceConfigPreviewDecorator(MaintenanceConfigDecorator):
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.show(
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 add(self):
try:
return self.client.add(
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,
no_wait=self.get_argument_no_wait())
except Exception as e:
handle_raw_exception(e)

def update(self):
try:
return self.client.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,
no_wait=self.get_argument_no_wait())
except Exception as e:
handle_raw_exception(e)

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

def show(self):
try:
return self.client.show(
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 @@ -83,6 +83,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_maintenance_config_decorator import MaintenanceConfigPreviewDecorator
from .dotnet_component_decorator import DotNetComponentDecorator
from ._client_factory import handle_raw_exception, handle_non_404_status_code_exception
from ._clients import (
Expand All @@ -101,7 +102,8 @@
JavaComponentPreviewClient,
SessionPoolPreviewClient,
SessionCodeInterpreterPreviewClient,
DotNetComponentPreviewClient
DotNetComponentPreviewClient,
MaintenanceConfigPreviewClient
)
from ._dev_service_utils import DevServiceUtils
from ._models import (
Expand Down Expand Up @@ -3258,3 +3260,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):
p-bouchon marked this conversation as resolved.
Show resolved Hide resolved
raw_parameters = locals()
maintenance_config_decorator = MaintenanceConfigPreviewDecorator(
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.add()
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 = MaintenanceConfigPreviewDecorator(
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.update()
return r


def delete_maintenance_config(cmd, resource_group_name, env_name):
raw_parameters = locals()
maintenance_config_decorator = MaintenanceConfigPreviewDecorator(
cmd=cmd,
client=MaintenanceConfigPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
r = maintenance_config_decorator.delete()
return r


def show_maintenance_config(cmd, resource_group_name, env_name):
raw_parameters = locals()
maintenance_config_decorator = MaintenanceConfigPreviewDecorator(
cmd=cmd,
client=MaintenanceConfigPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
r = maintenance_config_decorator.show()
return r
Loading
Loading