Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/command_modules/azure-cli-container/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Release History
0.3.6
+++++
* Add '--assign-identity' for adding a MSI identity to a container group
* Add '--scope' to create a role assignment for the system assigned MSI identity
* Show warning when creating a container group with an image without a long running process
* Fix table output issues for 'list' and 'show' commands

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ def cf_resource(cli_ctx):
return get_mgmt_service_client(cli_ctx, ResourceManagementClient)


def get_auth_management_client(cli_ctx, scope=None, **_):
import re
from azure.cli.core.profiles import ResourceType
from azure.cli.core.commands.client_factory import get_mgmt_service_client

subscription_id = None
if scope:
matched = re.match('/subscriptions/(?P<subscription>[^/]*)/', scope)
if matched:
subscription_id = matched.groupdict()['subscription']
return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_AUTHORIZATION, subscription_id=subscription_id)


def cf_network(cli_ctx):
from azure.mgmt.network import NetworkManagementClient
from azure.cli.core.commands.client_factory import get_mgmt_service_client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
text: az container create -g MyResourceGroup --name myapp --log-analytics-workspace myworkspace
- name: Create a container group with a system assigned identity.
text: az container create -g MyResourceGroup --name myapp --image myimage:latest --assign-identity
- name: Create a container group with a system assigned identity. The group will have a 'Contributor' role with access to a storage account.
text: az container create -g MyResourceGroup --name myapp --image myimage:latest --assign-identity --scope /subscriptions/99999999-1bf0-4dda-aec3-cb9272f09590/MyResourceGroup/myRG/providers/Microsoft.Storage/storageAccounts/storage1
- name: Create a container group with a user assigned identity.
text: az container create -g MyResourceGroup --name myapp --image myimage:latest --assign-identity /subscriptions/mySubscrpitionId/resourcegroups/myRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myID
- name: Create a container group with both system and user assigned identity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from azure.cli.core.commands.validators import get_default_location_from_resource_group
from azure.mgmt.containerinstance.models import (
ContainerGroupRestartPolicy, OperatingSystemTypes, ContainerNetworkProtocol)
from ._validators import (validate_volume_mount_path, validate_secrets, validate_subnet,
from ._validators import (validate_volume_mount_path, validate_secrets, validate_subnet, validate_msi,
validate_gitrepo_directory, validate_network_profile, validate_image)

# pylint: disable=line-too-long
Expand Down Expand Up @@ -78,7 +78,12 @@ def load_arguments(self, _):
c.argument('secrets', secrets_type)
c.argument('secrets_mount_path', validator=validate_volume_mount_path, help="The path within the container where the secrets volume should be mounted. Must not contain colon ':'.")
c.argument('file', options_list=['--file', '-f'], help="The path to the input file.")
c.argument('assign_identity', nargs='*', arg_group='Managed Service Identity', help="Space-separated list of assigned identities. Assigned identities are either user assigned identities (resource IDs) and / or the system assigned identity ('[system]'). See examples for more info.")

with self.argument_context('container create', arg_group='Managed Service Identity') as c:
c.argument('assign_identity', nargs='*', validator=validate_msi, help="Space-separated list of assigned identities. Assigned identities are either user assigned identities (resource IDs) and / or the system assigned identity ('[system]'). See examples for more info.")
c.argument('identity_scope', options_list=['--scope'], help="Scope that the system assigned identity can access")
c.argument('identity_role', options_list=['--role'], help="Role name or id the system assigned identity will have")
c.ignore('identity_role_id')

with self.argument_context('container create', arg_group='Network') as c:
c.argument('network_profile', network_profile_type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,54 @@ def validate_image(ns):
ns.image)


def validate_msi(cmd, namespace):
MSI_LOCAL_ID = '[system]'
if namespace.assign_identity is not None:
identities = namespace.assign_identity or []
if not namespace.identity_scope and getattr(namespace.identity_role, 'is_default', None) is None:
raise CLIError("usage error: '--role {}' is not applicable as the '--scope' is not provided".format(
namespace.identity_role))

if namespace.identity_scope:
if identities and MSI_LOCAL_ID not in identities:
raise CLIError("usage error: '--scope'/'--role' is only applicable when assign system identity")
# keep 'identity_role' for output as logical name is more readable
setattr(namespace, 'identity_role_id', _resolve_role_id(cmd.cli_ctx, namespace.identity_role,
namespace.identity_scope))
elif namespace.identity_scope or getattr(namespace.identity_role, 'is_default', None) is None:
raise CLIError('usage error: --assign-identity [--scope SCOPE] [--role ROLE]')


def _resolve_role_id(cli_ctx, role, scope):
import re
import uuid
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.profiles import ResourceType

client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_AUTHORIZATION).role_definitions
role_id = None
if re.match(r'/subscriptions/.+/providers/Microsoft.Authorization/roleDefinitions/',
role, re.I):
role_id = role
else:
try:
uuid.UUID(role)
role_id = '/subscriptions/{}/providers/Microsoft.Authorization/roleDefinitions/{}'.format(
client.config.subscription_id, role)
except ValueError:
pass
if not role_id: # retrieve role id
role_defs = list(client.list(scope, "roleName eq '{}'".format(role)))
if not role_defs:
raise CLIError("Role '{}' doesn't exist.".format(role))
elif len(role_defs) > 1:
ids = [r.id for r in role_defs]
err = "More than one role matches the given name '{}'. Please pick an id from '{}'"
raise CLIError(err.format(role, ids))
role_id = role_defs[0].id
return role_id


def validate_subnet(ns):
from msrestazure.tools import is_valid_resource_id

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
GitRepoVolume, LogAnalytics, ContainerGroupDiagnostics, ContainerGroupNetworkProfile,
ContainerGroupIpAddressType, ResourceIdentityType, ContainerGroupIdentity)
from azure.cli.core.util import sdk_no_wait
from ._client_factory import cf_container_groups, cf_container, cf_log_analytics, cf_resource, cf_network

from ._client_factory import (cf_container_groups, cf_container, cf_log_analytics, cf_resource,
cf_network, get_auth_management_client)

logger = get_logger(__name__)
WINDOWS_NAME = 'Windows'
Expand Down Expand Up @@ -105,6 +105,9 @@ def create_container(cmd,
secrets_mount_path=None,
file=None,
assign_identity=None,
identity_scope=None,
identity_role='Contributor',
identity_role_id=None,
no_wait=False):
"""Create a container group. """
if file:
Expand Down Expand Up @@ -215,7 +218,15 @@ def create_container(cmd,
tags=tags)

container_group_client = cf_container_groups(cmd.cli_ctx)
return sdk_no_wait(no_wait, container_group_client.create_or_update, resource_group_name, name, cgroup)

lro = sdk_no_wait(no_wait, container_group_client.create_or_update, resource_group_name,
name, cgroup)

if assign_identity is not None and identity_scope:
cg = container_group_client.get(resource_group_name, name)
_create_update_msi_role_assignment(cmd, resource_group_name, name, cg.identity.principal_id,
identity_role_id, identity_scope)
return lro


def _build_identities_info(identities):
Expand All @@ -234,6 +245,25 @@ def _build_identities_info(identities):
return identity


def _create_update_msi_role_assignment(cmd, resource_group_name, cg_name, identity_principle_id,
role_definition_id, identity_scope):
from azure.cli.core.profiles import ResourceType, get_sdk

RoleAssignmentCreateParameters = get_sdk(cmd.cli_ctx, ResourceType.MGMT_AUTHORIZATION,
'RoleAssignmentCreateParameters',
mod='models', operation_group='role_assignments')

assignment_client = get_auth_management_client(cmd.cli_ctx, identity_scope).role_assignments

role_assignment_guid = str(_gen_guid())

create_params = RoleAssignmentCreateParameters(
role_definition_id=role_definition_id,
principal_id=identity_principle_id
)
return assignment_client.create(identity_scope, role_assignment_guid, create_params, custom_headers=None)


def _get_resource(client, resource_group_name, *subresources):
from msrestazure.azure_exceptions import CloudError
try:
Expand Down Expand Up @@ -801,3 +831,8 @@ def _move_console_cursor_up(lines):
if lines > 0:
# Use stdout.write to support Python 2
sys.stdout.write('\033[{}A\033[K\033[J'.format(lines))


def _gen_guid():
import uuid
return uuid.uuid4()
Loading