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
1 change: 1 addition & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ upcoming
* 'az containerapp auth': support --token-store, --sas-url-secret, --sas-url-secret-name, --yes
* 'az containerapp create'/'az containerapp job create': When --environment is provided and the environmentId value does not exist in --yaml, use the value in --environment as environmentId
* Added 'az containerapp show-custom-domain-verification-id' to show verfication id used for binding custom domain
* 'az containerapp job create': support --environment-type parameter

0.3.37
++++++
Expand Down
4 changes: 4 additions & 0 deletions src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,10 @@ class ContainerAppPreviewClient(ContainerAppClient):
api_version = PREVIEW_API_VERSION


class ContainerAppsJobPreviewClient(ContainerAppsJobClient):
api_version = PREVIEW_API_VERSION


class SubscriptionClient():
api_version = CURRENT_API_VERSION

Expand Down
15 changes: 15 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@
- name: Create a container apps job with Trigger Type as Manual.
text: |
az containerapp job create -n MyContainerappsjob -g MyResourceGroup \\
--environment MyContainerappEnv
--trigger-type Manual \\
--replica-timeout 5 \\
--replica-retry-limit 2 \\
Expand All @@ -673,6 +674,7 @@
- name: Create a container apps job with Trigger Type as Schedule.
text: |
az containerapp job create -n MyContainerappsjob -g MyResourceGroup \\
--environment MyContainerappEnv
--trigger-type Schedule \\
--replica-timeout 5 \\
--replica-retry-limit 2 \\
Expand All @@ -683,6 +685,7 @@
- name: Create a container apps job with Trigger Type as Event.
text: |
az containerapp job create -n MyContainerappsjob -g MyResourceGroup \\
--environment MyContainerappEnv
--trigger-type Event \\
--replica-timeout 5 \\
--replica-retry-limit 2 \\
Expand All @@ -698,6 +701,18 @@
"queueLength": "5" "queueName": "foo" \\
--scale-rule-auth "connection=my-connection-string-secret-name" \\
--image imageName
- name: Create a container apps job hosted on a Connected Environment.
text: |
az containerapp job create -n MyContainerappsjob -g MyResourceGroup \\
--environment MyContainerappConnectedEnv
--environment-type connected
--trigger-type Manual \\
--replica-timeout 5 \\
--replica-retry-limit 2 \\
--replica-completion-count 1 \\
--parallelism 1 \\
--image imageName \\
--workload-profile-name my-wlp
"""

helps['containerapp job update'] = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,8 @@ def construct_payload(self):
def set_up_extended_location(self):
if self.get_argument_environment_type() == CONNECTED_ENVIRONMENT_TYPE:
if not self.containerapp_def.get('extendedLocation'):
parsed_env = parse_resource_id(self.get_argument_managed_env()) # custom_location check here perhaps
env_id = safe_get(self.containerapp_def, "properties", 'environmentId') or self.get_argument_managed_env()
parsed_env = parse_resource_id(env_id)
env_name = parsed_env['name']
env_rg = parsed_env['resource_group']
env_info = self.get_environment_client().show(cmd=self.cmd, resource_group_name=env_rg, name=env_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
from msrest.exceptions import DeserializationError

from ._decorator_utils import process_loaded_yaml, load_yaml_file, create_deserializer
from ._constants import HELLO_WORLD_IMAGE, CONTAINER_APPS_RP
from ._constants import HELLO_WORLD_IMAGE, CONTAINER_APPS_RP, CONNECTED_ENVIRONMENT_RESOURCE_TYPE, \
MANAGED_ENVIRONMENT_TYPE, CONNECTED_ENVIRONMENT_TYPE
from ._validators import validate_create
from .base_resource import BaseResource
from ._clients import ManagedEnvironmentClient
from ._client_factory import handle_raw_exception
from ._clients import ManagedEnvironmentClient, ConnectedEnvironmentClient, ManagedEnvironmentPreviewClient
from ._client_factory import handle_raw_exception, handle_non_404_exception

from ._models import (
JobConfiguration as JobConfigurationModel,
Expand Down Expand Up @@ -482,12 +483,67 @@ def set_up_create_containerapp_job_yaml(self, name, file_name):

try:
env_info = self.get_environment_client().show(cmd=self.cmd, resource_group_name=env_rg, name=env_name)
except:
pass
except Exception as e:
handle_non_404_exception(e)

if not env_info:
raise ValidationError("The environment '{}' in resource group '{}' was not found".format(env_name, env_rg))

# Validate location
if not self.containerappjob_def.get('location'):
self.containerappjob_def['location'] = env_info['location']


class ContainerAppJobPreviewCreateDecorator(ContainerAppJobCreateDecorator):
def construct_payload(self):
super().construct_payload()
self.set_up_extended_location()

def set_up_extended_location(self):
if self.get_argument_environment_type() == CONNECTED_ENVIRONMENT_TYPE:
if not self.containerappjob_def.get('extendedLocation'):
env_id = safe_get(self.containerappjob_def, "properties", 'environmentId') or self.get_argument_managed_env()
parsed_env = parse_resource_id(env_id)
env_name = parsed_env['name']
env_rg = parsed_env['resource_group']
env_info = self.get_environment_client().show(cmd=self.cmd, resource_group_name=env_rg, name=env_name)
self.containerappjob_def["extendedLocation"] = env_info["extendedLocation"]

def get_environment_client(self):
if self.get_argument_yaml():
env = safe_get(self.containerappjob_def, "properties", "environmentId")
else:
env = self.get_argument_managed_env()

environment_type = self.get_argument_environment_type()
if not env and not environment_type:
return ManagedEnvironmentClient

parsed_env = parse_resource_id(env)

# Validate environment type
if parsed_env.get('resource_type').lower() == CONNECTED_ENVIRONMENT_RESOURCE_TYPE.lower():
if environment_type == MANAGED_ENVIRONMENT_TYPE:
logger.warning(f"User passed a connectedEnvironment resource id but did not specify --environment-type {CONNECTED_ENVIRONMENT_TYPE}. Using environment type {CONNECTED_ENVIRONMENT_TYPE}.")
environment_type = CONNECTED_ENVIRONMENT_TYPE
else:
if environment_type == CONNECTED_ENVIRONMENT_TYPE:
logger.warning(f"User passed a managedEnvironment resource id but specified --environment-type {CONNECTED_ENVIRONMENT_TYPE}. Using environment type {MANAGED_ENVIRONMENT_TYPE}.")
environment_type = MANAGED_ENVIRONMENT_TYPE

self.set_argument_environment_type(environment_type)
self.set_argument_managed_env(env)

if environment_type == CONNECTED_ENVIRONMENT_TYPE:
return ConnectedEnvironmentClient
else:
return ManagedEnvironmentPreviewClient

def get_argument_environment_type(self):
return self.get_param("environment_type")

def set_argument_managed_env(self, managed_env):
self.set_param("managed_env", managed_env)

def set_argument_environment_type(self, environment_type):
self.set_param("environment_type", environment_type)
12 changes: 7 additions & 5 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from msrestazure.tools import parse_resource_id, is_valid_resource_id
from msrest.exceptions import DeserializationError

from .containerapp_job_decorator import ContainerAppJobDecorator, ContainerAppJobCreateDecorator
from .containerapp_job_decorator import ContainerAppJobDecorator, ContainerAppJobPreviewCreateDecorator
from .containerapp_env_decorator import ContainerAppEnvDecorator, ContainerAppEnvCreateDecorator, ContainerAppEnvUpdateDecorator
from .containerapp_auth_decorator import ContainerAppPreviewAuthDecorator
from .containerapp_decorator import BaseContainerAppDecorator, ContainerAppPreviewCreateDecorator, ContainerAppPreviewListDecorator
Expand All @@ -49,7 +49,8 @@
ContainerAppsJobClient,
ContainerAppPreviewClient,
AuthPreviewClient,
SubscriptionPreviewClient
SubscriptionPreviewClient,
ContainerAppsJobPreviewClient
)
from ._dev_service_utils import DevServiceUtils
from ._github_oauth import get_github_access_token
Expand Down Expand Up @@ -1217,11 +1218,12 @@ def create_containerappsjob(cmd,
disable_warnings=False,
user_assigned=None,
registry_identity=None,
workload_profile_name=None):
workload_profile_name=None,
environment_type="managed"):
raw_parameters = locals()
containerapp_job_create_decorator = ContainerAppJobCreateDecorator(
containerapp_job_create_decorator = ContainerAppJobPreviewCreateDecorator(
cmd=cmd,
client=ContainerAppsJobClient,
client=ContainerAppsJobPreviewClient,
raw_parameters=raw_parameters,
models=CONTAINER_APPS_SDK_MODELS
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# --------------------------------------------------------------------------------------------
# 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.testsdk import CliTestError
from azure.cli.testsdk.reverse_dependency import get_dummy_cli
from azure.cli.testsdk.scenario_tests import SingleValueReplacer
from azure.cli.testsdk.preparers import NoTrafficRecordingPreparer, ResourceGroupPreparer


# pylint: disable=too-many-instance-attributes
class ConnectedClusterPreparer(NoTrafficRecordingPreparer, SingleValueReplacer):
def __init__(self, name_prefix='aks', location='eastus2euap', aks_name='my-aks-cluster', connected_cluster_name='my-connected-cluster',
resource_group_parameter_name='resource_group', skip_delete=False):
super(ConnectedClusterPreparer, self).__init__(name_prefix, 15)
self.cli_ctx = get_dummy_cli()
self.location = location
self.infra_cluster = aks_name
self.connected_cluster_name = connected_cluster_name
self.resource_group_parameter_name = resource_group_parameter_name
self.skip_delete = skip_delete

def create_resource(self, name, **kwargs):
group = self._get_resource_group(**kwargs)
try:
self.live_only_execute(self.cli_ctx, f'az aks create --resource-group {group} --name {self.infra_cluster} --enable-aad --generate-ssh-keys --enable-cluster-autoscaler --min-count 4 --max-count 10 --node-count 4')
self.live_only_execute(self.cli_ctx, f'az aks get-credentials --resource-group {group} --name {self.infra_cluster} --overwrite-existing --admin')
self.live_only_execute(self.cli_ctx, f'az connectedk8s connect --resource-group {group} --name {self.connected_cluster_name}')
except AttributeError: # live only execute returns None if playing from record
pass
return {'infra_cluster': self.infra_cluster,
'connected_cluster_name': self.connected_cluster_name}

def _get_resource_group(self, **kwargs):
try:
return kwargs.get(self.resource_group_parameter_name)
except KeyError:
template = 'Resource group is required. Please add ' \
'decorator @{} in front of this preparer.'
raise CliTestError(template.format(ResourceGroupPreparer.__name__,
self.resource_group_parameter_name))
Loading