Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,4 @@

/src/quota/ @kairu-ms @ZengTaoxu

/src/containerapp/ @calvinsID @haroonf @panchagnula
/src/containerapp/ @calvinsID @haroonf @panchagnula @StrawnSC
4 changes: 4 additions & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Release History
===============

0.3.1
++++++
* Update "az containerapp github-action add" parameters
Copy link
Contributor

Choose a reason for hiding this comment

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

lets explicitly call out docker-path replaced by context-path

Copy link
Contributor

Choose a reason for hiding this comment

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

Just fixed this


0.3.0
++++++
* Subgroup commands for managed identities: az containerapp identity
Expand Down
4 changes: 2 additions & 2 deletions src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ class GitHubActionClient():
@classmethod
def create_or_update(cls, cmd, resource_group_name, name, github_action_envelope, headers, no_wait=False):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
api_version = NEW_API_VERSION
api_version = "2022-03-01"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why hard code the version here instead of upgrading the version of NEW_API_VERSION? We usually use one version according to the RP dimension, because it is easy to maintain

Copy link
Contributor

Choose a reason for hiding this comment

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

@zhoxing-ms We will have 2 versions for a while until we test all commands with stable api-version '2022-03-01'
The NEW_API_VERSION points to the preview version & we will move out of this in time for GA. Once we have all tests covered we will make the change as a separate PR. A lot of the nee features we are adding support for will be with the non-preview version, like this updates to GitHub actions. Hope that explains.

sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/sourcecontrols/current?api-version={}"
request_url = url_fmt.format(
Expand Down Expand Up @@ -556,7 +556,7 @@ def create_or_update(cls, cmd, resource_group_name, name, github_action_envelope
@classmethod
def show(cls, cmd, resource_group_name, name):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
api_version = NEW_API_VERSION
api_version = "2022-03-01"
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above

Copy link
Contributor

Choose a reason for hiding this comment

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

@StrawnSC we should probably name these preview-version & stable-version, so the swap when we need to do is easy. "NEW_API_VERSION" name here is misleading, since this is the not the new version.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just resolved this @panchagnula @zhoxing-ms

sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/sourcecontrols/current?api-version={}"
request_url = url_fmt.format(
Expand Down
3 changes: 2 additions & 1 deletion src/containerapp/azext_containerapp/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@
GitHubActionConfiguration = {
"registryInfo": None, # [RegistryInfo]
"azureCredentials": None, # [AzureCredentials]
"dockerfilePath": None, # str
"image": None, # str
"contextPath": None, # str
"publishType": None, # str
"os": None, # str
"runtimeStack": None, # str
Expand Down
3 changes: 2 additions & 1 deletion src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ def load_arguments(self, _):
c.argument('registry_url', help='The container registry server, e.g. myregistry.azurecr.io')
c.argument('registry_username', help='The username of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied')
c.argument('registry_password', help='The password of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied')
c.argument('docker_file_path', help='The dockerfile location, e.g. ./Dockerfile')
c.argument('context_path', help='Path in the repo from which to run the docker build. Defaults to "./"')
c.argument('service_principal_client_id', help='The service principal client ID. ')
c.argument('service_principal_client_secret', help='The service principal client secret.')
c.argument('service_principal_tenant_id', help='The service principal tenant ID.')
c.argument('image', type=str, options_list=['--image', '-i'], help="Container image name that the Github Action should use. Defaults to the Container App name.")

with self.argument_context('containerapp github-action delete') as c:
c.argument('token', help='A Personal Access Token with write access to the specified repository. For more information: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line')
Expand Down
6 changes: 4 additions & 2 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,8 @@ def create_or_update_github_action(cmd,
branch=None,
token=None,
login_with_github=False,
docker_file_path=None,
image=None,
context_path=None,
service_principal_client_id=None,
service_principal_client_secret=None,
service_principal_tenant_id=None):
Expand Down Expand Up @@ -1131,7 +1132,8 @@ def create_or_update_github_action(cmd,
github_action_configuration = GitHubActionConfiguration
github_action_configuration["registryInfo"] = registry_info
github_action_configuration["azureCredentials"] = azure_credentials
github_action_configuration["dockerfilePath"] = docker_file_path
github_action_configuration["contextPath"] = context_path
github_action_configuration["image"] = image

source_control_info["properties"]["githubActionConfiguration"] = github_action_configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,176 @@
import unittest

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


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


@unittest.skip("Managed environment flaky")
@live_only()
class ContainerappScenarioTest(ScenarioTest):
@AllowLargeResponse(8192)
@ResourceGroupPreparer(location="centraluseuap")
Copy link
Contributor

Choose a reason for hiding this comment

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

not all subs have permissions to EUAP regions, & these tests don't run on our subs so avoid using this region. Use our non-canary region for testing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed this to "eastus2"

def test_containerapp_e2e(self, resource_group):
containerapp_name = self.create_random_name(prefix='containerapp-e2e', length=24)
env_name = self.create_random_name(prefix='containerapp-e2e-env', length=24)

self.cmd('containerapp env create -g {} -n {}'.format(resource_group, env_name))

# Sleep in case env create takes a while
time.sleep(60)
self.cmd('containerapp env list -g {}'.format(resource_group), checks=[
JMESPathCheck('length(@)', 1),
JMESPathCheck('[0].name', env_name),
])
# Ensure environment is completed
containerapp_env = self.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json()

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

containerapp_name = self.create_random_name(prefix='containerapp-e2e', length=24)

# Create basic Container App with default image
self.cmd('containerapp create -g {} -n {} --environment {}'.format(resource_group, containerapp_name, env_name), checks=[
JMESPathCheck('name', containerapp_name)
])

# Sleep in case containerapp create takes a while
time.sleep(60)
self.cmd('containerapp show -g {} -n {}'.format(resource_group, containerapp_name), checks=[
JMESPathCheck('name', containerapp_name)
JMESPathCheck('name', containerapp_name),
])

self.cmd('containerapp list -g {}'.format(resource_group), checks=[
JMESPathCheck('length(@)', 1),
JMESPathCheck('[0].name', containerapp_name),
])

# Create Container App with image, resource and replica limits
create_string = "containerapp create -g {} -n {} --environment {} --image nginx --cpu 0.5 --memory 1.0Gi --min-replicas 2 --max-replicas 4".format(resource_group, containerapp_name, env_name)
self.cmd(create_string, checks=[
JMESPathCheck('name', containerapp_name),
JMESPathCheck('properties.template.containers[0].image', 'nginx'),
JMESPathCheck('properties.template.containers[0].resources.cpu', '0.5'),
JMESPathCheck('properties.template.containers[0].resources.memory', '1Gi'),
JMESPathCheck('properties.template.scale.minReplicas', '2'),
JMESPathCheck('properties.template.scale.maxReplicas', '4')
])

self.cmd('containerapp create -g {} -n {} --environment {} --ingress external --target-port 8080'.format(resource_group, containerapp_name, env_name), checks=[
JMESPathCheck('properties.configuration.ingress.external', True),
JMESPathCheck('properties.configuration.ingress.targetPort', 8080)
])

# Container App with ingress should fail unless target port is specified
with self.assertRaises(CLIError):
self.cmd('containerapp create -g {} -n {} --environment {} --ingress external'.format(resource_group, containerapp_name, env_name))

# Create Container App with secrets and environment variables
containerapp_name = self.create_random_name(prefix='containerapp-e2e', length=24)
create_string = 'containerapp create -g {} -n {} --environment {} --secrets mysecret=secretvalue1 anothersecret="secret value 2" --env-vars GREETING="Hello, world" SECRETENV=secretref:anothersecret'.format(
resource_group, containerapp_name, env_name)
self.cmd(create_string, checks=[
JMESPathCheck('name', containerapp_name),
JMESPathCheck('length(properties.template.containers[0].env)', 2),
JMESPathCheck('length(properties.configuration.secrets)', 2)
])


@AllowLargeResponse(8192)
@ResourceGroupPreparer(location="eastus2")
def test_container_acr(self, resource_group):
env_name = self.create_random_name(prefix='containerapp-e2e-env', length=24)

self.cmd('containerapp env create -g {} -n {}'.format(resource_group, env_name))

# Ensure environment is completed
containerapp_env = self.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json()

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

containerapp_name = self.create_random_name(prefix='containerapp-e2e', length=24)
registry_name = self.create_random_name(prefix='containerapp', length=24)

# Create ACR
acr = self.cmd('acr create -g {} -n {} --sku Basic --admin-enabled'.format(resource_group, registry_name)).get_output_in_json()
registry_server = acr["loginServer"]

acr_credentials = self.cmd('acr credential show -g {} -n {}'.format(resource_group, registry_name)).get_output_in_json()
registry_username = acr_credentials["username"]
registry_password = acr_credentials["passwords"][0]["value"]

# Create Container App with ACR
containerapp_name = self.create_random_name(prefix='containerapp-e2e', length=24)
create_string = 'containerapp create -g {} -n {} --environment {} --registry-username {} --registry-server {} --registry-password {}'.format(
resource_group, containerapp_name, env_name, registry_username, registry_server, registry_password)
self.cmd(create_string, checks=[
JMESPathCheck('name', containerapp_name),
JMESPathCheck('properties.configuration.registries[0].server', registry_server),
JMESPathCheck('properties.configuration.registries[0].username', registry_username),
JMESPathCheck('length(properties.configuration.secrets)', 1),
])


@AllowLargeResponse(8192)
@ResourceGroupPreparer(location="eastus")
def test_containerapp_update(self, resource_group):
env_name = self.create_random_name(prefix='containerapp-e2e-env', length=24)

self.cmd('containerapp env create -g {} -n {}'.format(resource_group, env_name))

# Ensure environment is completed
containerapp_env = self.cmd('containerapp env show -g {} -n {}'.format(resource_group, env_name)).get_output_in_json()

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

# Create basic Container App with default image
containerapp_name = self.create_random_name(prefix='containerapp-update', length=24)
self.cmd('containerapp create -g {} -n {} --environment {}'.format(resource_group, containerapp_name, env_name), checks=[
JMESPathCheck('name', containerapp_name),
JMESPathCheck('length(properties.template.containers)', 1),
JMESPathCheck('properties.template.containers[0].name', containerapp_name)
])

# Update existing Container App that has a single container

update_string = 'containerapp update -g {} -n {} --image {} --cpu 0.5 --memory 1.0Gi --args mycommand mycommand2 --command "mycommand" --revision-suffix suffix --min-replicas 2 --max-replicas 4'.format(
resource_group, containerapp_name, 'nginx')
self.cmd(update_string, checks=[
JMESPathCheck('name', containerapp_name),
JMESPathCheck('length(properties.template.containers)', 1),
JMESPathCheck('properties.template.containers[0].name', containerapp_name),
JMESPathCheck('properties.template.containers[0].image', 'nginx'),
JMESPathCheck('properties.template.containers[0].resources.cpu', '0.5'),
JMESPathCheck('properties.template.containers[0].resources.memory', '1Gi'),
JMESPathCheck('properties.template.scale.minReplicas', '2'),
JMESPathCheck('properties.template.scale.maxReplicas', '4'),
JMESPathCheck('properties.template.containers[0].command[0]', "mycommand"),
JMESPathCheck('length(properties.template.containers[0].args)', 2)
])

# Add new container to existing Container App
update_string = 'containerapp update -g {} -n {} --container-name {} --image {}'.format(
resource_group, containerapp_name, "newcontainer", "nginx")
self.cmd(update_string, checks=[
JMESPathCheck('name', containerapp_name),
JMESPathCheck('length(properties.template.containers)', 2)
])

# Updating container properties in a Container App with multiple containers, without providing container name should error
update_string = 'containerapp update -g {} -n {} --cpu {} --memory {}'.format(
resource_group, containerapp_name, '1.0', '2.0Gi')
with self.assertRaises(CLIError):
self.cmd(update_string)

# Updating container properties in a Container App with multiple containers, should work when container name provided
update_string = 'containerapp update -g {} -n {} --container-name {} --cpu {} --memory {}'.format(
resource_group, containerapp_name, 'newcontainer', '0.75', '1.5Gi')
self.cmd(update_string)

update_string = 'containerapp update -g {} -n {} --container-name {} --cpu {} --memory {}'.format(
resource_group, containerapp_name, containerapp_name, '0.75', '1.5Gi')
self.cmd(update_string, checks=[
JMESPathCheck('properties.template.containers[0].resources.cpu', '0.75'),
JMESPathCheck('properties.template.containers[0].resources.memory', '1.5Gi'),
JMESPathCheck('properties.template.containers[1].resources.cpu', '0.75'),
JMESPathCheck('properties.template.containers[1].resources.memory', '1.5Gi'),
])
2 changes: 1 addition & 1 deletion src/containerapp/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# TODO: Confirm this is the right version number you want and it matches your
# HISTORY.rst entry.
VERSION = '0.3.0'
VERSION = '0.3.1'

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand Down