Skip to content
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import os
from unittest import mock
import time
import requests
import docker

from azure.cli.testsdk.reverse_dependency import get_dummy_cli
from azure.cli.testsdk.scenario_tests import AllowLargeResponse
from azure.cli.testsdk import (ScenarioTest, ResourceGroupPreparer, JMESPathCheck, live_only)
from knack.util import CLIError

from azext_containerapp.tests.latest.common import TEST_LOCATION
from azext_containerapp import _utils
from azext_containerapp.tests.latest.utils import create_and_verify_containerapp_up
TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..'))

class ContainerAppPatchTest(ScenarioTest):
@live_only()
Copy link
Contributor

Choose a reason for hiding this comment

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

May I ask why do you need to mark these new tests as @live_only()?

Copy link
Contributor Author

@snehapar9 snehapar9 Jun 16, 2023

Choose a reason for hiding this comment

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

@zhoxing-ms, these tests are marked @live_only() because they require docker to be running. Since CI does not have docker installed they would not work if they aren't run live.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, thanks~

@ResourceGroupPreparer(location = "eastus2")
def test_containerapp_patch_list_and_apply_with_resource_group_e2e(self, resource_group):
source_path = os.path.join(TEST_DIR, os.path.join("data", "source_built_using_buildpack"))
ingress = 'external'
target_port = '8080'

# Generate a name for the Container App
app_name = self.create_random_name(prefix='containerapp', length=24)

# Create container app and re-tag image to an older version to simulate patch list
old_tag = "run-dotnet-aspnet-7.0.1-cbl-mariner2.0"
create_and_verify_containerapp_up(self,resource_group,source_path=source_path,ingress=ingress,target_port=target_port,app_name=app_name)
self._retag_image_to_older_version_and_push(resource_group=resource_group,app_name=app_name,old_tag=old_tag)

# Execute and verify patch list command
patchable_images = self.cmd(f'containerapp patch list -g {resource_group}').get_output_in_json()
newRunImageTag = patchable_images[0]["newRunImage"].split(':')[1]
self.assertEquals(newRunImageTag,_utils.get_latest_buildpack_run_tag("aspnet", "7.0"))
self.assertEquals(patchable_images[0]["oldRunImage"], old_tag)

# Execute and verify patch apply command
self.cmd(f'containerapp patch apply -g {resource_group}')
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
patched_image = app["properties"]["template"]["containers"][0]["image"]
patched_image_tag = patched_image.split(':')[1]
self.assertEquals(patched_image_tag,_utils.get_latest_buildpack_run_tag("aspnet", "7.0"))

@live_only()
@ResourceGroupPreparer(location = "eastus2")
def test_containerapp_patch_list_and_apply_with_environment_e2e(self, resource_group):
source_path = os.path.join(TEST_DIR, os.path.join("data", "source_built_using_buildpack"))
ingress = 'external'
target_port = '80'

# Generate a name for the Container App
app_name = self.create_random_name(prefix='containerapp', length=24)

# Create managed environment
env_name = self.create_random_name(prefix='env', length=24)
self.cmd(f'containerapp env create -g {resource_group} -n {env_name}')

# Create container app and re-tag image to an older version to simulate patch list
old_tag = "run-dotnet-aspnet-7.0.1-cbl-mariner2.0"
create_and_verify_containerapp_up(self,resource_group,env_name=env_name,source_path=source_path,ingress=ingress,target_port=target_port,app_name=app_name)
self._retag_image_to_older_version_and_push(resource_group=resource_group,app_name=app_name,old_tag=old_tag)

# Execute and verify patch list command
patchable_images = self.cmd(f'containerapp patch list -g {resource_group} --environment {env_name}').get_output_in_json()
newRunImageTag = patchable_images[0]["newRunImage"].split(':')[1]
self.assertEquals(newRunImageTag,_utils.get_latest_buildpack_run_tag("aspnet", "7.0"))
self.assertEquals(patchable_images[0]["oldRunImage"], old_tag)

# Execute and verify patch apply command
self.cmd(f'containerapp patch apply -g {resource_group} --environment {env_name}')
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
patched_image = app["properties"]["template"]["containers"][0]["image"]
patched_image_tag = patched_image.split(':')[1]
self.assertEquals(patched_image_tag,_utils.get_latest_buildpack_run_tag("aspnet", "7.0"))

@live_only()
@ResourceGroupPreparer(location = "eastus2")
def test_containerapp_patch_list_and_apply_with_show_all_e2e(self, resource_group):
image = "mcr.microsoft.com/k8se/quickstart:latest"

# Generate a name for the Container App
app_name = self.create_random_name(prefix='containerapp', length=24)

# Create managed environment
env_name = self.create_random_name(prefix='env', length=24)
self.cmd(f'containerapp env create -g {resource_group} -n {env_name}')

create_and_verify_containerapp_up(self,resource_group,env_name=env_name,image=image,app_name=app_name)

# Execute and verify patch list command
patch_cmd = f'containerapp patch list -g {resource_group} --environment {env_name} --show-all'
output = self.cmd(patch_cmd).get_output_in_json()
self.assertEquals(output[0]["targetImageName"],"mcr.microsoft.com/k8se/quickstart:latest")

# Execute and verify patch apply command
self.cmd(f'containerapp patch apply -g {resource_group} --environment {env_name} --show-all')
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
image = app["properties"]["template"]["containers"][0]["image"]
self.assertEquals(image,"mcr.microsoft.com/k8se/quickstart:latest")

Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we could deploy 2 container apps (one with quick start image, one with an old patchable image) to make sure that the --show-all is working properly by adding assert to check how many results have been returned by the patcher list command.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@harryli0108 In my opinion, it may be better to explicitly check for the image name than the count of container apps returned from patch list. The ordering may not be the same every time if we have multiple apps and would make it difficult to assert against the image name. I would think it may be okay to keep it simple with just a single container app. Thoughts?


@live_only()
@ResourceGroupPreparer(location = "eastus2")
def test_containerapp_patch_list_and_apply_without_arguments_e2e(self, resource_group):
source_path = os.path.join(TEST_DIR, os.path.join("data", "source_built_using_buildpack"))
ingress = 'external'
target_port = '80'

# Generate a name for the Container App
app_name = self.create_random_name(prefix='containerapp', length=24)

# Create container app and re-tag image to an older version to simulate patch list
old_tag = "run-dotnet-aspnet-7.0.1-cbl-mariner2.0"
create_and_verify_containerapp_up(self,resource_group,source_path=source_path,ingress=ingress,target_port=target_port,app_name=app_name)
self._retag_image_to_older_version_and_push(resource_group=resource_group,app_name=app_name,old_tag=old_tag)

# Execute and verify patch list command
self.cmd(f'configure --defaults group={resource_group}')
patch_cmd = f'containerapp patch list'
patchable_images = self.cmd(patch_cmd).get_output_in_json()
newRunImageTag = patchable_images[0]["newRunImage"].split(':')[1]
self.assertEquals(newRunImageTag,_utils.get_latest_buildpack_run_tag("aspnet", "7.0"))
self.assertEquals(patchable_images[0]["oldRunImage"], old_tag)

# Execute and verify patch apply command
self.cmd(f'containerapp patch apply')
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
patched_image = app["properties"]["template"]["containers"][0]["image"]
patched_image_tag = patched_image.split(':')[1]
self.assertEquals(patched_image_tag,_utils.get_latest_buildpack_run_tag("aspnet", "7.0"))

def _retag_image_to_older_version_and_push(self,resource_group,app_name,old_tag):
client = docker.from_env()
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
image_name = app["properties"]["template"]["containers"][0]["image"]
current_image = client.images.get(image_name)
registry = app["properties"]["configuration"]["registries"][0]["server"]
repo = app["properties"]["template"]["containers"][0]["name"]

# Re-tag image to an older version to simulate patch list
old_image_name = registry + "/" + repo + ":" + old_tag
current_image.tag(registry + "/" + repo, tag = old_tag)

# Re-tag and push
client.images.push(registry + "/" + repo, tag = old_tag)
self.cmd('az containerapp update -n {} -g {} --image {}'.format(app_name, resource_group, old_image_name))
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
retagged_image = app["properties"]["template"]["containers"][0]["image"]
retagged_image_tag = retagged_image.split(':')[1]

# Verify if image was updated
self.assertEquals(retagged_image_tag,old_tag)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from azure.cli.testsdk import (ScenarioTest, ResourceGroupPreparer, live_only)

from azext_containerapp.tests.latest.common import TEST_LOCATION
from azext_containerapp.tests.latest.utils import create_and_verify_containerapp_up

TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..'))

Expand All @@ -19,7 +20,7 @@ class ContainerAppUpImageTest(ScenarioTest):
@ResourceGroupPreparer(location="eastus2")
def test_containerapp_up_image_e2e(self, resource_group):
image = "mcr.microsoft.com/k8se/quickstart:latest"
self._create_and_verify_containerapp_up(resource_group, image=image)
create_and_verify_containerapp_up(self,resource_group=resource_group, image=image)


@live_only()
Expand All @@ -28,7 +29,7 @@ def test_containerapp_up_source_with_buildpack_e2e(self, resource_group):
source_path = os.path.join(TEST_DIR, os.path.join("data", "source_built_using_buildpack"))
ingress = 'external'
target_port = '8080'
self._create_and_verify_containerapp_up(resource_group, source_path=source_path, ingress=ingress, target_port=target_port)
create_and_verify_containerapp_up(self,resource_group=resource_group, source_path=source_path, ingress=ingress, target_port=target_port)


@live_only()
Expand All @@ -37,7 +38,7 @@ def test_containerapp_up_source_with_dockerfile_e2e(self, resource_group):
source_path = os.path.join(TEST_DIR, os.path.join("data", "source_built_using_dockerfile"))
ingress = 'external'
target_port = '80'
self._create_and_verify_containerapp_up(resource_group, source_path=source_path, ingress=ingress, target_port=target_port)
create_and_verify_containerapp_up(self,resource_group=resource_group, source_path=source_path, ingress=ingress, target_port=target_port)


@live_only()
Expand All @@ -47,49 +48,4 @@ def test_containerapp_up_source_with_acr_task_e2e(self, resource_group):
source_path = os.path.join(TEST_DIR, os.path.join("data", "source_built_using_acr_task"))
ingress = 'external'
target_port = '8080'
self._create_and_verify_containerapp_up(resource_group, source_path=source_path, ingress=ingress, target_port=target_port)


def _create_and_verify_containerapp_up(
self,
resource_group,
source_path = None,
image = None,
location = None,
ingress = None,
target_port = None):
# Configure the default location
self.cmd('configure --defaults location={}'.format(TEST_LOCATION))

# Ensure that the Container App environment is created
env_name = self.create_random_name(prefix='env', length=24)
self.cmd(f'containerapp env create -g {resource_group} -n {env_name}')

# Generate a name for the Container App
app_name = self.create_random_name(prefix='containerapp', length=24)

# Construct the 'az containerapp up' command
up_cmd = f"containerapp up -g {resource_group} -n {app_name} --environment {env_name}"
if source_path:
up_cmd += f" --source \"{source_path}\""
if image:
up_cmd += f" --image {image}"
if ingress:
up_cmd += f" --ingress {ingress}"
if target_port:
up_cmd += f" --target-port {target_port}"

# Execute the 'az containerapp up' command
self.cmd(up_cmd)

# Verify that the Container App is running
app = self.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
url = app["properties"]["configuration"]["ingress"]["fqdn"]
url = url if url.startswith("http") else f"http://{url}"
resp = requests.get(url)
self.assertTrue(resp.ok)

# Re-run the 'az containerapp up' command with the location parameter if provided
if location:
up_cmd += f" -l {location.upper()}"
self.cmd(up_cmd)
create_and_verify_containerapp_up(self,resource_group=resource_group, source_path=source_path, ingress=ingress, target_port=target_port)
53 changes: 51 additions & 2 deletions src/containerapp/azext_containerapp/tests/latest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# --------------------------------------------------------------------------------------------

import time

import requests
from azext_containerapp.tests.latest.common import TEST_LOCATION

def create_containerapp_env(test_cls, env_name, resource_group, location=None):
logs_workspace_name = test_cls.create_random_name(prefix='containerapp-env', length=24)
Expand All @@ -20,4 +21,52 @@ def create_containerapp_env(test_cls, env_name, resource_group, location=None):

while containerapp_env["properties"]["provisioningState"].lower() == "waiting":
time.sleep(5)
containerapp_env = test_cls.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json()
containerapp_env = test_cls.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json()

def create_and_verify_containerapp_up(
test_cls,
resource_group,
env_name = None,
source_path = None,
image = None,
location = None,
ingress = None,
target_port = None,
app_name = None):
# Configure the default location
test_cls.cmd('configure --defaults location={}'.format(TEST_LOCATION))

# Ensure that the Container App environment is created
if env_name is None:
env_name = test_cls.create_random_name(prefix='env', length=24)
test_cls.cmd(f'containerapp env create -g {resource_group} -n {env_name}')

if app_name is None:
# Generate a name for the Container App
app_name = test_cls.create_random_name(prefix='containerapp', length=24)

# Construct the 'az containerapp up' command
up_cmd = f"containerapp up -g {resource_group} -n {app_name} --environment {env_name}"
if source_path:
up_cmd += f" --source \"{source_path}\""
if image:
up_cmd += f" --image {image}"
if ingress:
up_cmd += f" --ingress {ingress}"
if target_port:
up_cmd += f" --target-port {target_port}"

# Execute the 'az containerapp up' command
test_cls.cmd(up_cmd)

# Verify that the Container App is running
app = test_cls.cmd(f"containerapp show -g {resource_group} -n {app_name}").get_output_in_json()
url = app["properties"]["configuration"]["ingress"]["fqdn"]
url = url if url.startswith("http") else f"http://{url}"
resp = requests.get(url)
test_cls.assertTrue(resp.ok)

# Re-run the 'az containerapp up' command with the location parameter if provided
if location:
up_cmd += f" -l {location.upper()}"
test_cls.cmd(up_cmd)
2 changes: 2 additions & 0 deletions src/containerapp/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[bdist_wheel]
universal=1
[options]
install_requires=docker
2 changes: 1 addition & 1 deletion src/containerapp/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
]

# TODO: Add any additional SDK dependencies here
DEPENDENCIES = ['pycomposefile>=0.0.29']
DEPENDENCIES = ['pycomposefile>=0.0.29', 'docker']

# Install pack CLI to build runnable application images from source
_ = get_pack_exec_path()
Expand Down