Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various p0 Fixes #19

Merged
merged 7 commits into from
Mar 10, 2022
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
2 changes: 1 addition & 1 deletion src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
--registry-password mypassword
- name: Update a Containerapp using a specified startup command and arguments
text: |
az containerapp create -n MyContainerapp -g MyResourceGroup \\
az containerapp update -n MyContainerapp -g MyResourceGroup \\
--image MyContainerImage \\
--command "/bin/sh"
--args "-c", "while true; do echo hello; sleep 10;done"
Expand Down
6 changes: 3 additions & 3 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def load_arguments(self, _):

with self.argument_context('containerapp') as c:
c.argument('tags', arg_type=tags_type)
c.argument('managed_env', validator=validate_managed_env_name_or_id, options_list=['--environment', '-e'], help="Name or resource ID of the containerapp's environment.")
c.argument('managed_env', validator=validate_managed_env_name_or_id, options_list=['--environment'], help="Name or resource ID of the containerapp's environment.")
c.argument('yaml', type=file_type, help='Path to a .yaml file with the configuration of a containerapp. All other parameters will be ignored')

# Container
Expand All @@ -35,7 +35,7 @@ def load_arguments(self, _):
c.argument('image_name', type=str, options_list=['--image-name'], help="Name of the Container image.")
c.argument('cpu', type=float, validator=validate_cpu, options_list=['--cpu'], help="Required CPU in cores, e.g. 0.5")
c.argument('memory', type=str, validator=validate_memory, options_list=['--memory'], help="Required memory, e.g. 1.0Gi")
c.argument('env_vars', nargs='*', options_list=['--environment-variables'], help="A list of environment variable(s) for the containerapp. Space-separated values in 'key=value' format.")
c.argument('env_vars', nargs='*', options_list=['--env-vars', '--environment-variables'], help="A list of environment variable(s) for the containerapp. Space-separated values in 'key=value' format. Empty string to clear existing values")
c.argument('startup_command', nargs='*', options_list=['--command'], help="A list of supported commands on the container app that will executed during container startup. Space-separated values e.g. \"/bin/queue\" \"mycommand\". Empty string to clear existing values")
c.argument('args', nargs='*', options_list=['--args'], help="A list of container startup command argument(s). Space-separated values e.g. \"-c\" \"mycommand\". Empty string to clear existing values")
c.argument('revision_suffix', type=str, options_list=['--revision-suffix'], help='User friendly suffix that is appended to the revision name')
Expand All @@ -56,7 +56,7 @@ def load_arguments(self, _):
# Configuration
with self.argument_context('containerapp', arg_group='Configuration') as c:
c.argument('revisions_mode', arg_type=get_enum_type(['single', 'multiple']), options_list=['--revisions-mode'], help="The active revisions mode for the containerapp.")
c.argument('registry_server', type=str, validator=validate_registry_server, options_list=['--registry-login-server'], help="The url of the registry, e.g. myregistry.azurecr.io")
c.argument('registry_server', type=str, validator=validate_registry_server, options_list=['--registry-server', '--registry-login-server'], help="The url of the registry, e.g. myregistry.azurecr.io")
c.argument('registry_pass', type=str, validator=validate_registry_pass, options_list=['--registry-password'], help="The password to log in container image registry server. If stored as a secret, value must start with \'secretref:\' followed by the secret name.")
c.argument('registry_user', type=str, validator=validate_registry_user, options_list=['--registry-username'], help="The username to log in container image registry server")
c.argument('secrets', nargs='*', options_list=['--secrets', '-s'], help="A list of secret(s) for the containerapp. Space-separated values in 'key=value' format.")
Expand Down
21 changes: 18 additions & 3 deletions src/containerapp/azext_containerapp/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from distutils.filelist import findall
from operator import is_
from azure.cli.command_modules.appservice.custom import (_get_acr_cred)
from azure.cli.core.azclierror import (ResourceNotFoundError, 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
Expand Down Expand Up @@ -73,8 +73,8 @@ def parse_env_var_flags(env_list, is_update_containerapp=False):
key_val = pair.split('=', 1)
if len(key_val) != 2:
if is_update_containerapp:
raise ValidationError("Environment variables must be in the format \"<key>=<value>,<key>=secretref:<value>,...\". If you are updating a Containerapp, did you pass in the flag \"--environment\"? Updating a containerapp environment is not supported, please re-run the command without this flag.")
raise ValidationError("Environment variables must be in the format \"<key>=<value>,<key>=secretref:<value>,...\".")
raise ValidationError("Environment variables must be in the format \"<key>=<value>\" \"<key>=secretref:<value>\" ...\".")
raise ValidationError("Environment variables must be in the format \"<key>=<value>\" \"<key>=secretref:<value>\" ...\".")
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]))
value = key_val[1].split('secretref:')
Expand Down Expand Up @@ -444,3 +444,18 @@ def _get_app_from_revision(revision):
revision.pop()
revision = "--".join(revision)
return revision


def _infer_acr_credentials(cmd, registry_server):
# If registry is Azure Container Registry, we can try inferring credentials
if '.azurecr.io' not in registry_server:
raise RequiredArgumentMissingError('Registry url is required if using Azure Container Registry, otherwise Registry username and password are required.')
logger.warning('No credential was provided to access Azure Container Registry. Trying to look up credentials...')
parsed = urlparse(registry_server)
registry_name = (parsed.netloc if parsed.scheme else parsed.path).split('.')[0]

try:
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))
11 changes: 6 additions & 5 deletions src/containerapp/azext_containerapp/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,20 @@ def validate_registry_server(namespace):
if "create" in namespace.command.lower():
if namespace.registry_server:
if not namespace.registry_user or not namespace.registry_pass:
raise ValidationError("Usage error: --registry-login-server, --registry-password and --registry-username are required together")
if ".azurecr.io" not in namespace.registry_server:
raise ValidationError("Usage error: --registry-login-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:
raise ValidationError("Usage error: --registry-login-server, --registry-password and --registry-username are required together")
if not namespace.registry_server or (not namespace.registry_pass and ".azurecr.io" not in namespace.registry_server):
raise ValidationError("Usage error: --registry-login-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_user or not namespace.registry_server:
raise ValidationError("Usage error: --registry-login-server, --registry-password and --registry-username are required together")
if not namespace.registry_server or (not namespace.registry_user and ".azurecr.io" not in namespace.registry_server):
raise ValidationError("Usage error: --registry-login-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():
Expand Down
47 changes: 30 additions & 17 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.command_modules.appservice.custom import (_get_acr_cred)
from azure.cli.core.azclierror import (RequiredArgumentMissingError, ResourceNotFoundError, ValidationError)
from azure.cli.core.commands.client_factory import get_subscription_id
from azure.cli.command_modules.appservice.custom import (_get_acr_cred)
from azure.cli.core.util import sdk_no_wait
from knack.util import CLIError
from knack.log import get_logger
Expand Down Expand Up @@ -37,7 +37,7 @@
_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)
_get_app_from_revision, raise_missing_token_suggestion, _infer_acr_credentials)

logger = get_logger(__name__)

Expand Down Expand Up @@ -298,7 +298,7 @@ def create_containerapp(cmd,
target_port=None,
transport="auto",
ingress=None,
revisions_mode=None,
revisions_mode="single",
secrets=None,
env_vars=None,
cpu=None,
Expand All @@ -312,15 +312,11 @@ def create_containerapp(cmd,
dapr_app_protocol=None,
# dapr_components=None,
revision_suffix=None,
location=None,
startup_command=None,
args=None,
tags=None,
no_wait=False):
location = location or _get_location_from_resource_group(cmd.cli_ctx, resource_group_name)

_validate_subscription_registered(cmd, "Microsoft.App")
_ensure_location_allowed(cmd, location, "Microsoft.App", "containerApps")

if yaml:
if image or managed_env or min_replicas or max_replicas or target_port or ingress or\
Expand Down Expand Up @@ -350,13 +346,8 @@ def create_containerapp(cmd,
if not managed_env_info:
raise ValidationError("The environment '{}' does not exist. Specify a valid environment".format(managed_env))

if not location:
location = managed_env_info["location"]
elif location.lower() != managed_env_info["location"].lower():
raise ValidationError("The location \"{}\" of the containerapp must be the same as the Managed Environment location \"{}\"".format(
location,
managed_env_info["location"]
))
location = managed_env_info["location"]
_ensure_location_allowed(cmd, location, "Microsoft.App", "containerApps")

external_ingress = None
if ingress is not None:
Expand All @@ -376,9 +367,19 @@ def create_containerapp(cmd,
if secrets is not None:
secrets_def = parse_secret_flags(secrets)

# If ACR image and registry_server is not supplied, infer it
if image and '.azurecr.io' in image:
if not registry_server:
registry_server = image.split('/')[0]

registries_def = None
if registry_server is not None:
registries_def = RegistryCredentialsModel

# Infer credentials if not supplied and its azurecr
if not registry_user or not registry_pass:
registry_user, registry_pass = _infer_acr_credentials(cmd, registry_server)

registries_def["server"] = registry_server
registries_def["username"] = registry_user

Expand Down Expand Up @@ -500,12 +501,17 @@ def update_containerapp(cmd,
if not containerapp_def:
raise CLIError("The containerapp '{}' does not exist".format(name))

# If ACR image and registry_server is not supplied, infer it
if image and '.azurecr.io' in image:
if not registry_server:
registry_server = image.split('/')[0]

update_map = {}
update_map['secrets'] = secrets is not None
update_map['ingress'] = ingress or target_port or transport or traffic_weights
update_map['registries'] = registry_server or registry_user or registry_pass
update_map['scale'] = min_replicas or max_replicas
update_map['container'] = image or image_name or env_vars or cpu or memory or startup_command is not None or args is not None
update_map['container'] = image or image_name or env_vars is not None or cpu or memory or startup_command is not None or args is not None
update_map['dapr'] = dapr_enabled or dapr_app_port or dapr_app_id or dapr_app_protocol
update_map['configuration'] = update_map['secrets'] or update_map['ingress'] or update_map['registries'] or revisions_mode is not None

Expand All @@ -532,9 +538,12 @@ def update_containerapp(cmd,
if image is not None:
c["image"] = image
if env_vars is not None:
if "env" not in c or not c["env"]:
if isinstance(env_vars, list) and not env_vars:
c["env"] = []
_add_or_update_env_vars(c["env"], parse_env_var_flags(env_vars))
else:
if "env" not in c or not c["env"]:
c["env"] = []
_add_or_update_env_vars(c["env"], parse_env_var_flags(env_vars))
if startup_command is not None:
if isinstance(startup_command, list) and not startup_command:
c["command"] = None
Expand Down Expand Up @@ -654,6 +663,10 @@ def update_containerapp(cmd,
if not registry_server:
raise ValidationError("Usage error: --registry-login-server is required when adding or updating a registry")

# Infer credentials if not supplied and its azurecr
if not registry_user or not registry_pass:
registry_user, registry_pass = _infer_acr_credentials(cmd, registry_server)

# Check if updating existing registry
updating_existing_registry = False
for r in registries_def:
Expand Down