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

[containerapp] Add new options for labels mode #8324

Open
wants to merge 9 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
6 changes: 5 additions & 1 deletion src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Release History
upcoming
++++++
* 'az containerapp debug': Open an SSH-like interactive shell within a container app debug console.
* 'az containerapp create': New target-label option for Labels revision mode.
* 'az containerapp up/update': New revisions-mode and target-label options for Labels revision mode.
* 'az containerapp label-history': New list and show commands to see label revision assignment history.
* 'az containerapp revision set-mode': New target-label option for Labels revision mode.

1.1.0b1
++++++
Expand All @@ -13,7 +17,7 @@ upcoming
* 'az containerapp create/update': `--yaml` support property pollingInterval and cooldownPeriod
* 'az containerapp session code-interpreter upload-file/list-files/show-file-content/show-file-metadata/delete-file': Support `--path` to specify the path of code interpreter session file resource
* 'az containerapp session code-interpreter': Update response payload format for api-version 2024-10-02-preview
* 'az containerapp env maintenance-config add/update/list/remove': Support environment maintenance config management
* '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
39 changes: 39 additions & 0 deletions src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,45 @@ class ContainerAppPreviewClient(ContainerAppClient):
api_version = PREVIEW_API_VERSION


class LabelHistoryPreviewClient:
api_version = PREVIEW_API_VERSION

@classmethod
def list(cls, cmd, resource_group_name, name):
history_list = []
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerapps/{}/labelhistory?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "GET", request_url, body=None)
j = r.json()
for route in j["value"]:
history_list.append(route)
return history_list

@classmethod
def show(cls, cmd, resource_group_name, name, label):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerapps/{}/labelhistory/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
label,
cls.api_version)

r = send_raw_request(cmd.cli_ctx, "GET", request_url, body=None)
return r.json()


class ContainerAppsJobPreviewClient(ContainerAppsJobClient):
api_version = PREVIEW_API_VERSION
LOG_STREAM_API_VERSION = "2023-11-02-preview"
Expand Down
27 changes: 27 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2198,3 +2198,30 @@
text: |
az containerapp debug -n MyContainerapp -g MyResourceGroup --revision MyRevision --replica MyReplica --container MyContainer
"""

helps['containerapp label-history'] = """
type: group
short-summary: Show the history for one or more labels on the Container App.
examples:
- name: Show Label History
text: |
az containerapp label-history show -n my-containerapp -g MyResourceGroup --label LabelName
"""

helps['containerapp label-history list'] = """
type: command
short-summary: List the history for all labels on the Container App.
examples:
- name: List All Label History
text: |
az containerapp label-history list -n my-containerapp -g MyResourceGroup
"""

helps['containerapp label-history show'] = """
type: command
short-summary: Show the history for a specific label on the Container App.
examples:
- name: Show Label History
text: |
az containerapp label-history show -n my-containerapp -g MyResourceGroup --label LabelName
"""
18 changes: 18 additions & 0 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ def load_arguments(self, _):

name_type = CLIArgumentType(options_list=['--name', '-n'])

with self.argument_context('containerapp', arg_group='Configuration') as c:
c.argument('revisions_mode', arg_type=get_enum_type(['single', 'multiple', 'labels']), help="The active revisions mode for the container app.")

with self.argument_context('containerapp create') as c:
c.argument('source', help="Local directory path containing the application source and Dockerfile for building the container image. Preview: If no Dockerfile is present, a container image is generated using buildpacks. If Docker is not running or buildpacks cannot be used, Oryx will be used to generate the image. See the supported Oryx runtimes here: https://aka.ms/SourceToCloudSupportedVersions.", is_preview=True)
c.argument('artifact', help="Local path to the application artifact for building the container image. See the supported artifacts here: https://aka.ms/SourceToCloudSupportedArtifacts.", is_preview=True)
c.argument('build_env_vars', nargs='*', help="A list of environment variable(s) for the build. Space-separated values in 'key=value' format.",
validator=validate_build_env_vars, is_preview=True)
c.argument('max_inactive_revisions', type=int, help="Max inactive revisions a Container App can have.", is_preview=True)
c.argument('registry_identity', help="The managed identity with which to authenticate to the Azure Container Registry (instead of username/password). Use 'system' for a system-defined identity, Use 'system-environment' for an environment level system-defined identity or a resource id for a user-defined environment/containerapp level identity. The managed identity should have been assigned acrpull permissions on the ACR before deployment (use 'az role assignment create --role acrpull ...').")
c.argument('target_label', help="The label to apply to new revisions. Required for revisions-mode 'labels'.", is_preview=True)

# Springboard
with self.argument_context('containerapp create', arg_group='Service Binding', is_preview=True) as c:
Expand Down Expand Up @@ -63,6 +67,7 @@ def load_arguments(self, _):
c.argument('build_env_vars', nargs='*', help="A list of environment variable(s) for the build. Space-separated values in 'key=value' format.",
validator=validate_build_env_vars, is_preview=True)
c.argument('max_inactive_revisions', type=int, help="Max inactive revisions a Container App can have.", is_preview=True)
c.argument('target_label', help="The label to apply to new revisions. Required for revisions-mode 'labels'.", is_preview=True)

# Springboard
with self.argument_context('containerapp update', arg_group='Service Binding', is_preview=True) as c:
Expand Down Expand Up @@ -247,6 +252,8 @@ def load_arguments(self, _):
validator=validate_build_env_vars, is_preview=True)
c.argument('custom_location_id', options_list=['--custom-location'], help="Resource ID of custom location. List using 'az customlocation list'.", is_preview=True)
c.argument('connected_cluster_id', help="Resource ID of connected cluster. List using 'az connectedk8s list'.", configured_default='connected_cluster_id', is_preview=True)
c.argument('revisions_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 revisions-mode 'labels'.", is_preview=True)

with self.argument_context('containerapp up', arg_group='Github Repo') as c:
c.argument('repo', help='Create an app via Github Actions. In the format: https://github.com/<owner>/<repository-name> or <owner>/<repository-name>')
Expand Down Expand Up @@ -464,3 +471,14 @@ def load_arguments(self, _):
help="The name of the container app revision. Default to the latest revision.")
c.argument('name', name_type, id_part=None, help="The name of the Containerapp.")
c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None)

with self.argument_context('containerapp label-history') as c:
c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None)
c.argument('name', name_type, id_part=None, help="The name of the Containerapp.")

with self.argument_context('containerapp label-history show') as c:
c.argument('label', help="The label name to show history for.")

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)
8 changes: 7 additions & 1 deletion src/containerapp/azext_containerapp/_up_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ def __init__(
registry_identity=None,
user_assigned=None,
system_assigned=None,
revisions_mode=None,
target_label=None,
):

super().__init__(cmd, name, resource_group, exists)
Expand All @@ -429,6 +431,8 @@ def __init__(
self.ingress = ingress
self.workload_profile_name = workload_profile_name
self.force_single_container_updates = force_single_container_updates
self.revisions_mode = revisions_mode
self.target_label = target_label

self.should_create_acr = False
self.acr: "AzureContainerRegistry" = None
Expand Down Expand Up @@ -465,7 +469,9 @@ def create(self, no_registry=False): # pylint: disable=arguments-differ
force_single_container_updates=self.force_single_container_updates,
registry_identity=self.registry_identity,
system_assigned=self.system_assigned,
user_assigned=self.user_assigned
user_assigned=self.user_assigned,
revisions_mode=self.revisions_mode,
target_label=self.target_label,
)

def set_force_single_container_updates(self, force_single_container_updates):
Expand Down
4 changes: 3 additions & 1 deletion src/containerapp/azext_containerapp/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


# called directly from custom method bc otherwise it disrupts the --environment auto RID functionality
def validate_create(registry_identity, registry_pass, registry_user, registry_server, no_wait, source=None, artifact=None, repo=None, yaml=None, environment_type=None):
def validate_create(registry_identity, registry_pass, registry_user, registry_server, no_wait, revisions_mode=None, target_label=None, source=None, artifact=None, repo=None, yaml=None, environment_type=None):
if source and repo:
raise MutuallyExclusiveArgumentError("Usage error: --source and --repo cannot be used together. Can either deploy from a local directory or a GitHub repository")
if (source or repo) and yaml:
Expand All @@ -54,6 +54,8 @@ def validate_create(registry_identity, registry_pass, registry_user, registry_se
raise InvalidArgumentValueError("--registry-identity must be an identity resource ID or 'system' or 'system-environment'")
if registry_identity and ACR_IMAGE_SUFFIX not in (registry_server or ""):
raise InvalidArgumentValueError("--registry-identity: expected an ACR registry (*.azurecr.io) for --registry-server")
if target_label and (not revisions_mode or revisions_mode.lower() != 'labels'):
raise InvalidArgumentValueError("--target-label must only be specified with --revisions-mode labels.")


def validate_runtime(runtime, enable_java_metrics, enable_java_agent):
Expand Down
11 changes: 11 additions & 0 deletions src/containerapp/azext_containerapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,14 @@ def load_command_table(self, args):
g.custom_command('update', 'update_maintenance_config')
g.custom_command('remove', 'remove_maintenance_config', confirmation=True)
g.custom_show_command('list', 'list_maintenance_config')

with self.command_group('containerapp label-history', is_preview=True) as g:
g.custom_show_command('show', 'show_labelhistory')
g.custom_command('list', 'list_labelhistory')

with self.command_group('containerapp revision') as g:
g.custom_command('set-mode', 'set_revision_mode', exception_handler=ex_handler_factory())

with self.command_group('containerapp revision label') as g:
g.custom_command('add', 'add_revision_label')
g.custom_command('remove', 'remove_revision_label')
27 changes: 21 additions & 6 deletions src/containerapp/azext_containerapp/containerapp_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ def get_argument_remove_all_env_vars(self):
def get_argument_from_revision(self):
return self.get_param("from_revision")

def get_argument_target_label(self):
return self.get_param("target_label")

def validate_arguments(self):
self.containerapp_def = None
try:
Expand Down Expand Up @@ -444,13 +447,18 @@ def construct_payload(self):
registries_def.append(registry)

if not self.get_argument_revision_suffix():
self.new_containerapp["properties"]["template"] = {} if "template" not in self.new_containerapp["properties"] else self.new_containerapp["properties"]["template"]
self.new_containerapp["properties"]["template"]["revisionSuffix"] = None
safe_set(self.new_containerapp, "properties", "template", "revisionSuffix", value=None)

if self.get_argument_revisions_mode():
safe_set(self.new_containerapp, "properties", "configuration", "activeRevisionsMode", value=self.get_argument_revisions_mode())

if self.get_argument_target_label():
safe_set(self.new_containerapp, "properties", "configuration", "targetLabel", value=self.get_argument_target_label())

def set_up_update_containerapp_yaml(self, name, file_name):
if self.get_argument_image() or self.get_argument_min_replicas() or self.get_argument_max_replicas() or \
self.get_argument_set_env_vars() or self.get_argument_remove_env_vars() or self.get_argument_replace_env_vars() or self.get_argument_remove_all_env_vars() or self.get_argument_cpu() or self.get_argument_memory() or \
self.get_argument_startup_command() or self.get_argument_args() or self.get_argument_tags():
self.get_argument_startup_command() or self.get_argument_args() or self.get_argument_tags() or self.get_argument_revisions_mode() or self.get_argument_target_label():
logger.warning(
'Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead')
yaml_containerapp = process_loaded_yaml(load_yaml_file(file_name))
Expand Down Expand Up @@ -664,6 +672,9 @@ def set_argument_registry_server(self, registry_server):
def set_argument_no_wait(self, no_wait):
self.set_param("no_wait", no_wait)

def get_argument_target_label(self):
return self.get_param("target_label")

# not craete role assignment if it's env system msi
def check_create_acrpull_role_assignment(self):
identity = self.get_argument_registry_identity()
Expand Down Expand Up @@ -797,6 +808,7 @@ def parent_construct_payload(self):
config_def = deepcopy(ConfigurationModel)
config_def["secrets"] = secrets_def
config_def["activeRevisionsMode"] = self.get_argument_revisions_mode()
config_def["targetLabel"] = self.get_argument_target_label()
Tratcher marked this conversation as resolved.
Show resolved Hide resolved
config_def["ingress"] = ingress_def
config_def["registries"] = [registries_def] if registries_def is not None else None
config_def["dapr"] = dapr_def
Expand Down Expand Up @@ -906,7 +918,9 @@ def parent_validate_arguments(self):
# end preview logic

def validate_create(self):
validate_create(self.get_argument_registry_identity(), self.get_argument_registry_pass(), self.get_argument_registry_user(), self.get_argument_registry_server(), self.get_argument_no_wait(), self.get_argument_source(), self.get_argument_artifact(), self.get_argument_repo(), self.get_argument_yaml(), self.get_argument_environment_type())
validate_create(self.get_argument_registry_identity(), self.get_argument_registry_pass(), self.get_argument_registry_user(), self.get_argument_registry_server(),
self.get_argument_no_wait(), self.get_argument_revisions_mode(), self.get_argument_target_label(), self.get_argument_source(), self.get_argument_artifact(),
self.get_argument_repo(), self.get_argument_yaml(), self.get_argument_environment_type())

def validate_arguments(self):
self.parent_validate_arguments()
Expand Down Expand Up @@ -1116,10 +1130,11 @@ def set_up_service_binds(self):
safe_set(self.containerapp_def, "properties", "template", "serviceBinds", value=service_bindings_def_list)

def set_up_create_containerapp_yaml(self, name, file_name):
# This list can not include anything that has a default value: transport, revisions_mode, environment_type
if self.get_argument_image() or self.get_argument_min_replicas() or self.get_argument_max_replicas() or self.get_argument_target_port() or self.get_argument_ingress() or \
self.get_argument_revisions_mode() or self.get_argument_secrets() or self.get_argument_env_vars() or self.get_argument_cpu() or self.get_argument_memory() or self.get_argument_registry_server() or \
self.get_argument_secrets() or self.get_argument_env_vars() or self.get_argument_cpu() or self.get_argument_memory() or self.get_argument_registry_server() or \
self.get_argument_registry_user() or self.get_argument_registry_pass() or self.get_argument_dapr_enabled() or self.get_argument_dapr_app_port() or self.get_argument_dapr_app_id() or \
self.get_argument_startup_command() or self.get_argument_args() or self.get_argument_tags():
self.get_argument_startup_command() or self.get_argument_args() or self.get_argument_tags() or self.get_argument_target_label():
not self.get_argument_disable_warnings() and logger.warning(
'Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead')

Expand Down
Loading
Loading