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-compose extension with support for compose files #4711

Merged
merged 17 commits into from
Jun 4, 2022
Merged
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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
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