1# -*- coding: utf-8 -*- # 2# Copyright 2020 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Convenience functions for dealing with instances create.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21import ipaddress 22 23from googlecloudsdk.api_lib.compute import alias_ip_range_utils 24from googlecloudsdk.api_lib.compute import constants 25from googlecloudsdk.api_lib.compute import csek_utils 26from googlecloudsdk.api_lib.compute import image_utils 27from googlecloudsdk.api_lib.compute import instance_utils 28from googlecloudsdk.api_lib.compute import kms_utils 29from googlecloudsdk.api_lib.compute import utils 30from googlecloudsdk.command_lib.compute import scope as compute_scopes 31from googlecloudsdk.command_lib.compute.instances import flags as instances_flags 32from googlecloudsdk.core import log 33import six 34 35 36def CheckSpecifiedDiskArgs(args, 37 support_disks=True, 38 skip_defaults=False, 39 support_kms=False, 40 support_nvdimm=False): 41 """Checks if relevant disk arguments have been specified.""" 42 flags_to_check = [ 43 'local_ssd', 44 'boot_disk_type', 45 'boot_disk_device_name', 46 'boot_disk_auto_delete', 47 ] 48 49 if support_disks: 50 flags_to_check.extend([ 51 'disk', 52 'require_csek_key_create', 53 ]) 54 if support_kms: 55 flags_to_check.extend([ 56 'create_disk', 57 'boot_disk_kms_key', 58 'boot_disk_kms_project', 59 'boot_disk_kms_location', 60 'boot_disk_kms_keyring', 61 ]) 62 if support_nvdimm: 63 flags_to_check.extend(['local_nvdimm']) 64 65 if (skip_defaults and 66 not instance_utils.IsAnySpecified(args, *flags_to_check)): 67 return False 68 return True 69 70 71def CreateDiskMessages(args, 72 project, 73 location, 74 scope, 75 compute_client, 76 resource_parser, 77 image_uri, 78 holder=None, 79 boot_disk_size_gb=None, 80 instance_name=None, 81 create_boot_disk=False, 82 csek_keys=None, 83 support_kms=False, 84 support_nvdimm=False, 85 support_disk_resource_policy=False, 86 support_source_snapshot_csek=False, 87 support_boot_snapshot_uri=False, 88 support_image_csek=False, 89 support_match_container_mount_disks=False, 90 support_create_disk_snapshots=False, 91 support_persistent_attached_disks=True, 92 support_replica_zones=False, 93 use_disk_type_uri=True, 94 support_multi_writer=False): 95 """Creates disk messages for a single instance.""" 96 97 container_mount_disk = [] 98 if support_match_container_mount_disks: 99 container_mount_disk = args.container_mount_disk 100 101 persistent_disks = [] 102 if support_persistent_attached_disks: 103 persistent_disks = ( 104 CreatePersistentAttachedDiskMessages( 105 resources=resource_parser, 106 compute_client=compute_client, 107 csek_keys=csek_keys, 108 disks=args.disk or [], 109 project=project, 110 location=location, 111 scope=scope, 112 container_mount_disk=container_mount_disk)) 113 114 persistent_create_disks = ( 115 CreatePersistentCreateDiskMessages( 116 compute_client=compute_client, 117 resources=resource_parser, 118 csek_keys=csek_keys, 119 create_disks=getattr(args, 'create_disk', []), 120 project=project, 121 location=location, 122 scope=scope, 123 holder=holder, 124 enable_kms=support_kms, 125 enable_snapshots=support_create_disk_snapshots, 126 container_mount_disk=container_mount_disk, 127 resource_policy=support_disk_resource_policy, 128 enable_source_snapshot_csek=support_source_snapshot_csek, 129 enable_image_csek=support_image_csek, 130 support_replica_zones=support_replica_zones, 131 use_disk_type_uri=use_disk_type_uri, 132 support_multi_writer=support_multi_writer)) 133 134 local_nvdimms = [] 135 if support_nvdimm: 136 local_nvdimms = CreateLocalNvdimmMessages(args, resource_parser, 137 compute_client.messages, location, 138 scope, project) 139 140 local_ssds = CreateLocalSsdMessages(args, resource_parser, 141 compute_client.messages, location, scope, 142 project, use_disk_type_uri) 143 if create_boot_disk: 144 boot_snapshot_uri = None 145 if support_boot_snapshot_uri: 146 boot_snapshot_uri = instance_utils.ResolveSnapshotURI( 147 user_project=project, 148 snapshot=args.source_snapshot, 149 resource_parser=resource_parser) 150 151 boot_disk = CreateDefaultBootAttachedDiskMessage( 152 compute_client=compute_client, 153 resources=resource_parser, 154 disk_type=args.boot_disk_type, 155 disk_device_name=args.boot_disk_device_name, 156 disk_auto_delete=args.boot_disk_auto_delete, 157 disk_size_gb=boot_disk_size_gb, 158 require_csek_key_create=(args.require_csek_key_create 159 if csek_keys else None), 160 image_uri=image_uri, 161 instance_name=instance_name, 162 project=project, 163 location=location, 164 scope=scope, 165 enable_kms=support_kms, 166 csek_keys=csek_keys, 167 kms_args=args, 168 snapshot_uri=boot_snapshot_uri) 169 persistent_disks = [boot_disk] + persistent_disks 170 171 return persistent_disks + persistent_create_disks + local_nvdimms + local_ssds 172 173 174def CreatePersistentAttachedDiskMessages(resources, 175 compute_client, 176 csek_keys, 177 disks, 178 project, 179 location, 180 scope, 181 container_mount_disk=None): 182 """Returns a list of AttachedDisk messages and the boot disk's reference.""" 183 disks_messages = [] 184 185 messages = compute_client.messages 186 compute = compute_client.apitools_client 187 for disk in disks: 188 name = disk.get('name') 189 190 # Resolves the mode. 191 mode_value = disk.get('mode', 'rw') 192 if mode_value == 'rw': 193 mode = messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE 194 else: 195 mode = messages.AttachedDisk.ModeValueValuesEnum.READ_ONLY 196 197 boot = disk.get('boot') == 'yes' 198 auto_delete = disk.get('auto-delete') == 'yes' 199 200 if 'scope' in disk and disk['scope'] == 'regional': 201 scope = compute_scopes.ScopeEnum.REGION 202 else: 203 scope = compute_scopes.ScopeEnum.ZONE 204 disk_ref = instance_utils.ParseDiskResource(resources, name, project, 205 location, scope) 206 207 # TODO(b/36051031) drop test after CSEK goes GA 208 if csek_keys: 209 disk_key_or_none = csek_utils.MaybeLookupKeyMessage( 210 csek_keys, disk_ref, compute) 211 kwargs = {'diskEncryptionKey': disk_key_or_none} 212 else: 213 kwargs = {} 214 215 device_name = instance_utils.GetDiskDeviceName(disk, name, 216 container_mount_disk) 217 218 attached_disk = messages.AttachedDisk( 219 autoDelete=auto_delete, 220 boot=boot, 221 deviceName=device_name, 222 mode=mode, 223 source=disk_ref.SelfLink(), 224 type=messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT, 225 **kwargs) 226 227 # The boot disk must end up at index 0. 228 if boot: 229 disks_messages = [attached_disk] + disks_messages 230 else: 231 disks_messages.append(attached_disk) 232 233 return disks_messages 234 235 236def CreatePersistentCreateDiskMessages(compute_client, 237 resources, 238 csek_keys, 239 create_disks, 240 project, 241 location, 242 scope, 243 holder, 244 enable_kms=False, 245 enable_snapshots=False, 246 container_mount_disk=None, 247 resource_policy=False, 248 enable_source_snapshot_csek=False, 249 enable_image_csek=False, 250 support_replica_zones=False, 251 use_disk_type_uri=True, 252 support_multi_writer=False): 253 """Returns a list of AttachedDisk messages for newly creating disks. 254 255 Args: 256 compute_client: creates resources, 257 resources: parser of resources, 258 csek_keys: customer suplied encryption keys, 259 create_disks: disk objects - contains following properties * name - the name 260 of disk, * description - an optional description for the disk, * mode - 261 'rw' (R/W), 'ro' (R/O) access mode, * disk-size - the size of the disk, * 262 disk-type - the type of the disk (HDD or SSD), * image - the name of the 263 image to initialize from, * image-csek-required - the name of the CSK 264 protected image, * image-family - the image family name, * image-project - 265 the project name that has the image, * auto-delete - whether disks is 266 deleted when VM is deleted, * device-name - device name on VM, * 267 source-snapshot - the snapshot to initialize from, * 268 source-snapshot-csek-required - CSK protected snapshot, * 269 disk-resource-policy - resource policies applied to disk. * 270 enable_source_snapshot_csek - CSK file for snapshot, * enable_image_csek - 271 CSK file for image 272 project: Project of instance that will own the new disks. 273 location: Location of the instance that will own the new disks. 274 scope: Location type of the instance that will own the new disks. 275 holder: Convenience class to hold lazy initialized client and resources. 276 enable_kms: True if KMS keys are supported for the disk. 277 enable_snapshots: True if snapshot initialization is supported for the disk. 278 container_mount_disk: list of disks to be mounted to container, if any. 279 resource_policy: True if resource-policies are enabled 280 enable_source_snapshot_csek: True if snapshot CSK files are enabled 281 enable_image_csek: True if image CSK files are enabled 282 support_replica_zones: True if we allow creation of regional disks 283 use_disk_type_uri: True to use disk type URI, False if naked type. 284 support_multi_writer: True if we allow multiple instances to write to disk. 285 286 Returns: 287 list of API messages for attached disks 288 """ 289 disks_messages = [] 290 291 messages = compute_client.messages 292 compute = compute_client.apitools_client 293 for disk in create_disks or []: 294 name = disk.get('name') 295 296 # Resolves the mode. 297 mode_value = disk.get('mode', 'rw') 298 if mode_value == 'rw': 299 mode = messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE 300 else: 301 mode = messages.AttachedDisk.ModeValueValuesEnum.READ_ONLY 302 303 auto_delete_value = disk.get('auto-delete', 'yes') 304 auto_delete = auto_delete_value == 'yes' 305 306 disk_size_gb = utils.BytesToGb(disk.get('size')) 307 disk_type = disk.get('type') 308 if disk_type: 309 if use_disk_type_uri: 310 disk_type_ref = instance_utils.ParseDiskType(resources, disk_type, 311 project, location, scope) 312 disk_type = disk_type_ref.SelfLink() 313 else: 314 disk_type = None 315 316 img = disk.get('image') 317 img_family = disk.get('image-family') 318 img_project = disk.get('image-project') 319 320 image_uri = None 321 if img or img_family: 322 image_expander = image_utils.ImageExpander(compute_client, resources) 323 image_uri, _ = image_expander.ExpandImageFlag( 324 user_project=project, 325 image=img, 326 image_family=img_family, 327 image_project=img_project, 328 return_image_resource=False) 329 330 image_key = None 331 disk_key = None 332 if csek_keys: 333 image_key = csek_utils.MaybeLookupKeyMessagesByUri( 334 csek_keys, resources, [image_uri], compute) 335 if name: 336 disk_ref = resources.Parse( 337 name, collection='compute.disks', params={'zone': location}) 338 disk_key = csek_utils.MaybeLookupKeyMessage(csek_keys, disk_ref, 339 compute) 340 341 if enable_kms: 342 disk_key = kms_utils.MaybeGetKmsKeyFromDict(disk, messages, disk_key) 343 344 initialize_params = messages.AttachedDiskInitializeParams( 345 diskName=name, 346 description=disk.get('description'), 347 sourceImage=image_uri, 348 diskSizeGb=disk_size_gb, 349 diskType=disk_type, 350 sourceImageEncryptionKey=image_key) 351 352 replica_zones = disk.get('replica-zones') 353 if support_replica_zones and replica_zones: 354 normalized_zones = [] 355 for zone in replica_zones: 356 zone_ref = holder.resources.Parse( 357 zone, collection='compute.zones', params={'project': project}) 358 normalized_zones.append(zone_ref.SelfLink()) 359 initialize_params.replicaZones = normalized_zones 360 361 if enable_snapshots: 362 snapshot_name = disk.get('source-snapshot') 363 attached_snapshot_uri = instance_utils.ResolveSnapshotURI( 364 snapshot=snapshot_name, 365 user_project=project, 366 resource_parser=resources) 367 if attached_snapshot_uri: 368 initialize_params.sourceImage = None 369 initialize_params.sourceSnapshot = attached_snapshot_uri 370 371 if resource_policy: 372 policies = disk.get('disk-resource-policy') 373 if policies: 374 initialize_params.resourcePolicies = policies 375 376 if enable_image_csek: 377 image_key_file = disk.get('image_csek') 378 if image_key_file: 379 initialize_params.imageKeyFile = image_key_file 380 381 if enable_source_snapshot_csek: 382 snapshot_key_file = disk.get('source_snapshot_csek') 383 if snapshot_key_file: 384 initialize_params.snapshotKeyFile = snapshot_key_file 385 boot = disk.get('boot') == 'yes' 386 387 multi_writer = disk.get('multi-writer') 388 if support_multi_writer and multi_writer: 389 initialize_params.multiWriter = True 390 391 device_name = instance_utils.GetDiskDeviceName(disk, name, 392 container_mount_disk) 393 create_disk = messages.AttachedDisk( 394 autoDelete=auto_delete, 395 boot=boot, 396 deviceName=device_name, 397 initializeParams=initialize_params, 398 mode=mode, 399 type=messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT, 400 diskEncryptionKey=disk_key) 401 disks_messages.append(create_disk) 402 403 return disks_messages 404 405 406def CreateDefaultBootAttachedDiskMessage(compute_client, 407 resources, 408 disk_type, 409 disk_device_name, 410 disk_auto_delete, 411 disk_size_gb, 412 require_csek_key_create, 413 image_uri, 414 instance_name, 415 project, 416 location, 417 scope, 418 csek_keys=None, 419 kms_args=None, 420 enable_kms=False, 421 snapshot_uri=None, 422 use_disk_type_uri=True): 423 """Returns an AttachedDisk message for creating a new boot disk.""" 424 messages = compute_client.messages 425 compute = compute_client.apitools_client 426 427 if disk_type and use_disk_type_uri: 428 disk_type_ref = instance_utils.ParseDiskType(resources, disk_type, project, 429 location, scope) 430 disk_type_uri = disk_type_ref.SelfLink() 431 else: 432 disk_type_uri = None 433 434 if csek_keys: 435 # If we're going to encrypt the boot disk make sure that we select 436 # a name predictably, instead of letting the API deal with name 437 # conflicts automatically. 438 # 439 # Note that when csek keys are being used we *always* want force this 440 # even if we don't have any encryption key for default disk name. 441 # 442 # Consider the case where the user's key file has a key for disk `foo-1` 443 # and no other disk. Assume she runs 444 # gcloud compute instances create foo --csek-key-file f \ 445 # --no-require-csek-key-create 446 # and gcloud doesn't force the disk name to be `foo`. The API might 447 # select name `foo-1` for the new disk, but has no way of knowing 448 # that the user has a key file mapping for that disk name. That 449 # behavior violates the principle of least surprise. 450 # 451 # Instead it's better for gcloud to force a specific disk name in the 452 # instance create, and fail if that name isn't available. 453 454 effective_boot_disk_name = (disk_device_name or instance_name) 455 456 disk_ref = resources.Parse( 457 effective_boot_disk_name, 458 collection='compute.disks', 459 params={ 460 'project': project, 461 'zone': location 462 }) 463 disk_key_or_none = csek_utils.MaybeToMessage( 464 csek_keys.LookupKey(disk_ref, require_csek_key_create), compute) 465 [image_key_or_none 466 ] = csek_utils.MaybeLookupKeyMessagesByUri(csek_keys, resources, 467 [image_uri], compute) 468 kwargs_init_parms = {'sourceImageEncryptionKey': image_key_or_none} 469 kwargs_disk = {'diskEncryptionKey': disk_key_or_none} 470 else: 471 kwargs_disk = {} 472 kwargs_init_parms = {} 473 effective_boot_disk_name = disk_device_name 474 475 if enable_kms: 476 kms_key = kms_utils.MaybeGetKmsKey( 477 kms_args, 478 messages, 479 kwargs_disk.get('diskEncryptionKey', None), 480 boot_disk_prefix=True) 481 if kms_key: 482 kwargs_disk = {'diskEncryptionKey': kms_key} 483 484 initialize_params = messages.AttachedDiskInitializeParams( 485 sourceImage=image_uri, 486 diskSizeGb=disk_size_gb, 487 diskType=disk_type_uri, 488 **kwargs_init_parms) 489 490 if snapshot_uri: 491 initialize_params.sourceImage = None 492 initialize_params.sourceSnapshot = snapshot_uri 493 494 return messages.AttachedDisk( 495 autoDelete=disk_auto_delete, 496 boot=True, 497 deviceName=effective_boot_disk_name, 498 initializeParams=initialize_params, 499 mode=messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE, 500 type=messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT, 501 **kwargs_disk) 502 503 504# TODO(b/116515070) Replace `aep-nvdimm` with `local-nvdimm` 505NVDIMM_DISK_TYPE = 'aep-nvdimm' 506 507 508def CreateLocalNvdimmMessages(args, 509 resources, 510 messages, 511 location=None, 512 scope=None, 513 project=None): 514 """Create messages representing local NVDIMMs.""" 515 local_nvdimms = [] 516 for local_nvdimm_disk in getattr(args, 'local_nvdimm', []) or []: 517 local_nvdimm = _CreateLocalNvdimmMessage(resources, messages, 518 local_nvdimm_disk.get('size'), 519 location, scope, project) 520 local_nvdimms.append(local_nvdimm) 521 return local_nvdimms 522 523 524def _CreateLocalNvdimmMessage(resources, 525 messages, 526 size_bytes=None, 527 location=None, 528 scope=None, 529 project=None): 530 """Create a message representing a local NVDIMM.""" 531 532 if location: 533 disk_type_ref = instance_utils.ParseDiskType(resources, NVDIMM_DISK_TYPE, 534 project, location, scope) 535 disk_type = disk_type_ref.SelfLink() 536 else: 537 disk_type = NVDIMM_DISK_TYPE 538 539 local_nvdimm = messages.AttachedDisk( 540 type=messages.AttachedDisk.TypeValueValuesEnum.SCRATCH, 541 autoDelete=True, 542 interface=messages.AttachedDisk.InterfaceValueValuesEnum.NVDIMM, 543 mode=messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE, 544 initializeParams=messages.AttachedDiskInitializeParams( 545 diskType=disk_type), 546 ) 547 548 if size_bytes is not None: 549 local_nvdimm.diskSizeGb = utils.BytesToGb(size_bytes) 550 551 return local_nvdimm 552 553 554def CreateLocalSsdMessages(args, 555 resources, 556 messages, 557 location=None, 558 scope=None, 559 project=None, 560 use_disk_type_uri=True): 561 """Create messages representing local ssds.""" 562 local_ssds = [] 563 for local_ssd_disk in getattr(args, 'local_ssd', []) or []: 564 local_ssd = _CreateLocalSsdMessage(resources, messages, 565 local_ssd_disk.get('device-name'), 566 local_ssd_disk.get('interface'), 567 local_ssd_disk.get('size'), location, 568 scope, project, use_disk_type_uri) 569 local_ssds.append(local_ssd) 570 return local_ssds 571 572 573def _CreateLocalSsdMessage(resources, 574 messages, 575 device_name, 576 interface, 577 size_bytes=None, 578 location=None, 579 scope=None, 580 project=None, 581 use_disk_type_uri=True): 582 """Create a message representing a local ssd.""" 583 584 if location and use_disk_type_uri: 585 disk_type_ref = instance_utils.ParseDiskType(resources, 'local-ssd', 586 project, location, scope) 587 disk_type = disk_type_ref.SelfLink() 588 else: 589 disk_type = 'local-ssd' 590 591 maybe_interface_enum = ( 592 messages.AttachedDisk.InterfaceValueValuesEnum(interface) 593 if interface else None) 594 595 local_ssd = messages.AttachedDisk( 596 type=messages.AttachedDisk.TypeValueValuesEnum.SCRATCH, 597 autoDelete=True, 598 deviceName=device_name, 599 interface=maybe_interface_enum, 600 mode=messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE, 601 initializeParams=messages.AttachedDiskInitializeParams( 602 diskType=disk_type), 603 ) 604 605 if size_bytes is not None: 606 local_ssd.diskSizeGb = utils.BytesToGb(size_bytes) 607 608 return local_ssd 609 610 611def GetBulkNetworkInterfaces(args, resource_parser, compute_client, holder, 612 project, location, scope, skip_defaults): 613 if (skip_defaults and not instance_utils.IsAnySpecified( 614 args, 'network_interface', 'network', 'network_tier', 'subnet', 615 'no_address')): 616 return [] 617 elif args.network_interface: 618 return CreateNetworkInterfaceMessages( 619 resources=resource_parser, 620 compute_client=compute_client, 621 network_interface_arg=args.network_interface, 622 project=project, 623 location=location, 624 scope=scope) 625 else: 626 return [ 627 CreateNetworkInterfaceMessage( 628 resources=holder.resources, 629 compute_client=compute_client, 630 network=args.network, 631 subnet=args.subnet, 632 no_address=args.no_address, 633 project=project, 634 location=location, 635 scope=scope, 636 network_tier=getattr(args, 'network_tier', None), 637 ) 638 ] 639 640 641def GetNetworkInterfaces(args, client, holder, project, location, scope, 642 skip_defaults): 643 """Get network interfaces.""" 644 if (skip_defaults and not args.IsSpecified('network') and 645 not instance_utils.IsAnySpecified( 646 args, 647 'address', 648 'network_tier', 649 'no_address', 650 'no_public_ptr', 651 'no_public_ptr_domain', 652 'private_network_ip', 653 'public_ptr', 654 'public_ptr_domain', 655 'subnet', 656 )): 657 return [] 658 return [ 659 CreateNetworkInterfaceMessage( 660 resources=holder.resources, 661 compute_client=client, 662 network=args.network, 663 subnet=args.subnet, 664 no_address=args.no_address, 665 address=args.address, 666 project=project, 667 location=location, 668 scope=scope, 669 no_public_ptr=args.no_public_ptr, 670 public_ptr=args.public_ptr, 671 no_public_ptr_domain=args.no_public_ptr_domain, 672 public_ptr_domain=args.public_ptr_domain, 673 private_network_ip=getattr(args, 'private_network_ip', None), 674 network_tier=getattr(args, 'network_tier', None), 675 ipv6_public_ptr_domain=getattr(args, 'ipv6_public_ptr_domain', None), 676 ) 677 ] 678 679 680def GetNetworkInterfacesAlpha(args, client, holder, project, location, scope, 681 skip_defaults): 682 if (skip_defaults and not instance_utils.IsAnySpecified( 683 args, 'network', 'subnet', 'private_network_ip', 'no_address', 'address', 684 'network_tier', 'no_public_dns', 'public_dns', 'no_public_ptr', 685 'public_ptr', 'no_public_ptr_domain', 'public_ptr_domain', 'stack_type', 686 'ipv6_network_tier')): 687 return [] 688 return [ 689 CreateNetworkInterfaceMessage( 690 resources=holder.resources, 691 compute_client=client, 692 network=args.network, 693 subnet=args.subnet, 694 no_address=args.no_address, 695 address=args.address, 696 project=project, 697 location=location, 698 scope=scope, 699 private_network_ip=getattr(args, 'private_network_ip', None), 700 network_tier=getattr(args, 'network_tier', None), 701 no_public_dns=getattr(args, 'no_public_dns', None), 702 public_dns=getattr(args, 'public_dns', None), 703 no_public_ptr=getattr(args, 'no_public_ptr', None), 704 public_ptr=getattr(args, 'public_ptr', None), 705 no_public_ptr_domain=getattr(args, 'no_public_ptr_domain', None), 706 public_ptr_domain=getattr(args, 'public_ptr_domain', None), 707 stack_type=getattr(args, 'stack_type', None), 708 ipv6_network_tier=getattr(args, 'ipv6_network_tier', None), 709 ipv6_public_ptr_domain=getattr(args, 'ipv6_public_ptr_domain', None)) 710 ] 711 712 713def CreateNetworkInterfaceMessage(resources, 714 compute_client, 715 network, 716 subnet, 717 project, 718 location, 719 scope, 720 nic_type=None, 721 no_address=None, 722 address=None, 723 private_network_ip=None, 724 alias_ip_ranges_string=None, 725 network_tier=None, 726 no_public_dns=None, 727 public_dns=None, 728 no_public_ptr=None, 729 public_ptr=None, 730 no_public_ptr_domain=None, 731 public_ptr_domain=None, 732 stack_type=None, 733 ipv6_network_tier=None, 734 ipv6_public_ptr_domain=None): 735 """Returns a new NetworkInterface message.""" 736 # TODO(b/30460572): instance reference should have zone name, not zone URI. 737 if scope == compute_scopes.ScopeEnum.ZONE: 738 region = utils.ZoneNameToRegionName(location.split('/')[-1]) 739 elif scope == compute_scopes.ScopeEnum.REGION: 740 region = location 741 messages = compute_client.messages 742 network_interface = messages.NetworkInterface() 743 # By default interface is attached to default network. If network or subnet 744 # are specified they're used instead. 745 if subnet is not None: 746 subnet_ref = resources.Parse( 747 subnet, 748 collection='compute.subnetworks', 749 params={ 750 'project': project, 751 'region': region 752 }) 753 network_interface.subnetwork = subnet_ref.SelfLink() 754 if network is not None: 755 network_ref = resources.Parse( 756 network, params={ 757 'project': project, 758 }, collection='compute.networks') 759 network_interface.network = network_ref.SelfLink() 760 elif subnet is None: 761 network_ref = resources.Parse( 762 constants.DEFAULT_NETWORK, 763 params={'project': project}, 764 collection='compute.networks') 765 network_interface.network = network_ref.SelfLink() 766 767 if private_network_ip is not None: 768 # Try interpreting the address as IPv4 or IPv6. 769 try: 770 # ipaddress only allows unicode input 771 ipaddress.ip_address(six.text_type(private_network_ip)) 772 network_interface.networkIP = private_network_ip 773 except ValueError: 774 # ipaddress could not resolve as an IPv4 or IPv6 address. 775 network_interface.networkIP = instances_flags.GetAddressRef( 776 resources, private_network_ip, region).SelfLink() 777 778 if nic_type is not None: 779 network_interface.nicType = ( 780 messages.NetworkInterface.NicTypeValueValuesEnum(nic_type)) 781 782 if alias_ip_ranges_string: 783 network_interface.aliasIpRanges = ( 784 alias_ip_range_utils.CreateAliasIpRangeMessagesFromString( 785 messages, True, alias_ip_ranges_string)) 786 787 if stack_type is not None: 788 network_interface.stackType = messages.NetworkInterface.StackTypeValueValuesEnum( 789 stack_type) 790 791 if not no_address: 792 access_config = messages.AccessConfig( 793 name=constants.DEFAULT_ACCESS_CONFIG_NAME, 794 type=messages.AccessConfig.TypeValueValuesEnum.ONE_TO_ONE_NAT) 795 if network_tier is not None: 796 access_config.networkTier = ( 797 messages.AccessConfig.NetworkTierValueValuesEnum(network_tier)) 798 799 # If the user provided an external IP, populate the access 800 # config with it. 801 address_resource = instances_flags.ExpandAddressFlag( 802 resources, compute_client, address, region) 803 if address_resource: 804 access_config.natIP = address_resource 805 806 if no_public_dns: 807 access_config.setPublicDns = False 808 elif public_dns: 809 access_config.setPublicDns = True 810 811 if no_public_ptr: 812 access_config.setPublicPtr = False 813 elif public_ptr: 814 access_config.setPublicPtr = True 815 816 if not no_public_ptr_domain and public_ptr_domain is not None: 817 access_config.publicPtrDomainName = public_ptr_domain 818 819 network_interface.accessConfigs = [access_config] 820 821 if ipv6_network_tier is not None or ipv6_public_ptr_domain is not None: 822 ipv6_access_config = messages.AccessConfig( 823 name=constants.DEFAULT_IPV6_ACCESS_CONFIG_NAME, 824 type=messages.AccessConfig.TypeValueValuesEnum.DIRECT_IPV6) 825 network_interface.ipv6AccessConfigs = [ipv6_access_config] 826 827 if ipv6_network_tier is not None: 828 ipv6_access_config.networkTier = ( 829 messages.AccessConfig.NetworkTierValueValuesEnum(ipv6_network_tier)) 830 831 if ipv6_public_ptr_domain is not None: 832 ipv6_access_config.publicPtrDomainName = ipv6_public_ptr_domain 833 834 return network_interface 835 836 837def CreateNetworkInterfaceMessages(resources, compute_client, 838 network_interface_arg, project, location, 839 scope): 840 """Create network interface messages. 841 842 Args: 843 resources: generates resource references. 844 compute_client: creates resources. 845 network_interface_arg: CLI argument specyfying network interfaces. 846 project: project of the instance that will own the generated network 847 interfaces. 848 zone: zone of the instance that will own the generated network interfaces. 849 850 Returns: 851 list, items are NetworkInterfaceMessages. 852 """ 853 result = [] 854 if network_interface_arg: 855 for interface in network_interface_arg: 856 address = interface.get('address', None) 857 no_address = 'no-address' in interface 858 network_tier = interface.get('network-tier', None) 859 860 result.append( 861 CreateNetworkInterfaceMessage( 862 resources=resources, 863 compute_client=compute_client, 864 network=interface.get('network', None), 865 subnet=interface.get('subnet', None), 866 private_network_ip=interface.get('private-network-ip', None), 867 nic_type=interface.get('nic-type', None), 868 no_address=no_address, 869 address=address, 870 project=project, 871 location=location, 872 scope=scope, 873 alias_ip_ranges_string=interface.get('aliases', None), 874 network_tier=network_tier)) 875 return result 876 877 878def GetNetworkInterfacesWithValidation(args, 879 resource_parser, 880 compute_client, 881 holder, 882 project, 883 location, 884 scope, 885 skip_defaults, 886 support_public_dns=False, 887 support_stack_type=False, 888 support_ipv6_network_tier=False, 889 support_ipv6_public_ptr_domain=False): 890 """Validates and retrieves the network interface message.""" 891 if args.network_interface: 892 return CreateNetworkInterfaceMessages( 893 resources=resource_parser, 894 compute_client=compute_client, 895 network_interface_arg=args.network_interface, 896 project=project, 897 location=location, 898 scope=scope) 899 else: 900 instances_flags.ValidatePublicPtrFlags(args) 901 if (support_public_dns or support_stack_type or support_ipv6_network_tier or 902 support_ipv6_public_ptr_domain): 903 if support_public_dns: 904 instances_flags.ValidatePublicDnsFlags(args) 905 return GetNetworkInterfacesAlpha(args, compute_client, holder, project, 906 location, scope, skip_defaults) 907 return GetNetworkInterfaces(args, compute_client, holder, project, location, 908 scope, skip_defaults) 909 910 911def GetProjectToServiceAccountMap(args, instance_refs, client, skip_defaults): 912 """Creates a mapping of projects to service accounts.""" 913 project_to_sa = {} 914 for instance_ref in instance_refs: 915 if instance_ref.project not in project_to_sa: 916 project_to_sa[instance_ref.project] = GetProjectServiceAccount( 917 args=args, 918 project=instance_ref.project, 919 client=client, 920 skip_defaults=skip_defaults, 921 instance_name=instance_ref.Name()) 922 return project_to_sa 923 924 925def GetProjectServiceAccount(args, 926 project, 927 client, 928 skip_defaults, 929 instance_name=None): 930 """Retrieves service accounts for the specified project.""" 931 scopes = None 932 if not args.no_scopes and not args.scopes: 933 # User didn't provide any input on scopes. If project has no default 934 # service account then we want to create a VM with no scopes 935 request = (client.apitools_client.projects, 'Get', 936 client.messages.ComputeProjectsGetRequest(project=project)) 937 errors = [] 938 result = client.MakeRequests([request], errors) 939 if not errors: 940 if not result[0].defaultServiceAccount: 941 scopes = [] 942 scope_warning = 'There is no default service account for project {}.'.format( 943 project) 944 if instance_name: 945 scope_warning += ' Instance {} will not have scopes.'.format( 946 instance_name) 947 log.status.Print(scope_warning) 948 if scopes is None: 949 scopes = [] if args.no_scopes else args.scopes 950 951 if args.no_service_account: 952 service_account = None 953 else: 954 service_account = args.service_account 955 if (skip_defaults and not args.IsSpecified('scopes') and 956 not args.IsSpecified('no_scopes') and 957 not args.IsSpecified('service_account') and 958 not args.IsSpecified('no_service_account')): 959 service_accounts = [] 960 else: 961 service_accounts = instance_utils.CreateServiceAccountMessages( 962 messages=client.messages, 963 scopes=scopes, 964 service_account=service_account) 965 return service_accounts 966 967 968def BuildShieldedInstanceConfigMessage(messages, args): 969 """Builds a shielded instance configuration message.""" 970 if (args.IsSpecified('shielded_vm_secure_boot') or 971 args.IsSpecified('shielded_vm_vtpm') or 972 args.IsSpecified('shielded_vm_integrity_monitoring')): 973 return instance_utils.CreateShieldedInstanceConfigMessage( 974 messages, args.shielded_vm_secure_boot, args.shielded_vm_vtpm, 975 args.shielded_vm_integrity_monitoring) 976 else: 977 return None 978 979 980def BuildConfidentialInstanceConfigMessage(messages, args): 981 """Builds a confidential instance configuration message.""" 982 if args.IsSpecified('confidential_compute'): 983 return instance_utils.CreateConfidentialInstanceMessage( 984 messages, args.confidential_compute) 985 else: 986 return None 987 988 989def GetImageUri(args, 990 client, 991 create_boot_disk, 992 project, 993 resource_parser, 994 confidential_vm=False): 995 """Retrieves the image uri for the specified image.""" 996 if create_boot_disk: 997 image_expander = image_utils.ImageExpander(client, resource_parser) 998 image_uri, _ = image_expander.ExpandImageFlag( 999 user_project=project, 1000 image=args.image, 1001 image_family=args.image_family, 1002 image_project=args.image_project, 1003 return_image_resource=False, 1004 confidential_vm=confidential_vm) 1005 return image_uri 1006 1007 1008def GetAccelerators(args, compute_client, resource_parser, project, location, 1009 scope): 1010 """Returns list of messages with accelerators for the instance.""" 1011 if args.accelerator: 1012 accelerator_type_name = args.accelerator['type'] 1013 accelerator_type = instance_utils.ParseAcceleratorType( 1014 accelerator_type_name, resource_parser, project, location, scope) 1015 # Accelerator count is default to 1. 1016 accelerator_count = int(args.accelerator.get('count', 1)) 1017 return CreateAcceleratorConfigMessages(compute_client.messages, 1018 accelerator_type, accelerator_count) 1019 return [] 1020 1021 1022def GetAcceleratorsForInstanceProperties(args, compute_client): 1023 if args.accelerator: 1024 accelerator_type = args.accelerator['type'] 1025 accelerator_count = int(args.accelerator.get('count', 1)) 1026 return CreateAcceleratorConfigMessages(compute_client.messages, 1027 accelerator_type, accelerator_count) 1028 return [] 1029 1030 1031def CreateAcceleratorConfigMessages(msgs, accelerator_type, accelerator_count): 1032 """Returns a list of accelerator config messages. 1033 1034 Args: 1035 msgs: tracked GCE API messages. 1036 accelerator_type: reference to the accelerator type. 1037 accelerator_count: number of accelerators to attach to the VM. 1038 1039 Returns: 1040 a list of accelerator config message that specifies the type and number of 1041 accelerators to attach to an instance. 1042 """ 1043 1044 accelerator_config = msgs.AcceleratorConfig( 1045 acceleratorType=accelerator_type, acceleratorCount=accelerator_count) 1046 return [accelerator_config] 1047 1048 1049def CreateMachineTypeUri(args, 1050 compute_client, 1051 resource_parser, 1052 project, 1053 location, 1054 scope, 1055 confidential_vm=False): 1056 """Create a machine type URI for given args and instance reference.""" 1057 1058 machine_type = args.machine_type 1059 custom_cpu = args.custom_cpu 1060 custom_memory = args.custom_memory 1061 vm_type = getattr(args, 'custom_vm_type', None) 1062 ext = getattr(args, 'custom_extensions', None) 1063 1064 # Setting the machine type 1065 machine_type_name = instance_utils.InterpretMachineType( 1066 machine_type=machine_type, 1067 custom_cpu=custom_cpu, 1068 custom_memory=custom_memory, 1069 ext=ext, 1070 vm_type=vm_type, 1071 confidential_vm=confidential_vm) 1072 1073 # Check to see if the custom machine type ratio is supported 1074 instance_utils.CheckCustomCpuRamRatio(compute_client, project, location, 1075 machine_type_name) 1076 1077 machine_type_uri = instance_utils.ParseMachineType(resource_parser, 1078 machine_type_name, project, 1079 location, scope) 1080 return machine_type_uri 1081