From 5fe8e5f45adabf05049ecebc1b8957141b4f8d90 Mon Sep 17 00:00:00 2001 From: Haroon Feisal <38823870+haroonf@users.noreply.github.com> Date: Fri, 18 Mar 2022 13:05:12 -0400 Subject: [PATCH] Fixed style issues, various bug fixes (#27) * Moved dapr arguments to env as a subgroup. * Added env variable options. * Changed revision mode set to revision set-mode. * Added env var options to revision copy. * Fixed revision copy bug related to env secret refs. * Changed registry and secret delete to remove. Added registry param helps. Removed replica from table output and added trafficWeight. * Updating warning text. * Updated warning text once more. * Made name optional for revision copy if from-revision flag is passed. * Fixed whitespace style issues. * Styled clients and utils to pass pylint. * Finished client.py pylint fixes. * Fixed pylint issues. * Fixed flake8 commands and custom. * Fixed flake issues in src. * Added license header to _sdk_models. * Added confirmation for containerapp delete. Co-authored-by: Haroon Feisal --- .../azext_containerapp/__init__.py | 3 +- .../azext_containerapp/_client_factory.py | 4 +- .../azext_containerapp/_clients.py | 33 +- .../azext_containerapp/_github_oauth.py | 6 +- src/containerapp/azext_containerapp/_help.py | 11 +- .../azext_containerapp/_models.py | 101 ++--- .../azext_containerapp/_params.py | 16 +- .../azext_containerapp/_sdk_models.py | 10 +- src/containerapp/azext_containerapp/_utils.py | 142 ++++--- .../azext_containerapp/_validators.py | 16 +- .../azext_containerapp/commands.py | 17 +- src/containerapp/azext_containerapp/custom.py | 397 ++++++++---------- 12 files changed, 383 insertions(+), 373 deletions(-) diff --git a/src/containerapp/azext_containerapp/__init__.py b/src/containerapp/azext_containerapp/__init__.py index f772766731..dcff6d86de 100644 --- a/src/containerapp/azext_containerapp/__init__.py +++ b/src/containerapp/azext_containerapp/__init__.py @@ -2,6 +2,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +# pylint: disable=super-with-arguments from azure.cli.core import AzCommandsLoader @@ -16,7 +17,7 @@ def __init__(self, cli_ctx=None): operations_tmpl='azext_containerapp.custom#{}', client_factory=None) super(ContainerappCommandsLoader, self).__init__(cli_ctx=cli_ctx, - custom_command_type=containerapp_custom) + custom_command_type=containerapp_custom) def load_command_table(self, args): from azext_containerapp.commands import load_command_table diff --git a/src/containerapp/azext_containerapp/_client_factory.py b/src/containerapp/azext_containerapp/_client_factory.py index f998486c63..9a249cdbe7 100644 --- a/src/containerapp/azext_containerapp/_client_factory.py +++ b/src/containerapp/azext_containerapp/_client_factory.py @@ -2,6 +2,7 @@ # 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, consider-using-f-string from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.cli.core.profiles import ResourceType @@ -13,7 +14,6 @@ def ex_handler_factory(no_throw=False): def _polish_bad_errors(ex): import json - from knack.util import CLIError try: content = json.loads(ex.response.content) if 'message' in content: @@ -63,11 +63,13 @@ def cf_resource_groups(cli_ctx, subscription_id=None): return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, subscription_id=subscription_id).resource_groups + def log_analytics_client_factory(cli_ctx): from azure.mgmt.loganalytics import LogAnalyticsManagementClient return get_mgmt_service_client(cli_ctx, LogAnalyticsManagementClient).workspaces + def log_analytics_shared_key_client_factory(cli_ctx): from azure.mgmt.loganalytics import LogAnalyticsManagementClient diff --git a/src/containerapp/azext_containerapp/_clients.py b/src/containerapp/azext_containerapp/_clients.py index 108ee5b004..2dc138a603 100644 --- a/src/containerapp/azext_containerapp/_clients.py +++ b/src/containerapp/azext_containerapp/_clients.py @@ -2,11 +2,12 @@ # 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, super-with-arguments, too-many-instance-attributes, consider-using-f-string, no-else-return, no-self-use + import json import time import sys -from sys import api_version 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 @@ -15,8 +16,8 @@ API_VERSION = "2021-03-01" NEW_API_VERSION = "2022-01-01-preview" -POLLING_TIMEOUT = 60 # how many seconds before exiting -POLLING_SECONDS = 2 # how many seconds between requests +POLLING_TIMEOUT = 60 # how many seconds before exiting +POLLING_SECONDS = 2 # how many seconds between requests class PollingAnimation(): @@ -37,7 +38,7 @@ def flush(self): sys.stdout.write("\033[K") -def poll(cmd, request_url, poll_if_status): +def poll(cmd, request_url, poll_if_status): # pylint: disable=inconsistent-return-statements try: start = time.time() end = time.time() + POLLING_TIMEOUT @@ -53,19 +54,17 @@ def poll(cmd, request_url, poll_if_status): r = send_raw_request(cmd.cli_ctx, "GET", request_url) r2 = r.json() - if not "properties" in r2 or not "provisioningState" in r2["properties"] or not r2["properties"]["provisioningState"].lower() == poll_if_status: + if "properties" not in r2 or "provisioningState" not in r2["properties"] or not r2["properties"]["provisioningState"].lower() == poll_if_status: break start = time.time() animation.flush() return r.json() - except Exception as e: + except Exception as e: # pylint: disable=broad-except animation.flush() - if poll_if_status == "scheduledfordelete": # Catch "not found" errors if polling for delete - return - - raise e + if not poll_if_status == "scheduledfordelete": # Catch "not found" errors if polling for delete + raise e class ContainerAppClient(): @@ -144,7 +143,6 @@ def delete(cls, cmd, resource_group_name, name): if r.status_code == 202: logger.warning('Containerapp successfully deleted') - return @classmethod def show(cls, cmd, resource_group_name, name): @@ -222,7 +220,6 @@ def list_by_resource_group(cls, cmd, resource_group_name, formatter=lambda x: x) @classmethod def list_secrets(cls, cmd, resource_group_name, name): - secrets = [] management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager api_version = NEW_API_VERSION @@ -338,6 +335,7 @@ def deactivate_revision(cls, cmd, resource_group_name, container_app_name, name) r = send_raw_request(cmd.cli_ctx, "POST", request_url) return r.json() + class ManagedEnvironmentClient(): @classmethod def create(cls, cmd, resource_group_name, name, managed_environment_envelope, no_wait=False): @@ -413,7 +411,7 @@ def delete(cls, cmd, resource_group_name, name, no_wait=False): r = send_raw_request(cmd.cli_ctx, "DELETE", request_url) if no_wait: - return # API doesn't return JSON (it returns no content) + return # API doesn't return JSON (it returns no content) elif r.status_code in [200, 201, 202, 204]: url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/managedEnvironments/{}?api-version={}" request_url = url_fmt.format( @@ -506,6 +504,7 @@ def list_by_resource_group(cls, cmd, resource_group_name, formatter=lambda x: x) return env_list + class GitHubActionClient(): @classmethod def create_or_update(cls, cmd, resource_group_name, name, github_action_envelope, headers, no_wait=False): @@ -552,7 +551,6 @@ def show(cls, cmd, resource_group_name, name): r = send_raw_request(cmd.cli_ctx, "GET", request_url) return r.json() - #TODO @classmethod def delete(cls, cmd, resource_group_name, name, headers, no_wait=False): management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager @@ -569,7 +567,7 @@ def delete(cls, cmd, resource_group_name, name, headers, no_wait=False): r = send_raw_request(cmd.cli_ctx, "DELETE", request_url, headers=headers) if no_wait: - return # API doesn't return JSON (it returns no content) + return # API doesn't return JSON (it returns no content) elif r.status_code in [200, 201, 202, 204]: url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/sourcecontrols/current?api-version={}" request_url = url_fmt.format( @@ -588,10 +586,10 @@ def delete(cls, cmd, resource_group_name, name, headers, no_wait=False): logger.warning('Containerapp github action successfully deleted') return + class DaprComponentClient(): @classmethod def create_or_update(cls, cmd, resource_group_name, environment_name, name, dapr_component_envelope, no_wait=False): - #create_or_update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.App/managedEnvironments/{environmentName}/daprComponents/{name}'} management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager api_version = NEW_API_VERSION @@ -639,7 +637,7 @@ def delete(cls, cmd, resource_group_name, environment_name, name, no_wait=False) r = send_raw_request(cmd.cli_ctx, "DELETE", request_url) if no_wait: - return # API doesn't return JSON (it returns no content) + return # API doesn't return JSON (it returns no content) elif r.status_code in [200, 201, 202, 204]: url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/managedEnvironments/{}/daprComponents/{}?api-version={}" request_url = url_fmt.format( @@ -705,4 +703,3 @@ def list(cls, cmd, resource_group_name, environment_name, formatter=lambda x: x) app_list.append(formatted) return app_list - diff --git a/src/containerapp/azext_containerapp/_github_oauth.py b/src/containerapp/azext_containerapp/_github_oauth.py index 3df73a6b1a..659d43afc3 100644 --- a/src/containerapp/azext_containerapp/_github_oauth.py +++ b/src/containerapp/azext_containerapp/_github_oauth.py @@ -2,6 +2,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +# pylint: disable=consider-using-f-string from azure.cli.core.azclierror import (ValidationError, CLIInternalError, UnclassifiedUserFault) from knack.log import get_logger @@ -22,6 +23,7 @@ "workflow" ] + def get_github_access_token(cmd, scope_list=None): # pylint: disable=unused-argument if scope_list: for scope in scope_list: @@ -81,6 +83,6 @@ def get_github_access_token(cmd, scope_list=None): # pylint: disable=unused-arg return parsed_confirmation_response['access_token'][0] except Exception as e: raise CLIInternalError( - 'Error: {}. Please try again, or retrieve personal access token from the Github website'.format(e)) + 'Error: {}. Please try again, or retrieve personal access token from the Github website'.format(e)) from e - raise UnclassifiedUserFault('Activation did not happen in time. Please try again') \ No newline at end of file + raise UnclassifiedUserFault('Activation did not happen in time. Please try again') diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index cb5126c3f6..a4a71960f0 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -151,7 +151,7 @@ examples: - name: Set a container app to single revision mode. text: | - az containerapp revision set-mode -n MyContainerapp -g MyResourceGroup --mode Single + az containerapp revision set-mode -n MyContainerapp -g MyResourceGroup --mode Single """ helps['containerapp revision copy'] = """ @@ -368,7 +368,7 @@ examples: - name: Show a container app's ingress traffic configuration. text: | - az containerapp ingress traffic show -n MyContainerapp -g MyResourceGroup + az containerapp ingress traffic show -n MyContainerapp -g MyResourceGroup """ # Registry Commands @@ -392,7 +392,7 @@ examples: - name: List container registries configured in a container app. text: | - az containerapp registry list -n MyContainerapp -g MyResourceGroup + az containerapp registry list -n MyContainerapp -g MyResourceGroup """ helps['containerapp registry set'] = """ @@ -403,7 +403,6 @@ text: | az containerapp registry set -n MyContainerapp -g MyResourceGroup \\ --server MyExistingContainerappRegistry.azurecr.io --username MyRegistryUsername --password MyRegistryPassword - """ helps['containerapp registry remove'] = """ @@ -454,10 +453,10 @@ examples: - name: Add secrets to a container app. text: | - az containerapp secret set -n MyContainerapp -g MyResourceGroup --secrets MySecretName1=MySecretValue1 MySecretName2=MySecretValue2 + az containerapp secret set -n MyContainerapp -g MyResourceGroup --secrets MySecretName1=MySecretValue1 MySecretName2=MySecretValue2 - name: Update a secret. text: | - az containerapp secret set -n MyContainerapp -g MyResourceGroup --secrets MyExistingSecretName=MyNewSecretValue + az containerapp secret set -n MyContainerapp -g MyResourceGroup --secrets MyExistingSecretName=MyNewSecretValue """ helps['containerapp github-action'] = """ diff --git a/src/containerapp/azext_containerapp/_models.py b/src/containerapp/azext_containerapp/_models.py index b356adaa2a..d00798765c 100644 --- a/src/containerapp/azext_containerapp/_models.py +++ b/src/containerapp/azext_containerapp/_models.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long, too-many-statements, super-with-arguments VnetConfiguration = { "infrastructureSubnetId": None, @@ -17,7 +18,7 @@ "tags": None, "properties": { "daprAIInstrumentationKey": None, - "vnetConfiguration": None, # VnetConfiguration + "vnetConfiguration": None, # VnetConfiguration "internalLoadBalancerEnabled": None, "appLogsConfiguration": None } @@ -63,15 +64,15 @@ "name": None, "command": None, "args": None, - "env": None, # [EnvironmentVar] - "resources": None, # ContainerResources - "volumeMounts": None, # [VolumeMount] + "env": None, # [EnvironmentVar] + "resources": None, # ContainerResources + "volumeMounts": None, # [VolumeMount] } Volume = { "name": None, - "storageType": "EmptyDir", # AzureFile or EmptyDir - "storageName": None # None for EmptyDir, otherwise name of storage resource + "storageType": "EmptyDir", # AzureFile or EmptyDir + "storageName": None # None for EmptyDir, otherwise name of storage resource } ScaleRuleAuth = { @@ -82,25 +83,25 @@ QueueScaleRule = { "queueName": None, "queueLength": None, - "auth": None # ScaleRuleAuth + "auth": None # ScaleRuleAuth } CustomScaleRule = { "type": None, "metadata": {}, - "auth": None # ScaleRuleAuth + "auth": None # ScaleRuleAuth } HttpScaleRule = { "metadata": {}, - "auth": None # ScaleRuleAuth + "auth": None # ScaleRuleAuth } ScaleRule = { "name": None, - "azureQueue": None, # QueueScaleRule - "customScaleRule": None, # CustomScaleRule - "httpScaleRule": None, # HttpScaleRule + "azureQueue": None, # QueueScaleRule + "customScaleRule": None, # CustomScaleRule + "httpScaleRule": None, # HttpScaleRule } Secret = { @@ -111,7 +112,7 @@ Scale = { "minReplicas": None, "maxReplicas": None, - "rules": [] # list of ScaleRule + "rules": [] # list of ScaleRule } TrafficWeight = { @@ -126,7 +127,7 @@ CustomDomain = { "name": None, - "bindingType": None, # BindingType + "bindingType": None, # BindingType "certificateId": None } @@ -134,9 +135,9 @@ "fqdn": None, "external": False, "targetPort": None, - "transport": None, # 'auto', 'http', 'http2' - "traffic": None, # TrafficWeight - "customDomains": None # [CustomDomain] + "transport": None, # 'auto', 'http', 'http2' + "traffic": None, # TrafficWeight + "customDomains": None # [CustomDomain] } RegistryCredentials = { @@ -147,17 +148,17 @@ Template = { "revisionSuffix": None, - "containers": None, # [Container] + "containers": None, # [Container] "scale": Scale, "dapr": Dapr, - "volumes": None # [Volume] + "volumes": None # [Volume] } Configuration = { - "secrets": None, # [Secret] - "activeRevisionsMode": None, # 'multiple' or 'single' - "ingress": None, # Ingress - "registries": None # [RegistryCredentials] + "secrets": None, # [Secret] + "activeRevisionsMode": None, # 'multiple' or 'single' + "ingress": None, # Ingress + "registries": None # [RegistryCredentials] } UserAssignedIdentity = { @@ -165,26 +166,26 @@ } ManagedServiceIdentity = { - "type": None, # 'None', 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned' - "userAssignedIdentities": None # {string: UserAssignedIdentity} + "type": None, # 'None', 'SystemAssigned', 'UserAssigned', 'SystemAssigned,UserAssigned' + "userAssignedIdentities": None # {string: UserAssignedIdentity} } ContainerApp = { "location": None, - "identity": None, # ManagedServiceIdentity + "identity": None, # ManagedServiceIdentity "properties": { "managedEnvironmentId": None, - "configuration": None, # Configuration - "template": None # Template + "configuration": None, # Configuration + "template": None # Template }, "tags": None } DaprComponent = { "properties": { - "componentType": None, #String + "componentType": None, # String "version": None, - "ignoreErrors": None, + "ignoreErrors": None, "initTimeout": None, "secrets": None, "metadata": None, @@ -193,39 +194,39 @@ } DaprMetadata = { - "key": None, #str - "value": None, #str - "secret_ref": None #str + "key": None, # str + "value": None, # str + "secret_ref": None # str } SourceControl = { "properties": { - "repoUrl": None, - "branch": None, - "githubActionConfiguration": None # [GitHubActionConfiguration] + "repoUrl": None, + "branch": None, + "githubActionConfiguration": None # [GitHubActionConfiguration] } } GitHubActionConfiguration = { - "registryInfo": None, # [RegistryInfo] - "azureCredentials": None, # [AzureCredentials] - "dockerfilePath": None, # str - "publishType": None, # str - "os": None, # str - "runtimeStack": None, # str - "runtimeVersion": None # str + "registryInfo": None, # [RegistryInfo] + "azureCredentials": None, # [AzureCredentials] + "dockerfilePath": None, # str + "publishType": None, # str + "os": None, # str + "runtimeStack": None, # str + "runtimeVersion": None # str } RegistryInfo = { - "registryUrl": None, # str - "registryUserName": None, # str - "registryPassword": None # str + "registryUrl": None, # str + "registryUserName": None, # str + "registryPassword": None # str } AzureCredentials = { - "clientId": None, # str - "clientSecret": None, # str - "tenantId": None, #str - "subscriptionId": None #str + "clientId": None, # str + "clientSecret": None, # str + "tenantId": None, # str + "subscriptionId": None # str } diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 1d3e3b6dc2..169b65edbe 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -2,18 +2,19 @@ # 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 +# pylint: disable=line-too-long, too-many-statements, consider-using-f-string from knack.arguments import CLIArgumentType from azure.cli.core.commands.parameters import (resource_group_name_type, get_location_type, - get_resource_name_completion_list, file_type, + file_type, get_three_state_flag, get_enum_type, tags_type) -from azure.cli.core.commands.validators import get_default_location_from_resource_group +# from azure.cli.core.commands.validators import get_default_location_from_resource_group from ._validators import (validate_memory, validate_cpu, validate_managed_env_name_or_id, validate_registry_server, validate_registry_user, validate_registry_pass, validate_target_port, validate_ingress) + def load_arguments(self, _): name_type = CLIArgumentType(options_list=['--name', '-n']) @@ -73,7 +74,7 @@ def load_arguments(self, _): c.argument('ingress', validator=validate_ingress, options_list=['--ingress'], default=None, arg_type=get_enum_type(['internal', 'external']), help="The ingress type.") c.argument('target_port', type=int, validator=validate_target_port, options_list=['--target-port'], help="The application port used for ingress traffic.") c.argument('transport', arg_type=get_enum_type(['auto', 'http', 'http2']), help="The transport protocol used for ingress traffic.") - + with self.argument_context('containerapp create') as c: c.argument('assign_identity', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity.") c.argument('traffic_weights', nargs='*', options_list=['--traffic-weight'], help="A list of revision weight(s) for the container app. Space-separated values in 'revision_name=weight' format. For latest revision, use 'latest=weight'") @@ -101,7 +102,7 @@ def load_arguments(self, _): c.argument('docker_bridge_cidr', type=str, options_list=['--docker-bridge-cidr'], help='CIDR notation IP range assigned to the Docker bridge. It must not overlap with any Subnet IP ranges or the IP range defined in Platform Reserved CIDR, if defined') c.argument('platform_reserved_cidr', type=str, options_list=['--platform-reserved-cidr'], help='IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. It must not overlap with any other Subnet IP ranges') c.argument('platform_reserved_dns_ip', type=str, options_list=['--platform-reserved-dns-ip'], help='An IP address from the IP range defined by Platform Reserved CIDR that will be reserved for the internal DNS server.') - c.argument('internal_only', arg_type=get_three_state_flag(), options_list=['--internal-only'], help='Boolean indicating the environment only has an internal load balancer. These environments do not have a public static IP resource, therefore must provide infrastructureSubnetResourceId and appSubnetResourceId if enabling this property') + c.argument('internal_only', arg_type=get_three_state_flag(), options_list=['--internal-only'], help='Boolean indicating the environment only has an internal load balancer. These environments do not have a public static IP resource, therefore must provide infrastructureSubnetResourceId and appSubnetResourceId if enabling this property') with self.argument_context('containerapp env update') as c: c.argument('name', name_type, help='Name of the Container Apps environment.') @@ -135,7 +136,7 @@ def load_arguments(self, _): 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') c.argument('login_with_github', help='Interactively log in with Github to retrieve the Personal Access Token') - + with self.argument_context('containerapp revision') as c: c.argument('revision_name', options_list=['--revision'], type=str, help='Name of the revision.') @@ -162,7 +163,7 @@ def load_arguments(self, _): c.argument('dapr_app_port', help="The port of your app.") c.argument('dapr_app_protocol', help="Tells Dapr which protocol your application is using. Allowed values: grpc, http.") c.argument('dapr_component_name', help="The dapr component name.") - c.argument('environment_name', options_list=['--name','-n'], help="The environment name.") + c.argument('environment_name', options_list=['--name', '-n'], help="The environment name.") with self.argument_context('containerapp revision set-mode') as c: c.argument('mode', arg_type=get_enum_type(['single', 'multiple']), help="The active revisions mode for the container app.") @@ -171,4 +172,3 @@ def load_arguments(self, _): c.argument('server', help="The container registry server, e.g. myregistry.azurecr.io") c.argument('username', help='The username of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied') c.argument('password', help='The password of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied') - diff --git a/src/containerapp/azext_containerapp/_sdk_models.py b/src/containerapp/azext_containerapp/_sdk_models.py index 9472034039..b34325cdb9 100644 --- a/src/containerapp/azext_containerapp/_sdk_models.py +++ b/src/containerapp/azext_containerapp/_sdk_models.py @@ -1,9 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + # coding=utf-8 # -------------------------------------------------------------------------- # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. # -------------------------------------------------------------------------- +# pylint: disable=line-too-long, super-with-arguments, too-many-instance-attributes from msrest.serialization import Model from msrest.exceptions import HttpOperationError @@ -196,8 +202,8 @@ class ProxyResource(Resource): 'system_data': {'key': 'systemData', 'type': 'SystemData'}, } - def __init__(self, **kwargs): - super(ProxyResource, self).__init__(**kwargs) + # def __init__(self, **kwargs): + # super(ProxyResource, self).__init__(**kwargs) class AuthConfig(ProxyResource): diff --git a/src/containerapp/azext_containerapp/_utils.py b/src/containerapp/azext_containerapp/_utils.py index 1c5a10e5d2..b1b3fa9bf9 100644 --- a/src/containerapp/azext_containerapp/_utils.py +++ b/src/containerapp/azext_containerapp/_utils.py @@ -2,15 +2,14 @@ # 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, consider-using-f-string, no-else-return, duplicate-string-formatting-argument -from distutils.filelist import findall -from operator import is_ +from urllib.parse import urlparse from azure.cli.command_modules.appservice.custom import (_get_acr_cred) -from azure.cli.core.azclierror import (ResourceNotFoundError, ValidationError, RequiredArgumentMissingError) +from azure.cli.core.azclierror import (ValidationError, RequiredArgumentMissingError) from azure.cli.core.commands.client_factory import get_subscription_id from knack.log import get_logger from msrestazure.tools import parse_resource_id -from urllib.parse import urlparse from ._clients import ContainerAppClient from ._client_factory import handle_raw_exception, providers_client_factory, cf_resource_groups, log_analytics_client_factory, log_analytics_shared_key_client_factory @@ -38,7 +37,7 @@ def _validate_subscription_registered(cmd, resource_provider, subscription_id=No subscription_id, resource_provider, resource_provider)) except ValidationError as ex: raise ex - except Exception: + except Exception: # pylint: disable=broad-except pass @@ -62,7 +61,7 @@ def _ensure_location_allowed(cmd, location, resource_provider, resource_type): location, resource_provider, resource_type)) except ValidationError as ex: raise ex - except Exception: + except Exception: # pylint: disable=broad-except pass @@ -76,7 +75,7 @@ def parse_env_var_flags(env_list, is_update_containerapp=False): raise ValidationError("Environment variables must be in the format \"=\" \"=secretref:\" ...\".") raise ValidationError("Environment variables must be in the format \"=\" \"=secretref:\" ...\".") if key_val[0] in env_pairs: - raise ValidationError("Duplicate environment variable {env} found, environment variable names must be unique.".format(env = key_val[0])) + raise ValidationError("Duplicate environment variable {env} found, environment variable names must be unique.".format(env=key_val[0])) value = key_val[1].split('secretref:') env_pairs[key_val[0]] = value @@ -104,7 +103,7 @@ def parse_secret_flags(secret_list): if len(key_val) != 2: raise ValidationError("--secrets: must be in format \"=,=,...\"") if key_val[0] in secret_pairs: - raise ValidationError("--secrets: duplicate secret {secret} found, secret names must be unique.".format(secret = key_val[0])) + raise ValidationError("--secrets: duplicate secret {secret} found, secret names must be unique.".format(secret=key_val[0])) secret_pairs[key_val[0]] = key_val[1] secret_var_def = [] @@ -116,13 +115,23 @@ def parse_secret_flags(secret_list): return secret_var_def + def _update_revision_env_secretrefs(containers, name): for container in containers: - if "env" in container: + if "env" in container: for var in container["env"]: if "secretRef" in var: var["secretRef"] = var["secretRef"].replace("{}-".format(name), "") + +def _update_revision_env_secretrefs(containers, name): + for container in containers: + if "env" in container: + for var in container["env"]: + if "secretRef" in var: + var["secretRef"] = var["secretRef"].replace("{}-".format(name), "") + + def store_as_secret_and_return_secret_ref(secrets_list, registry_user, registry_server, registry_pass, update_existing_secret=False): if registry_pass.startswith("secretref:"): # If user passed in registry password using a secret @@ -139,33 +148,34 @@ def store_as_secret_and_return_secret_ref(secrets_list, registry_user, registry_ return registry_pass else: # If user passed in registry password - if (urlparse(registry_server).hostname is not None): - registry_secret_name = "{server}-{user}".format(server=urlparse(registry_server).hostname.replace('.', ''), user=registry_user.lower()) - else: - registry_secret_name = "{server}-{user}".format(server=registry_server.replace('.', ''), user=registry_user.lower()) - - for secret in secrets_list: - if secret['name'].lower() == registry_secret_name.lower(): - if secret['value'].lower() != registry_pass.lower(): - if update_existing_secret: - secret['value'] = registry_pass - else: - raise ValidationError('Found secret with name \"{}\" but value does not equal the supplied registry password.'.format(registry_secret_name)) - return registry_secret_name - - logger.warning('Adding registry password as a secret with name \"{}\"'.format(registry_secret_name)) - secrets_list.append({ - "name": registry_secret_name, - "value": registry_pass - }) + if urlparse(registry_server).hostname is not None: + registry_secret_name = "{server}-{user}".format(server=urlparse(registry_server).hostname.replace('.', ''), user=registry_user.lower()) + else: + registry_secret_name = "{server}-{user}".format(server=registry_server.replace('.', ''), user=registry_user.lower()) + + for secret in secrets_list: + if secret['name'].lower() == registry_secret_name.lower(): + if secret['value'].lower() != registry_pass.lower(): + if update_existing_secret: + secret['value'] = registry_pass + else: + raise ValidationError('Found secret with name \"{}\" but value does not equal the supplied registry password.'.format(registry_secret_name)) + return registry_secret_name + + logger.warning('Adding registry password as a secret with name \"{}\"'.format(registry_secret_name)) # pylint: disable=logging-format-interpolation + secrets_list.append({ + "name": registry_secret_name, + "value": registry_pass + }) - return registry_secret_name + return registry_secret_name def parse_list_of_strings(comma_separated_string): comma_separated = comma_separated_string.split(',') return [s.strip() for s in comma_separated] + def raise_missing_token_suggestion(): pat_documentation = "https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line" raise RequiredArgumentMissingError("GitHub access token is required to authenticate to your repositories. " @@ -173,6 +183,7 @@ def raise_missing_token_suggestion(): "please run with the '--login-with-github' flag or follow " "the steps found at the following link:\n{0}".format(pat_documentation)) + def _get_default_log_analytics_location(cmd): default_location = "eastus" providers_client = None @@ -184,20 +195,23 @@ def _get_default_log_analytics_location(cmd): if res and getattr(res, 'resource_type', "") == "workspaces": res_locations = getattr(res, 'locations', []) - if len(res_locations): + if len(res_locations) > 0: location = res_locations[0].lower().replace(" ", "").replace("(", "").replace(")", "") if location: return location - except Exception: + except Exception: # pylint: disable=broad-except return default_location return default_location + # Generate random 4 character string def _new_tiny_guid(): - import random, string + import random + import string return ''.join(random.choices(string.ascii_letters + string.digits, k=4)) + # Follow same naming convention as Portal def _generate_log_analytics_workspace_name(resource_group_name): import re @@ -229,7 +243,7 @@ def _generate_log_analytics_if_not_provided(cmd, logs_customer_id, logs_key, loc log_analytics_location = location try: _ensure_location_allowed(cmd, log_analytics_location, "Microsoft.OperationalInsights", "workspaces") - except Exception: + except Exception: # pylint: disable=broad-except log_analytics_location = _get_default_log_analytics_location(cmd) from azure.cli.core.commands import LongRunningOperation @@ -237,7 +251,7 @@ def _generate_log_analytics_if_not_provided(cmd, logs_customer_id, logs_key, loc workspace_name = _generate_log_analytics_workspace_name(resource_group_name) workspace_instance = Workspace(location=log_analytics_location) - logger.warning("Generating a Log Analytics workspace with name \"{}\"".format(workspace_name)) + logger.warning("Generating a Log Analytics workspace with name \"{}\"".format(workspace_name)) # pylint: disable=logging-format-interpolation poller = log_analytics_client.begin_create_or_update(resource_group_name, workspace_name, workspace_instance) log_analytics_workspace = LongRunningOperation(cmd.cli_ctx)(poller) @@ -248,10 +262,10 @@ def _generate_log_analytics_if_not_provided(cmd, logs_customer_id, logs_key, loc resource_group_name=resource_group_name).primary_shared_key except Exception as ex: - raise ValidationError("Unable to generate a Log Analytics workspace. You can use \"az monitor log-analytics workspace create\" to create one and supply --logs-customer-id and --logs-key") + raise ValidationError("Unable to generate a Log Analytics workspace. You can use \"az monitor log-analytics workspace create\" to create one and supply --logs-customer-id and --logs-key") from ex elif logs_customer_id is None: raise ValidationError("Usage error: Supply the --logs-customer-id associated with the --logs-key") - elif logs_key is None: # Try finding the logs-key + elif logs_key is None: # Try finding the logs-key log_analytics_client = log_analytics_client_factory(cmd.cli_ctx) log_analytics_shared_key_client = log_analytics_shared_key_client_factory(cmd.cli_ctx) @@ -285,11 +299,12 @@ def _get_existing_secrets(cmd, resource_group_name, name, containerapp_def): secrets = [] try: secrets = ContainerAppClient.list_secrets(cmd=cmd, resource_group_name=resource_group_name, name=name) - except Exception as e: + except Exception as e: # pylint: disable=broad-except handle_raw_exception(e) containerapp_def["properties"]["configuration"]["secrets"] = secrets["value"] + def _ensure_identity_resource_id(subscription_id, resource_group, resource): from msrestazure.tools import resource_id, is_valid_resource_id if is_valid_resource_id(resource): @@ -301,6 +316,7 @@ def _ensure_identity_resource_id(subscription_id, resource_group, resource): type='userAssignedIdentities', name=resource) + def _add_or_update_secrets(containerapp_def, add_secrets): if "secrets" not in containerapp_def["properties"]["configuration"]: containerapp_def["properties"]["configuration"]["secrets"] = [] @@ -312,28 +328,31 @@ def _add_or_update_secrets(containerapp_def, add_secrets): is_existing = True existing_secret["value"] = new_secret["value"] break - + if not is_existing: containerapp_def["properties"]["configuration"]["secrets"].append(new_secret) + def _remove_registry_secret(containerapp_def, server, username): - if (urlparse(server).hostname is not None): + if urlparse(server).hostname is not None: registry_secret_name = "{server}-{user}".format(server=urlparse(server).hostname.replace('.', ''), user=username.lower()) else: registry_secret_name = "{server}-{user}".format(server=server.replace('.', ''), user=username.lower()) - + _remove_secret(containerapp_def, secret_name=registry_secret_name) + def _remove_secret(containerapp_def, secret_name): if "secrets" not in containerapp_def["properties"]["configuration"]: containerapp_def["properties"]["configuration"]["secrets"] = [] - for i in range(0, len(containerapp_def["properties"]["configuration"]["secrets"])): - existing_secret = containerapp_def["properties"]["configuration"]["secrets"][i] + for index, value in enumerate(containerapp_def["properties"]["configuration"]["secrets"]): + existing_secret = value if existing_secret["name"].lower() == secret_name.lower(): - containerapp_def["properties"]["configuration"]["secrets"].pop(i) + containerapp_def["properties"]["configuration"]["secrets"].pop(index) break + def _add_or_update_env_vars(existing_env_vars, new_env_vars, is_add=False): for new_env_var in new_env_vars: @@ -343,7 +362,7 @@ def _add_or_update_env_vars(existing_env_vars, new_env_vars, is_add=False): if existing_env_var["name"].lower() == new_env_var["name"].lower(): is_existing = True if is_add: - logger.warning("Environment variable {} already exists. Replacing environment variable value.".format(new_env_var["name"])) + logger.warning("Environment variable {} already exists. Replacing environment variable value.".format(new_env_var["name"])) # pylint: disable=logging-format-interpolation if "value" in new_env_var: existing_env_var["value"] = new_env_var["value"] @@ -359,16 +378,17 @@ def _add_or_update_env_vars(existing_env_vars, new_env_vars, is_add=False): # If not updating existing env var, add it as a new env var if not is_existing: if not is_add: - logger.warning("Environment variable {} does not exist. Adding as new environment variable.".format(new_env_var["name"])) + logger.warning("Environment variable {} does not exist. Adding as new environment variable.".format(new_env_var["name"])) # pylint: disable=logging-format-interpolation existing_env_vars.append(new_env_var) + def _remove_env_vars(existing_env_vars, remove_env_vars): for old_env_var in remove_env_vars: # Check if updating existing env var is_existing = False - for i in range(0, len(existing_env_vars)): - existing_env_var = existing_env_vars[i] + for i, value in enumerate(existing_env_vars): + existing_env_var = value if existing_env_var["name"].lower() == old_env_var.lower(): is_existing = True existing_env_vars.pop(i) @@ -376,7 +396,25 @@ def _remove_env_vars(existing_env_vars, remove_env_vars): # If not updating existing env var, add it as a new env var if not is_existing: - logger.warning("Environment variable {} does not exist.".format(old_env_var)) + logger.warning("Environment variable {} does not exist.".format(old_env_var)) # pylint: disable=logging-format-interpolation + + +def _remove_env_vars(existing_env_vars, remove_env_vars): + for old_env_var in remove_env_vars: + + # Check if updating existing env var + is_existing = False + for index, value in enumerate(existing_env_vars): + existing_env_var = value + if existing_env_var["name"].lower() == old_env_var.lower(): + is_existing = True + existing_env_vars.pop(index) + break + + # If not updating existing env var, add it as a new env var + if not is_existing: + logger.warning("Environment variable {} does not exist.".format(old_env_var)) # pylint: disable=logging-format-interpolation + def _add_or_update_tags(containerapp_def, tags): if 'tags' not in containerapp_def: @@ -439,6 +477,7 @@ def _remove_readonly_attributes(containerapp_def): elif unneeded_property in containerapp_def['properties']: del containerapp_def['properties'][unneeded_property] + def _remove_dapr_readonly_attributes(daprcomponent_def): unneeded_properties = [ "id", @@ -457,13 +496,14 @@ def _remove_dapr_readonly_attributes(daprcomponent_def): if unneeded_property in daprcomponent_def: del daprcomponent_def[unneeded_property] + def update_nested_dictionary(orig_dict, new_dict): # Recursively update a nested dictionary. If the value is a list, replace the old list with new list import collections for key, val in new_dict.items(): if isinstance(val, collections.Mapping): - tmp = update_nested_dictionary(orig_dict.get(key, { }), val) + tmp = update_nested_dictionary(orig_dict.get(key, {}), val) orig_dict[key] = tmp elif isinstance(val, list): if new_dict[key]: @@ -477,7 +517,7 @@ def update_nested_dictionary(orig_dict, new_dict): def _is_valid_weight(weight): try: n = int(weight) - if n >= 0 and n <= 100: + if 0 <= n <= 100: return True return False except ValueError: @@ -527,7 +567,7 @@ def _infer_acr_credentials(cmd, registry_server): registry_user, registry_pass = _get_acr_cred(cmd.cli_ctx, registry_name) return (registry_user, registry_pass) except Exception as ex: - raise RequiredArgumentMissingError('Failed to retrieve credentials for container registry {}. Please provide the registry username and password'.format(registry_name)) + raise RequiredArgumentMissingError('Failed to retrieve credentials for container registry {}. Please provide the registry username and password'.format(registry_name)) from ex def _registry_exists(containerapp_def, registry_server): diff --git a/src/containerapp/azext_containerapp/_validators.py b/src/containerapp/azext_containerapp/_validators.py index 916d9eb5b5..e7fe0435a1 100644 --- a/src/containerapp/azext_containerapp/_validators.py +++ b/src/containerapp/azext_containerapp/_validators.py @@ -2,9 +2,9 @@ # 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 unicodedata import name -from azure.cli.core.azclierror import (ValidationError, RequiredArgumentMissingError) +from azure.cli.core.azclierror import (ValidationError) def _is_number(s): @@ -14,6 +14,7 @@ def _is_number(s): except ValueError: return False + def validate_memory(namespace): memory = namespace.memory @@ -26,13 +27,15 @@ def validate_memory(namespace): if not valid: raise ValidationError("Usage error: --memory must be a number ending with \"Gi\"") + def validate_cpu(namespace): if namespace.cpu: cpu = namespace.cpu try: float(cpu) - except ValueError: - raise ValidationError("Usage error: --cpu must be a number eg. \"0.5\"") + except ValueError as e: + raise ValidationError("Usage error: --cpu must be a number eg. \"0.5\"") from e + def validate_managed_env_name_or_id(cmd, namespace): from azure.cli.core.commands.client_factory import get_subscription_id @@ -48,6 +51,7 @@ def validate_managed_env_name_or_id(cmd, namespace): name=namespace.managed_env ) + def validate_registry_server(namespace): if "create" in namespace.command.lower(): if namespace.registry_server: @@ -55,24 +59,28 @@ def validate_registry_server(namespace): if ".azurecr.io" not in namespace.registry_server: raise ValidationError("Usage error: --registry-server, --registry-password and --registry-username are required together if not using Azure Container Registry") + def validate_registry_user(namespace): if "create" in namespace.command.lower(): if namespace.registry_user: if not namespace.registry_server or (not namespace.registry_pass and ".azurecr.io" not in namespace.registry_server): raise ValidationError("Usage error: --registry-server, --registry-password and --registry-username are required together if not using Azure Container Registry") + def validate_registry_pass(namespace): if "create" in namespace.command.lower(): if namespace.registry_pass: if not namespace.registry_server or (not namespace.registry_user and ".azurecr.io" not in namespace.registry_server): raise ValidationError("Usage error: --registry-server, --registry-password and --registry-username are required together if not using Azure Container Registry") + def validate_target_port(namespace): if "create" in namespace.command.lower(): if namespace.target_port: if not namespace.ingress: raise ValidationError("Usage error: must specify --ingress with --target-port") + def validate_ingress(namespace): if "create" in namespace.command.lower(): if namespace.ingress: diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 9fd58c7575..87a892201a 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -3,9 +3,9 @@ # 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 import CliCommandType -from msrestazure.tools import is_valid_resource_id, parse_resource_id +# pylint: disable=line-too-long, too-many-statements, bare-except +# from azure.cli.core.commands import CliCommandType +# from msrestazure.tools import is_valid_resource_id, parse_resource_id from azext_containerapp._client_factory import ex_handler_factory @@ -15,7 +15,7 @@ def transform_containerapp_output(app): try: result['fqdn'] = app['properties']['configuration']['ingress']['fqdn'] - except Exception: + except: result['fqdn'] = None return result @@ -48,7 +48,7 @@ def load_command_table(self, _): g.custom_command('list', 'list_containerapp', table_transformer=transform_containerapp_list_output) g.custom_command('create', 'create_containerapp', supports_no_wait=True, exception_handler=ex_handler_factory()) g.custom_command('update', 'update_containerapp', supports_no_wait=True, exception_handler=ex_handler_factory()) - g.custom_command('delete', 'delete_containerapp', exception_handler=ex_handler_factory()) + g.custom_command('delete', 'delete_containerapp', confirmation=True, exception_handler=ex_handler_factory()) with self.command_group('containerapp env') as g: g.custom_command('show', 'show_managed_environment') @@ -70,8 +70,8 @@ def load_command_table(self, _): with self.command_group('containerapp github-action') as g: g.custom_command('add', 'create_or_update_github_action', exception_handler=ex_handler_factory()) - g.custom_command('show', 'show_github_action', exception_handler=ex_handler_factory()) - g.custom_command('delete', 'delete_github_action', exception_handler=ex_handler_factory()) + g.custom_command('show', 'show_github_action', exception_handler=ex_handler_factory()) + g.custom_command('delete', 'delete_github_action', exception_handler=ex_handler_factory()) with self.command_group('containerapp revision') as g: g.custom_command('activate', 'activate_revision') @@ -86,7 +86,7 @@ def load_command_table(self, _): g.custom_command('enable', 'enable_ingress', exception_handler=ex_handler_factory()) g.custom_command('disable', 'disable_ingress', exception_handler=ex_handler_factory()) g.custom_command('show', 'show_ingress') - + with self.command_group('containerapp ingress traffic') as g: g.custom_command('set', 'set_ingress_traffic', exception_handler=ex_handler_factory()) g.custom_command('show', 'show_ingress_traffic') @@ -106,4 +106,3 @@ def load_command_table(self, _): with self.command_group('containerapp dapr') as g: g.custom_command('enable', 'enable_dapr', exception_handler=ex_handler_factory()) g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) - diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 961fbd500f..d19ff49ea6 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -2,23 +2,21 @@ # 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, consider-using-f-string, logging-format-interpolation, inconsistent-return-statements, broad-except, bare-except, too-many-statements, too-many-locals, too-many-boolean-expressions, too-many-branches, too-many-nested-blocks, pointless-statement +from urllib.parse import urlparse from azure.cli.command_modules.appservice.custom import (_get_acr_cred) -from azure.cli.core.azclierror import (RequiredArgumentMissingError, ResourceNotFoundError, ValidationError) +from azure.cli.core.azclierror import (RequiredArgumentMissingError, ValidationError) from azure.cli.core.commands.client_factory import get_subscription_id -from azure.cli.core.util import sdk_no_wait from knack.util import CLIError from knack.log import get_logger -from urllib.parse import urlparse from msrestazure.tools import parse_resource_id, is_valid_resource_id from msrest.exceptions import DeserializationError -from azure.cli.command_modules.appservice.custom import _get_acr_cred -from urllib.parse import urlparse from ._client_factory import handle_raw_exception from ._clients import ManagedEnvironmentClient, ContainerAppClient, GitHubActionClient, DaprComponentClient -from ._sdk_models import * +# from ._sdk_models import * # pylint: disable=wildcard-import, unused-wildcard-import from ._github_oauth import get_github_access_token from ._models import ( ManagedEnvironment as ManagedEnvironmentModel, @@ -33,19 +31,19 @@ Dapr as DaprModel, ContainerResources as ContainerResourcesModel, Scale as ScaleModel, - Container as ContainerModel, - GitHubActionConfiguration, - RegistryInfo as RegistryInfoModel, - AzureCredentials as AzureCredentialsModel, + Container as ContainerModel, + GitHubActionConfiguration, + RegistryInfo as RegistryInfoModel, + AzureCredentials as AzureCredentialsModel, SourceControl as SourceControlModel, ManagedServiceIdentity as ManagedServiceIdentityModel) from ._utils import (_validate_subscription_registered, _get_location_from_resource_group, _ensure_location_allowed, - parse_secret_flags, store_as_secret_and_return_secret_ref, parse_list_of_strings, parse_env_var_flags, - _generate_log_analytics_if_not_provided, _get_existing_secrets, _convert_object_from_snake_to_camel_case, - _object_to_dict, _add_or_update_secrets, _remove_additional_attributes, _remove_readonly_attributes, - _add_or_update_env_vars, _add_or_update_tags, update_nested_dictionary, _update_traffic_weights, - _get_app_from_revision, raise_missing_token_suggestion, _infer_acr_credentials, _remove_registry_secret, _remove_secret, - _ensure_identity_resource_id, _remove_dapr_readonly_attributes, _registry_exists, _remove_env_vars, _update_revision_env_secretrefs) + parse_secret_flags, store_as_secret_and_return_secret_ref, parse_env_var_flags, + _generate_log_analytics_if_not_provided, _get_existing_secrets, _convert_object_from_snake_to_camel_case, + _object_to_dict, _add_or_update_secrets, _remove_additional_attributes, _remove_readonly_attributes, + _add_or_update_env_vars, _add_or_update_tags, update_nested_dictionary, _update_traffic_weights, + _get_app_from_revision, raise_missing_token_suggestion, _infer_acr_credentials, _remove_registry_secret, _remove_secret, + _ensure_identity_resource_id, _remove_dapr_readonly_attributes, _registry_exists, _remove_env_vars, _update_revision_env_secretrefs) logger = get_logger(__name__) @@ -70,19 +68,20 @@ def load_yaml_file(file_name): import errno try: - with open(file_name) as stream: + with open(file_name) as stream: # pylint: disable=unspecified-encoding return yaml.safe_load(stream) except (IOError, OSError) as ex: if getattr(ex, 'errno', 0) == errno.ENOENT: - raise CLIError('{} does not exist'.format(file_name)) + raise CLIError('{} does not exist'.format(file_name)) from ex raise except (yaml.parser.ParserError, UnicodeDecodeError) as ex: - raise CLIError('Error parsing {} ({})'.format(file_name, str(ex))) + raise CLIError('Error parsing {} ({})'.format(file_name, str(ex))) from ex def create_deserializer(): from msrest import Deserializer - import sys, inspect + import sys + import inspect sdkClasses = inspect.getmembers(sys.modules["azext_containerapp._sdk_models"]) deserializer = {} @@ -95,7 +94,7 @@ def create_deserializer(): def update_containerapp_yaml(cmd, name, resource_group_name, file_name, from_revision=None, no_wait=False): yaml_containerapp = process_loaded_yaml(load_yaml_file(file_name)) - if type(yaml_containerapp) != dict: + if type(yaml_containerapp) != dict: # pylint: disable=unidiomatic-typecheck raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') if not yaml_containerapp.get('name'): @@ -114,7 +113,7 @@ def update_containerapp_yaml(cmd, name, resource_group_name, file_name, from_rev containerapp_def = None try: current_containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) - except Exception as ex: + except Exception: pass if not current_containerapp_def: @@ -126,17 +125,16 @@ def update_containerapp_yaml(cmd, name, resource_group_name, file_name, from_rev r = ContainerAppClient.show_revision(cmd=cmd, resource_group_name=resource_group_name, container_app_name=name, name=from_revision) except CLIError as e: handle_raw_exception(e) - _update_revision_env_secretrefs(r["properties"]["template"]["containers"], name) current_containerapp_def["properties"]["template"] = r["properties"]["template"] - + # Deserialize the yaml into a ContainerApp object. Need this since we're not using SDK try: deserializer = create_deserializer() containerapp_def = deserializer('ContainerApp', yaml_containerapp) except DeserializationError as ex: - raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') + raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') from ex # Remove tags before converting from snake case to camel case, then re-add tags. We don't want to change the case of the tags. Need this since we're not using SDK tags = None @@ -158,54 +156,6 @@ def update_containerapp_yaml(cmd, name, resource_group_name, file_name, from_rev _remove_additional_attributes(current_containerapp_def) _remove_readonly_attributes(current_containerapp_def) - ''' - # Not sure if update should replace items that are a list, or do createOrUpdate. This commented out section is the implementation for createOrUpdate. - # (If a property is a list, do createOrUpdate, rather than just replace with new list) - - if 'properties' in containerapp_def and 'template' in containerapp_def['properties']: - # Containers - if 'containers' in containerapp_def['properties']['template'] and containerapp_def['properties']['template']['containers']: - for new_container in containerapp_def['properties']['template']['containers']: - if "name" not in new_container or not new_container["name"]: - raise ValidationError("The container name is not specified.") - - # Check if updating existing container - updating_existing_container = False - for existing_container in current_containerapp_def["properties"]["template"]["containers"]: - if existing_container['name'].lower() == new_container['name'].lower(): - updating_existing_container = True - - if 'image' in new_container and new_container['image']: - existing_container['image'] = new_container['image'] - if 'env' in new_container and new_container['env']: - if 'env' not in existing_container or not existing_container['env']: - existing_container['env'] = [] - _add_or_update_env_vars(existing_container['env'], new_container['env']) - if 'command' in new_container and new_container['command']: - existing_container['command'] = new_container['command'] - if 'args' in new_container and new_container['args']: - existing_container['args'] = new_container['args'] - if 'resources' in new_container and new_container['resources']: - if 'cpu' in new_container['resources'] and new_container['resources']['cpu'] is not None: - existing_container['resources']['cpu'] = new_container['resources']['cpu'] - if 'memory' in new_container['resources'] and new_container['resources']['memory'] is not None: - existing_container['resources']['memory'] = new_container['resources']['memory'] - - # If not updating existing container, add as new container - if not updating_existing_container: - current_containerapp_def["properties"]["template"]["containers"].append(new_container) - - # Traffic Weights - - # Secrets - - # Registries - - # Scale rules - - # Source Controls - - ''' try: r = ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=current_containerapp_def, no_wait=no_wait) @@ -222,7 +172,7 @@ def update_containerapp_yaml(cmd, name, resource_group_name, file_name, from_rev def create_containerapp_yaml(cmd, name, resource_group_name, file_name, no_wait=False): yaml_containerapp = process_loaded_yaml(load_yaml_file(file_name)) - if type(yaml_containerapp) != dict: + if type(yaml_containerapp) != dict: # pylint: disable=unidiomatic-typecheck raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') if not yaml_containerapp.get('name'): @@ -244,7 +194,7 @@ def create_containerapp_yaml(cmd, name, resource_group_name, file_name, no_wait= containerapp_def = deserializer('ContainerApp', yaml_containerapp) except DeserializationError as ex: - raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') + raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') from ex # Remove tags before converting from snake case to camel case, then re-add tags. We don't want to change the case of the tags. Need this since we're not using SDK tags = None @@ -271,7 +221,7 @@ def create_containerapp_yaml(cmd, name, resource_group_name, file_name, no_wait= env_rg = None env_info = None - if (is_valid_resource_id(env_id)): + if is_valid_resource_id(env_id): parsed_managed_env = parse_resource_id(env_id) env_name = parsed_managed_env['name'] env_rg = parsed_managed_env['resource_group'] @@ -334,14 +284,14 @@ def create_containerapp(cmd, args=None, tags=None, no_wait=False, - assign_identity=[]): + assign_identity=None): _validate_subscription_registered(cmd, "Microsoft.App") if yaml: if image or managed_env or min_replicas or max_replicas or target_port or ingress or\ revisions_mode or secrets or env_vars or cpu or memory or registry_server or\ registry_user or registry_pass or dapr_enabled or dapr_app_port or dapr_app_id or\ - location or startup_command or args or tags: + startup_command or args or tags: logger.warning('Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead') return create_containerapp_yaml(cmd=cmd, name=name, resource_group_name=resource_group_name, file_name=yaml, no_wait=no_wait) @@ -351,6 +301,9 @@ def create_containerapp(cmd, if managed_env is None: raise RequiredArgumentMissingError('Usage error: --environment is required if not using --yaml') + if assign_identity is None: + assign_identity = [] + # Validate managed environment parsed_managed_env = parse_resource_id(managed_env) managed_env_name = parsed_managed_env['name'] @@ -416,19 +369,19 @@ def create_containerapp(cmd, if assign_system_identity and assign_user_identities: identity_def["type"] = "SystemAssigned, UserAssigned" - elif assign_system_identity: + elif assign_system_identity: identity_def["type"] = "SystemAssigned" - elif assign_user_identities: + elif assign_user_identities: identity_def["type"] = "UserAssigned" if assign_user_identities: identity_def["userAssignedIdentities"] = {} subscription_id = get_subscription_id(cmd.cli_ctx) - + for r in assign_user_identities: r = _ensure_identity_resource_id(subscription_id, resource_group_name, r) - identity_def["userAssignedIdentities"][r] = {} - + identity_def["userAssignedIdentities"][r] = {} # pylint: disable=unsupported-assignment-operation + scale_def = None if min_replicas is not None or max_replicas is not None: scale_def = ScaleModel @@ -517,9 +470,9 @@ def update_containerapp(cmd, if yaml: if image or min_replicas or max_replicas or\ - revisions_mode or secrets or set_env_vars or remove_env_vars or replace_env_vars or remove_all_env_vars or cpu or memory or registry_server or\ - registry_user or registry_pass or\ - startup_command or args or tags: + revisions_mode or secrets or set_env_vars or remove_env_vars or replace_env_vars or remove_all_env_vars or cpu or memory or registry_server or\ + registry_user or registry_pass or\ + startup_command or args or tags: logger.warning('Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead') return update_containerapp_yaml(cmd=cmd, name=name, resource_group_name=resource_group_name, file_name=yaml, no_wait=no_wait) @@ -771,20 +724,20 @@ def delete_containerapp(cmd, name, resource_group_name): def create_managed_environment(cmd, - name, - resource_group_name, - logs_customer_id=None, - logs_key=None, - location=None, - instrumentation_key=None, - infrastructure_subnet_resource_id=None, - app_subnet_resource_id=None, - docker_bridge_cidr=None, - platform_reserved_cidr=None, - platform_reserved_dns_ip=None, - internal_only=False, - tags=None, - no_wait=False): + name, + resource_group_name, + logs_customer_id=None, + logs_key=None, + location=None, + instrumentation_key=None, + infrastructure_subnet_resource_id=None, + app_subnet_resource_id=None, + docker_bridge_cidr=None, + platform_reserved_cidr=None, + platform_reserved_dns_ip=None, + internal_only=False, + tags=None, + no_wait=False): location = location or _get_location_from_resource_group(cmd.cli_ctx, resource_group_name) @@ -793,7 +746,7 @@ def create_managed_environment(cmd, # Microsoft.ContainerService RP registration is required for vnet enabled environments if infrastructure_subnet_resource_id is not None or app_subnet_resource_id is not None: - if (is_valid_resource_id(app_subnet_resource_id)): + if is_valid_resource_id(app_subnet_resource_id): parsed_app_subnet_resource_id = parse_resource_id(app_subnet_resource_id) subnet_subscription = parsed_app_subnet_resource_id["subscription"] _validate_subscription_registered(cmd, "Microsoft.ContainerService", subnet_subscription) @@ -862,28 +815,12 @@ def create_managed_environment(cmd, def update_managed_environment(cmd, - name, - resource_group_name, - tags=None, - no_wait=False): + name, + resource_group_name, + tags=None, + no_wait=False): raise CLIError('Containerapp env update is not yet supported.') - _validate_subscription_registered(cmd, "Microsoft.App") - - managed_env_def = ManagedEnvironmentModel - managed_env_def["tags"] = tags - - try: - r = ManagedEnvironmentClient.update( - cmd=cmd, resource_group_name=resource_group_name, name=name, managed_environment_envelope=managed_env_def, no_wait=no_wait) - - if "properties" in r and "provisioningState" in r["properties"] and r["properties"]["provisioningState"].lower() == "waiting" and not no_wait: - logger.warning('Containerapp environment update in progress. Please monitor the creation using `az containerapp env show -n {} -g {}`'.format(name, resource_group_name)) - - return r - except Exception as e: - handle_raw_exception(e) - def show_managed_environment(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -925,9 +862,9 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ if not identities: identities = ['[system]'] logger.warning('Identities not specified. Assigning managed system identity.') - + identities = [x.lower() for x in identities] - assign_system_identity = '[system]' in identities + assign_system_identity = '[system]' in identities assign_user_identities = [x for x in identities if x != '[system]'] containerapp_def = None @@ -944,7 +881,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) # If identity not returned - try: + try: containerapp_def["identity"] containerapp_def["identity"]["type"] except: @@ -956,30 +893,30 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ # Assign correct type try: - if containerapp_def["identity"]["type"] != "None": + if containerapp_def["identity"]["type"] != "None": if containerapp_def["identity"]["type"] == "SystemAssigned" and assign_user_identities: containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" if containerapp_def["identity"]["type"] == "UserAssigned" and assign_system_identity: containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" - else: + else: if assign_system_identity and assign_user_identities: containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" - elif assign_system_identity: + elif assign_system_identity: containerapp_def["identity"]["type"] = "SystemAssigned" - elif assign_user_identities: + elif assign_user_identities: containerapp_def["identity"]["type"] = "UserAssigned" - except: - # Always returns "type": "None" when CA has no previous identities + except: + # Always returns "type": "None" when CA has no previous identities pass - + if assign_user_identities: - try: + try: containerapp_def["identity"]["userAssignedIdentities"] - except: + except: containerapp_def["identity"]["userAssignedIdentities"] = {} subscription_id = get_subscription_id(cmd.cli_ctx) - + for r in assign_user_identities: old_id = r r = _ensure_identity_resource_id(subscription_id, resource_group_name, r).replace("resourceGroup", "resourcegroup") @@ -987,7 +924,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ containerapp_def["identity"]["userAssignedIdentities"][r] logger.warning("User identity {} is already assigned to containerapp".format(old_id)) except: - containerapp_def["identity"]["userAssignedIdentities"][r] = {} + containerapp_def["identity"]["userAssignedIdentities"][r] = {} try: r = ContainerAppClient.create_or_update(cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) @@ -997,7 +934,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ except Exception as e: handle_raw_exception(e) - + def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1010,7 +947,7 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= remove_user_identities = list(set(remove_user_identities)) if remove_id_size != len(remove_user_identities): logger.warning("At least one identity was passed twice.") - + containerapp_def = None # Get containerapp properties of CA we are updating try: @@ -1024,7 +961,7 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) # If identity not returned - try: + try: containerapp_def["identity"] containerapp_def["identity"]["type"] except: @@ -1041,17 +978,17 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= if remove_user_identities: subscription_id = get_subscription_id(cmd.cli_ctx) - try: + try: containerapp_def["identity"]["userAssignedIdentities"] - except: + except: containerapp_def["identity"]["userAssignedIdentities"] = {} - for id in remove_user_identities: - given_id = id - id = _ensure_identity_resource_id(subscription_id, resource_group_name, id) + for remove_id in remove_user_identities: + given_id = remove_id + remove_id = _ensure_identity_resource_id(subscription_id, resource_group_name, remove_id) wasRemoved = False for old_user_identity in containerapp_def["identity"]["userAssignedIdentities"]: - if old_user_identity.lower() == id.lower(): + if old_user_identity.lower() == remove_id.lower(): containerapp_def["identity"]["userAssignedIdentities"].pop(old_user_identity) wasRemoved = True break @@ -1062,14 +999,14 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= if containerapp_def["identity"]["userAssignedIdentities"] == {}: containerapp_def["identity"]["userAssignedIdentities"] = None containerapp_def["identity"]["type"] = ("None" if containerapp_def["identity"]["type"] == "UserAssigned" else "SystemAssigned") - + try: r = ContainerAppClient.create_or_update(cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) return r["identity"] except Exception as e: handle_raw_exception(e) - - + + def show_managed_identity(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1080,10 +1017,12 @@ def show_managed_identity(cmd, name, resource_group_name): try: return r["identity"] - except: + except: r["identity"] = {} r["identity"]["type"] = "None" return r["identity"] + + def create_or_update_github_action(cmd, name, resource_group_name, @@ -1109,13 +1048,13 @@ def create_or_update_github_action(cmd, try: # Verify github repo from github import Github, GithubException - from github.GithubException import BadCredentialsException, UnknownObjectException + from github.GithubException import BadCredentialsException repo = None repo = repo_url.split('/') if len(repo) >= 2: repo = '/'.join(repo[-2:]) - + if repo: g = Github(token) github_repo = None @@ -1129,32 +1068,31 @@ def create_or_update_github_action(cmd, error_msg = "Encountered GitHub error when accessing {} branch in {} repo.".format(branch, repo) if e.data and e.data['message']: error_msg += " Error: {}".format(e.data['message']) - raise CLIError(error_msg) + raise CLIError(error_msg) from e logger.warning('Verified GitHub repo and branch') - except BadCredentialsException: + except BadCredentialsException as e: raise CLIError("Could not authenticate to the repository. Please create a Personal Access Token and use " - "the --token argument. Run 'az webapp deployment github-actions add --help' " - "for more information.") + "the --token argument. Run 'az webapp deployment github-actions add --help' " + "for more information.") from e except GithubException as e: error_msg = "Encountered GitHub error when accessing {} repo".format(repo) if e.data and e.data['message']: error_msg += " Error: {}".format(e.data['message']) - raise CLIError(error_msg) + raise CLIError(error_msg) from e except CLIError as clierror: raise clierror - except Exception as ex: + except Exception: # If exception due to github package missing, etc just continue without validating the repo and rely on api validation pass source_control_info = None try: - #source_control_info = client.get_source_control_info(resource_group_name, name).properties source_control_info = GitHubActionClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) except Exception as ex: if not service_principal_client_id or not service_principal_client_secret or not service_principal_tenant_id: - raise RequiredArgumentMissingError('Service principal client ID, secret and tenant ID are required to add github actions for the first time. Please create one using the command \"az ad sp create-for-rbac --name \{name\} --role contributor --scopes /subscriptions/\{subscription\}/resourceGroups/\{resourceGroup\} --sdk-auth\"') + raise RequiredArgumentMissingError('Service principal client ID, secret and tenant ID are required to add github actions for the first time. Please create one using the command \"az ad sp create-for-rbac --name {{name}} --role contributor --scopes /subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}} --sdk-auth\"') from ex source_control_info = SourceControlModel source_control_info["properties"]["repoUrl"] = repo_url @@ -1185,7 +1123,7 @@ def create_or_update_github_action(cmd, try: registry_username, registry_password = _get_acr_cred(cmd.cli_ctx, registry_name) except Exception as ex: - raise RequiredArgumentMissingError('Failed to retrieve credentials for container registry. Please provide the registry username and password') + raise RequiredArgumentMissingError('Failed to retrieve credentials for container registry. Please provide the registry username and password') from ex registry_info = RegistryInfoModel registry_info["registryUrl"] = registry_url @@ -1201,13 +1139,13 @@ def create_or_update_github_action(cmd, headers = ["x-ms-github-auxiliary={}".format(token)] - try: - r = GitHubActionClient.create_or_update(cmd = cmd, resource_group_name=resource_group_name, name=name, github_action_envelope=source_control_info, headers = headers) + try: + r = GitHubActionClient.create_or_update(cmd=cmd, resource_group_name=resource_group_name, name=name, github_action_envelope=source_control_info, headers=headers) return r except Exception as e: handle_raw_exception(e) - - + + def show_github_action(cmd, name, resource_group_name): try: return GitHubActionClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) @@ -1217,7 +1155,7 @@ def show_github_action(cmd, name, resource_group_name): def delete_github_action(cmd, name, resource_group_name, token=None, login_with_github=False): # Check if there is an existing source control to delete - try: + try: github_action_config = GitHubActionClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) except Exception as e: handle_raw_exception(e) @@ -1236,13 +1174,13 @@ def delete_github_action(cmd, name, resource_group_name, token=None, login_with_ try: # Verify github repo from github import Github, GithubException - from github.GithubException import BadCredentialsException, UnknownObjectException + from github.GithubException import BadCredentialsException repo = None repo = repo_url.split('/') if len(repo) >= 2: repo = '/'.join(repo[-2:]) - + if repo: g = Github(token) github_repo = None @@ -1250,21 +1188,21 @@ def delete_github_action(cmd, name, resource_group_name, token=None, login_with_ github_repo = g.get_repo(repo) if not github_repo.permissions.push or not github_repo.permissions.maintain: raise CLIError("The token does not have appropriate access rights to repository {}.".format(repo)) - except BadCredentialsException: + except BadCredentialsException as e: raise CLIError("Could not authenticate to the repository. Please create a Personal Access Token and use " - "the --token argument. Run 'az webapp deployment github-actions add --help' " - "for more information.") + "the --token argument. Run 'az webapp deployment github-actions add --help' " + "for more information.") from e except GithubException as e: error_msg = "Encountered GitHub error when accessing {} repo".format(repo) if e.data and e.data['message']: error_msg += " Error: {}".format(e.data['message']) - raise CLIError(error_msg) + raise CLIError(error_msg) from e except CLIError as clierror: raise clierror - except Exception as ex: + except Exception: # If exception due to github package missing, etc just continue without validating the repo and rely on api validation pass - + headers = ["x-ms-github-auxiliary={}".format(token)] try: @@ -1309,6 +1247,7 @@ def activate_revision(cmd, resource_group_name, revision_name, name=None): except CLIError as e: handle_raw_exception(e) + def deactivate_revision(cmd, resource_group_name, revision_name, name=None): if not name: name = _get_app_from_revision(revision_name) @@ -1318,10 +1257,11 @@ def deactivate_revision(cmd, resource_group_name, revision_name, name=None): except CLIError as e: handle_raw_exception(e) + def copy_revision(cmd, resource_group_name, from_revision=None, - #label=None, + # label=None, name=None, yaml=None, image=None, @@ -1351,7 +1291,7 @@ def copy_revision(cmd, if image or min_replicas or max_replicas or\ set_env_vars or replace_env_vars or remove_env_vars or \ remove_all_env_vars or cpu or memory or \ - startup_command or args or tags: + startup_command or args or tags: logger.warning('Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead') return update_containerapp_yaml(cmd=cmd, name=name, resource_group_name=resource_group_name, file_name=yaml, from_revision=from_revision, no_wait=no_wait) @@ -1519,6 +1459,7 @@ def copy_revision(cmd, except Exception as e: handle_raw_exception(e) + def set_revision_mode(cmd, resource_group_name, name, mode, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1539,9 +1480,10 @@ def set_revision_mode(cmd, resource_group_name, name, mode, no_wait=False): r = ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) return r["properties"]["configuration"]["activeRevisionsMode"] - except Exception as e: + except Exception as e: handle_raw_exception(e) + def show_ingress(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1556,10 +1498,11 @@ def show_ingress(cmd, name, resource_group_name): try: return containerapp_def["properties"]["configuration"]["ingress"] - except: - raise CLIError("The containerapp '{}' does not have ingress enabled.".format(name)) + except Exception as e: + raise CLIError("The containerapp '{}' does not have ingress enabled.".format(name)) from e + -def enable_ingress(cmd, name, resource_group_name, type, target_port, transport, allow_insecure=False, no_wait=False): +def enable_ingress(cmd, name, resource_group_name, type, target_port, transport, allow_insecure=False, no_wait=False): # pylint: disable=redefined-builtin _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None @@ -1585,7 +1528,7 @@ def enable_ingress(cmd, name, resource_group_name, type, target_port, transport, ingress_def["targetPort"] = target_port ingress_def["transport"] = transport ingress_def["allowInsecure"] = allow_insecure - + containerapp_def["properties"]["configuration"]["ingress"] = ingress_def _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) @@ -1594,9 +1537,10 @@ def enable_ingress(cmd, name, resource_group_name, type, target_port, transport, r = ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) return r["properties"]["configuration"]["ingress"] - except Exception as e: + except Exception as e: handle_raw_exception(e) + def disable_ingress(cmd, name, resource_group_name, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1614,13 +1558,14 @@ def disable_ingress(cmd, name, resource_group_name, no_wait=False): _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) try: - r = ContainerAppClient.create_or_update( + ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) logger.warning("Ingress has been disabled successfully.") - return - except Exception as e: + return + except Exception as e: handle_raw_exception(e) + def set_ingress_traffic(cmd, name, resource_group_name, traffic_weights, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1635,8 +1580,8 @@ def set_ingress_traffic(cmd, name, resource_group_name, traffic_weights, no_wait try: containerapp_def["properties"]["configuration"]["ingress"] - except: - raise CLIError("Ingress must be enabled to set ingress traffic. Try running `az containerapp ingress -h` for more info.") + except Exception as e: + raise CLIError("Ingress must be enabled to set ingress traffic. Try running `az containerapp ingress -h` for more info.") from e if traffic_weights is not None: _update_traffic_weights(containerapp_def, traffic_weights) @@ -1647,9 +1592,10 @@ def set_ingress_traffic(cmd, name, resource_group_name, traffic_weights, no_wait r = ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) return r["properties"]["configuration"]["ingress"]["traffic"] - except Exception as e: + except Exception as e: handle_raw_exception(e) + def show_ingress_traffic(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1664,8 +1610,9 @@ def show_ingress_traffic(cmd, name, resource_group_name): try: return containerapp_def["properties"]["configuration"]["ingress"]["traffic"] - except: - raise CLIError("Ingress must be enabled to show ingress traffic. Try running `az containerapp ingress -h` for more info.") + except Exception as e: + raise CLIError("Ingress must be enabled to show ingress traffic. Try running `az containerapp ingress -h` for more info.") from e + def show_registry(cmd, name, resource_group_name, server): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1681,8 +1628,8 @@ def show_registry(cmd, name, resource_group_name, server): try: containerapp_def["properties"]["configuration"]["registries"] - except: - raise CLIError("The containerapp {} has no assigned registries.".format(name)) + except Exception as e: + raise CLIError("The containerapp {} has no assigned registries.".format(name)) from e registries_def = containerapp_def["properties"]["configuration"]["registries"] @@ -1691,6 +1638,7 @@ def show_registry(cmd, name, resource_group_name, server): return r raise CLIError("The containerapp {} does not have specified registry assigned.".format(name)) + def list_registry(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1705,8 +1653,9 @@ def list_registry(cmd, name, resource_group_name): try: return containerapp_def["properties"]["configuration"]["registries"] - except: - raise CLIError("The containerapp {} has no assigned registries.".format(name)) + except Exception as e: + raise CLIError("The containerapp {} has no assigned registries.".format(name)) from e + def set_registry(cmd, name, resource_group_name, server, username=None, password=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1741,7 +1690,7 @@ def set_registry(cmd, name, resource_group_name, server, username=None, password try: username, password = _get_acr_cred(cmd.cli_ctx, registry_name) except Exception as ex: - raise RequiredArgumentMissingError('Failed to retrieve credentials for container registry. Please provide the registry username and password') + raise RequiredArgumentMissingError('Failed to retrieve credentials for container registry. Please provide the registry username and password') from ex # Check if updating existing registry updating_existing_registry = False @@ -1770,10 +1719,9 @@ def set_registry(cmd, name, resource_group_name, server, username=None, password server, password, update_existing_secret=True) - # Should this be false? ^ registries_def.append(registry) - + try: r = ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) @@ -1782,6 +1730,7 @@ def set_registry(cmd, name, resource_group_name, server, username=None, password except Exception as e: handle_raw_exception(e) + def remove_registry(cmd, name, resource_group_name, server, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1797,18 +1746,17 @@ def remove_registry(cmd, name, resource_group_name, server, no_wait=False): _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) registries_def = None - registry = None try: containerapp_def["properties"]["configuration"]["registries"] - except: - raise CLIError("The containerapp {} has no assigned registries.".format(name)) + except Exception as e: + raise CLIError("The containerapp {} has no assigned registries.".format(name)) from e registries_def = containerapp_def["properties"]["configuration"]["registries"] wasRemoved = False - for i in range(0, len(registries_def)): - r = registries_def[i] + for i, value in enumerate(registries_def): + r = value if r['server'].lower() == server.lower(): registries_def.pop(i) _remove_registry_secret(containerapp_def=containerapp_def, server=server, username=r["username"]) @@ -1827,8 +1775,9 @@ def remove_registry(cmd, name, resource_group_name, server, no_wait=False): logger.warning("Registry successfully removed.") return r["properties"]["configuration"]["registries"] # No registries to return, so return nothing - except Exception as e: - return + except Exception: + pass + def list_secrets(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1844,8 +1793,9 @@ def list_secrets(cmd, name, resource_group_name): try: return ContainerAppClient.list_secrets(cmd=cmd, resource_group_name=resource_group_name, name=name)["value"] - except: - raise CLIError("The containerapp {} has no assigned secrets.".format(name)) + except Exception as e: + raise CLIError("The containerapp {} has no assigned secrets.".format(name)) from e + def show_secret(cmd, name, resource_group_name, secret_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1865,7 +1815,8 @@ def show_secret(cmd, name, resource_group_name, secret_name): return secret raise CLIError("The containerapp {} does not have a secret assigned with name {}.".format(name, secret_name)) -def remove_secrets(cmd, name, resource_group_name, secret_names, no_wait = False): + +def remove_secrets(cmd, name, resource_group_name, secret_names, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None @@ -1900,10 +1851,10 @@ def remove_secrets(cmd, name, resource_group_name, secret_names, no_wait = False except Exception as e: handle_raw_exception(e) -def set_secrets(cmd, name, resource_group_name, secrets, - #secrets=None, - #yaml=None, - no_wait = False): + +def set_secrets(cmd, name, resource_group_name, secrets, + # yaml=None, + no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") # if not yaml and not secrets: @@ -1911,7 +1862,7 @@ def set_secrets(cmd, name, resource_group_name, secrets, # if not secrets: # secrets = [] - + # if yaml: # yaml_secrets = load_yaml_file(yaml).split(' ') # try: @@ -1940,6 +1891,7 @@ def set_secrets(cmd, name, resource_group_name, secrets, except Exception as e: handle_raw_exception(e) + def enable_dapr(cmd, name, resource_group_name, dapr_app_id=None, dapr_app_port=None, dapr_app_protocol=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") @@ -1956,13 +1908,13 @@ def enable_dapr(cmd, name, resource_group_name, dapr_app_id=None, dapr_app_port= if 'dapr' not in containerapp_def['properties']: containerapp_def['properties']['dapr'] = {} - + if dapr_app_id: containerapp_def['properties']['dapr']['dapr_app_id'] = dapr_app_id - + if dapr_app_port: containerapp_def['properties']['dapr']['dapr_app_port'] = dapr_app_port - + if dapr_app_protocol: containerapp_def['properties']['dapr']['dapr_app_protocol'] = dapr_app_protocol @@ -1975,7 +1927,8 @@ def enable_dapr(cmd, name, resource_group_name, dapr_app_id=None, dapr_app_port= except Exception as e: handle_raw_exception(e) -def disable_dapr(cmd, name, resource_group_name, no_wait=False): + +def disable_dapr(cmd, name, resource_group_name, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None @@ -1998,21 +1951,24 @@ def disable_dapr(cmd, name, resource_group_name, no_wait=False): except Exception as e: handle_raw_exception(e) + def list_dapr_components(cmd, resource_group_name, environment_name): _validate_subscription_registered(cmd, "Microsoft.App") return DaprComponentClient.list(cmd, resource_group_name, environment_name) + def show_dapr_component(cmd, resource_group_name, dapr_component_name, environment_name): _validate_subscription_registered(cmd, "Microsoft.App") return DaprComponentClient.show(cmd, resource_group_name, environment_name, name=dapr_component_name) + def create_or_update_dapr_component(cmd, resource_group_name, environment_name, dapr_component_name, yaml): _validate_subscription_registered(cmd, "Microsoft.App") yaml_containerapp = load_yaml_file(yaml) - if type(yaml_containerapp) != dict: + if type(yaml_containerapp) != dict: # pylint: disable=unidiomatic-typecheck raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') # Deserialize the yaml into a DaprComponent object. Need this since we're not using SDK @@ -2022,9 +1978,8 @@ def create_or_update_dapr_component(cmd, resource_group_name, environment_name, daprcomponent_def = deserializer('DaprComponent', yaml_containerapp) except DeserializationError as ex: - raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') + raise ValidationError('Invalid YAML provided. Please see https://docs.microsoft.com/azure/container-apps/azure-resource-manager-api-spec#examples for a valid containerapps YAML spec.') from ex - #daprcomponent_def = _object_to_dict(daprcomponent_def) daprcomponent_def = _convert_object_from_snake_to_camel_case(_object_to_dict(daprcomponent_def)) # Remove "additionalProperties" and read-only attributes that are introduced in the deserialization. Need this since we're not using SDK @@ -2044,13 +1999,14 @@ def create_or_update_dapr_component(cmd, resource_group_name, environment_name, except Exception as e: handle_raw_exception(e) + def remove_dapr_component(cmd, resource_group_name, dapr_component_name, environment_name): _validate_subscription_registered(cmd, "Microsoft.App") - try: + try: DaprComponentClient.show(cmd, resource_group_name, environment_name, name=dapr_component_name) - except: - raise CLIError("Dapr component not found.") + except Exception as e: + raise CLIError("Dapr component not found.") from e try: r = DaprComponentClient.delete(cmd, resource_group_name, environment_name, name=dapr_component_name) @@ -2058,4 +2014,3 @@ def remove_dapr_component(cmd, resource_group_name, dapr_component_name, environ return r except Exception as e: handle_raw_exception(e) -