1# --------------------------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for license information.
4# --------------------------------------------------------------------------------------------
5
6# pylint:disable=too-many-lines
7
8import os
9
10try:
11    from urllib.parse import urlparse
12except ImportError:
13    from urlparse import urlparse  # pylint: disable=import-error
14
15from knack.log import get_logger
16from knack.util import CLIError
17
18from azure.cli.core.azclierror import ValidationError, ArgumentUsageError
19from azure.cli.core.commands.validators import (
20    get_default_location_from_resource_group, validate_file_or_dict, validate_parameter_set, validate_tags)
21from azure.cli.core.util import (hash_string, DISALLOWED_USER_NAMES, get_default_admin_username)
22from azure.cli.command_modules.vm._vm_utils import (
23    check_existence, get_target_network_api, get_storage_blob_uri, list_sku_info)
24from azure.cli.command_modules.vm._template_builder import StorageProfile
25import azure.cli.core.keys as keys
26from azure.core.exceptions import ResourceNotFoundError
27
28from ._client_factory import _compute_client_factory
29from ._actions import _get_latest_image_version
30logger = get_logger(__name__)
31
32
33def validate_asg_names_or_ids(cmd, namespace):
34    from msrestazure.tools import resource_id, is_valid_resource_id
35    from azure.cli.core.profiles import ResourceType
36    from azure.cli.core.commands.client_factory import get_subscription_id
37    ApplicationSecurityGroup = cmd.get_models('ApplicationSecurityGroup',
38                                              resource_type=ResourceType.MGMT_NETWORK)
39
40    resource_group = namespace.resource_group_name
41    subscription_id = get_subscription_id(cmd.cli_ctx)
42    names_or_ids = getattr(namespace, 'application_security_groups')
43    ids = []
44
45    if names_or_ids == [""] or not names_or_ids:
46        return
47
48    for val in names_or_ids:
49        if not is_valid_resource_id(val):
50            val = resource_id(
51                subscription=subscription_id,
52                resource_group=resource_group,
53                namespace='Microsoft.Network', type='applicationSecurityGroups',
54                name=val
55            )
56        ids.append(ApplicationSecurityGroup(id=val))
57    setattr(namespace, 'application_security_groups', ids)
58
59
60def validate_nsg_name(cmd, namespace):
61    from msrestazure.tools import resource_id
62    from azure.cli.core.commands.client_factory import get_subscription_id
63    vm_id = resource_id(name=namespace.vm_name, resource_group=namespace.resource_group_name,
64                        namespace='Microsoft.Compute', type='virtualMachines',
65                        subscription=get_subscription_id(cmd.cli_ctx))
66    namespace.network_security_group_name = namespace.network_security_group_name \
67        or '{}_NSG_{}'.format(namespace.vm_name, hash_string(vm_id, length=8))
68
69
70def validate_keyvault(cmd, namespace):
71    namespace.keyvault = _get_resource_id(cmd.cli_ctx, namespace.keyvault, namespace.resource_group_name,
72                                          'vaults', 'Microsoft.KeyVault')
73
74
75def validate_vm_name_for_monitor_metrics(cmd, namespace):
76    if hasattr(namespace, 'resource'):
77        namespace.resource = _get_resource_id(cmd.cli_ctx, namespace.resource, namespace.resource_group_name,
78                                              'virtualMachines', 'Microsoft.Compute')
79    elif hasattr(namespace, 'resource_uri'):
80        namespace.resource_uri = _get_resource_id(cmd.cli_ctx, namespace.resource_uri, namespace.resource_group_name,
81                                                  'virtualMachines', 'Microsoft.Compute')
82    del namespace.resource_group_name
83
84
85def _validate_proximity_placement_group(cmd, namespace):
86    from msrestazure.tools import parse_resource_id
87
88    if namespace.proximity_placement_group:
89        namespace.proximity_placement_group = _get_resource_id(cmd.cli_ctx, namespace.proximity_placement_group,
90                                                               namespace.resource_group_name,
91                                                               'proximityPlacementGroups', 'Microsoft.Compute')
92
93        parsed = parse_resource_id(namespace.proximity_placement_group)
94        rg, name = parsed['resource_group'], parsed['name']
95
96        if not check_existence(cmd.cli_ctx, name, rg, 'Microsoft.Compute', 'proximityPlacementGroups'):
97            raise CLIError("Proximity Placement Group '{}' does not exist.".format(name))
98
99
100def process_vm_secret_format(cmd, namespace):
101    from msrestazure.tools import is_valid_resource_id
102    from azure.cli.core._output import (get_output_format, set_output_format)
103
104    keyvault_usage = CLIError('usage error: [--keyvault NAME --resource-group NAME | --keyvault ID]')
105    kv = namespace.keyvault
106    rg = namespace.resource_group_name
107
108    if rg:
109        if not kv or is_valid_resource_id(kv):
110            raise keyvault_usage
111        validate_keyvault(cmd, namespace)
112    else:
113        if kv and not is_valid_resource_id(kv):
114            raise keyvault_usage
115
116    warning_msg = "This command does not support the {} output format. Showing JSON format instead."
117    desired_formats = ["json", "jsonc"]
118
119    output_format = get_output_format(cmd.cli_ctx)
120    if output_format not in desired_formats:
121        warning_msg = warning_msg.format(output_format)
122        logger.warning(warning_msg)
123        set_output_format(cmd.cli_ctx, desired_formats[0])
124
125
126def _get_resource_group_from_vault_name(cli_ctx, vault_name):
127    """
128    Fetch resource group from vault name
129    :param str vault_name: name of the key vault
130    :return: resource group name or None
131    :rtype: str
132    """
133    from azure.cli.core.profiles import ResourceType
134    from azure.cli.core.commands.client_factory import get_mgmt_service_client
135    from msrestazure.tools import parse_resource_id
136    client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_KEYVAULT).vaults
137    for vault in client.list():
138        id_comps = parse_resource_id(vault.id)
139        if id_comps['name'] == vault_name:
140            return id_comps['resource_group']
141    return None
142
143
144def _get_resource_id(cli_ctx, val, resource_group, resource_type, resource_namespace):
145    from msrestazure.tools import resource_id, is_valid_resource_id
146    from azure.cli.core.commands.client_factory import get_subscription_id
147    if is_valid_resource_id(val):
148        return val
149
150    kwargs = {
151        'name': val,
152        'resource_group': resource_group,
153        'namespace': resource_namespace,
154        'type': resource_type,
155        'subscription': get_subscription_id(cli_ctx)
156    }
157    missing_kwargs = {k: v for k, v in kwargs.items() if not v}
158
159    return resource_id(**kwargs) if not missing_kwargs else None
160
161
162def _get_nic_id(cli_ctx, val, resource_group):
163    return _get_resource_id(cli_ctx, val, resource_group,
164                            'networkInterfaces', 'Microsoft.Network')
165
166
167def validate_vm_nic(cmd, namespace):
168    namespace.nic = _get_nic_id(cmd.cli_ctx, namespace.nic, namespace.resource_group_name)
169
170
171def validate_vm_nics(cmd, namespace):
172    rg = namespace.resource_group_name
173    nic_ids = []
174
175    for n in namespace.nics:
176        nic_ids.append(_get_nic_id(cmd.cli_ctx, n, rg))
177    namespace.nics = nic_ids
178
179    if hasattr(namespace, 'primary_nic') and namespace.primary_nic:
180        namespace.primary_nic = _get_nic_id(cmd.cli_ctx, namespace.primary_nic, rg)
181
182
183def _validate_secrets(secrets, os_type):
184    """
185    Validates a parsed JSON array containing secrets for use in VM Creation
186    Secrets JSON structure
187    [{
188        "sourceVault": { "id": "value" },
189        "vaultCertificates": [{
190            "certificateUrl": "value",
191            "certificateStore": "cert store name (only on windows)"
192        }]
193    }]
194    :param dict secrets: Dict fitting the JSON description above
195    :param string os_type: the type of OS (linux or windows)
196    :return: errors if any were found
197    :rtype: list
198    """
199    is_windows = os_type == 'windows'
200    errors = []
201
202    try:
203        loaded_secret = [validate_file_or_dict(secret) for secret in secrets]
204    except Exception as err:
205        raise CLIError('Error decoding secrets: {0}'.format(err))
206
207    for idx_arg, narg_secret in enumerate(loaded_secret):
208        for idx, secret in enumerate(narg_secret):
209            if 'sourceVault' not in secret:
210                errors.append(
211                    'Secret is missing sourceVault key at index {0} in arg {1}'.format(
212                        idx, idx_arg))
213            if 'sourceVault' in secret and 'id' not in secret['sourceVault']:
214                errors.append(
215                    'Secret is missing sourceVault.id key at index {0}  in arg {1}'.format(
216                        idx, idx_arg))
217            if 'vaultCertificates' not in secret or not secret['vaultCertificates']:
218                err = 'Secret is missing vaultCertificates array or it is empty at index {0} in ' \
219                      'arg {1} '
220                errors.append(err.format(idx, idx_arg))
221            else:
222                for jdx, cert in enumerate(secret['vaultCertificates']):
223                    message = 'Secret is missing {0} within vaultCertificates array at secret ' \
224                              'index {1} and vaultCertificate index {2} in arg {3}'
225                    if 'certificateUrl' not in cert:
226                        errors.append(message.format('certificateUrl', idx, jdx, idx_arg))
227                    if is_windows and 'certificateStore' not in cert:
228                        errors.append(message.format('certificateStore', idx, jdx, idx_arg))
229
230    if errors:
231        raise CLIError('\n'.join(errors))
232
233
234# region VM Create Validators
235
236
237def _parse_image_argument(cmd, namespace):
238    """ Systematically determines what type is supplied for the --image parameter. Updates the
239        namespace and returns the type for subsequent processing. """
240    from msrestazure.tools import is_valid_resource_id
241    from msrestazure.azure_exceptions import CloudError
242    import re
243
244    # 1 - check if a fully-qualified ID (assumes it is an image ID)
245    if is_valid_resource_id(namespace.image):
246        return 'image_id'
247
248    from ._vm_utils import is_shared_gallery_image_id
249    if is_shared_gallery_image_id(namespace.image):
250        return 'shared_gallery_image_id'
251
252    # 2 - attempt to match an URN pattern
253    urn_match = re.match('([^:]*):([^:]*):([^:]*):([^:]*)', namespace.image)
254    if urn_match:
255        namespace.os_publisher = urn_match.group(1)
256        namespace.os_offer = urn_match.group(2)
257        namespace.os_sku = urn_match.group(3)
258        namespace.os_version = urn_match.group(4)
259
260        if not any([namespace.plan_name, namespace.plan_product, namespace.plan_publisher]):
261            image_plan = _get_image_plan_info_if_exists(cmd, namespace)
262            if image_plan:
263                namespace.plan_name = image_plan.name
264                namespace.plan_product = image_plan.product
265                namespace.plan_publisher = image_plan.publisher
266
267        return 'urn'
268
269    # 3 - unmanaged vhd based images?
270    if urlparse(namespace.image).scheme and "://" in namespace.image:
271        return 'uri'
272
273    # 4 - attempt to match an URN alias (most likely)
274    from azure.cli.command_modules.vm._actions import load_images_from_aliases_doc
275    import requests
276    try:
277        images = None
278        images = load_images_from_aliases_doc(cmd.cli_ctx)
279        matched = next((x for x in images if x['urnAlias'].lower() == namespace.image.lower()), None)
280        if matched:
281            namespace.os_publisher = matched['publisher']
282            namespace.os_offer = matched['offer']
283            namespace.os_sku = matched['sku']
284            namespace.os_version = matched['version']
285            return 'urn'
286    except requests.exceptions.ConnectionError:
287        pass
288
289    # 5 - check if an existing managed disk image resource
290    compute_client = _compute_client_factory(cmd.cli_ctx)
291    try:
292        compute_client.images.get(namespace.resource_group_name, namespace.image)
293        namespace.image = _get_resource_id(cmd.cli_ctx, namespace.image, namespace.resource_group_name,
294                                           'images', 'Microsoft.Compute')
295        return 'image_id'
296    except CloudError:
297        if images is not None:
298            err = 'Invalid image "{}". Use a valid image URN, custom image name, custom image id, ' \
299                  'VHD blob URI, or pick an image from {}.\nSee vm create -h for more information ' \
300                  'on specifying an image.'.format(namespace.image, [x['urnAlias'] for x in images])
301        else:
302            err = 'Failed to connect to remote source of image aliases or find a local copy. Invalid image "{}". ' \
303                  'Use a valid image URN, custom image name, custom image id, or VHD blob URI.\nSee vm ' \
304                  'create -h for more information on specifying an image.'.format(namespace.image)
305        raise CLIError(err)
306
307
308def _get_image_plan_info_if_exists(cmd, namespace):
309    from msrestazure.azure_exceptions import CloudError
310    try:
311        compute_client = _compute_client_factory(cmd.cli_ctx)
312        if namespace.os_version.lower() == 'latest':
313            image_version = _get_latest_image_version(cmd.cli_ctx, namespace.location, namespace.os_publisher,
314                                                      namespace.os_offer, namespace.os_sku)
315        else:
316            image_version = namespace.os_version
317
318        image = compute_client.virtual_machine_images.get(namespace.location,
319                                                          namespace.os_publisher,
320                                                          namespace.os_offer,
321                                                          namespace.os_sku,
322                                                          image_version)
323
324        # pylint: disable=no-member
325        return image.plan
326    except CloudError as ex:
327        logger.warning("Querying the image of '%s' failed for an error '%s'. Configuring plan settings "
328                       "will be skipped", namespace.image, ex.message)
329
330
331# pylint: disable=inconsistent-return-statements, too-many-return-statements
332def _get_storage_profile_description(profile):
333    if profile == StorageProfile.SACustomImage:
334        return 'create unmanaged OS disk created from generalized VHD'
335    if profile == StorageProfile.SAPirImage:
336        return 'create unmanaged OS disk from Azure Marketplace image'
337    if profile == StorageProfile.SASpecializedOSDisk:
338        return 'attach to existing unmanaged OS disk'
339    if profile == StorageProfile.ManagedCustomImage:
340        return 'create managed OS disk from custom image'
341    if profile == StorageProfile.ManagedPirImage:
342        return 'create managed OS disk from Azure Marketplace image'
343    if profile == StorageProfile.ManagedSpecializedOSDisk:
344        return 'attach existing managed OS disk'
345    if profile == StorageProfile.SharedGalleryImage:
346        return 'create OS disk from shared gallery image'
347
348
349def _validate_location(cmd, namespace, zone_info, size_info):
350    if not namespace.location:
351        get_default_location_from_resource_group(cmd, namespace)
352        if zone_info:
353            sku_infos = list_sku_info(cmd.cli_ctx, namespace.location)
354            temp = next((x for x in sku_infos if x.name.lower() == size_info.lower()), None)
355            # For Stack (compute - 2017-03-30), Resource_sku doesn't implement location_info property
356            if not hasattr(temp, 'location_info'):
357                return
358            if not temp or not [x for x in (temp.location_info or []) if x.zones]:
359                raise CLIError("{}'s location can't be used to create the VM/VMSS because availability zone is not yet "
360                               "supported. Please use '--location' to specify a capable one. 'az vm list-skus' can be "
361                               "used to find such locations".format(namespace.resource_group_name))
362
363
364# pylint: disable=too-many-branches, too-many-statements
365def _validate_vm_create_storage_profile(cmd, namespace, for_scale_set=False):
366    from msrestazure.tools import parse_resource_id
367
368    # specialized is only for image
369    if getattr(namespace, 'specialized', None) is not None and namespace.image is None:
370        raise CLIError('usage error: --specialized is only configurable when --image is specified.')
371
372    # use minimal parameters to resolve the expected storage profile
373    if getattr(namespace, 'attach_os_disk', None) and not namespace.image:
374        if namespace.use_unmanaged_disk:
375            # STORAGE PROFILE #3
376            namespace.storage_profile = StorageProfile.SASpecializedOSDisk
377        else:
378            # STORAGE PROFILE #6
379            namespace.storage_profile = StorageProfile.ManagedSpecializedOSDisk
380    elif namespace.image and not getattr(namespace, 'attach_os_disk', None):
381        image_type = _parse_image_argument(cmd, namespace)
382        if image_type == 'uri':
383            # STORAGE PROFILE #2
384            namespace.storage_profile = StorageProfile.SACustomImage
385        elif image_type == 'image_id':
386            # STORAGE PROFILE #5
387            namespace.storage_profile = StorageProfile.ManagedCustomImage
388        elif image_type == 'shared_gallery_image_id':
389            namespace.storage_profile = StorageProfile.SharedGalleryImage
390        elif image_type == 'urn':
391            if namespace.use_unmanaged_disk:
392                # STORAGE PROFILE #1
393                namespace.storage_profile = StorageProfile.SAPirImage
394            else:
395                # STORAGE PROFILE #4
396                namespace.storage_profile = StorageProfile.ManagedPirImage
397        else:
398            raise CLIError('Unrecognized image type: {}'.format(image_type))
399    else:
400        # did not specify image XOR attach-os-disk
401        raise CLIError('incorrect usage: --image IMAGE | --attach-os-disk DISK')
402
403    auth_params = ['admin_password', 'admin_username', 'authentication_type',
404                   'generate_ssh_keys', 'ssh_dest_key_path', 'ssh_key_value']
405
406    # perform parameter validation for the specific storage profile
407    # start with the required/forbidden parameters for VM
408    if namespace.storage_profile == StorageProfile.ManagedPirImage:
409        required = ['image']
410        forbidden = ['os_type', 'attach_os_disk', 'storage_account',
411                     'storage_container_name', 'use_unmanaged_disk']
412        if for_scale_set:
413            forbidden.append('os_disk_name')
414
415    elif namespace.storage_profile == StorageProfile.ManagedCustomImage:
416        required = ['image']
417        forbidden = ['os_type', 'attach_os_disk', 'storage_account',
418                     'storage_container_name', 'use_unmanaged_disk']
419        if for_scale_set:
420            forbidden.append('os_disk_name')
421
422    elif namespace.storage_profile == StorageProfile.SharedGalleryImage:
423        required = ['image']
424        forbidden = ['attach_os_disk', 'storage_account', 'storage_container_name', 'use_unmanaged_disk']
425
426    elif namespace.storage_profile == StorageProfile.ManagedSpecializedOSDisk:
427        required = ['os_type', 'attach_os_disk']
428        forbidden = ['os_disk_name', 'os_caching', 'storage_account', 'ephemeral_os_disk',
429                     'storage_container_name', 'use_unmanaged_disk', 'storage_sku'] + auth_params
430
431    elif namespace.storage_profile == StorageProfile.SAPirImage:
432        required = ['image', 'use_unmanaged_disk']
433        forbidden = ['os_type', 'attach_os_disk', 'data_disk_sizes_gb', 'ephemeral_os_disk']
434
435    elif namespace.storage_profile == StorageProfile.SACustomImage:
436        required = ['image', 'os_type', 'use_unmanaged_disk']
437        forbidden = ['attach_os_disk', 'data_disk_sizes_gb', 'ephemeral_os_disk']
438
439    elif namespace.storage_profile == StorageProfile.SASpecializedOSDisk:
440        required = ['os_type', 'attach_os_disk', 'use_unmanaged_disk']
441        forbidden = ['os_disk_name', 'os_caching', 'image', 'storage_account', 'ephemeral_os_disk',
442                     'storage_container_name', 'data_disk_sizes_gb', 'storage_sku'] + auth_params
443
444    else:
445        raise CLIError('Unrecognized storage profile: {}'.format(namespace.storage_profile))
446
447    logger.debug("storage profile '%s'", namespace.storage_profile)
448
449    if for_scale_set:
450        # VMSS lacks some parameters, so scrub these out
451        props_to_remove = ['attach_os_disk', 'storage_account']
452        for prop in props_to_remove:
453            if prop in required:
454                required.remove(prop)
455            if prop in forbidden:
456                forbidden.remove(prop)
457
458    # set default storage SKU if not provided and using an image based OS
459    if not namespace.storage_sku and namespace.storage_profile in [StorageProfile.SAPirImage, StorageProfile.SACustomImage]:  # pylint: disable=line-too-long
460        namespace.storage_sku = ['Standard_LRS'] if for_scale_set else ['Premium_LRS']
461
462    if namespace.ultra_ssd_enabled is None and namespace.storage_sku:
463        for sku in namespace.storage_sku:
464            if 'ultrassd_lrs' in sku.lower():
465                namespace.ultra_ssd_enabled = True
466
467    # Now verify the presence of required and absence of forbidden parameters
468    validate_parameter_set(
469        namespace, required, forbidden,
470        description='storage profile: {}:'.format(_get_storage_profile_description(namespace.storage_profile)))
471
472    image_data_disks = []
473    if namespace.storage_profile == StorageProfile.ManagedCustomImage:
474        # extract additional information from a managed custom image
475        res = parse_resource_id(namespace.image)
476        namespace.aux_subscriptions = [res['subscription']]
477        compute_client = _compute_client_factory(cmd.cli_ctx, subscription_id=res['subscription'])
478        if res['type'].lower() == 'images':
479            image_info = compute_client.images.get(res['resource_group'], res['name'])
480            namespace.os_type = image_info.storage_profile.os_disk.os_type
481            image_data_disks = image_info.storage_profile.data_disks or []
482            image_data_disks = [{'lun': disk.lun} for disk in image_data_disks]
483
484        elif res['type'].lower() == 'galleries':
485            image_info = compute_client.gallery_images.get(resource_group_name=res['resource_group'],
486                                                           gallery_name=res['name'],
487                                                           gallery_image_name=res['child_name_1'])
488            namespace.os_type = image_info.os_type
489            gallery_image_version = res.get('child_name_2', '')
490            if gallery_image_version.lower() in ['latest', '']:
491                image_version_infos = compute_client.gallery_image_versions.list_by_gallery_image(
492                    resource_group_name=res['resource_group'], gallery_name=res['name'],
493                    gallery_image_name=res['child_name_1'])
494                image_version_infos = [x for x in image_version_infos if not x.publishing_profile.exclude_from_latest]
495                if not image_version_infos:
496                    raise CLIError('There is no latest image version exists for "{}"'.format(namespace.image))
497                image_version_info = sorted(image_version_infos, key=lambda x: x.publishing_profile.published_date)[-1]
498            else:
499                image_version_info = compute_client.gallery_image_versions.get(
500                    resource_group_name=res['resource_group'], gallery_name=res['name'],
501                    gallery_image_name=res['child_name_1'], gallery_image_version_name=res['child_name_2'])
502            image_data_disks = image_version_info.storage_profile.data_disk_images or []
503            image_data_disks = [{'lun': disk.lun} for disk in image_data_disks]
504
505        else:
506            raise CLIError('usage error: unrecognized image information "{}"'.format(namespace.image))
507
508        # pylint: disable=no-member
509
510    elif namespace.storage_profile == StorageProfile.ManagedSpecializedOSDisk:
511        # accept disk name or ID
512        namespace.attach_os_disk = _get_resource_id(
513            cmd.cli_ctx, namespace.attach_os_disk, namespace.resource_group_name, 'disks', 'Microsoft.Compute')
514
515    if getattr(namespace, 'attach_data_disks', None):
516        if not namespace.use_unmanaged_disk:
517            namespace.attach_data_disks = [_get_resource_id(cmd.cli_ctx, d, namespace.resource_group_name, 'disks',
518                                                            'Microsoft.Compute') for d in namespace.attach_data_disks]
519
520    if not namespace.os_type:
521        if namespace.storage_profile == StorageProfile.SharedGalleryImage:
522
523            if namespace.location is None:
524                from azure.cli.core.azclierror import RequiredArgumentMissingError
525                raise RequiredArgumentMissingError(
526                    'Please input the location of the shared gallery image through the parameter --location.')
527
528            from ._vm_utils import parse_shared_gallery_image_id
529            image_info = parse_shared_gallery_image_id(namespace.image)
530
531            from ._client_factory import cf_shared_gallery_image
532            shared_gallery_image_info = cf_shared_gallery_image(cmd.cli_ctx).get(
533                location=namespace.location, gallery_unique_name=image_info[0], gallery_image_name=image_info[1])
534            namespace.os_type = shared_gallery_image_info.os_type
535
536        else:
537            namespace.os_type = 'windows' if 'windows' in namespace.os_offer.lower() else 'linux'
538
539    from ._vm_utils import normalize_disk_info
540    # attach_data_disks are not exposed yet for VMSS, so use 'getattr' to avoid crash
541    vm_size = (getattr(namespace, 'size', None) or getattr(namespace, 'vm_sku', None))
542
543    namespace.disk_info = normalize_disk_info(size=vm_size,
544                                              image_data_disks=image_data_disks,
545                                              data_disk_sizes_gb=namespace.data_disk_sizes_gb,
546                                              attach_data_disks=getattr(namespace, 'attach_data_disks', []),
547                                              storage_sku=namespace.storage_sku,
548                                              os_disk_caching=namespace.os_caching,
549                                              data_disk_cachings=namespace.data_caching,
550                                              ephemeral_os_disk=getattr(namespace, 'ephemeral_os_disk', None),
551                                              data_disk_delete_option=getattr(
552                                                  namespace, 'data_disk_delete_option', None))
553
554
555def _validate_vm_create_storage_account(cmd, namespace):
556    from msrestazure.tools import parse_resource_id
557    if namespace.storage_account:
558        storage_id = parse_resource_id(namespace.storage_account)
559        rg = storage_id.get('resource_group', namespace.resource_group_name)
560        if check_existence(cmd.cli_ctx, storage_id['name'], rg, 'Microsoft.Storage', 'storageAccounts'):
561            # 1 - existing storage account specified
562            namespace.storage_account_type = 'existing'
563            logger.debug("using specified existing storage account '%s'", storage_id['name'])
564        else:
565            # 2 - params for new storage account specified
566            namespace.storage_account_type = 'new'
567            logger.debug("specified storage account '%s' not found and will be created", storage_id['name'])
568    else:
569        from azure.cli.core.profiles import ResourceType
570        from azure.cli.core.commands.client_factory import get_mgmt_service_client
571        storage_client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_STORAGE).storage_accounts
572
573        # find storage account in target resource group that matches the VM's location
574        sku_tier = 'Standard'
575        for sku in namespace.storage_sku:
576            if 'Premium' in sku:
577                sku_tier = 'Premium'
578                break
579
580        account = next(
581            (a for a in storage_client.list_by_resource_group(namespace.resource_group_name)
582             if a.sku.tier == sku_tier and a.location == namespace.location), None)
583
584        if account:
585            # 3 - nothing specified - find viable storage account in target resource group
586            namespace.storage_account = account.name
587            namespace.storage_account_type = 'existing'
588            logger.debug("suitable existing storage account '%s' will be used", account.name)
589        else:
590            # 4 - nothing specified - create a new storage account
591            namespace.storage_account_type = 'new'
592            logger.debug('no suitable storage account found. One will be created.')
593
594
595def _validate_vm_create_availability_set(cmd, namespace):
596    from msrestazure.tools import parse_resource_id, resource_id
597    from azure.cli.core.commands.client_factory import get_subscription_id
598    if namespace.availability_set:
599        as_id = parse_resource_id(namespace.availability_set)
600        name = as_id['name']
601        rg = as_id.get('resource_group', namespace.resource_group_name)
602
603        if not check_existence(cmd.cli_ctx, name, rg, 'Microsoft.Compute', 'availabilitySets'):
604            raise CLIError("Availability set '{}' does not exist.".format(name))
605
606        namespace.availability_set = resource_id(
607            subscription=get_subscription_id(cmd.cli_ctx),
608            resource_group=rg,
609            namespace='Microsoft.Compute',
610            type='availabilitySets',
611            name=name)
612        logger.debug("adding to specified availability set '%s'", namespace.availability_set)
613
614
615def _validate_vm_create_vmss(cmd, namespace):
616    from msrestazure.tools import parse_resource_id, resource_id
617    from azure.cli.core.commands.client_factory import get_subscription_id
618    if namespace.vmss:
619        as_id = parse_resource_id(namespace.vmss)
620        name = as_id['name']
621        rg = as_id.get('resource_group', namespace.resource_group_name)
622
623        if not check_existence(cmd.cli_ctx, name, rg, 'Microsoft.Compute', 'virtualMachineScaleSets'):
624            raise CLIError("virtual machine scale set '{}' does not exist.".format(name))
625
626        namespace.vmss = resource_id(
627            subscription=get_subscription_id(cmd.cli_ctx),
628            resource_group=rg,
629            namespace='Microsoft.Compute',
630            type='virtualMachineScaleSets',
631            name=name)
632        logger.debug("adding to specified virtual machine scale set '%s'", namespace.vmss)
633
634
635def _validate_vm_create_dedicated_host(cmd, namespace):
636    """
637    "host": {
638      "$ref": "#/definitions/SubResource",
639      "description": "Specifies information about the dedicated host that the virtual machine resides in.
640      <br><br>Minimum api-version: 2018-10-01."
641    },
642    "hostGroup": {
643      "$ref": "#/definitions/SubResource",
644      "description": "Specifies information about the dedicated host group that the virtual machine resides in.
645      <br><br>Minimum api-version: 2020-06-01. <br><br>NOTE: User cannot specify both host and hostGroup properties."
646    }
647
648    :param cmd:
649    :param namespace:
650    :return:
651    """
652    from msrestazure.tools import resource_id, is_valid_resource_id
653    from azure.cli.core.commands.client_factory import get_subscription_id
654
655    if namespace.dedicated_host and namespace.dedicated_host_group:
656        raise CLIError('usage error: User cannot specify both --host and --host-group properties.')
657
658    if namespace.dedicated_host and not is_valid_resource_id(namespace.dedicated_host):
659        raise CLIError('usage error: --host is not a valid resource ID.')
660
661    if namespace.dedicated_host_group:
662        if not is_valid_resource_id(namespace.dedicated_host_group):
663            namespace.dedicated_host_group = resource_id(
664                subscription=get_subscription_id(cmd.cli_ctx), resource_group=namespace.resource_group_name,
665                namespace='Microsoft.Compute', type='hostGroups', name=namespace.dedicated_host_group
666            )
667
668
669def _validate_vm_vmss_create_vnet(cmd, namespace, for_scale_set=False):
670    from msrestazure.tools import is_valid_resource_id
671    vnet = namespace.vnet_name
672    subnet = namespace.subnet
673    rg = namespace.resource_group_name
674    location = namespace.location
675    nics = getattr(namespace, 'nics', None)
676
677    if vnet and '/' in vnet:
678        raise CLIError("incorrect usage: --subnet ID | --subnet NAME --vnet-name NAME")
679
680    if not vnet and not subnet and not nics:
681        logger.debug('no subnet specified. Attempting to find an existing Vnet and subnet...')
682
683        # if nothing specified, try to find an existing vnet and subnet in the target resource group
684        client = get_network_client(cmd.cli_ctx).virtual_networks
685
686        # find VNET in target resource group that matches the VM's location with a matching subnet
687        for vnet_match in (v for v in client.list(rg) if v.location == location and v.subnets):
688
689            # 1 - find a suitable existing vnet/subnet
690            result = None
691            if not for_scale_set:
692                result = next((s for s in vnet_match.subnets if s.name.lower() != 'gatewaysubnet'), None)
693            else:
694                def _check_subnet(s):
695                    if s.name.lower() == 'gatewaysubnet':
696                        return False
697                    subnet_mask = s.address_prefix.split('/')[-1]
698                    return _subnet_capacity_check(subnet_mask, namespace.instance_count,
699                                                  not namespace.disable_overprovision)
700
701                result = next((s for s in vnet_match.subnets if _check_subnet(s)), None)
702            if not result:
703                continue
704            namespace.subnet = result.name
705            namespace.vnet_name = vnet_match.name
706            namespace.vnet_type = 'existing'
707            logger.debug("existing vnet '%s' and subnet '%s' found", namespace.vnet_name, namespace.subnet)
708            return
709
710    if subnet:
711        subnet_is_id = is_valid_resource_id(subnet)
712        if (subnet_is_id and vnet) or (not subnet_is_id and not vnet):
713            raise CLIError("incorrect usage: --subnet ID | --subnet NAME --vnet-name NAME")
714
715        subnet_exists = \
716            check_existence(cmd.cli_ctx, subnet, rg, 'Microsoft.Network', 'subnets', vnet, 'virtualNetworks')
717
718        if subnet_is_id and not subnet_exists:
719            raise CLIError("Subnet '{}' does not exist.".format(subnet))
720        if subnet_exists:
721            # 2 - user specified existing vnet/subnet
722            namespace.vnet_type = 'existing'
723            logger.debug("using specified vnet '%s' and subnet '%s'", namespace.vnet_name, namespace.subnet)
724            return
725    # 3 - create a new vnet/subnet
726    namespace.vnet_type = 'new'
727    logger.debug('no suitable subnet found. One will be created.')
728
729
730def _subnet_capacity_check(subnet_mask, vmss_instance_count, over_provision):
731    mask = int(subnet_mask)
732    # '2' are the reserved broadcasting addresses
733    # '*1.5' so we have enough leeway for over-provision
734    factor = 1.5 if over_provision else 1
735    return ((1 << (32 - mask)) - 2) > int(vmss_instance_count * factor)
736
737
738def _validate_vm_vmss_accelerated_networking(cli_ctx, namespace):
739    if namespace.accelerated_networking is None:
740        size = getattr(namespace, 'size', None) or getattr(namespace, 'vm_sku', None)
741        size = size.lower()
742
743        # Use the following code to refresh the list
744        # skus = list_sku_info(cli_ctx, namespace.location)
745        # aval_sizes = [x.name.lower() for x in skus if x.resource_type == 'virtualMachines' and
746        #               any(c.name == 'AcceleratedNetworkingEnabled' and c.value == 'True' for c in x.capabilities)]
747
748        aval_sizes = ['standard_b12ms', 'standard_b16ms', 'standard_b20ms', 'standard_ds2_v2', 'standard_ds3_v2',
749                      'standard_ds4_v2', 'standard_ds5_v2', 'standard_ds11-1_v2', 'standard_ds11_v2',
750                      'standard_ds12-1_v2', 'standard_ds12-2_v2', 'standard_ds12_v2', 'standard_ds13-2_v2',
751                      'standard_ds13-4_v2', 'standard_ds13_v2', 'standard_ds14-4_v2', 'standard_ds14-8_v2',
752                      'standard_ds14_v2', 'standard_ds15_v2', 'standard_ds2_v2_promo', 'standard_ds3_v2_promo',
753                      'standard_ds4_v2_promo', 'standard_ds5_v2_promo', 'standard_ds11_v2_promo',
754                      'standard_ds12_v2_promo', 'standard_ds13_v2_promo', 'standard_ds14_v2_promo', 'standard_f2s',
755                      'standard_f4s', 'standard_f8s', 'standard_f16s', 'standard_d4s_v3', 'standard_d8s_v3',
756                      'standard_d16s_v3', 'standard_d32s_v3', 'standard_d2_v2', 'standard_d3_v2', 'standard_d4_v2',
757                      'standard_d5_v2', 'standard_d11_v2', 'standard_d12_v2', 'standard_d13_v2', 'standard_d14_v2',
758                      'standard_d15_v2', 'standard_d2_v2_promo', 'standard_d3_v2_promo', 'standard_d4_v2_promo',
759                      'standard_d5_v2_promo', 'standard_d11_v2_promo', 'standard_d12_v2_promo', 'standard_d13_v2_promo',
760                      'standard_d14_v2_promo', 'standard_f2', 'standard_f4', 'standard_f8', 'standard_f16',
761                      'standard_d4_v3', 'standard_d8_v3', 'standard_d16_v3', 'standard_d32_v3', 'standard_d48_v3',
762                      'standard_d64_v3', 'standard_d48s_v3', 'standard_d64s_v3', 'standard_e4_v3', 'standard_e8_v3',
763                      'standard_e16_v3', 'standard_e20_v3', 'standard_e32_v3', 'standard_e48_v3', 'standard_e64i_v3',
764                      'standard_e64_v3', 'standard_e4-2s_v3', 'standard_e4s_v3', 'standard_e8-2s_v3',
765                      'standard_e8-4s_v3', 'standard_e8s_v3', 'standard_e16-4s_v3', 'standard_e16-8s_v3',
766                      'standard_e16s_v3', 'standard_e20s_v3', 'standard_e32-8s_v3', 'standard_e32-16s_v3',
767                      'standard_e32s_v3', 'standard_e48s_v3', 'standard_e64-16s_v3', 'standard_e64-32s_v3',
768                      'standard_e64is_v3', 'standard_e64s_v3', 'standard_l8s_v2', 'standard_l16s_v2',
769                      'standard_l32s_v2', 'standard_l48s_v2', 'standard_l64s_v2', 'standard_l80s_v2', 'standard_e4_v4',
770                      'standard_e8_v4', 'standard_e16_v4', 'standard_e20_v4', 'standard_e32_v4', 'standard_e48_v4',
771                      'standard_e64_v4', 'standard_e4d_v4', 'standard_e8d_v4', 'standard_e16d_v4', 'standard_e20d_v4',
772                      'standard_e32d_v4', 'standard_e48d_v4', 'standard_e64d_v4', 'standard_e4-2s_v4',
773                      'standard_e4s_v4', 'standard_e8-2s_v4', 'standard_e8-4s_v4', 'standard_e8s_v4',
774                      'standard_e16-4s_v4', 'standard_e16-8s_v4', 'standard_e16s_v4', 'standard_e20s_v4',
775                      'standard_e32-8s_v4', 'standard_e32-16s_v4', 'standard_e32s_v4', 'standard_e48s_v4',
776                      'standard_e64-16s_v4', 'standard_e64-32s_v4', 'standard_e64s_v4', 'standard_e4-2ds_v4',
777                      'standard_e4ds_v4', 'standard_e8-2ds_v4', 'standard_e8-4ds_v4', 'standard_e8ds_v4',
778                      'standard_e16-4ds_v4', 'standard_e16-8ds_v4', 'standard_e16ds_v4', 'standard_e20ds_v4',
779                      'standard_e32-8ds_v4', 'standard_e32-16ds_v4', 'standard_e32ds_v4', 'standard_e48ds_v4',
780                      'standard_e64-16ds_v4', 'standard_e64-32ds_v4', 'standard_e64ds_v4', 'standard_d4d_v4',
781                      'standard_d8d_v4', 'standard_d16d_v4', 'standard_d32d_v4', 'standard_d48d_v4', 'standard_d64d_v4',
782                      'standard_d4_v4', 'standard_d8_v4', 'standard_d16_v4', 'standard_d32_v4', 'standard_d48_v4',
783                      'standard_d64_v4', 'standard_d4ds_v4', 'standard_d8ds_v4', 'standard_d16ds_v4',
784                      'standard_d32ds_v4', 'standard_d48ds_v4', 'standard_d64ds_v4', 'standard_d4s_v4',
785                      'standard_d8s_v4', 'standard_d16s_v4', 'standard_d32s_v4', 'standard_d48s_v4', 'standard_d64s_v4',
786                      'standard_f4s_v2', 'standard_f8s_v2', 'standard_f16s_v2', 'standard_f32s_v2', 'standard_f48s_v2',
787                      'standard_f64s_v2', 'standard_f72s_v2', 'standard_m208ms_v2', 'standard_m208s_v2',
788                      'standard_m416-208s_v2', 'standard_m416s_v2', 'standard_m416-208ms_v2', 'standard_m416ms_v2',
789                      'standard_m64', 'standard_m64m', 'standard_m128', 'standard_m128m', 'standard_m8-2ms',
790                      'standard_m8-4ms', 'standard_m8ms', 'standard_m16-4ms', 'standard_m16-8ms', 'standard_m16ms',
791                      'standard_m32-8ms', 'standard_m32-16ms', 'standard_m32ls', 'standard_m32ms', 'standard_m32ts',
792                      'standard_m64-16ms', 'standard_m64-32ms', 'standard_m64ls', 'standard_m64ms', 'standard_m64s',
793                      'standard_m128-32ms', 'standard_m128-64ms', 'standard_m128ms', 'standard_m128s',
794                      'standard_d4a_v4', 'standard_d8a_v4', 'standard_d16a_v4', 'standard_d32a_v4', 'standard_d48a_v4',
795                      'standard_d64a_v4', 'standard_d96a_v4', 'standard_d4as_v4', 'standard_d8as_v4',
796                      'standard_d16as_v4', 'standard_d32as_v4', 'standard_d48as_v4', 'standard_d64as_v4',
797                      'standard_d96as_v4', 'standard_e4a_v4', 'standard_e8a_v4', 'standard_e16a_v4', 'standard_e20a_v4',
798                      'standard_e32a_v4', 'standard_e48a_v4', 'standard_e64a_v4', 'standard_e96a_v4',
799                      'standard_e4as_v4', 'standard_e8as_v4', 'standard_e16as_v4', 'standard_e20as_v4',
800                      'standard_e32as_v4', 'standard_e48as_v4', 'standard_e64as_v4', 'standard_e96as_v4']
801        if size not in aval_sizes:
802            return
803
804        new_4core_sizes = ['Standard_D3_v2', 'Standard_D3_v2_Promo', 'Standard_D3_v2_ABC', 'Standard_DS3_v2',
805                           'Standard_DS3_v2_Promo', 'Standard_D12_v2', 'Standard_D12_v2_Promo', 'Standard_D12_v2_ABC',
806                           'Standard_DS12_v2', 'Standard_DS12_v2_Promo', 'Standard_F8s_v2', 'Standard_F4',
807                           'Standard_F4_ABC', 'Standard_F4s', 'Standard_E8_v3', 'Standard_E8s_v3', 'Standard_D8_v3',
808                           'Standard_D8s_v3']
809        new_4core_sizes = [x.lower() for x in new_4core_sizes]
810        if size not in new_4core_sizes:
811            compute_client = _compute_client_factory(cli_ctx)
812            sizes = compute_client.virtual_machine_sizes.list(namespace.location)
813            size_info = next((s for s in sizes if s.name.lower() == size), None)
814            if size_info is None or size_info.number_of_cores < 8:
815                return
816
817        # VMs need to be a supported image in the marketplace
818        # Ubuntu 16.04 | 18.04, SLES 12 SP3, RHEL 7.4, CentOS 7.4, Flatcar, Debian "Stretch" with backports kernel
819        # Oracle Linux 7.4, Windows Server 2016, Windows Server 2012R2
820        publisher, offer, sku = namespace.os_publisher, namespace.os_offer, namespace.os_sku
821        if not publisher:
822            return
823        publisher, offer, sku = publisher.lower(), offer.lower(), sku.lower()
824
825        if publisher == 'coreos' or offer == 'coreos':
826            from azure.cli.core.parser import InvalidArgumentValueError
827            raise InvalidArgumentValueError("As CoreOS is deprecated and there is no image in the marketplace any more,"
828                                            " please use Flatcar Container Linux instead.")
829
830        distros = [('canonical', 'UbuntuServer', '^16.04|^18.04'),
831                   ('suse', 'sles', '^12-sp3'), ('redhat', 'rhel', '^7.4'),
832                   ('openlogic', 'centos', '^7.4'), ('kinvolk', 'flatcar-container-linux-free', None),
833                   ('kinvolk', 'flatcar-container-linux', None), ('credativ', 'debian', '-backports'),
834                   ('oracle', 'oracle-linux', '^7.4'), ('MicrosoftWindowsServer', 'WindowsServer', '^2016'),
835                   ('MicrosoftWindowsServer', 'WindowsServer', '^2012-R2')]
836        import re
837        for p, o, s in distros:
838            if p.lower() == publisher and (o is None or o.lower() == offer) and (s is None or re.match(s, sku, re.I)):
839                namespace.accelerated_networking = True
840
841
842def _validate_vmss_create_subnet(namespace):
843    if namespace.vnet_type == 'new':
844        if namespace.subnet_address_prefix is None:
845            cidr = namespace.vnet_address_prefix.split('/', 1)[0]
846            i = 0
847            for i in range(24, 16, -1):
848                if _subnet_capacity_check(i, namespace.instance_count, not namespace.disable_overprovision):
849                    break
850            if i < 16:
851                err = "instance count '{}' is out of range of 2^16 subnet size'"
852                raise CLIError(err.format(namespace.instance_count))
853            namespace.subnet_address_prefix = '{}/{}'.format(cidr, i)
854
855        if namespace.app_gateway_type and namespace.app_gateway_subnet_address_prefix is None:
856            namespace.app_gateway_subnet_address_prefix = _get_next_subnet_addr_suffix(
857                namespace.vnet_address_prefix, namespace.subnet_address_prefix, 24)
858
859
860def _get_next_subnet_addr_suffix(vnet_cidr, subnet_cidr, new_mask):
861    def _convert_to_int(address, bit_mask_len):
862        a, b, c, d = [int(x) for x in address.split('.')]
863        result = '{0:08b}{1:08b}{2:08b}{3:08b}'.format(a, b, c, d)
864        return int(result[:-bit_mask_len], 2)
865
866    error_msg = "usage error: --subnet-address-prefix value should be a subrange of --vnet-address-prefix's"
867    # extract vnet information needed to verify the defaults we are coming out
868    vnet_ip_address, mask = vnet_cidr.split('/')
869    vnet_bit_mask_len = 32 - int(mask)
870    vnet_int = _convert_to_int(vnet_ip_address, vnet_bit_mask_len)
871
872    subnet_ip_address, mask = subnet_cidr.split('/')
873    subnet_bit_mask_len = 32 - int(mask)
874
875    if vnet_bit_mask_len <= subnet_bit_mask_len:
876        raise CLIError(error_msg)
877
878    candidate_int = _convert_to_int(subnet_ip_address, subnet_bit_mask_len) + 1
879    if (candidate_int >> (vnet_bit_mask_len - subnet_bit_mask_len)) > vnet_int:  # overflows?
880        candidate_int = candidate_int - 2  # try the other way around
881        if (candidate_int >> (vnet_bit_mask_len - subnet_bit_mask_len)) > vnet_int:
882            raise CLIError(error_msg)
883
884    # format back to the cidr
885    candaidate_str = '{0:32b}'.format(candidate_int << subnet_bit_mask_len)
886    return '{0}.{1}.{2}.{3}/{4}'.format(int(candaidate_str[0:8], 2), int(candaidate_str[8:16], 2),
887                                        int(candaidate_str[16:24], 2), int(candaidate_str[24:32], 2),
888                                        new_mask)
889
890
891def _validate_vm_create_nsg(cmd, namespace):
892
893    if namespace.nsg:
894        if check_existence(cmd.cli_ctx, namespace.nsg, namespace.resource_group_name,
895                           'Microsoft.Network', 'networkSecurityGroups'):
896            namespace.nsg_type = 'existing'
897            logger.debug("using specified NSG '%s'", namespace.nsg)
898        else:
899            namespace.nsg_type = 'new'
900            logger.debug("specified NSG '%s' not found. It will be created.", namespace.nsg)
901    elif namespace.nsg == '':
902        namespace.nsg_type = None
903        logger.debug('no NSG will be used')
904    elif namespace.nsg is None:
905        namespace.nsg_type = 'new'
906        logger.debug('new NSG will be created')
907
908
909def _validate_vmss_create_nsg(cmd, namespace):
910    if namespace.nsg:
911        namespace.nsg = _get_resource_id(cmd.cli_ctx, namespace.nsg, namespace.resource_group_name,
912                                         'networkSecurityGroups', 'Microsoft.Network')
913
914
915def _validate_vm_vmss_create_public_ip(cmd, namespace):
916    if namespace.public_ip_address:
917        if check_existence(cmd.cli_ctx, namespace.public_ip_address, namespace.resource_group_name,
918                           'Microsoft.Network', 'publicIPAddresses'):
919            namespace.public_ip_address_type = 'existing'
920            logger.debug("using existing specified public IP '%s'", namespace.public_ip_address)
921        else:
922            namespace.public_ip_address_type = 'new'
923            logger.debug("specified public IP '%s' not found. It will be created.", namespace.public_ip_address)
924    elif namespace.public_ip_address == '':
925        namespace.public_ip_address_type = None
926        logger.debug('no public IP address will be used')
927    elif namespace.public_ip_address is None:
928        namespace.public_ip_address_type = 'new'
929        logger.debug('new public IP address will be created')
930
931    from azure.cli.core.profiles import ResourceType
932    PublicIPAddressSkuName, IPAllocationMethod = cmd.get_models('PublicIPAddressSkuName', 'IPAllocationMethod',
933                                                                resource_type=ResourceType.MGMT_NETWORK)
934    # Use standard public IP address automatically when using zones.
935    if hasattr(namespace, 'zone') and namespace.zone is not None:
936        namespace.public_ip_sku = PublicIPAddressSkuName.standard.value
937
938    # Public-IP SKU is only exposed for VM. VMSS has no such needs so far
939    if getattr(namespace, 'public_ip_sku', None):
940        if namespace.public_ip_sku == PublicIPAddressSkuName.standard.value:
941            if not namespace.public_ip_address_allocation:
942                namespace.public_ip_address_allocation = IPAllocationMethod.static.value
943
944
945def _validate_vmss_create_public_ip(cmd, namespace):
946    if namespace.load_balancer_type is None and namespace.app_gateway_type is None:
947        if namespace.public_ip_address:
948            raise CLIError('--public-ip-address can only be used when creating a new load '
949                           'balancer or application gateway frontend.')
950        namespace.public_ip_address = ''
951    _validate_vm_vmss_create_public_ip(cmd, namespace)
952
953
954def validate_delete_options(resources, delete_option):
955    """ Extracts multiple space-separated delete_option in key[=value] format """
956    if resources and isinstance(delete_option, list):
957        if len(delete_option) == 1 and len(delete_option[0].split('=', 1)) == 1:
958            return delete_option[0]
959        delete_option_dict = {}
960        for item in delete_option:
961            delete_option_dict.update(validate_delete_option(item))
962        return delete_option_dict
963    return None
964
965
966def validate_delete_option(string):
967    """ Extracts a single delete_option in key[=value] format """
968    from azure.cli.core.azclierror import InvalidArgumentValueError
969    result = {}
970    if string:
971        comps = string.split('=', 1)
972        if len(comps) == 2:
973            result = {comps[0]: comps[1]}
974        else:
975            raise InvalidArgumentValueError(
976                "Invalid value for delete option. Use a singular value to apply on all resources, or use "
977                "<Name>=<Value> to configure the delete behavior for individual resources.")
978    return result
979
980
981def _validate_vm_create_nics(cmd, namespace):
982    from msrestazure.tools import resource_id
983    from azure.cli.core.commands.client_factory import get_subscription_id
984    nic_ids = namespace.nics
985    delete_option = validate_delete_options(nic_ids, getattr(namespace, 'nic_delete_option', None))
986    nics = []
987
988    if not nic_ids:
989        namespace.nic_type = 'new'
990        logger.debug('new NIC will be created')
991        return
992
993    if not isinstance(nic_ids, list):
994        nic_ids = [nic_ids]
995
996    for n in nic_ids:
997        nic = {'id': n if '/' in n else resource_id(name=n,
998                                                    resource_group=namespace.resource_group_name,
999                                                    namespace='Microsoft.Network',
1000                                                    type='networkInterfaces',
1001                                                    subscription=get_subscription_id(cmd.cli_ctx)),
1002               'properties': {'primary': nic_ids[0] == n}
1003               }
1004        if delete_option:
1005            nic['properties']['deleteOption'] = delete_option if isinstance(delete_option, str) else \
1006                delete_option.get(n, None)
1007        nics.append(nic)
1008
1009    namespace.nics = nics
1010    namespace.nic_type = 'existing'
1011    namespace.public_ip_address_type = None
1012    logger.debug('existing NIC(s) will be used')
1013
1014
1015def _validate_vm_vmss_create_auth(namespace, cmd=None):
1016    if namespace.storage_profile in [StorageProfile.ManagedSpecializedOSDisk,
1017                                     StorageProfile.SASpecializedOSDisk]:
1018        return
1019
1020    if namespace.admin_username is None:
1021        namespace.admin_username = get_default_admin_username()
1022    if namespace.admin_username and namespace.os_type:
1023        namespace.admin_username = _validate_admin_username(namespace.admin_username, namespace.os_type)
1024
1025    # if not namespace.os_type:
1026    #     raise CLIError("Unable to resolve OS type. Specify '--os-type' argument.")
1027
1028    if not namespace.authentication_type:
1029        # if both ssh key and password, infer that authentication_type is all.
1030        if namespace.ssh_key_value and namespace.admin_password:
1031            namespace.authentication_type = 'all'
1032        else:
1033            # apply default auth type (password for Windows, ssh for Linux) by examining the OS type
1034            namespace.authentication_type = 'password' \
1035                if ((namespace.os_type and namespace.os_type.lower() == 'windows') or
1036                    namespace.admin_password) else 'ssh'
1037
1038    if namespace.os_type and namespace.os_type.lower() == 'windows' and namespace.authentication_type == 'ssh':
1039        raise CLIError('SSH not supported for Windows VMs.')
1040
1041    # validate proper arguments supplied based on the authentication type
1042    if namespace.authentication_type == 'password':
1043        if namespace.ssh_key_value or namespace.ssh_dest_key_path:
1044            raise CLIError('SSH key cannot be used with password authentication type.')
1045
1046        # if password not given, attempt to prompt user for password.
1047        if not namespace.admin_password:
1048            _prompt_for_password(namespace)
1049
1050        # validate password
1051        _validate_admin_password(namespace.admin_password, namespace.os_type)
1052
1053    elif namespace.authentication_type == 'ssh':
1054
1055        if namespace.admin_password:
1056            raise CLIError('Admin password cannot be used with SSH authentication type.')
1057
1058        validate_ssh_key(namespace, cmd)
1059
1060        if not namespace.ssh_dest_key_path:
1061            namespace.ssh_dest_key_path = '/home/{}/.ssh/authorized_keys'.format(namespace.admin_username)
1062
1063    elif namespace.authentication_type == 'all':
1064        if namespace.os_type and namespace.os_type.lower() == 'windows':
1065            raise CLIError('SSH not supported for Windows VMs. Use password authentication.')
1066
1067        if not namespace.admin_password:
1068            _prompt_for_password(namespace)
1069        _validate_admin_password(namespace.admin_password, namespace.os_type)
1070
1071        validate_ssh_key(namespace, cmd)
1072        if not namespace.ssh_dest_key_path:
1073            namespace.ssh_dest_key_path = '/home/{}/.ssh/authorized_keys'.format(namespace.admin_username)
1074
1075
1076def _prompt_for_password(namespace):
1077    from knack.prompting import prompt_pass, NoTTYException
1078    try:
1079        namespace.admin_password = prompt_pass('Admin Password: ', confirm=True)
1080    except NoTTYException:
1081        raise CLIError('Please specify password in non-interactive mode.')
1082
1083
1084def _validate_admin_username(username, os_type):
1085    import re
1086    if not username:
1087        raise CLIError("admin user name can not be empty")
1088    is_linux = (os_type.lower() == 'linux')
1089    # pylint: disable=line-too-long
1090    pattern = (r'[\\\/"\[\]:|<>+=;,?*@#()!A-Z]+' if is_linux else r'[\\\/"\[\]:|<>+=;,?*@]+')
1091    linux_err = r'admin user name cannot contain upper case character A-Z, special characters \/"[]:|<>+=;,?*@#()! or start with $ or -'
1092    win_err = r'admin user name cannot contain special characters \/"[]:|<>+=;,?*@# or ends with .'
1093    if re.findall(pattern, username):
1094        raise CLIError(linux_err if is_linux else win_err)
1095    if is_linux and re.findall(r'^[$-]+', username):
1096        raise CLIError(linux_err)
1097    if not is_linux and username.endswith('.'):
1098        raise CLIError(win_err)
1099    if username.lower() in DISALLOWED_USER_NAMES:
1100        raise CLIError("This user name '{}' meets the general requirements, but is specifically disallowed for this image. Please try a different value.".format(username))
1101    return username
1102
1103
1104def _validate_admin_password(password, os_type):
1105    import re
1106    is_linux = (os_type.lower() == 'linux')
1107    max_length = 72 if is_linux else 123
1108    min_length = 12
1109
1110    contains_lower = re.findall('[a-z]+', password)
1111    contains_upper = re.findall('[A-Z]+', password)
1112    contains_digit = re.findall('[0-9]+', password)
1113    contains_special_char = re.findall(r'[ `~!@#$%^&*()=+_\[\]{}\|;:.\/\'\",<>?]+', password)
1114    count = len([x for x in [contains_lower, contains_upper,
1115                             contains_digit, contains_special_char] if x])
1116
1117    # pylint: disable=line-too-long
1118    error_msg = ("The password length must be between {} and {}. Password must have the 3 of the following: "
1119                 "1 lower case character, 1 upper case character, 1 number and 1 special character.").format(min_length, max_length)
1120    if len(password) not in range(min_length, max_length + 1) or count < 3:
1121        raise CLIError(error_msg)
1122
1123
1124def validate_ssh_key(namespace, cmd=None):
1125    from azure.core.exceptions import HttpResponseError
1126    if hasattr(namespace, 'ssh_key_name') and namespace.ssh_key_name:
1127        client = _compute_client_factory(cmd.cli_ctx)
1128        # --ssh-key-name
1129        if not namespace.ssh_key_value and not namespace.generate_ssh_keys:
1130            # Use existing key, key must exist
1131            try:
1132                ssh_key_resource = client.ssh_public_keys.get(namespace.resource_group_name, namespace.ssh_key_name)
1133            except HttpResponseError:
1134                raise ValidationError('SSH key {} does not exist!'.format(namespace.ssh_key_name))
1135            namespace.ssh_key_value = [ssh_key_resource.public_key]
1136            logger.info('Get a key from --ssh-key-name successfully')
1137        elif namespace.ssh_key_value:
1138            raise ValidationError('--ssh-key-name and --ssh-key-values cannot be used together')
1139        elif namespace.generate_ssh_keys:
1140            parameters = {}
1141            parameters['location'] = namespace.location
1142            public_key = _validate_ssh_key_helper("", namespace.generate_ssh_keys)
1143            parameters['public_key'] = public_key
1144            client.ssh_public_keys.create(resource_group_name=namespace.resource_group_name,
1145                                          ssh_public_key_name=namespace.ssh_key_name,
1146                                          parameters=parameters)
1147            namespace.ssh_key_value = [public_key]
1148    elif namespace.ssh_key_value:
1149        if namespace.generate_ssh_keys and len(namespace.ssh_key_value) > 1:
1150            logger.warning("Ignoring --generate-ssh-keys as multiple ssh key values have been specified.")
1151            namespace.generate_ssh_keys = False
1152
1153        processed_ssh_key_values = []
1154        for ssh_key_value in namespace.ssh_key_value:
1155            processed_ssh_key_values.append(_validate_ssh_key_helper(ssh_key_value, namespace.generate_ssh_keys))
1156        namespace.ssh_key_value = processed_ssh_key_values
1157    # if no ssh keys processed, try to generate new key / use existing at root.
1158    else:
1159        namespace.ssh_key_value = [_validate_ssh_key_helper("", namespace.generate_ssh_keys)]
1160
1161
1162def _validate_ssh_key_helper(ssh_key_value, should_generate_ssh_keys):
1163    string_or_file = (ssh_key_value or
1164                      os.path.join(os.path.expanduser('~'), '.ssh', 'id_rsa.pub'))
1165    content = string_or_file
1166    if os.path.exists(string_or_file):
1167        logger.info('Use existing SSH public key file: %s', string_or_file)
1168        with open(string_or_file, 'r') as f:
1169            content = f.read()
1170    elif not keys.is_valid_ssh_rsa_public_key(content):
1171        if should_generate_ssh_keys:
1172            # figure out appropriate file names:
1173            # 'base_name'(with private keys), and 'base_name.pub'(with public keys)
1174            public_key_filepath = string_or_file
1175            if public_key_filepath[-4:].lower() == '.pub':
1176                private_key_filepath = public_key_filepath[:-4]
1177            else:
1178                private_key_filepath = public_key_filepath + '.private'
1179            content = keys.generate_ssh_keys(private_key_filepath, public_key_filepath)
1180            logger.warning("SSH key files '%s' and '%s' have been generated under ~/.ssh to "
1181                           "allow SSH access to the VM. If using machines without "
1182                           "permanent storage, back up your keys to a safe location.",
1183                           private_key_filepath, public_key_filepath)
1184        else:
1185            raise CLIError('An RSA key file or key value must be supplied to SSH Key Value. '
1186                           'You can use --generate-ssh-keys to let CLI generate one for you')
1187    return content
1188
1189
1190def _validate_vm_vmss_msi(cmd, namespace, from_set_command=False):
1191    if from_set_command or namespace.assign_identity is not None:
1192        identities = namespace.assign_identity or []
1193        from ._vm_utils import MSI_LOCAL_ID
1194        for i, _ in enumerate(identities):
1195            if identities[i] != MSI_LOCAL_ID:
1196                identities[i] = _get_resource_id(cmd.cli_ctx, identities[i], namespace.resource_group_name,
1197                                                 'userAssignedIdentities', 'Microsoft.ManagedIdentity')
1198        if not namespace.identity_scope and getattr(namespace.identity_role, 'is_default', None) is None:
1199            raise CLIError("usage error: '--role {}' is not applicable as the '--scope' is not provided".format(
1200                namespace.identity_role))
1201        user_assigned_identities = [x for x in identities if x != MSI_LOCAL_ID]
1202        if user_assigned_identities and not cmd.supported_api_version(min_api='2017-12-01'):
1203            raise CLIError('usage error: user assigned identity is only available under profile '
1204                           'with minimum Compute API version of 2017-12-01')
1205        if namespace.identity_scope:
1206            if identities and MSI_LOCAL_ID not in identities:
1207                raise CLIError("usage error: '--scope'/'--role' is only applicable when assign system identity")
1208            # keep 'identity_role' for output as logical name is more readable
1209            setattr(namespace, 'identity_role_id', _resolve_role_id(cmd.cli_ctx, namespace.identity_role,
1210                                                                    namespace.identity_scope))
1211    elif namespace.identity_scope or getattr(namespace.identity_role, 'is_default', None) is None:
1212        raise CLIError('usage error: --assign-identity [--scope SCOPE] [--role ROLE]')
1213
1214
1215def _resolve_role_id(cli_ctx, role, scope):
1216    import re
1217    import uuid
1218    from azure.cli.core.commands.client_factory import get_mgmt_service_client
1219    from azure.cli.core.profiles import ResourceType
1220    client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_AUTHORIZATION).role_definitions
1221
1222    role_id = None
1223    if re.match(r'/subscriptions/.+/providers/Microsoft.Authorization/roleDefinitions/',
1224                role, re.I):
1225        role_id = role
1226    else:
1227        try:
1228            uuid.UUID(role)
1229            role_id = '/subscriptions/{}/providers/Microsoft.Authorization/roleDefinitions/{}'.format(
1230                client.config.subscription_id, role)
1231        except ValueError:
1232            pass
1233        if not role_id:  # retrieve role id
1234            role_defs = list(client.list(scope, "roleName eq '{}'".format(role)))
1235            if not role_defs:
1236                raise CLIError("Role '{}' doesn't exist.".format(role))
1237            if len(role_defs) > 1:
1238                ids = [r.id for r in role_defs]
1239                err = "More than one role matches the given name '{}'. Please pick an id from '{}'"
1240                raise CLIError(err.format(role, ids))
1241            role_id = role_defs[0].id
1242    return role_id
1243
1244
1245def process_vm_create_namespace(cmd, namespace):
1246    validate_tags(namespace)
1247    _validate_location(cmd, namespace, namespace.zone, namespace.size)
1248    validate_edge_zone(cmd, namespace)
1249    if namespace.count is not None:
1250        _validate_count(namespace)
1251    validate_asg_names_or_ids(cmd, namespace)
1252    _validate_vm_create_storage_profile(cmd, namespace)
1253    if namespace.storage_profile in [StorageProfile.SACustomImage,
1254                                     StorageProfile.SAPirImage]:
1255        _validate_vm_create_storage_account(cmd, namespace)
1256
1257    _validate_vm_create_availability_set(cmd, namespace)
1258    _validate_vm_create_vmss(cmd, namespace)
1259    _validate_vm_vmss_create_vnet(cmd, namespace)
1260    _validate_vm_create_nsg(cmd, namespace)
1261    _validate_vm_vmss_create_public_ip(cmd, namespace)
1262    _validate_vm_create_nics(cmd, namespace)
1263    _validate_vm_vmss_accelerated_networking(cmd.cli_ctx, namespace)
1264    _validate_vm_vmss_create_auth(namespace, cmd)
1265
1266    _validate_proximity_placement_group(cmd, namespace)
1267    _validate_vm_create_dedicated_host(cmd, namespace)
1268
1269    if namespace.secrets:
1270        _validate_secrets(namespace.secrets, namespace.os_type)
1271    _validate_vm_vmss_msi(cmd, namespace)
1272    if namespace.boot_diagnostics_storage:
1273        namespace.boot_diagnostics_storage = get_storage_blob_uri(cmd.cli_ctx, namespace.boot_diagnostics_storage)
1274
1275    _validate_capacity_reservation_group(cmd, namespace)
1276
1277# endregion
1278
1279
1280def process_vm_update_namespace(cmd, namespace):
1281    _validate_capacity_reservation_group(cmd, namespace)
1282
1283
1284# region VMSS Create Validators
1285def _get_default_address_pool(cli_ctx, resource_group, balancer_name, balancer_type):
1286    option_name = '--backend-pool-name'
1287    client = getattr(get_network_client(cli_ctx), balancer_type, None)
1288    if not client:
1289        raise CLIError('unrecognized balancer type: {}'.format(balancer_type))
1290
1291    balancer = client.get(resource_group, balancer_name)
1292    values = [x.name for x in balancer.backend_address_pools]
1293    if len(values) > 1:
1294        raise CLIError("Multiple possible values found for '{0}': {1}\nSpecify '{0}' "
1295                       "explicitly.".format(option_name, ', '.join(values)))
1296    if not values:
1297        raise CLIError("No existing values found for '{0}'. Create one first and try "
1298                       "again.".format(option_name))
1299    return values[0]
1300
1301
1302# Client end hack per: https://github.com/Azure/azure-cli/issues/9943
1303def _validate_vmss_single_placement_group(namespace):
1304    if namespace.zones or namespace.instance_count > 100:
1305        if namespace.single_placement_group is None:
1306            namespace.single_placement_group = False
1307
1308
1309def _validate_vmss_create_load_balancer_or_app_gateway(cmd, namespace):
1310    from msrestazure.tools import parse_resource_id
1311    from azure.cli.core.profiles import ResourceType
1312    from azure.core.exceptions import HttpResponseError
1313    std_lb_is_available = cmd.supported_api_version(min_api='2017-08-01', resource_type=ResourceType.MGMT_NETWORK)
1314
1315    if namespace.load_balancer and namespace.application_gateway:
1316        raise CLIError('incorrect usage: --load-balancer NAME_OR_ID | '
1317                       '--application-gateway NAME_OR_ID')
1318
1319    # Resolve the type of balancer (if any) being used
1320    balancer_type = 'None'
1321    if namespace.load_balancer is None and namespace.application_gateway is None:
1322        if std_lb_is_available:
1323            balancer_type = 'loadBalancer'
1324        else:  # needed for Stack profile 2017_03_09
1325            balancer_type = 'loadBalancer' if namespace.single_placement_group is not False else 'applicationGateway'
1326            logger.debug("W/o STD LB, defaulting to '%s' under because single placement group is disabled",
1327                         balancer_type)
1328
1329    elif namespace.load_balancer:
1330        balancer_type = 'loadBalancer'
1331    elif namespace.application_gateway:
1332        balancer_type = 'applicationGateway'
1333
1334    if balancer_type == 'applicationGateway':
1335
1336        if namespace.application_gateway:
1337            client = get_network_client(cmd.cli_ctx).application_gateways
1338            try:
1339                rg = parse_resource_id(namespace.application_gateway).get(
1340                    'resource_group', namespace.resource_group_name)
1341                ag_name = parse_resource_id(namespace.application_gateway)['name']
1342                client.get(rg, ag_name)
1343                namespace.app_gateway_type = 'existing'
1344                namespace.backend_pool_name = namespace.backend_pool_name or \
1345                    _get_default_address_pool(cmd.cli_ctx, rg, ag_name, 'application_gateways')
1346                logger.debug("using specified existing application gateway '%s'", namespace.application_gateway)
1347            except HttpResponseError:
1348                namespace.app_gateway_type = 'new'
1349                logger.debug("application gateway '%s' not found. It will be created.", namespace.application_gateway)
1350        elif namespace.application_gateway == '':
1351            namespace.app_gateway_type = None
1352            logger.debug('no application gateway will be used')
1353        elif namespace.application_gateway is None:
1354            namespace.app_gateway_type = 'new'
1355            logger.debug('new application gateway will be created')
1356
1357        # AppGateway frontend
1358        required = []
1359        if namespace.app_gateway_type == 'new':
1360            required.append('app_gateway_sku')
1361            required.append('app_gateway_capacity')
1362            if namespace.vnet_type != 'new':
1363                required.append('app_gateway_subnet_address_prefix')
1364        elif namespace.app_gateway_type == 'existing':
1365            required.append('backend_pool_name')
1366        forbidden = ['nat_pool_name', 'load_balancer', 'health_probe']
1367        validate_parameter_set(namespace, required, forbidden, description='network balancer: application gateway')
1368
1369    elif balancer_type == 'loadBalancer':
1370        # LoadBalancer frontend
1371        required = []
1372        forbidden = ['app_gateway_subnet_address_prefix', 'application_gateway', 'app_gateway_sku',
1373                     'app_gateway_capacity']
1374        validate_parameter_set(namespace, required, forbidden, description='network balancer: load balancer')
1375
1376        if namespace.load_balancer:
1377            rg = parse_resource_id(namespace.load_balancer).get('resource_group', namespace.resource_group_name)
1378            lb_name = parse_resource_id(namespace.load_balancer)['name']
1379            lb = get_network_lb(cmd.cli_ctx, namespace.resource_group_name, lb_name)
1380            if lb:
1381                namespace.load_balancer_type = 'existing'
1382                namespace.backend_pool_name = namespace.backend_pool_name or \
1383                    _get_default_address_pool(cmd.cli_ctx, rg, lb_name, 'load_balancers')
1384                if not namespace.nat_pool_name:
1385                    if len(lb.inbound_nat_pools) > 1:
1386                        raise CLIError("Multiple possible values found for '{0}': {1}\nSpecify '{0}' explicitly.".format(  # pylint: disable=line-too-long
1387                            '--nat-pool-name', ', '.join([n.name for n in lb.inbound_nat_pools])))
1388                    if not lb.inbound_nat_pools:  # Associated scaleset will be missing ssh/rdp, so warn here.
1389                        logger.warning("No inbound nat pool was configured on '%s'", namespace.load_balancer)
1390                    else:
1391                        namespace.nat_pool_name = lb.inbound_nat_pools[0].name
1392                logger.debug("using specified existing load balancer '%s'", namespace.load_balancer)
1393            else:
1394                namespace.load_balancer_type = 'new'
1395                logger.debug("load balancer '%s' not found. It will be created.", namespace.load_balancer)
1396        elif namespace.load_balancer == '':
1397            namespace.load_balancer_type = None
1398            logger.debug('no load balancer will be used')
1399        elif namespace.load_balancer is None:
1400            namespace.load_balancer_type = 'new'
1401            logger.debug('new load balancer will be created')
1402
1403        if namespace.load_balancer_type == 'new' and namespace.single_placement_group is False and std_lb_is_available:
1404            LBSkuName = cmd.get_models('LoadBalancerSkuName', resource_type=ResourceType.MGMT_NETWORK)
1405            if namespace.load_balancer_sku is None:
1406                namespace.load_balancer_sku = LBSkuName.standard.value
1407                logger.debug("use Standard sku as single placement group is turned off")
1408            elif namespace.load_balancer_sku == LBSkuName.basic.value:
1409                if namespace.zones:
1410                    err = "'Standard' load balancer is required for zonal scale-sets"
1411                elif namespace.instance_count > 100:
1412                    err = "'Standard' load balancer is required for scale-sets with 100+ instances"
1413                else:
1414                    err = "'Standard' load balancer is required because 'single placement group' is turned off"
1415
1416                raise CLIError('usage error:{}'.format(err))
1417
1418
1419def get_network_client(cli_ctx):
1420    from azure.cli.core.profiles import ResourceType
1421    from azure.cli.core.commands.client_factory import get_mgmt_service_client
1422    return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_NETWORK, api_version=get_target_network_api(cli_ctx))
1423
1424
1425def get_network_lb(cli_ctx, resource_group_name, lb_name):
1426    from azure.core.exceptions import HttpResponseError
1427    network_client = get_network_client(cli_ctx)
1428    try:
1429        return network_client.load_balancers.get(resource_group_name, lb_name)
1430    except HttpResponseError:
1431        return None
1432
1433
1434def process_vmss_create_namespace(cmd, namespace):
1435    from azure.cli.core.azclierror import InvalidArgumentValueError
1436    # uniform_str = 'Uniform'
1437    flexible_str = 'Flexible'
1438    if namespace.orchestration_mode.lower() == flexible_str.lower():
1439
1440        # The commentted parameters are also forbidden, but they have default values.
1441        # I don't know whether they are provided by user.
1442
1443        namespace.load_balancer_sku = 'Standard'  # lb sku MUST be standard
1444        # namespace.public_ip_per_vm = True  # default to true for VMSS Flex
1445        # namespace.disable_overprovision = True  # overprovisioning must be false for vmss flex preview
1446        # namespace.single_placement_group = False  # SPG must be false for VMSS flex
1447        namespace.upgrade_policy_mode = None
1448        namespace.use_unmanaged_disk = None
1449
1450        banned_params = [
1451            # namespace.accelerated_networking,
1452            # namespace.admin_password,
1453            # namespace.admin_username,
1454            # namespace.application_gateway,
1455            # namespace.app_gateway_capacity,
1456            # namespace.app_gateway_sku,
1457            # namespace.app_gateway_subnet_address_prefix,
1458            # namespace.application_security_groups,
1459            # namespace.assign_identity,
1460            # namespace.authentication_type,
1461            # namespace.backend_pool_name,
1462            # namespace.backend_port,
1463            # namespace.computer_name_prefix,
1464            # namespace.custom_data,
1465            # namespace.data_caching,
1466            # namespace.data_disk_sizes_gb,
1467            # namespace.disable_overprovision,
1468            # namespace.dns_servers,
1469            # namespace.ephemeral_os_disk,
1470            # namespace.eviction_policy,
1471            # namespace.generate_ssh_keys,
1472            namespace.health_probe,
1473            namespace.host_group,
1474            # namespace.image,
1475            # namespace.instance_count,
1476            # namespace.load_balancer,
1477            namespace.nat_pool_name,
1478            # namespace.load_balancer_sku,
1479            # namespace.license_type,
1480            # namespace.max_price,
1481            # namespace.nsg,
1482            # namespace.os_caching,
1483            # namespace.os_disk_name,
1484            # namespace.os_type,
1485            # namespace.plan_name,
1486            # namespace.plan_product,
1487            # namespace.plan_promotion_code,
1488            # namespace.plan_publisher,
1489            # namespace.priority,
1490            # namespace.public_ip_address,
1491            # namespace.public_ip_address_allocation,
1492            # namespace.public_ip_address_dns_name,
1493            # namespace.public_ip_per_vm,
1494            # namespace.identity_role,
1495            # namespace.identity_scope,
1496            namespace.scale_in_policy,
1497            # namespace.secrets,
1498            # namespace.ssh_dest_key_path,
1499            # namespace.ssh_key_value,
1500            # namespace.storage_container_name,
1501            # namespace.storage_sku,
1502            # namespace.subnet,
1503            # namespace.subnet_address_prefix,
1504            # namespace.terminate_notification_time,
1505            # namespace.ultra_ssd_enabled,
1506            # namespace.upgrade_policy_mode,
1507            # namespace.use_unmanaged_disk,
1508            # namespace.vm_domain_name,
1509            # namespace.vm_sku,
1510            # namespace.vnet_address_prefix,
1511            # namespace.vnet_name,
1512            namespace.user_data
1513        ]
1514        if any(param is not None for param in banned_params):
1515            raise CLIError('usage error: In VM mode, only name, resource-group, location, '
1516                           'tags, zones, platform-fault-domain-count, single-placement-group and ppg are allowed')
1517
1518        if namespace.image:
1519
1520            if namespace.vm_sku is None:
1521                from azure.cli.core.cloud import AZURE_US_GOV_CLOUD
1522                if cmd.cli_ctx.cloud.name != AZURE_US_GOV_CLOUD.name:
1523                    namespace.vm_sku = 'Standard_DS1_v2'
1524                else:
1525                    namespace.vm_sku = 'Standard_D1_v2'
1526
1527            if namespace.single_placement_group:
1528                raise ArgumentUsageError(
1529                    'usage error: single placement group can only be set to False in Flexible VMSS')
1530            namespace.single_placement_group = False
1531
1532            if namespace.network_api_version is None:
1533                namespace.network_api_version = '2020-11-01'
1534
1535            if namespace.platform_fault_domain_count is None:
1536                namespace.platform_fault_domain_count = 1
1537
1538            if namespace.computer_name_prefix is None:
1539                namespace.computer_name_prefix = namespace.vmss_name[:8]
1540
1541        # if namespace.platform_fault_domain_count is None:
1542        #     raise CLIError("usage error: --platform-fault-domain-count is required in Flexible mode")
1543
1544        if namespace.tags is not None:
1545            validate_tags(namespace)
1546        _validate_location(cmd, namespace, namespace.zones, namespace.vm_sku)
1547        # validate_edge_zone(cmd, namespace)
1548        if namespace.application_security_groups is not None:
1549            validate_asg_names_or_ids(cmd, namespace)
1550
1551        if getattr(namespace, 'attach_os_disk', None) or namespace.image is not None:
1552            _validate_vm_create_storage_profile(cmd, namespace, for_scale_set=True)
1553
1554        if namespace.vnet_name or namespace.subnet or namespace.image:
1555            _validate_vm_vmss_create_vnet(cmd, namespace, for_scale_set=True)
1556            _validate_vmss_create_subnet(namespace)
1557
1558        if namespace.load_balancer or namespace.application_gateway or namespace.image:
1559            _validate_vmss_create_load_balancer_or_app_gateway(cmd, namespace)
1560
1561        if namespace.public_ip_address or namespace.image:
1562            _validate_vmss_create_public_ip(cmd, namespace)
1563
1564        if namespace.nsg is not None:
1565            _validate_vmss_create_nsg(cmd, namespace)
1566        if namespace.accelerated_networking is not None:
1567            _validate_vm_vmss_accelerated_networking(cmd.cli_ctx, namespace)
1568        if any([namespace.admin_password, namespace.ssh_dest_key_path, namespace.generate_ssh_keys,
1569                namespace.authentication_type, namespace.os_type]):
1570            _validate_vm_vmss_create_auth(namespace, cmd)
1571        if namespace.assign_identity == '[system]':
1572            raise InvalidArgumentValueError('usage error: only user assigned indetity is suppoprted for Flex mode.')
1573        if namespace.assign_identity is not None:
1574            _validate_vm_vmss_msi(cmd, namespace)  # -- UserAssignedOnly
1575        _validate_proximity_placement_group(cmd, namespace)
1576        _validate_vmss_terminate_notification(cmd, namespace)
1577        if namespace.automatic_repairs_grace_period is not None:
1578            _validate_vmss_create_automatic_repairs(cmd, namespace)
1579        _validate_vmss_create_host_group(cmd, namespace)
1580
1581        if namespace.secrets is not None:
1582            _validate_secrets(namespace.secrets, namespace.os_type)
1583
1584        if namespace.eviction_policy and not namespace.priority:
1585            raise ArgumentUsageError('usage error: --priority PRIORITY [--eviction-policy POLICY]')
1586
1587        return
1588
1589    # Uniform mode
1590    if namespace.disable_overprovision is None:
1591        namespace.disable_overprovision = False
1592    validate_tags(namespace)
1593    if namespace.vm_sku is None:
1594        from azure.cli.core.cloud import AZURE_US_GOV_CLOUD
1595        if cmd.cli_ctx.cloud.name != AZURE_US_GOV_CLOUD.name:
1596            namespace.vm_sku = 'Standard_DS1_v2'
1597        else:
1598            namespace.vm_sku = 'Standard_D1_v2'
1599    _validate_location(cmd, namespace, namespace.zones, namespace.vm_sku)
1600    validate_edge_zone(cmd, namespace)
1601    validate_asg_names_or_ids(cmd, namespace)
1602    _validate_vm_create_storage_profile(cmd, namespace, for_scale_set=True)
1603    _validate_vm_vmss_create_vnet(cmd, namespace, for_scale_set=True)
1604
1605    _validate_vmss_single_placement_group(namespace)
1606    _validate_vmss_create_load_balancer_or_app_gateway(cmd, namespace)
1607    _validate_vmss_create_subnet(namespace)
1608    _validate_vmss_create_public_ip(cmd, namespace)
1609    _validate_vmss_create_nsg(cmd, namespace)
1610    _validate_vm_vmss_accelerated_networking(cmd.cli_ctx, namespace)
1611    _validate_vm_vmss_create_auth(namespace, cmd)
1612    _validate_vm_vmss_msi(cmd, namespace)
1613    _validate_proximity_placement_group(cmd, namespace)
1614    _validate_vmss_terminate_notification(cmd, namespace)
1615    _validate_vmss_create_automatic_repairs(cmd, namespace)
1616    _validate_vmss_create_host_group(cmd, namespace)
1617
1618    if namespace.secrets:
1619        _validate_secrets(namespace.secrets, namespace.os_type)
1620
1621    if not namespace.public_ip_per_vm and namespace.vm_domain_name:
1622        raise CLIError('usage error: --vm-domain-name can only be used when --public-ip-per-vm is enabled')
1623
1624    if namespace.eviction_policy and not namespace.priority:
1625        raise CLIError('usage error: --priority PRIORITY [--eviction-policy POLICY]')
1626
1627    _validate_capacity_reservation_group(cmd, namespace)
1628
1629
1630def validate_vmss_update_namespace(cmd, namespace):  # pylint: disable=unused-argument
1631    if not namespace.instance_id:
1632        if namespace.protect_from_scale_in is not None or namespace.protect_from_scale_set_actions is not None:
1633            raise CLIError("usage error: protection policies can only be applied to VM instances within a VMSS."
1634                           " Please use --instance-id to specify a VM instance")
1635    _validate_vmss_update_terminate_notification_related(cmd, namespace)
1636    _validate_vmss_update_automatic_repairs(cmd, namespace)
1637    _validate_capacity_reservation_group(cmd, namespace)
1638# endregion
1639
1640
1641# region disk, snapshot, image validators
1642def validate_vm_disk(cmd, namespace):
1643    namespace.disk = _get_resource_id(cmd.cli_ctx, namespace.disk,
1644                                      namespace.resource_group_name, 'disks', 'Microsoft.Compute')
1645
1646
1647def validate_vmss_disk(cmd, namespace):
1648    if namespace.disk:
1649        namespace.disk = _get_resource_id(cmd.cli_ctx, namespace.disk,
1650                                          namespace.resource_group_name, 'disks', 'Microsoft.Compute')
1651    if bool(namespace.disk) == bool(namespace.size_gb):
1652        raise CLIError('usage error: --disk EXIST_DISK --instance-id ID | --size-gb GB')
1653    if bool(namespace.disk) != bool(namespace.instance_id):
1654        raise CLIError('usage error: --disk EXIST_DISK --instance-id ID')
1655
1656
1657def process_disk_or_snapshot_create_namespace(cmd, namespace):
1658    from msrestazure.azure_exceptions import CloudError
1659    validate_tags(namespace)
1660    validate_edge_zone(cmd, namespace)
1661    if namespace.source:
1662        usage_error = 'usage error: --source {SNAPSHOT | DISK} | --source VHD_BLOB_URI [--source-storage-account-id ID]'
1663        try:
1664            namespace.source_blob_uri, namespace.source_disk, namespace.source_snapshot = _figure_out_storage_source(
1665                cmd.cli_ctx, namespace.resource_group_name, namespace.source)
1666            if not namespace.source_blob_uri and namespace.source_storage_account_id:
1667                raise CLIError(usage_error)
1668        except CloudError:
1669            raise CLIError(usage_error)
1670
1671
1672def process_image_create_namespace(cmd, namespace):
1673    from msrestazure.tools import parse_resource_id
1674    validate_tags(namespace)
1675    validate_edge_zone(cmd, namespace)
1676    source_from_vm = False
1677    try:
1678        # try capturing from VM, a most common scenario
1679        res_id = _get_resource_id(cmd.cli_ctx, namespace.source, namespace.resource_group_name,
1680                                  'virtualMachines', 'Microsoft.Compute')
1681        res = parse_resource_id(res_id)
1682        if res['type'] == 'virtualMachines':
1683            compute_client = _compute_client_factory(cmd.cli_ctx, subscription_id=res['subscription'])
1684            vm_info = compute_client.virtual_machines.get(res['resource_group'], res['name'])
1685            source_from_vm = True
1686    except ResourceNotFoundError:
1687        pass
1688
1689    if source_from_vm:
1690        # pylint: disable=no-member
1691        namespace.os_type = vm_info.storage_profile.os_disk.os_type
1692        namespace.source_virtual_machine = res_id
1693        if namespace.data_disk_sources:
1694            raise CLIError("'--data-disk-sources' is not allowed when capturing "
1695                           "images from virtual machines")
1696    else:
1697        namespace.os_blob_uri, namespace.os_disk, namespace.os_snapshot = _figure_out_storage_source(cmd.cli_ctx, namespace.resource_group_name, namespace.source)  # pylint: disable=line-too-long
1698        namespace.data_blob_uris = []
1699        namespace.data_disks = []
1700        namespace.data_snapshots = []
1701        if namespace.data_disk_sources:
1702            for data_disk_source in namespace.data_disk_sources:
1703                source_blob_uri, source_disk, source_snapshot = _figure_out_storage_source(
1704                    cmd.cli_ctx, namespace.resource_group_name, data_disk_source)
1705                if source_blob_uri:
1706                    namespace.data_blob_uris.append(source_blob_uri)
1707                if source_disk:
1708                    namespace.data_disks.append(source_disk)
1709                if source_snapshot:
1710                    namespace.data_snapshots.append(source_snapshot)
1711        if not namespace.os_type:
1712            raise CLIError("usage error: os type is required to create the image, "
1713                           "please specify '--os-type OS_TYPE'")
1714
1715
1716def _figure_out_storage_source(cli_ctx, resource_group_name, source):
1717    source_blob_uri = None
1718    source_disk = None
1719    source_snapshot = None
1720    if urlparse(source).scheme:  # a uri?
1721        source_blob_uri = source
1722    elif '/disks/' in source.lower():
1723        source_disk = source
1724    elif '/snapshots/' in source.lower():
1725        source_snapshot = source
1726    else:
1727        compute_client = _compute_client_factory(cli_ctx)
1728        # pylint: disable=no-member
1729        try:
1730            info = compute_client.snapshots.get(resource_group_name, source)
1731            source_snapshot = info.id
1732        except ResourceNotFoundError:
1733            info = compute_client.disks.get(resource_group_name, source)
1734            source_disk = info.id
1735
1736    return (source_blob_uri, source_disk, source_snapshot)
1737
1738
1739def process_disk_encryption_namespace(cmd, namespace):
1740    namespace.disk_encryption_keyvault = _get_resource_id(cmd.cli_ctx, namespace.disk_encryption_keyvault,
1741                                                          namespace.resource_group_name,
1742                                                          'vaults', 'Microsoft.KeyVault')
1743
1744    if namespace.key_encryption_keyvault:
1745        if not namespace.key_encryption_key:
1746            raise CLIError("Incorrect usage '--key-encryption-keyvault': "
1747                           "'--key-encryption-key' is required")
1748        namespace.key_encryption_keyvault = _get_resource_id(cmd.cli_ctx, namespace.key_encryption_keyvault,
1749                                                             namespace.resource_group_name,
1750                                                             'vaults', 'Microsoft.KeyVault')
1751
1752
1753def process_assign_identity_namespace(cmd, namespace):
1754    _validate_vm_vmss_msi(cmd, namespace, from_set_command=True)
1755
1756
1757def process_remove_identity_namespace(cmd, namespace):
1758    if namespace.identities:
1759        from ._vm_utils import MSI_LOCAL_ID
1760        for i in range(len(namespace.identities)):
1761            if namespace.identities[i] != MSI_LOCAL_ID:
1762                namespace.identities[i] = _get_resource_id(cmd.cli_ctx, namespace.identities[i],
1763                                                           namespace.resource_group_name,
1764                                                           'userAssignedIdentities',
1765                                                           'Microsoft.ManagedIdentity')
1766
1767
1768def process_gallery_image_version_namespace(cmd, namespace):
1769    TargetRegion, EncryptionImages, OSDiskImageEncryption, DataDiskImageEncryption = cmd.get_models(
1770        'TargetRegion', 'EncryptionImages', 'OSDiskImageEncryption', 'DataDiskImageEncryption')
1771    storage_account_types_list = [item.lower() for item in ['Standard_LRS', 'Standard_ZRS', 'Premium_LRS']]
1772    storage_account_types_str = ", ".join(storage_account_types_list)
1773
1774    if namespace.target_regions:
1775        if hasattr(namespace, 'target_region_encryption') and namespace.target_region_encryption:
1776            if len(namespace.target_regions) != len(namespace.target_region_encryption):
1777                raise CLIError(
1778                    'usage error: Length of --target-region-encryption should be as same as length of target regions')
1779        regions_info = []
1780        for i, t in enumerate(namespace.target_regions):
1781            parts = t.split('=', 2)
1782            replica_count = None
1783            storage_account_type = None
1784
1785            # Region specified, but also replica count or storage account type
1786            if len(parts) == 2:
1787                try:
1788                    replica_count = int(parts[1])
1789                except ValueError:
1790                    storage_account_type = parts[1]
1791                    if parts[1].lower() not in storage_account_types_list:
1792                        raise CLIError("usage error: {} is an invalid target region argument. The second part is "
1793                                       "neither an integer replica count or a valid storage account type. "
1794                                       "Storage account types must be one of {}."
1795                                       .format(t, storage_account_types_str))
1796
1797            # Region specified, but also replica count and storage account type
1798            elif len(parts) == 3:
1799                try:
1800                    replica_count = int(parts[1])   # raises ValueError if this is not a replica count, try other order.
1801                    storage_account_type = parts[2]
1802                    if storage_account_type not in storage_account_types_list:
1803                        raise CLIError("usage error: {} is an invalid target region argument. The third part is "
1804                                       "not a valid storage account type. Storage account types must be one of {}."
1805                                       .format(t, storage_account_types_str))
1806                except ValueError:
1807                    raise CLIError("usage error: {} is an invalid target region argument. "
1808                                   "The second part must be a valid integer replica count.".format(t))
1809
1810            # Parse target region encryption, example: ['des1,0,des2,1,des3', 'null', 'des4']
1811            encryption = None
1812            if hasattr(namespace, 'target_region_encryption') and namespace.target_region_encryption:
1813                terms = namespace.target_region_encryption[i].split(',')
1814                # OS disk
1815                os_disk_image = terms[0]
1816                if os_disk_image == 'null':
1817                    os_disk_image = None
1818                else:
1819                    des_id = _disk_encryption_set_format(cmd, namespace, os_disk_image)
1820                    os_disk_image = OSDiskImageEncryption(disk_encryption_set_id=des_id)
1821                # Data disk
1822                if len(terms) > 1:
1823                    data_disk_images = terms[1:]
1824                    data_disk_images_len = len(data_disk_images)
1825                    if data_disk_images_len % 2 != 0:
1826                        raise CLIError('usage error: LUN and disk encryption set for data disk should appear '
1827                                       'in pair in --target-region-encryption. Example: osdes,0,datades0,1,datades1')
1828                    data_disk_image_encryption_list = []
1829                    for j in range(int(data_disk_images_len / 2)):
1830                        lun = data_disk_images[j * 2]
1831                        des_id = data_disk_images[j * 2 + 1]
1832                        des_id = _disk_encryption_set_format(cmd, namespace, des_id)
1833                        data_disk_image_encryption_list.append(DataDiskImageEncryption(
1834                            lun=lun, disk_encryption_set_id=des_id))
1835                    data_disk_images = data_disk_image_encryption_list
1836                else:
1837                    data_disk_images = None
1838                encryption = EncryptionImages(os_disk_image=os_disk_image, data_disk_images=data_disk_images)
1839
1840            # At least the region is specified
1841            if len(parts) >= 1:
1842                regions_info.append(TargetRegion(name=parts[0], regional_replica_count=replica_count,
1843                                                 storage_account_type=storage_account_type,
1844                                                 encryption=encryption))
1845
1846        namespace.target_regions = regions_info
1847
1848
1849def _disk_encryption_set_format(cmd, namespace, name):
1850    """
1851    Transform name to ID. If it's already a valid ID, do nothing.
1852    :param name: string
1853    :return: ID
1854    """
1855    from msrestazure.tools import resource_id, is_valid_resource_id
1856    from azure.cli.core.commands.client_factory import get_subscription_id
1857    if name is not None and not is_valid_resource_id(name):
1858        name = resource_id(
1859            subscription=get_subscription_id(cmd.cli_ctx), resource_group=namespace.resource_group_name,
1860            namespace='Microsoft.Compute', type='diskEncryptionSets', name=name)
1861    return name
1862# endregion
1863
1864
1865def process_vm_vmss_stop(cmd, namespace):  # pylint: disable=unused-argument
1866    if "vmss" in cmd.name:
1867        logger.warning("About to power off the VMSS instances...\nThey will continue to be billed. "
1868                       "To deallocate VMSS instances, run: az vmss deallocate.")
1869    else:
1870        logger.warning("About to power off the specified VM...\nIt will continue to be billed. "
1871                       "To deallocate a VM, run: az vm deallocate.")
1872
1873
1874def _validate_vmss_update_terminate_notification_related(cmd, namespace):  # pylint: disable=unused-argument
1875    """
1876    Validate vmss update enable_terminate_notification and terminate_notification_time.
1877    If terminate_notification_time is specified, enable_terminate_notification should not be false
1878    If enable_terminate_notification is true, must specify terminate_notification_time
1879    """
1880    if namespace.enable_terminate_notification is False and namespace.terminate_notification_time is not None:
1881        raise CLIError("usage error: please enable --enable-terminate-notification")
1882    if namespace.enable_terminate_notification is True and namespace.terminate_notification_time is None:
1883        raise CLIError("usage error: please set --terminate-notification-time")
1884    _validate_vmss_terminate_notification(cmd, namespace)
1885
1886
1887def _validate_vmss_terminate_notification(cmd, namespace):  # pylint: disable=unused-argument
1888    """
1889    Transform minutes to ISO 8601 formmat
1890    """
1891    if namespace.terminate_notification_time is not None:
1892        namespace.terminate_notification_time = 'PT' + namespace.terminate_notification_time + 'M'
1893
1894
1895def _validate_vmss_create_automatic_repairs(cmd, namespace):  # pylint: disable=unused-argument
1896    if namespace.automatic_repairs_grace_period is not None:
1897        if namespace.load_balancer is None or namespace.health_probe is None:
1898            raise CLIError("usage error: --load-balancer and --health-probe are required "
1899                           "when creating vmss with automatic repairs")
1900    _validate_vmss_automatic_repairs(cmd, namespace)
1901
1902
1903def _validate_vmss_update_automatic_repairs(cmd, namespace):  # pylint: disable=unused-argument
1904    if namespace.enable_automatic_repairs is False and namespace.automatic_repairs_grace_period is not None:
1905        raise CLIError("usage error: please enable --enable-automatic-repairs")
1906    if namespace.enable_automatic_repairs is True and namespace.automatic_repairs_grace_period is None:
1907        raise CLIError("usage error: please set --automatic-repairs-grace-period")
1908    _validate_vmss_automatic_repairs(cmd, namespace)
1909
1910
1911def _validate_vmss_automatic_repairs(cmd, namespace):  # pylint: disable=unused-argument
1912    """
1913        Transform minutes to ISO 8601 formmat
1914    """
1915    if namespace.automatic_repairs_grace_period is not None:
1916        namespace.automatic_repairs_grace_period = 'PT' + namespace.automatic_repairs_grace_period + 'M'
1917
1918
1919def _validate_vmss_create_host_group(cmd, namespace):
1920    from msrestazure.tools import resource_id, is_valid_resource_id
1921    from azure.cli.core.commands.client_factory import get_subscription_id
1922    if namespace.host_group:
1923        if not is_valid_resource_id(namespace.host_group):
1924            namespace.host_group = resource_id(
1925                subscription=get_subscription_id(cmd.cli_ctx), resource_group=namespace.resource_group_name,
1926                namespace='Microsoft.Compute', type='hostGroups', name=namespace.host_group
1927            )
1928
1929
1930def _validate_count(namespace):
1931    if namespace.count < 2 or namespace.count > 250:
1932        raise ValidationError(
1933            '--count should be in [2, 250]. Please make sure your subscription has enough quota of resources')
1934    banned_params = [
1935        namespace.attach_data_disks,
1936        namespace.attach_os_disk,
1937        namespace.boot_diagnostics_storage,
1938        namespace.computer_name,
1939        namespace.dedicated_host,
1940        namespace.dedicated_host_group,
1941        namespace.nics,
1942        namespace.os_disk_name,
1943        namespace.private_ip_address,
1944        namespace.public_ip_address,
1945        namespace.public_ip_address_dns_name,
1946        namespace.storage_account,
1947        namespace.storage_container_name,
1948        namespace.use_unmanaged_disk,
1949    ]
1950    params_str = [
1951        '--attach-data-disks',
1952        '--attach-os-disk',
1953        '--boot-diagnostics-storage',
1954        '--computer-name',
1955        '--host',
1956        '--host-group',
1957        '--nics',
1958        '--os-disk-name',
1959        '--private-ip-address',
1960        '--public-ip-address',
1961        '--public-ip-address-dns-name',
1962        '--storage-account',
1963        '--storage-container-name',
1964        '--subnet',
1965        '--use-unmanaged-disk',
1966        '--vnet-name'
1967    ]
1968    if any(param for param in banned_params):
1969        raise ValidationError('When --count is specified, {} are not allowed'.format(', '.join(params_str)))
1970
1971
1972def validate_edge_zone(cmd, namespace):  # pylint: disable=unused-argument
1973    if namespace.edge_zone:
1974        namespace.edge_zone = {
1975            'name': namespace.edge_zone,
1976            'type': 'EdgeZone'
1977        }
1978
1979
1980def _validate_capacity_reservation_group(cmd, namespace):
1981
1982    if namespace.capacity_reservation_group and namespace.capacity_reservation_group != 'None':
1983
1984        from msrestazure.tools import is_valid_resource_id, resource_id
1985        from azure.cli.core.commands.client_factory import get_subscription_id
1986        if not is_valid_resource_id(namespace.capacity_reservation_group):
1987            namespace.capacity_reservation_group = resource_id(
1988                subscription=get_subscription_id(cmd.cli_ctx),
1989                resource_group=namespace.resource_group_name,
1990                namespace='Microsoft.Compute',
1991                type='CapacityReservationGroups',
1992                name=namespace.capacity_reservation_group
1993            )
1994