Skip to content

Commit

Permalink
containerapp-compose extension with support for compose files (#4711)
Browse files Browse the repository at this point in the history
  • Loading branch information
smurawski authored Jun 4, 2022
1 parent a1d3dd8 commit c6d86ee
Show file tree
Hide file tree
Showing 47 changed files with 51,444 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,5 @@
/src/scvmm/ @nascarsayan

/src/spring/ @yuwzho

/src/containerapp-compose/ @smurawski @jldeen
26 changes: 26 additions & 0 deletions scripts/ci/credscan/CredScanSuppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,32 @@
"src\\containerapp\\azext_containerapp\\tests\\latest\test_containerapp_env_commands.py"
],
"_justification": "Dummy resources' keys left during testing Microsoft.App (required for log-analytics to create managedEnvironments)"
},
{
"file": [
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_basic_no_existing_resources.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_resources_from_both_cpus_and_deploy_cpu.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_environment.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_resources_from_deploy_cpu.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_environment_prompt.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_resources_from_service_cpus.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_ingress_both.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_secrets.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_ingress_external.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_secrets_and_existing_environment.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_ingress_internal.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_secrets_and_existing_environment_conflict.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_ingress_prompt.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_transport_arg.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_registry_all_args.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_with_command_list.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_registry_server_arg_only.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_with_command_list_and_entrypoint.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_replicas_global_scale.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_with_command_string.yaml",
"src\\containerapp-compose\\azext_containerapp_compose\\tests\\latest\\recordings\\test_containerapp_compose_create_with_replicas_replicated_mode.yaml"
],
"_justification": "Dummy resources' tokens left during testing."
}
]
}
8 changes: 8 additions & 0 deletions src/containerapp-compose/HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. :changelog:
Release History
===============

0.1.0
++++++
* Initial release.
7 changes: 7 additions & 0 deletions src/containerapp-compose/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Microsoft Azure CLI 'containerapps' Extension
==========================================

This package is for the 'containerapps-compose' extension.

This preview contains the `containerapp compose` command group, enabling the creation of Azure Container Apps environments and instances from a Docker Compose file.

33 changes: 33 additions & 0 deletions src/containerapp-compose/azext_containerapp_compose/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core import AzCommandsLoader
from ._help import helps # pylint: disable=unused-import


class ContainerappPreviewCommandsLoader(AzCommandsLoader):

def __init__(self, cli_ctx=None):
from azure.cli.core.commands import CliCommandType
containerapp_preview_custom = CliCommandType(
operations_tmpl='azext_containerapp_compose.custom#{}',
client_factory=None)
# pylint: disable=R1725
super(ContainerappPreviewCommandsLoader, self).__init__(
cli_ctx=cli_ctx,
custom_command_type=containerapp_preview_custom
)

def load_command_table(self, args):
from azext_containerapp_compose.commands import load_command_table
load_command_table(self, args)
return self.command_table

def load_arguments(self, command):
from azext_containerapp_compose._params import load_arguments
load_arguments(self, command)


COMMAND_LOADER_CLS = ContainerappPreviewCommandsLoader
23 changes: 23 additions & 0 deletions src/containerapp-compose/azext_containerapp_compose/_help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.
# --------------------------------------------------------------------------------------------

from knack.help_files import helps # pylint: disable=unused-import

if 'containerapp' not in helps.keys():
helps['containerapp'] = """
type: group
short-summary: Manage Azure Container Apps.
"""

helps['containerapp compose'] = """
type: group
short-summary: Commands to create Azure Container Apps from Compose specifications.
"""

helps['containerapp compose create'] = """
type: command
short-summary: Create one or more Container Apps in a new or existing Container App Environment from a Compose specification.
"""
115 changes: 115 additions & 0 deletions src/containerapp-compose/azext_containerapp_compose/_monkey_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import sys

from knack.log import get_logger
from azure.cli.core.azclierror import AzCLIError


class RequiredExtensionMissing(AzCLIError):
def __init__(self, error_msg) -> None:
recommendation = "Please install the containerapp extension: "
recommendation += "`az extension add containerapp`"
super().__init__(error_msg, recommendation)


def uncache(exclude):
pkgs = []
for mod in exclude:
pkg = mod.split('.', 1)[0]
pkgs.append(pkg)
to_uncache = []
for mod in sys.modules:
if mod in exclude:
continue
if mod in pkgs:
to_uncache.append(mod)
continue
for pkg in pkgs:
if mod.startswith(pkg + '.'):
to_uncache.append(mod)
break
for mod in to_uncache:
del sys.modules[mod]


# Monkey patch for the PollingAnimation
# removes the spinner and message written to standard out
# which breaks the ability to re-use output from the
# the containerapp compose create command
# example:
# `URL=$(az containerapp compose create -e myenv -g myrg --query [0].properties.configuration.ingress.fqdn -o tsv)`
# In that example, the URL variable would include a number of lines with the polling animation,
# making it difficult to reusue the output from the CLI command.
def tick(self):
self.currTicker += 1
self.currTicker = self.currTicker % len(self.tickers)


# Monkey patch for the PollingAnimation (see above)
def flush(self): # noqa: W0613 pylint: disable=unused-argument
pass


logger = get_logger(__name__)


def log_containerapp_extension_required():
message = "Please install the containerapp extension before proceeding with "
message += "`az containerapp compose create`"
logger.fatal(message)
raise RequiredExtensionMissing(message)


try:
from azext_containerapp import custom # pylint: disable=unused-import
from azext_containerapp import _utils # pylint: disable=unused-import
from azext_containerapp import _clients # pylint: disable=unused-import
_clients.PollingAnimation.tick = tick
_clients.PollingAnimation.flush = flush
uncache("azext_containerapp._clients")
from azext_containerapp import _clients # pylint: disable=unused-import
from azext_containerapp._clients import ManagedEnvironmentClient # pylint: disable=unused-import
except ModuleNotFoundError:
log_containerapp_extension_required()
except ImportError:
log_containerapp_extension_required()


# Monkey patch for log analytics workspace name
# this allows the test framework to pass down a specific
# name to support playback of recorded tests.
def override_random_log_analytics_name(resource_group_name): # pylint: disable=unused-argument
return _utils.logs_workspace_name # noqa: F821 pylint: disable=undefined-variable


def create_containerapps_compose_environment(cmd,
name,
resource_group_name,
logs_workspace_name=None,
tags=None):
if logs_workspace_name is not None:
monkey_patch = override_random_log_analytics_name
_utils._generate_log_analytics_workspace_name = monkey_patch # pylint: disable=protected-access
_utils.logs_workspace_name = logs_workspace_name
return custom.create_managed_environment(cmd,
name,
resource_group_name,
tags=tags)


def create_containerapp_from_service(*args, **kwargs):
return custom.create_containerapp(*args, **kwargs)


def load_yaml_file(filename):
return custom.load_yaml_file(filename)


def show_managed_environment(cmd, resource_group_name, managed_env_name):
return ManagedEnvironmentClient.show(cmd=cmd,
resource_group_name=resource_group_name,
name=managed_env_name)
25 changes: 25 additions & 0 deletions src/containerapp-compose/azext_containerapp_compose/_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# --------------------------------------------------------------------------------------------
# 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


def load_arguments(self, _):

from argparse import SUPPRESS
from azure.cli.core.commands.parameters import (tags_type, get_location_type)
from azure.cli.core.commands.validators import get_default_location_from_resource_group

with self.argument_context('containerapp compose') as c:
c.argument('tags', tags_type)
c.argument('location', get_location_type(self.cli_ctx), validator=get_default_location_from_resource_group)
c.argument('managed_env', options_list=['--environment', '-e'], help="Name of the Container App's environment.")

with self.argument_context('containerapp compose create') as c:
c.argument('compose_file_path', options_list=['--compose-file-path', '-f'], help='Path to a Docker Compose file with the configuration to import to Azure Container Apps.')
c.argument('registry_server', help='Path to a container registry')
c.argument('registry_user', options_list=['--registry-username'], help="Supplied container registry's username")
c.argument('registry_pass', options_list=['--registry-password'], help="Supplied container registry's password")
c.argument('logs_workspace_name', options_list=['--logs-workspace', '-w'], help=SUPPRESS)
c.argument('transport', action='append', nargs='+', help="Transport options per Container App instance (servicename=transportsetting).")
20 changes: 20 additions & 0 deletions src/containerapp-compose/azext_containerapp_compose/_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------


def example_name_or_id_validator(cmd, namespace):
# Example of a storage account name or ID validator.
# See: https://github.com/Azure/azure-cli/blob/dev/doc/authoring_command_modules/authoring_commands.md#supporting-name-or-id-parameters # pylint: disable=C0301
from azure.cli.core.commands.client_factory import get_subscription_id
from msrestazure.tools import is_valid_resource_id, resource_id
if namespace.storage_account:
if not is_valid_resource_id(namespace.RESOURCE):
namespace.storage_account = resource_id(
subscription=get_subscription_id(cmd.cli_ctx),
resource_group=namespace.resource_group_name,
namespace='Microsoft.Storage',
type='storageAccounts',
name=namespace.storage_account
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"azext.isPreview": true,
"azext.minCliCoreVersion": "2.15.0"
}
10 changes: 10 additions & 0 deletions src/containerapp-compose/azext_containerapp_compose/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------


def load_command_table(self, _):

with self.command_group('containerapp compose', is_preview=True) as g:
g.custom_command('create', 'create_containerapps_from_compose')
Loading

0 comments on commit c6d86ee

Please sign in to comment.