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 containerapp-preview extension #6419

Merged
merged 54 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d0c524e
init containerapp-preview
Greedygre Jun 1, 2023
f7be550
add GA extension dependency
Greedygre Jun 6, 2023
d6d8f7c
add --environment-type
Greedygre Jun 6, 2023
93c8960
fix bug for prompt
Greedygre Jun 7, 2023
62debc5
warning for update extension
Greedygre Jun 7, 2023
a98d7d3
remove useless code
Greedygre Jun 7, 2023
12820f4
fix
Greedygre Jun 7, 2023
d948cdf
remove
Greedygre Jun 8, 2023
1bb1cea
remove setup
Greedygre Jun 8, 2023
2afbe83
fix
Greedygre Jun 8, 2023
42ab370
fix lint
Greedygre Jun 8, 2023
d6beff8
fix test
Greedygre Jun 8, 2023
0cf803f
test tbd
Greedygre Jun 8, 2023
9b9b237
test 0.0.0
Greedygre Jun 8, 2023
7c34333
add create containerapp
Greedygre Jun 13, 2023
1e81de8
fix
Greedygre Jun 13, 2023
ec88eb3
add env and environment type
Greedygre Jun 13, 2023
ac2c33c
set is preview
Greedygre Jun 13, 2023
bd96ab5
fix
Greedygre Jun 14, 2023
724a381
support --yaml with extended-location
Greedygre Jun 14, 2023
88eaa6a
fix extend
Greedygre Jun 15, 2023
94eebe4
add show/delete/list containerapp in preview
Greedygre Jun 16, 2023
6a4fb88
fix preview
Greedygre Jun 19, 2023
2a4a168
fix error
Greedygre Jun 19, 2023
8b17806
fix error
Greedygre Jun 19, 2023
c7410b7
rebase main
Greedygre Jun 19, 2023
4c9f52e
fix delete
Greedygre Jun 19, 2023
8cf2fc8
refactor client and make constants
Greedygre Jun 29, 2023
368fae6
add test for environment type on arc
Greedygre Jul 3, 2023
2618e94
set version
Greedygre Jul 3, 2023
22bb79a
add test yaml
Greedygre Jul 3, 2023
0f0ffb4
fix error
Greedygre Jul 3, 2023
958e075
fix containerapp preview test
Greedygre Jul 3, 2023
c8662e2
add rely GA extension in test
Greedygre Jul 3, 2023
5cd3762
fix test
Greedygre Jul 3, 2023
44e6078
remove useless code
Greedygre Jul 4, 2023
94be4f6
fix install for loading
Greedygre Jul 5, 2023
8cf778f
Update verify_linter.py
wangzelin007 Jul 4, 2023
5446f32
fix recording and set version to 1.0.0b1
Greedygre Jul 5, 2023
52533f7
fix static analysis: blank line at the end
Greedygre Jul 5, 2023
eb2b6e6
Update verify_linter.py
wangzelin007 Jul 5, 2023
fd55f66
fix comments
Greedygre Jul 6, 2023
2e799d9
remove blank line
Greedygre Jul 6, 2023
5faa2ca
remove python 3.6 support in setup and add more comments for command …
Greedygre Jul 6, 2023
37147a9
add test for managed, add helps, parse resource type and compare
Greedygre Jul 7, 2023
8ae8319
add blank line
Greedygre Jul 7, 2023
18f613d
remove prompt logic
Greedygre Jul 10, 2023
fd90a41
fix
Greedygre Jul 10, 2023
c592553
fix
Greedygre Jul 10, 2023
99e67f9
fix help
Greedygre Jul 10, 2023
03aa729
raise error in _get_azext_module
Greedygre Jul 11, 2023
26cf02c
fix comments
Greedygre Jul 11, 2023
a6d4f0d
add a blank line
Greedygre Jul 11, 2023
acecec6
rename _get_azext_containerapp_module
Greedygre Jul 11, 2023
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 @@ -240,6 +240,8 @@

/src/containerapp/ @ruslany @sanchitmehta @ebencarek @JennyLawrance @howang-ms @vinisoto @chinadragon0515 @vturecek @torosent @pagariyaalok @Juliehzl @jijohn14

/src/containerapp-preview/ @ruslany @sanchitmehta @ebencarek @JennyLawrance @howang-ms @vinisoto @chinadragon0515 @vturecek @torosent @pagariyaalok @Juliehzl @jijohn14

/src/scvmm/ @nascarsayan

/src/spring/ @yuwzho
Expand Down
8 changes: 8 additions & 0 deletions scripts/ci/verify_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,17 @@ def _cmd(cmd):
check_call(cmd, shell=True)

def add_from_code(self):
# The containerapp-preview extension is a special case,
# it must depend on the continerapp extension and cannot run independently.
if self.extension_name == 'containerapp-preview':
self._cmd('azdev extension add containerapp')
self._cmd('azdev extension add {}'.format(self.extension_name))

def remove(self):
# The containerapp-preview extension is a special case,
# it must depend on the continerapp extension and cannot run independently.
if self.extension_name == 'containerapp-preview':
self._cmd('azdev extension remove containerapp')
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to remove containerapp when removing containerapp-preivew

Copy link
Contributor Author

@Greedygre Greedygre Jul 7, 2023

Choose a reason for hiding this comment

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

For pipeline linter test for containerapp-preview extension, need to install containerapp extension.
After completing the test need to remove containerapp.

self._cmd('azdev extension remove {}'.format(self.extension_name))

def linter(self):
Expand Down
10 changes: 10 additions & 0 deletions src/containerapp-preview/HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. :changelog:

Release History
===============

1.0.0b1
++++++
* Initial containerapp-preview extension.
* `az containerapp list/show/delete/create`: support for preview extension
* `az containerapp create`: support --environment-type parameter
9 changes: 9 additions & 0 deletions src/containerapp-preview/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Microsoft Azure CLI 'containerapp-preview' Extension
==========================================

This package is for the 'containerapp-preview' extension.
i.e. 'az containerapp'

We have two extensions: containerapp and containerapp-preview.
The containerapp-preview extension does not run independently, it requires the containerapp extension.
The commands in containerapp-preview can override the same commands in containerapp.
36 changes: 36 additions & 0 deletions src/containerapp-preview/azext_containerapp_preview/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# --------------------------------------------------------------------------------------------
# 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 azext_containerapp_preview._help import helps # pylint: disable=unused-import
from azext_containerapp_preview._utils import (_get_azext_module)


class ContainerappPreviewCommandsLoader(AzCommandsLoader):

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

def load_command_table(self, args):
from azext_containerapp_preview.commands import load_command_table

load_command_table(self, args)
return self.command_table

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

ga_params = _get_azext_module("azext_containerapp._params")
Greedygre marked this conversation as resolved.
Show resolved Hide resolved
ga_params.load_arguments(self, command)
load_arguments(self, command)


COMMAND_LOADER_CLS = ContainerappPreviewCommandsLoader
190 changes: 190 additions & 0 deletions src/containerapp-preview/azext_containerapp_preview/_clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# 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.
# --------------------------------------------------------------------------------------------
import json

from azure.cli.core.azclierror import ResourceNotFoundError
from azure.cli.core.util import send_raw_request
from azure.cli.core.commands.client_factory import get_subscription_id
from knack.log import get_logger

from ._utils import (_get_azext_module)

logger = get_logger(__name__)

PREVIEW_API_VERSION = "2023-04-01-preview"
HEADER_AZURE_ASYNC_OPERATION = "azure-asyncoperation"
HEADER_LOCATION = "location"


def poll_status(cmd, request_url):
azext_client = _get_azext_module("azext_containerapp._clients")
return azext_client.poll_status(cmd, request_url)


def poll_results(cmd, request_url):
azext_client = _get_azext_module("azext_containerapp._clients")
return azext_client.poll_results(cmd, request_url)


class ContainerAppClient(_get_azext_module("azext_containerapp._clients").ContainerAppClient):
@classmethod
def get_api_version(cls):
return PREVIEW_API_VERSION


class ManagedEnvironmentClient(_get_azext_module("azext_containerapp._clients").ManagedEnvironmentClient):
@classmethod
def get_api_version(cls):
return PREVIEW_API_VERSION


class ConnectedEnvironmentClient():
@classmethod
def get_api_version(cls):
return PREVIEW_API_VERSION

@classmethod
def create(cls, cmd, resource_group_name, name, connected_environment_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/connectedEnvironments/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.get_api_version())
Greedygre marked this conversation as resolved.
Show resolved Hide resolved

r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(connected_environment_envelope))

if no_wait:
return r.json()
elif r.status_code == 201:
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, name, managed_environment_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/connectedEnvironments/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.get_api_version())

r = send_raw_request(cmd.cli_ctx, "PATCH", request_url, body=json.dumps(managed_environment_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 a connected environment")
else:
return response

return r.json()

@classmethod
def delete(cls, cmd, resource_group_name, 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/connectedEnvironments/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.get_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]:
if r.status_code == 202:
operation_url = r.headers.get(HEADER_LOCATION)
poll_results(cmd, operation_url)
logger.warning('Connected environment successfully deleted')
return

@classmethod
def show(cls, cmd, resource_group_name, 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/connectedEnvironments/{}?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
cls.get_api_version())

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

@classmethod
def list_by_subscription(cls, cmd, formatter=lambda x: x):
env_list = []

management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
request_url = "{}/subscriptions/{}/providers/Microsoft.App/connectedEnvironments?api-version={}".format(
management_hostname.strip('/'),
sub_id,
cls.get_api_version())

r = send_raw_request(cmd.cli_ctx, "GET", request_url)
j = r.json()
for env in j["value"]:
formatted = formatter(env)
env_list.append(formatted)

while j.get("nextLink") is not None:
request_url = j["nextLink"]
r = send_raw_request(cmd.cli_ctx, "GET", request_url)
j = r.json()
for env in j["value"]:
formatted = formatter(env)
env_list.append(formatted)

return env_list

@classmethod
def list_by_resource_group(cls, cmd, resource_group_name, formatter=lambda x: x):
env_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/connectedEnvironments?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
cls.get_api_version())

r = send_raw_request(cmd.cli_ctx, "GET", request_url)
j = r.json()
for env in j["value"]:
formatted = formatter(env)
env_list.append(formatted)

while j.get("nextLink") is not None:
request_url = j["nextLink"]
r = send_raw_request(cmd.cli_ctx, "GET", request_url)
j = r.json()
for env in j["value"]:
formatted = formatter(env)
env_list.append(formatted)

return env_list
12 changes: 12 additions & 0 deletions src/containerapp-preview/azext_containerapp_preview/_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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.
# --------------------------------------------------------------------------------------------
MANAGED_ENVIRONMENT_TYPE = "managed"
CONNECTED_ENVIRONMENT_TYPE = "connected"
MANAGED_ENVIRONMENT_RESOURCE_TYPE = "managedEnvironments"
CONNECTED_ENVIRONMENT_RESOURCE_TYPE = "connectedEnvironments"
GA_CONTAINERAPP_EXTENSION_NAME = 'containerapp'
MIN_GA_VERSION = '0.3.35'
CONTAINERAPP_NAMESPACE = 'Microsoft.App'
64 changes: 64 additions & 0 deletions src/containerapp-preview/azext_containerapp_preview/_help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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
Greedygre marked this conversation as resolved.
Show resolved Hide resolved

helps['containerapp create'] = """
type: command
short-summary: Create a container app.
examples:
- name: Create a container app and retrieve its fully qualified domain name.
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
--image myregistry.azurecr.io/my-app:v1.0 --environment MyContainerappEnv \\
--ingress external --target-port 80 \\
--registry-server myregistry.azurecr.io --registry-username myregistry --registry-password $REGISTRY_PASSWORD \\
--query properties.configuration.ingress.fqdn
- name: Create a container app with resource requirements and replica count limits.
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
--image nginx --environment MyContainerappEnv \\
--cpu 0.5 --memory 1.0Gi \\
--min-replicas 4 --max-replicas 8
- name: Create a container app with secrets and environment variables.
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
--image my-app:v1.0 --environment MyContainerappEnv \\
--secrets mysecret=secretvalue1 anothersecret="secret value 2" \\
--env-vars GREETING="Hello, world" SECRETENV=secretref:anothersecret
- name: Create a container app using a YAML configuration. Example YAML configuration - https://aka.ms/azure-container-apps-yaml
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
--environment MyContainerappEnv \\
--yaml "path/to/yaml/file.yml"
- name: Create a container app with an http scale rule
text: |
az containerapp create -n myapp -g mygroup --environment myenv --image nginx \\
--scale-rule-name my-http-rule \\
--scale-rule-http-concurrency 50
- name: Create a container app with a custom scale rule
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
Greedygre marked this conversation as resolved.
Show resolved Hide resolved
--image my-queue-processor --environment MyContainerappEnv \\
--min-replicas 4 --max-replicas 8 \\
--scale-rule-name queue-based-autoscaling \\
--scale-rule-type azure-queue \\
--scale-rule-metadata "accountName=mystorageaccountname" \\
"cloud=AzurePublicCloud" \\
"queueLength": "5" "queueName": "foo" \\
--scale-rule-auth "connection=my-connection-string-secret-name"
- name: Create a container app with secrets and mounts them in a volume.
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
--image my-app:v1.0 --environment MyContainerappEnv \\
--secrets mysecret=secretvalue1 anothersecret="secret value 2" \\
--secret-volume-mount "mnt/secrets"
- name: Create a container app hosted on a Connected Environment.
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
--image my-app:v1.0 --environment MyContainerappConnectedEnv \\
--environment-type connected
"""
18 changes: 18 additions & 0 deletions src/containerapp-preview/azext_containerapp_preview/_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# --------------------------------------------------------------------------------------------
# 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
from azure.cli.core.commands.parameters import get_enum_type

from ._validators import validate_env_name_or_id


# This method cannot directly rely on GA resources.
# When the switch core.use_command_index is turned off, possibly unrelated commands may also trigger unnecessary loads.
# It will throw a warning if the GA resource does not exist.
def load_arguments(self, _):

with self.argument_context('containerapp create') as c:
c.argument('managed_env', validator=validate_env_name_or_id, options_list=['--environment'], help="Name or resource ID of the container app's environment.")
c.argument('environment_type', arg_type=get_enum_type(["managed", "connected"]), help="Type of environment.", is_preview=True)
Loading