Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
Comment thread
p-bouchon marked this conversation as resolved.
Outdated

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"
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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):
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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:
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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))
Comment thread
p-bouchon marked this conversation as resolved.
Outdated

if no_wait:
return
elif r.status_code == 202:
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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):
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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]:
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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'] = """
Comment thread
p-bouchon marked this conversation as resolved.
type: command
short-summary: Add a Maintenance Configuration to a Container App Environment
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
examples:
- name: Configure an Env to use a maintenance config
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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'] = """
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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

@Greedygre Greedygre Nov 7, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please update the --environment, it still --env-name

"""

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.")
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
c.argument('duration', options_list=['--duration', '-d'], help="The duration in hours of the maintenance configuration.")
Comment thread
p-bouchon marked this conversation as resolved.
Outdated

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)
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
g.custom_command('show', 'show_maintenance_config')
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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)
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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):
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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):
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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:
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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()
Comment thread
p-bouchon marked this conversation as resolved.
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):
Comment thread
p-bouchon marked this conversation as resolved.
Outdated
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)
Comment thread
p-bouchon marked this conversation as resolved.
Outdated

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):
Comment thread
p-bouchon marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will you support customized name in the future? Current design is not very flexible for extensibility. Consider adding --name support for your commands with default value = "default" which will be extensible in the future.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @Juliehzl

#8190 (comment)

only one maintenance config of name "default" is allowed.
If we want to support name in the future, we can add name in that time. If we add it now, it will be exposed to customer.

@Greedygre Greedygre Nov 12, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@p-bouchon please correct me if I am wrong

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We currently have no plans to allow for non default names for maintenance configuration. If any were to be added, they would be specific non-customizable names to specify upgrades on specific aspects of the service

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