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

Containerapp 0.3.10 Release #5241

Merged
merged 18 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 13 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
5 changes: 4 additions & 1 deletion scripts/ci/credscan/CredScanSuppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@
"src\\containerapp\\azext_containerapp\\tests\\latest\\test_containerapp_commands.py",
"src\\containerapp\\azext_containerapp\\tests\\latest\\test_containerapp_env_commands.py",
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_registry_msi.yaml",
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_update_containers.yaml"
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_update_containers.yaml",
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_anonymous_registry.yaml",
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_identity_user.yaml",
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_registry_identity_user.yaml"
],
"_justification": "Dummy resources' keys left during testing Microsoft.App (required for log-analytics to create managedEnvironments)"
},
Expand Down
6 changes: 6 additions & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Release History
===============

0.3.10
++++++
* 'az containerapp create': Fix bug with --image caused by assuming a value for --registry-server
* 'az containerapp hostname bind': Remove location set automatically by resource group
* 'az containerapp env create': Add location validation

0.3.9
++++++
* 'az containerapp create': Allow authenticating with managed identity (MSI) instead of ACR username & password
Expand Down
4 changes: 1 addition & 3 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@

from knack.arguments import CLIArgumentType

from azure.cli.core.commands.validators import get_default_location_from_resource_group
from azure.cli.core.commands.parameters import (resource_group_name_type, get_location_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 ._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)
Expand Down Expand Up @@ -338,7 +336,7 @@ def load_arguments(self, _):

with self.argument_context('containerapp hostname list') as c:
c.argument('name', id_part=None)
c.argument('location', arg_type=get_location_type(self.cli_ctx), validator=get_default_location_from_resource_group)
c.argument('location', arg_type=get_location_type(self.cli_ctx))
StrawnSC marked this conversation as resolved.
Show resolved Hide resolved

with self.argument_context('containerapp hostname delete') as c:
c.argument('hostname', help='The custom domain name.')
55 changes: 2 additions & 53 deletions src/containerapp/azext_containerapp/_up_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
get_container_app_if_exists,
trigger_workflow,
_ensure_location_allowed,
register_provider_if_needed
register_provider_if_needed,
validate_environment_location
)

from ._constants import MAXIMUM_SECRET_LENGTH, LOG_ANALYTICS_RP, CONTAINER_APPS_RP, ACR_IMAGE_SUFFIX, MAXIMUM_CONTAINER_APP_NAME_LENGTH
Expand Down Expand Up @@ -810,58 +811,6 @@ def find_existing_acr(cmd, app: "ContainerApp"):
return None, None


def validate_environment_location(cmd, location):
from ._constants import MAX_ENV_PER_LOCATION
env_list = list_managed_environments(cmd)

locations = [loc["location"] for loc in env_list]
locations = list(set(locations)) # remove duplicates

location_count = {}
for loc in locations:
location_count[loc] = len([e for e in env_list if e["location"] == loc])

disallowed_locations = []
for _, value in enumerate(location_count):
if location_count[value] > MAX_ENV_PER_LOCATION - 1:
disallowed_locations.append(value)

res_locations = list_environment_locations(cmd)
res_locations = [loc for loc in res_locations if loc not in disallowed_locations]

allowed_locs = ", ".join(res_locations)

if location:
try:
_ensure_location_allowed(cmd, location, CONTAINER_APPS_RP, "managedEnvironments")
except Exception as e: # pylint: disable=broad-except
raise ValidationError("You cannot create a Containerapp environment in location {}. List of eligible locations: {}.".format(location, allowed_locs)) from e

if len(res_locations) > 0:
if not location:
logger.warning("Creating environment on location {}.".format(res_locations[0]))
return res_locations[0]
if location in disallowed_locations:
raise ValidationError("You have more than {} environments in location {}. List of eligible locations: {}.".format(MAX_ENV_PER_LOCATION, location, allowed_locs))
return location
else:
raise ValidationError("You cannot create any more environments. Environments are limited to {} per location in a subscription. Please specify an existing environment using --environment.".format(MAX_ENV_PER_LOCATION))


def list_environment_locations(cmd):
from ._utils import providers_client_factory
providers_client = providers_client_factory(cmd.cli_ctx, get_subscription_id(cmd.cli_ctx))
resource_types = getattr(providers_client.get(CONTAINER_APPS_RP), 'resource_types', [])
res_locations = []
for res in resource_types:
if res and getattr(res, 'resource_type', "") == "managedEnvironments":
res_locations = getattr(res, 'locations', [])

res_locations = [res_loc.lower().replace(" ", "").replace("(", "").replace(")", "") for res_loc in res_locations if res_loc.strip()]

return res_locations


def check_env_name_on_rg(cmd, managed_env, resource_group_name, location):
if location:
_ensure_location_allowed(cmd, location, CONTAINER_APPS_RP, "managedEnvironments")
Expand Down
80 changes: 70 additions & 10 deletions src/containerapp/azext_containerapp/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1447,19 +1447,79 @@ def create_acrpull_role_assignment(cmd, registry_server, registry_identity=None,

client = get_mgmt_service_client(cmd.cli_ctx, ContainerRegistryManagementClient).registries
acr_id = acr_show(cmd, client, registry_server[: registry_server.rindex(ACR_IMAGE_SUFFIX)]).id
try:
create_role_assignment(cmd, role="acrpull", assignee=sp_id, scope=acr_id)
except Exception as e:
message = (f"Role assignment failed with error message: \"{' '.join(e.args)}\". \n"
f"To add the role assignment manually, please run 'az role assignment create --assignee {sp_id} --scope {acr_id} --role acrpull'. \n"
"You may have to restart the containerapp with 'az containerapp revision restart'.")
if skip_error:
logger.error(message)
else:
raise UnauthorizedError(message)
retries = 10
while retries > 0:
try:
create_role_assignment(cmd, role="acrpull", assignee=sp_id, scope=acr_id)
return
except Exception as e:
retries -= 1
if retries <= 0:
message = (f"Role assignment failed with error message: \"{' '.join(e.args)}\". \n"
f"To add the role assignment manually, please run 'az role assignment create --assignee {sp_id} --scope {acr_id} --role acrpull'. \n"
"You may have to restart the containerapp with 'az containerapp revision restart'.")
if skip_error:
logger.error(message)
else:
raise UnauthorizedError(message)
else:
time.sleep(5)



def is_registry_msi_system(identity):
if identity is None:
return False
return identity.lower() == "system"


def validate_environment_location(cmd, location):
from ._constants import MAX_ENV_PER_LOCATION
from .custom import list_managed_environments
env_list = list_managed_environments(cmd)

locations = [loc["location"] for loc in env_list]
locations = list(set(locations)) # remove duplicates

location_count = {}
for loc in locations:
location_count[loc] = len([e for e in env_list if e["location"] == loc])

disallowed_locations = []
for _, value in enumerate(location_count):
if location_count[value] > MAX_ENV_PER_LOCATION - 1:
disallowed_locations.append(value)

res_locations = list_environment_locations(cmd)
res_locations = [loc for loc in res_locations if loc not in disallowed_locations]

allowed_locs = ", ".join(res_locations)

if location:
try:
_ensure_location_allowed(cmd, location, CONTAINER_APPS_RP, "managedEnvironments")
except Exception as e: # pylint: disable=broad-except
raise ValidationError("You cannot create a Containerapp environment in location {}. List of eligible locations: {}.".format(location, allowed_locs)) from e

if len(res_locations) > 0:
if not location:
logger.warning("Creating environment on location %s.", res_locations[0])
return res_locations[0]
if location in disallowed_locations:
raise ValidationError("You have more than {} environments in location {}. List of eligible locations: {}.".format(MAX_ENV_PER_LOCATION, location, allowed_locs))
return location
else:
raise ValidationError("You cannot create any more environments. Environments are limited to {} per location in a subscription. Please specify an existing environment using --environment.".format(MAX_ENV_PER_LOCATION))


def list_environment_locations(cmd):
providers_client = providers_client_factory(cmd.cli_ctx, get_subscription_id(cmd.cli_ctx))
resource_types = getattr(providers_client.get(CONTAINER_APPS_RP), 'resource_types', [])
res_locations = []
for res in resource_types:
if res and getattr(res, 'resource_type', "") == "managedEnvironments":
res_locations = getattr(res, 'locations', [])

res_locations = [res_loc.lower().replace(" ", "").replace("(", "").replace(")", "") for res_loc in res_locations if res_loc.strip()]

return res_locations
7 changes: 3 additions & 4 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
validate_container_app_name, _update_weights, get_vnet_location, register_provider_if_needed,
generate_randomized_cert_name, _get_name, load_cert_file, check_cert_name_availability,
validate_hostname, patch_new_custom_domain, get_custom_domains, _validate_revision_name, set_managed_identity,
create_acrpull_role_assignment, is_registry_msi_system, clean_null_values, _populate_secret_values)
create_acrpull_role_assignment, is_registry_msi_system, clean_null_values, _populate_secret_values,
validate_environment_location)
from ._validators import validate_create
from ._ssh_utils import (SSH_DEFAULT_ENCODING, WebSocketConnection, read_ssh, get_stdin_writer, SSH_CTRL_C_MSG,
SSH_BACKUP_ENCODING)
Expand Down Expand Up @@ -329,8 +330,6 @@ def create_containerapp(cmd,
disable_warnings=False,
user_assigned=None,
registry_identity=None):
if image and "/" in image and not registry_server:
registry_server = image[:image.index("/")]
register_provider_if_needed(cmd, CONTAINER_APPS_RP)
validate_container_app_name(name)
validate_create(registry_identity, registry_pass, registry_user, registry_server, no_wait)
Expand Down Expand Up @@ -919,7 +918,7 @@ def create_managed_environment(cmd,
else:
location = vnet_location

location = location or _get_location_from_resource_group(cmd.cli_ctx, resource_group_name)
location = validate_environment_location(cmd, location)

register_provider_if_needed(cmd, CONTAINER_APPS_RP)
_ensure_location_allowed(cmd, location, CONTAINER_APPS_RP, "managedEnvironments")
Expand Down
Loading