1# -*- coding: utf-8 -*- # 2# Copyright 2016 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"""Flags and helpers for the compute VM instances commands.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21import copy 22import functools 23import ipaddress 24 25from googlecloudsdk.api_lib.compute import constants 26from googlecloudsdk.api_lib.compute import containers_utils 27from googlecloudsdk.api_lib.compute import csek_utils 28from googlecloudsdk.api_lib.compute import image_utils 29from googlecloudsdk.api_lib.compute import kms_utils 30from googlecloudsdk.api_lib.compute import utils 31from googlecloudsdk.api_lib.compute.zones import service as zones_service 32from googlecloudsdk.api_lib.util import apis 33from googlecloudsdk.calliope import actions 34from googlecloudsdk.calliope import arg_parsers 35from googlecloudsdk.calliope import base 36from googlecloudsdk.calliope import exceptions 37from googlecloudsdk.command_lib.compute import completers as compute_completers 38from googlecloudsdk.command_lib.compute import flags as compute_flags 39from googlecloudsdk.command_lib.compute import scope as compute_scope 40from googlecloudsdk.command_lib.compute.kms import resource_args as kms_resource_args 41from googlecloudsdk.command_lib.util.apis import arg_utils 42from googlecloudsdk.core import log 43from googlecloudsdk.core import properties 44from googlecloudsdk.core import resources as core_resources 45 46import six 47 48ZONE_PROPERTY_EXPLANATION = """\ 49If not specified, you may be prompted to select a zone (interactive mode 50only). `gcloud` attempts to identify the appropriate zone by searching for 51resources in your currently active project. If the zone cannot be determined, 52`gcloud` prompts you for a selection with all available Google Cloud Platform 53zones. 54 55To avoid prompting when this flag is omitted, the user can set the 56``compute/zone'' property: 57 58 $ gcloud config set compute/zone ZONE 59 60A list of zones can be fetched by running: 61 62 $ gcloud compute zones list 63 64To unset the property, run: 65 66 $ gcloud config unset compute/zone 67 68Alternatively, the zone can be stored in the environment variable 69``CLOUDSDK_COMPUTE_ZONE''. 70""" 71 72MIGRATION_OPTIONS = { 73 'MIGRATE': ( 74 'The instances should be migrated to a new host. This will temporarily ' 75 'impact the performance of instances during a migration event.'), 76 'TERMINATE': 'The instances should be terminated.', 77} 78 79LOCAL_SSD_INTERFACES = ['NVME', 'SCSI'] 80 81DISK_METAVAR = ( 82 'name=NAME [mode={ro,rw}] [boot={yes,no}] [device-name=DEVICE_NAME] ' 83 '[auto-delete={yes,no}]') 84 85DISK_METAVAR_ZONAL_OR_REGIONAL = ( 86 'name=NAME [mode={ro,rw}] [boot={yes,no}] [device-name=DEVICE_NAME] ' 87 '[auto-delete={yes,no}] [scope={zonal,regional}]') 88 89DEFAULT_LIST_FORMAT = """\ 90 table( 91 name, 92 zone.basename(), 93 machineType.machine_type().basename(), 94 scheduling.preemptible.yesno(yes=true, no=''), 95 networkInterfaces[].networkIP.notnull().list():label=INTERNAL_IP, 96 networkInterfaces[].accessConfigs[0].natIP.notnull().list()\ 97 :label=EXTERNAL_IP, 98 status 99 )""" 100 101INSTANCE_ARG = compute_flags.ResourceArgument( 102 resource_name='instance', 103 name='instance_name', 104 completer=compute_completers.InstancesCompleter, 105 zonal_collection='compute.instances', 106 zone_explanation=ZONE_PROPERTY_EXPLANATION) 107 108INSTANCE_ARG_NOT_REQUIRED = compute_flags.ResourceArgument( 109 resource_name='instance', 110 name='instance_name', 111 required=False, 112 completer=compute_completers.InstancesCompleter, 113 zonal_collection='compute.instances', 114 zone_explanation=ZONE_PROPERTY_EXPLANATION) 115 116INSTANCES_ARG = compute_flags.ResourceArgument( 117 resource_name='instance', 118 name='instance_names', 119 completer=compute_completers.InstancesCompleter, 120 zonal_collection='compute.instances', 121 zone_explanation=ZONE_PROPERTY_EXPLANATION, 122 plural=True) 123 124INSTANCES_ARG_FOR_CREATE = compute_flags.ResourceArgument( 125 resource_name='instance', 126 name='instance_names', 127 completer=compute_completers.InstancesCompleter, 128 zonal_collection='compute.instances', 129 zone_explanation=compute_flags.ZONE_PROPERTY_EXPLANATION, 130 plural=True) 131 132INSTANCES_ARG_FOR_IMPORT = compute_flags.ResourceArgument( 133 resource_name='instance', 134 name='instance_name', 135 completer=compute_completers.InstancesCompleter, 136 zonal_collection='compute.instances', 137 zone_explanation=compute_flags.ZONE_PROPERTY_EXPLANATION, 138 plural=False) 139 140SSH_INSTANCE_RESOLVER = compute_flags.ResourceResolver.FromMap( 141 'instance', {compute_scope.ScopeEnum.ZONE: 'compute.instances'}) 142 143 144def GetInstanceZoneScopeLister(compute_client): 145 return functools.partial(InstanceZoneScopeLister, compute_client) 146 147 148def InstanceZoneScopeLister(compute_client, _, underspecified_names): 149 """Scope lister for zones of underspecified instances.""" 150 messages = compute_client.messages 151 instance_name = underspecified_names[0] 152 project = properties.VALUES.core.project.Get(required=True) 153 # TODO(b/33813901): look in cache if possible 154 request = (compute_client.apitools_client.instances, 'AggregatedList', 155 messages.ComputeInstancesAggregatedListRequest( 156 filter='name eq ^{0}$'.format(instance_name), 157 project=project, 158 maxResults=constants.MAX_RESULTS_PER_PAGE)) 159 errors = [] 160 matching_instances = compute_client.MakeRequests([request], 161 errors_to_collect=errors) 162 zones = [] 163 if errors: 164 # Fall back to displaying all possible zones if can't resolve 165 log.debug('Errors fetching filtered aggregate list:\n{}'.format(errors)) 166 log.status.Print( 167 'Error fetching possible zones for instance: [{0}].'.format( 168 ', '.join(underspecified_names))) 169 zones = zones_service.List(compute_client, project) 170 elif not matching_instances: 171 # Fall back to displaying all possible zones if can't resolve 172 log.debug('Errors fetching filtered aggregate list:\n{}'.format(errors)) 173 log.status.Print( 174 'Unable to find an instance with name [{0}].'.format(instance_name)) 175 zones = zones_service.List(compute_client, project) 176 else: 177 for i in matching_instances: 178 zone = core_resources.REGISTRY.Parse( 179 i.zone, collection='compute.zones', params={'project': project}) 180 zones.append(messages.Zone(name=zone.Name())) 181 return {compute_scope.ScopeEnum.ZONE: zones} 182 183 184def InstanceArgumentForRoute(required=True): 185 return compute_flags.ResourceArgument( 186 resource_name='instance', 187 name='--next-hop-instance', 188 completer=compute_completers.InstancesCompleter, 189 required=required, 190 zonal_collection='compute.instances', 191 zone_explanation=ZONE_PROPERTY_EXPLANATION) 192 193 194def InstanceArgumentForRouter(required=False, operation_type='added'): 195 return compute_flags.ResourceArgument( 196 resource_name='instance', 197 name='--instance', 198 completer=compute_completers.InstancesCompleter, 199 required=required, 200 zonal_collection='compute.instances', 201 short_help='Router appliance instance of the BGP peer being {0}.'.format( 202 operation_type), 203 zone_explanation=ZONE_PROPERTY_EXPLANATION) 204 205 206def InstanceArgumentForTargetInstance(required=True): 207 return compute_flags.ResourceArgument( 208 resource_name='instance', 209 name='--instance', 210 completer=compute_completers.InstancesCompleter, 211 required=required, 212 zonal_collection='compute.instances', 213 short_help=('The name of the virtual machine instance that will handle ' 214 'the traffic.'), 215 zone_explanation=( 216 'If not specified, it will be set to the same as zone.')) 217 218 219def InstanceArgumentForTargetPool(action, required=True): 220 return compute_flags.ResourceArgument( 221 resource_name='instance', 222 name='--instances', 223 completer=compute_completers.InstancesCompleter, 224 required=required, 225 zonal_collection='compute.instances', 226 short_help=('Specifies a list of instances to {0} the target pool.' 227 .format(action)), 228 plural=True, 229 zone_explanation=compute_flags.ZONE_PROPERTY_EXPLANATION) 230 231 232def MakeSourceInstanceTemplateArg(): 233 return compute_flags.ResourceArgument( 234 name='--source-instance-template', 235 resource_name='instance template', 236 completer=compute_completers.InstanceTemplatesCompleter, 237 required=False, 238 global_collection='compute.instanceTemplates', 239 short_help=('The name of the instance template that the instance will ' 240 'be created from.\n\nUsers can also override machine ' 241 'type and labels. Values of other flags will be ignored and ' 242 '`--source-instance-template` will be used instead.')) 243 244 245def MakeBulkSourceInstanceTemplateArg(): 246 return compute_flags.ResourceArgument( 247 name='--source-instance-template', 248 resource_name='instance template', 249 completer=compute_completers.InstanceTemplatesCompleter, 250 required=False, 251 global_collection='compute.instanceTemplates', 252 short_help=('The name of the instance template that the instance will ' 253 'be created from. Users can override fields by specifying ' 254 'other flags.')) 255 256 257def AddMachineImageArg(): 258 return compute_flags.ResourceArgument( 259 name='--source-machine-image', 260 resource_name='machine image', 261 completer=compute_completers.MachineImagesCompleter, 262 required=False, 263 global_collection='compute.machineImages', 264 short_help=('The name of the machine image that the instance will ' 265 'be created from.')) 266 267 268def AddSourceMachineImageEncryptionKey(parser): 269 parser.add_argument( 270 '--source-machine-image-csek-key-file', 271 metavar='FILE', 272 help="""\ 273 Path to a Customer-Supplied Encryption Key (CSEK) key file, mapping resources to user managed keys which were used to encrypt the source machine-image. 274 See {csek_help} for more details. 275 """.format(csek_help=csek_utils.CSEK_HELP_URL)) 276 277 278def AddImageArgs(parser, enable_snapshots=False): 279 """Adds arguments related to images for instances and instance-templates.""" 280 281 def AddImageHelp(): 282 """Returns the detailed help for the `--image` flag.""" 283 return """ 284 Specifies the boot image for the instances. For each 285 instance, a new boot disk will be created from the given 286 image. Each boot disk will have the same name as the 287 instance. To view a list of public images and projects, run 288 `$ gcloud compute images list`. It is best practice to use `--image` 289 when a specific version of an image is needed. 290 291 When using this option, ``--boot-disk-device-name'' and 292 ``--boot-disk-size'' can be used to override the boot disk's 293 device name and size, respectively. 294 """ 295 296 image_parent_group = parser.add_group() 297 image_group = image_parent_group.add_mutually_exclusive_group() 298 image_group.add_argument('--image', help=AddImageHelp, metavar='IMAGE') 299 image_utils.AddImageProjectFlag(image_parent_group) 300 301 image_group.add_argument( 302 '--image-family', 303 help="""\ 304 The image family for the operating system that the boot disk will 305 be initialized with. Compute Engine offers multiple Linux 306 distributions, some of which are available as both regular and 307 Shielded VM images. When a family is specified instead of an image, 308 the latest non-deprecated image associated with that family is 309 used. It is best practice to use `--image-family` when the latest 310 version of an image is needed. 311 312 By default, ``{default_image_family}'' is assumed for this flag. 313 """.format(default_image_family=constants.DEFAULT_IMAGE_FAMILY)) 314 if enable_snapshots: 315 image_group.add_argument( 316 '--source-snapshot', 317 help="""\ 318 The name of the source disk snapshot that the instance boot disk 319 will be created from. You can provide this as a full URL 320 to the snapshot or just the snapshot name. For example, the following 321 are valid values: 322 323 * https://compute.googleapis.com/compute/v1/projects/myproject/global/snapshots/snapshot 324 * snapshot 325 """) 326 327 328def AddCanIpForwardArgs(parser): 329 parser.add_argument( 330 '--can-ip-forward', 331 action='store_true', 332 help=('If provided, allows the instances to send and receive packets ' 333 'with non-matching destination or source IP addresses.')) 334 335 336def AddPrivateIpv6GoogleAccessArg(parser, api_version): 337 messages = apis.GetMessagesModule('compute', api_version) 338 GetPrivateIpv6GoogleAccessTypeFlagMapper(messages).choice_arg.AddToParser( 339 parser) 340 341 342def AddMaintenanceInterval(): 343 return base.Argument( 344 '--maintenance-interval', 345 type=lambda x: x.upper(), 346 choices={'PERIODIC': 'VMs receive infrastructure and hypervisor updates ' 347 'on a periodic basis, minimizing the number of' 348 ' maintenance operations (live migrations or ' 349 'terminations) on an individual VM. Security updates' 350 ' will still be applied as soon as they are ' 351 'available.'}, 352 help=""" 353 Specifies how infrastructure upgrades should be applied to the VM. 354 """) 355 356 357def AddMaintenanceFreezeDuration(): 358 return base.Argument( 359 '--maintenance-freeze-duration', 360 type=arg_parsers.Duration(), 361 help=""" 362 Specifies the amount of hours after instance creation where the instance 363 won't be scheduled for maintenance, e.g. `4h`, `2d6h`. 364 See $ gcloud topic datetimes for information on duration formats.""") 365 366 367def AddStableFleetArgs(parser): 368 """Add flags related to Stable Fleet.""" 369 AddMaintenanceInterval().AddToParser(parser) 370 AddMaintenanceFreezeDuration().AddToParser(parser) 371 372 373def GetPrivateIpv6GoogleAccessTypeFlagMapper(messages): 374 return arg_utils.ChoiceEnumMapper( 375 '--private-ipv6-google-access-type', 376 messages.Instance.PrivateIpv6GoogleAccessValueValuesEnum, 377 custom_mappings={ 378 'INHERIT_FROM_SUBNETWORK': 379 'inherit-subnetwork', 380 'ENABLE_BIDIRECTIONAL_ACCESS_TO_GOOGLE': 381 'enable-bidirectional-access', 382 'ENABLE_OUTBOUND_VM_ACCESS_TO_GOOGLE': 383 'enable-outbound-vm-access' 384 }, 385 help_str='The private IPv6 Google access type for the VM.') 386 387 388def AddPrivateIpv6GoogleAccessArgForTemplate(parser, api_version): 389 messages = apis.GetMessagesModule('compute', api_version) 390 GetPrivateIpv6GoogleAccessTypeFlagMapperForTemplate( 391 messages).choice_arg.AddToParser(parser) 392 393 394def GetPrivateIpv6GoogleAccessTypeFlagMapperForTemplate(messages): 395 return arg_utils.ChoiceEnumMapper( 396 '--private-ipv6-google-access-type', 397 messages.InstanceProperties.PrivateIpv6GoogleAccessValueValuesEnum, 398 custom_mappings={ 399 'INHERIT_FROM_SUBNETWORK': 400 'inherit-subnetwork', 401 'ENABLE_BIDIRECTIONAL_ACCESS_TO_GOOGLE': 402 'enable-bidirectional-access', 403 'ENABLE_OUTBOUND_VM_ACCESS_TO_GOOGLE': 404 'enable-outbound-vm-access' 405 }, 406 help_str='The private IPv6 Google access type for the VM.') 407 408 409def AddLocalSsdArgs(parser): 410 """Adds local SSD argument for instances and instance-templates.""" 411 412 parser.add_argument( 413 '--local-ssd', 414 type=arg_parsers.ArgDict(spec={ 415 'device-name': str, 416 'interface': (lambda x: x.upper()), 417 }), 418 action='append', 419 help="""\ 420 Attaches a local SSD to the instances. 421 422 *device-name*::: Optional. A name that indicates the disk name 423 the guest operating system will see. Can only be specified if 424 `interface` is `SCSI`. If omitted, a device name 425 of the form ``local-ssd-N'' will be used. 426 427 *interface*::: Optional. The kind of disk interface exposed to the VM 428 for this SSD. Valid values are ``SCSI'' and ``NVME''. SCSI is 429 the default and is supported by more guest operating systems. NVME 430 may provide higher performance. 431 """) 432 433 434def AddLocalNvdimmArgs(parser): 435 """Adds local NVDIMM argument for instances and instance-templates.""" 436 437 parser.add_argument( 438 '--local-nvdimm', 439 type=arg_parsers.ArgDict(spec={ 440 'size': arg_parsers.BinarySize(), 441 }), 442 action='append', 443 help="""\ 444 Attaches a local NVDIMM to the instances. 445 446 *size*::: Optional. Size of the NVDIMM disk. The value must be a whole 447 number followed by a size unit of ``KB'' for kilobyte, ``MB'' for 448 megabyte, ``GB'' for gigabyte, or ``TB'' for terabyte. For example, 449 ``3TB'' will produce a 3 terabyte disk. Allowed values are: 3TB and 6TB 450 and the default is 3 TB. 451 """) 452 453 454def AddLocalSsdArgsWithSize(parser): 455 """Adds local SSD argument for instances and instance-templates.""" 456 457 parser.add_argument( 458 '--local-ssd', 459 type=arg_parsers.ArgDict( 460 spec={ 461 'device-name': str, 462 'interface': (lambda x: x.upper()), 463 'size': arg_parsers.BinarySize(lower_bound='375GB'), 464 }), 465 action='append', 466 help="""\ 467 Attaches a local SSD to the instances. 468 469 This flag is currently in BETA and may change without notice. 470 471 *device-name*::: Optional. A name that indicates the disk name 472 the guest operating system will see. Can only be specified if `interface` 473 is `SCSI`. If omitted, a device name 474 of the form ``local-ssd-N'' will be used. 475 476 *interface*::: Optional. The kind of disk interface exposed to the VM 477 for this SSD. Valid values are ``SCSI'' and ``NVME''. SCSI is 478 the default and is supported by more guest operating systems. NVME 479 may provide higher performance. 480 481 *size*::: Optional. Size of the SSD disk. The value must be a whole number 482 followed by a size unit of ``KB'' for kilobyte, ``MB'' for megabyte, 483 ``GB'' for gigabyte, or ``TB'' for terabyte. For example, ``750GB'' will 484 produce a 750 gigabyte disk. The size must be a multiple of 375 GB and 485 the default is 375 GB. For Alpha API only. 486 """) 487 488 489def _GetDiskDeviceNameHelp(container_mount_enabled=False): 490 """Helper to get documentation for "device-name" param of disk spec.""" 491 if container_mount_enabled: 492 return ( 493 'An optional name that indicates the disk name the guest operating ' 494 'system will see. Must be the same as `name` if used with ' 495 '`--container-mount-disk`. If omitted, a device name of the form ' 496 '`persistent-disk-N` will be used. If omitted and used with ' 497 '`--container-mount-disk` (where the `name` of the container mount ' 498 'disk is the same as in this flag), a device name equal to disk `name` ' 499 'will be used.') 500 else: 501 return ('An optional name that indicates the disk name the guest operating ' 502 'system will see. If omitted, a device name of the form ' 503 '`persistent-disk-N` will be used.') 504 505 506def AddDiskArgs(parser, 507 enable_regional_disks=False, 508 enable_kms=False, 509 container_mount_enabled=False): 510 """Adds arguments related to disks for instances and instance-templates.""" 511 512 disk_device_name_help = _GetDiskDeviceNameHelp( 513 container_mount_enabled=container_mount_enabled) 514 515 AddBootDiskArgs(parser) 516 517 if enable_kms: 518 kms_resource_args.AddKmsKeyResourceArg( 519 parser, 'disk', boot_disk_prefix=True) 520 521 disk_arg_spec = { 522 'name': str, 523 'mode': str, 524 'boot': str, 525 'device-name': str, 526 'auto-delete': str, 527 } 528 529 if enable_regional_disks: 530 disk_arg_spec['scope'] = str 531 532 disk_help = """ 533 Attaches persistent disks to the instances. The disks 534 specified must already exist. 535 536 *name*::: The disk to attach to the instances. When creating 537 more than one instance and using this property, the only valid 538 mode for attaching the disk is read-only (see *mode* below). 539 540 *mode*::: Specifies the mode of the disk. Supported options 541 are ``ro'' for read-only and ``rw'' for read-write. If 542 omitted, ``rw'' is used as a default. It is an error for mode 543 to be ``rw'' when creating more than one instance because 544 read-write disks can only be attached to a single instance. 545 546 *boot*::: If ``yes'', indicates that this is a boot disk. The 547 virtual machines will use the first partition of the disk for 548 their root file systems. The default value for this is ``no''. 549 550 *device-name*::: {} 551 552 *auto-delete*::: If ``yes'', this persistent disk will be 553 automatically deleted when the instance is deleted. However, 554 if the disk is later detached from the instance, this option 555 won't apply. The default value for this is ``yes''. 556 """.format(disk_device_name_help) 557 if enable_regional_disks: 558 disk_help += """ 559 *scope*::: Can be `zonal` or `regional`. If ``zonal'', the disk is 560 interpreted as a zonal disk in the same zone as the instance (default). 561 If ``regional'', the disk is interpreted as a regional disk in the same 562 region as the instance. The default value for this is ``zonal''. 563 """ 564 565 parser.add_argument( 566 '--disk', 567 type=arg_parsers.ArgDict(spec=disk_arg_spec), 568 action='append', 569 help=disk_help) 570 571 572def AddBootDiskArgs(parser): 573 """Adds boot disk args.""" 574 parser.add_argument( 575 '--boot-disk-device-name', 576 help="""\ 577 The name the guest operating system will see for the boot disk. This 578 option can only be specified if a new boot disk is being created (as 579 opposed to mounting an existing persistent disk). 580 """) 581 parser.add_argument( 582 '--boot-disk-size', 583 type=arg_parsers.BinarySize(lower_bound='10GB'), 584 help="""\ 585 The size of the boot disk. This option can only be specified if a new 586 boot disk is being created (as opposed to mounting an existing 587 persistent disk). The value must be a whole number followed by a size 588 unit of ``KB'' for kilobyte, ``MB'' for megabyte, ``GB'' for gigabyte, 589 or ``TB'' for terabyte. For example, ``10GB'' will produce a 10 gigabyte 590 disk. The minimum size a boot disk can have is 10 GB. Disk size must be a 591 multiple of 1 GB. Limit boot disk size to 2 TB to account for MBR 592 partition table limitations. Default size unit is ``GB''. 593 """) 594 595 parser.add_argument( 596 '--boot-disk-type', 597 help="""\ 598 The type of the boot disk. This option can only be specified if a new boot 599 disk is being created (as opposed to mounting an existing persistent 600 disk). To get a list of available disk types, run 601 `$ gcloud compute disk-types list`. 602 """) 603 604 parser.add_argument( 605 '--boot-disk-auto-delete', 606 action='store_true', 607 default=True, 608 help='Automatically delete boot disks when their instances are deleted.') 609 610 611def AddCreateDiskArgs(parser, 612 enable_kms=False, 613 enable_snapshots=False, 614 container_mount_enabled=False, 615 resource_policy=False, 616 source_snapshot_csek=False, 617 image_csek=False, 618 include_name=True, 619 support_boot=False, 620 support_multi_writer=False, 621 support_replica_zones=False): 622 """Adds create-disk argument for instances and instance-templates.""" 623 624 disk_device_name_help = _GetDiskDeviceNameHelp( 625 container_mount_enabled=container_mount_enabled) 626 disk_name_extra_help = '' if not container_mount_enabled else ( 627 ' Must specify this option if attaching the disk ' 628 'to a container with `--container-mount-disk`.') 629 disk_mode_extra_help = '' if not container_mount_enabled else ( 630 ' It is an error to create a disk in `ro` mode ' 631 'if attaching it to a container with `--container-mount-disk`.') 632 633 disk_help = """\ 634 Creates and attaches persistent disks to the instances. 635 636 *name*::: Specifies the name of the disk. This option cannot be 637 specified if more than one instance is being created.{disk_name} 638 639 *description*::: Optional textual description for the disk being created. 640 641 *mode*::: Specifies the mode of the disk. Supported options 642 are ``ro'' for read-only and ``rw'' for read-write. If 643 omitted, ``rw'' is used as a default.{disk_mode} 644 645 *image*::: Specifies the name of the image that the disk will be 646 initialized with. A new disk will be created based on the given 647 image. To view a list of public images and projects, run 648 `$ gcloud compute images list`. It is best practice to use image when 649 a specific version of an image is needed. If both image and image-family 650 flags are omitted a blank disk will be created. 651 652 *image-family*::: The image family for the operating system that the boot 653 disk will be initialized with. Compute Engine offers multiple Linux 654 distributions, some of which are available as both regular and 655 Shielded VM images. When a family is specified instead of an image, 656 the latest non-deprecated image associated with that family is 657 used. It is best practice to use --image-family when the latest 658 version of an image is needed. 659 660 *image-project*::: The Google Cloud project against which all image and 661 image family references will be resolved. It is best practice to define 662 image-project. A full list of available projects can be generated by 663 running `gcloud projects list`. 664 665 * If specifying one of our public images, image-project must be 666 provided. 667 * If there are several of the same image-family value in multiple 668 projects, image-project must be specified to clarify the image to be 669 used. 670 * If not specified and either image or image-family is provided, the 671 current default project is used. 672 673 *size*::: The size of the disk. The value must be a whole number 674 followed by a size unit of ``KB'' for kilobyte, ``MB'' for 675 megabyte, ``GB'' for gigabyte, or ``TB'' for terabyte. For 676 example, ``10GB'' will produce a 10 gigabyte disk. Disk size must 677 be a multiple of 1 GB. If not specified, the default image size 678 will be used for the new disk. 679 680 *type*::: The type of the disk. To get a list of available disk 681 types, run $ gcloud compute disk-types list. The default disk type 682 is ``pd-standard''. 683 684 *device-name*::: {disk_device} 685 686 *auto-delete*::: If ``yes'', this persistent disk will be 687 automatically deleted when the instance is deleted. However, 688 if the disk is later detached from the instance, this option 689 won't apply. The default value for this is ``yes''. 690 """.format( 691 disk_name=disk_name_extra_help, 692 disk_mode=disk_mode_extra_help, 693 disk_device=disk_device_name_help) 694 if support_boot: 695 disk_help += """ 696 *boot*::: If ``yes'', indicates that this is a boot disk. The 697 instance will use the first partition of the disk for 698 its root file system. The default value for this is ``no''. 699 """ 700 if enable_kms: 701 disk_help += """ 702 *kms-key*::: Fully qualified Cloud KMS cryptokey name that will 703 protect the {resource}. 704 705 This can either be the fully qualified path or the name. 706 707 The fully qualified Cloud KMS cryptokey name format is: 708 ``projects/<kms-project>/locations/<kms-location>/keyRings/<kms-keyring>/ 709 cryptoKeys/<key-name>''. 710 711 If the value is not fully qualified then kms-location, kms-keyring, and 712 optionally kms-project are required. 713 714 See {kms_help} for more details. 715 716 *kms-project*::: Project that contains the Cloud KMS cryptokey that will 717 protect the {resource}. 718 719 If the project is not specified then the project where the {resource} is 720 being created will be used. 721 722 If this flag is set then key-location, kms-keyring, and kms-key 723 are required. 724 725 See {kms_help} for more details. 726 727 *kms-location*::: Location of the Cloud KMS cryptokey to be used for 728 protecting the {resource}. 729 730 All Cloud KMS cryptokeys are reside in a 'location'. 731 To get a list of possible locations run 'gcloud kms locations list'. 732 If this flag is set then kms-keyring and kms-key are required. 733 See {kms_help} for more details. 734 735 *kms-keyring*::: The keyring which contains the Cloud KMS cryptokey that 736 will protect the {resource}. 737 738 If this flag is set then kms-location and kms-key are required. 739 740 See {kms_help} for more details. 741 """.format( 742 resource='disk', kms_help=kms_utils.KMS_HELP_URL) 743 spec = { 744 'description': str, 745 'mode': str, 746 'image': str, 747 'image-family': str, 748 'image-project': str, 749 'size': arg_parsers.BinarySize(lower_bound='1GB'), 750 'type': str, 751 'device-name': str, 752 'auto-delete': str, 753 } 754 755 if include_name: 756 spec['name'] = str 757 758 if enable_kms: 759 spec['kms-key'] = str 760 spec['kms-project'] = str 761 spec['kms-location'] = str 762 spec['kms-keyring'] = str 763 764 if support_boot: 765 spec['boot'] = str 766 767 if enable_snapshots: 768 disk_help += """ 769 *source-snapshot*::: The source disk snapshot that will be used to 770 create the disk. You can provide this as a full URL 771 to the snapshot or just the snapshot name. For example, the following 772 are valid values: 773 774 * https://compute.googleapis.com/compute/v1/projects/myproject/global/snapshots/snapshot 775 * snapshot 776 """ 777 spec['source-snapshot'] = str 778 779 if resource_policy: 780 disk_help += """ 781 *disk-resource-policy*::: Resource policy that will be applied to created 782 disk. You can provide full or partial URL. For more details see 783 784 * https://cloud.google.com/sdk/gcloud/reference/beta/compute/resource-policies/ 785 * https://cloud.google.com/compute/docs/disks/scheduled-snapshots 786 787 """ 788 spec['disk-resource-policy'] = arg_parsers.ArgList(max_length=1) 789 790 if source_snapshot_csek: 791 disk_help += """ 792 *source-snapshot-csek-required*::: The CSK protected source disk snapshot 793 that will be used to create the disk. This can be provided as a full URL 794 to the snapshot or just the snapshot name. Must be specified with 795 `source-snapshot-csek-key-file`. The following are valid values: 796 797 * https://www.googleapis.com/compute/v1/projects/myproject/global/snapshots/snapshot 798 * snapshot 799 800 *source-snapshot-csek-key-file*::: Path to a Customer-Supplied Encryption 801 Key (CSEK) key file for the source snapshot. Must be specified with 802 `source-snapshot-csek-required`. 803 """ 804 spec['source-snapshot-csek-key-file'] = str 805 806 if image_csek: 807 disk_help += """ 808 *image-csek-required*::: Specifies the name of the CSK protected image 809 that the disk will be initialized with. A new disk will be created based 810 on the given image. To view a list of public images and projects, run 811 `$ gcloud compute images list`. It is best practice to use image when 812 a specific version of an image is needed. If both image and image-family 813 flags are omitted a blank disk will be created. Must be specified with 814 `image-csek-key-file`. 815 816 *image-csek-key-file*::: Path to a Customer-Supplied Encryption Key (CSEK) 817 key file for the image. Must be specified with `image-csek-required`. 818 """ 819 spec['image-csek-key-file'] = str 820 821 if support_multi_writer: 822 spec['multi-writer'] = arg_parsers.ArgBoolean() 823 disk_help += """ 824 *multi-writer*::: If ``yes'', create the disk in multi-writer mode so that 825 it can be attached with read-write access to multiple VMs. Can only be 826 used with zonal SSD persistent disks. Disks in multi-writer mode do not 827 support resize and snapshot operations. The default value is ``no''. 828 """ 829 830 if support_replica_zones: 831 disk_help += """ 832 *replica-zones*::: If specified, the created disk is regional. The disk 833 will be replicated to the specified replica zone and the zone of the 834 newly created instance. 835 """ 836 spec['replica-zones'] = arg_parsers.ArgList(max_length=1) 837 838 parser.add_argument( 839 '--create-disk', 840 type=arg_parsers.ArgDict(spec=spec), 841 action='append', 842 metavar='PROPERTY=VALUE', 843 help=disk_help) 844 845 846def AddCustomMachineTypeArgs(parser): 847 """Adds arguments related to custom machine types for instances.""" 848 custom_group = parser.add_group(help='Custom machine type extensions.') 849 custom_group.add_argument( 850 '--custom-cpu', 851 type=int, 852 required=True, 853 help="""\ 854 A whole number value indicating how many cores are desired in the custom 855 machine type. 856 """) 857 custom_group.add_argument( 858 '--custom-memory', 859 type=arg_parsers.BinarySize(), 860 required=True, 861 help="""\ 862 A whole number value indicating how much memory is desired in the custom 863 machine type. A size unit should be provided (eg. 3072MB or 9GB) - if no 864 units are specified, GB is assumed. 865 """) 866 custom_group.add_argument( 867 '--custom-extensions', 868 action='store_true', 869 help='Use the extended custom machine type.') 870 custom_group.add_argument( 871 '--custom-vm-type', 872 help=""" 873 Specifies VM type. n1 - VMs with CPU platforms Skylake and older, 874 n2 - VMs with CPU platform Cascade Lake. n2 offers flexible sizing from 875 2 to 80 vCPUs, and 1 to 640GBs of memory. 876 It also features a number of performance enhancements including exposing 877 a more accurate NUMA topology to the guest OS. The default is `n1`. 878 """) 879 880 881def _GetAddress(compute_client, address_ref): 882 """Returns the address resource corresponding to the given reference. 883 884 Args: 885 compute_client: GCE API client, 886 address_ref: resource reference to reserved IP address 887 888 Returns: 889 GCE reserved IP address resource 890 """ 891 errors = [] 892 messages = compute_client.messages 893 compute = compute_client.apitools_client 894 res = compute_client.MakeRequests( 895 requests=[(compute.addresses, 'Get', 896 messages.ComputeAddressesGetRequest( 897 address=address_ref.Name(), 898 project=address_ref.project, 899 region=address_ref.region))], 900 errors_to_collect=errors) 901 if errors: 902 utils.RaiseToolException( 903 errors, error_message='Could not fetch address resource:') 904 return res[0] 905 906 907def ExpandAddressFlag(resources, compute_client, address, region): 908 """Resolves the --address flag value. 909 910 If the value of --address is a name, the regional address is queried. 911 912 Args: 913 resources: resources object, 914 compute_client: GCE API client, 915 address: The command-line flags. The flag accessed is --address, 916 region: The region. 917 918 Returns: 919 If an --address is given, the resolved IP address; otherwise None. 920 """ 921 if not address: 922 return None 923 924 # Try interpreting the address as IPv4 or IPv6. 925 try: 926 # ipaddress only allows unicode input 927 ipaddress.ip_address(six.text_type(address)) 928 return address 929 except ValueError: 930 # ipaddress could not resolve as an IPv4 or IPv6 address. 931 pass 932 933 # Lookup the address. 934 address_ref = GetAddressRef(resources, address, region) 935 res = _GetAddress(compute_client, address_ref) 936 return res.address 937 938 939def GetAddressRef(resources, address, region): 940 """Generates an address reference from the specified address and region.""" 941 return resources.Parse( 942 address, 943 collection='compute.addresses', 944 params={ 945 'project': properties.VALUES.core.project.GetOrFail, 946 'region': region 947 }) 948 949 950def ValidateDiskFlags(args, 951 enable_kms=False, 952 enable_snapshots=False, 953 enable_source_snapshot_csek=False, 954 enable_image_csek=False): 955 """Validates the values of all disk-related flags.""" 956 ValidateDiskCommonFlags(args) 957 ValidateDiskAccessModeFlags(args) 958 ValidateDiskBootFlags(args, enable_kms=enable_kms) 959 ValidateCreateDiskFlags( 960 args, 961 enable_snapshots=enable_snapshots, 962 enable_source_snapshot_csek=enable_source_snapshot_csek, 963 enable_image_csek=enable_image_csek) 964 965 966def ValidateBulkDiskFlags(args, 967 enable_snapshots=False, 968 enable_source_snapshot_csek=False, 969 enable_image_csek=False): 970 """Validates the values of all disk-related flags.""" 971 ValidateCreateDiskFlags( 972 args, 973 enable_snapshots=enable_snapshots, 974 enable_source_snapshot_csek=enable_source_snapshot_csek, 975 enable_image_csek=enable_image_csek) 976 977 978def ValidateDiskCommonFlags(args, include_name=True): 979 """Validates the values of common disk-related flags.""" 980 981 for disk in args.disk or []: 982 disk_name = disk.get('name') 983 if not disk_name: 984 raise exceptions.InvalidArgumentException( 985 '--disk', 986 '[name] is missing in [--disk]. [--disk] value must be of the form ' 987 '[{0}].'.format(DISK_METAVAR)) 988 989 mode_value = disk.get('mode') 990 if mode_value and mode_value not in ('rw', 'ro'): 991 raise exceptions.InvalidArgumentException( 992 '--disk', 993 'Value for [mode] in [--disk] must be [rw] or [ro], not [{0}].' 994 .format(mode_value)) 995 996 auto_delete_value = disk.get('auto-delete') 997 if auto_delete_value and auto_delete_value not in ['yes', 'no']: 998 raise exceptions.InvalidArgumentException( 999 '--disk', 1000 'Value for [auto-delete] in [--disk] must be [yes] or [no], not ' 1001 '[{0}].'.format(auto_delete_value)) 1002 1003 1004def ValidateDiskAccessModeFlags(args): 1005 """Checks disks R/O and R/W access mode.""" 1006 for disk in args.disk or []: 1007 disk_name = disk.get('name') 1008 mode_value = disk.get('mode') 1009 # Ensures that the user is not trying to attach a read-write 1010 # disk to more than one instance. 1011 if len(args.instance_names) > 1 and mode_value == 'rw': 1012 raise exceptions.BadArgumentException( 1013 '--disk', 1014 'Cannot attach disk [{0}] in read-write mode to more than one ' 1015 'instance.'.format(disk_name)) 1016 1017 1018def ValidateDiskBootFlags(args, enable_kms=False): 1019 """Validates the values of boot disk-related flags.""" 1020 boot_disk_specified = False 1021 for disk in args.disk or []: 1022 # If this is a boot disk and we have already seen a boot disk, 1023 # we need to fail because only one boot disk can be attached. 1024 boot_value = disk.get('boot') 1025 if boot_value and boot_value not in ('yes', 'no'): 1026 raise exceptions.InvalidArgumentException( 1027 '--disk', 1028 'Value for [boot] in [--disk] must be [yes] or [no], not [{0}].' 1029 .format(boot_value)) 1030 1031 if boot_value == 'yes': 1032 if boot_disk_specified: 1033 raise exceptions.BadArgumentException( 1034 '--disk', 1035 'Each instance can have exactly one boot disk. At least two ' 1036 'boot disks were specified through [--disk].') 1037 else: 1038 boot_disk_specified = True 1039 1040 if args.image and boot_disk_specified: 1041 raise exceptions.BadArgumentException( 1042 '--disk', 1043 'Each instance can have exactly one boot disk. One boot disk ' 1044 'was specified through [--disk] and another through [--image].') 1045 1046 if boot_disk_specified: 1047 if args.boot_disk_device_name: 1048 raise exceptions.BadArgumentException( 1049 '--boot-disk-device-name', 1050 '[--boot-disk-device-name] can only be used when creating a new ' 1051 'boot disk.') 1052 1053 if args.boot_disk_type: 1054 raise exceptions.BadArgumentException( 1055 '--boot-disk-type', 1056 '[--boot-disk-type] can only be used when creating a new boot ' 1057 'disk.') 1058 1059 if args.boot_disk_size: 1060 raise exceptions.BadArgumentException( 1061 '--boot-disk-size', 1062 '[--boot-disk-size] can only be used when creating a new boot ' 1063 'disk.') 1064 1065 if not args.boot_disk_auto_delete: 1066 raise exceptions.BadArgumentException( 1067 '--no-boot-disk-auto-delete', 1068 '[--no-boot-disk-auto-delete] can only be used when creating a ' 1069 'new boot disk.') 1070 1071 if enable_kms: 1072 if args.boot_disk_kms_key: 1073 raise exceptions.BadArgumentException( 1074 '--boot-disk-kms-key', 1075 '[--boot-disk-kms-key] can only be used when creating a new boot ' 1076 'disk.') 1077 1078 if args.boot_disk_kms_keyring: 1079 raise exceptions.BadArgumentException( 1080 '--boot-disk-kms-keyring', 1081 '[--boot-disk-kms-keyring] can only be used when creating a new ' 1082 'boot disk.') 1083 1084 if args.boot_disk_kms_location: 1085 raise exceptions.BadArgumentException( 1086 '--boot-disk-kms-location', 1087 '[--boot-disk-kms-location] can only be used when creating a new ' 1088 'boot disk.') 1089 1090 if args.boot_disk_kms_project: 1091 raise exceptions.BadArgumentException( 1092 '--boot-disk-kms-project', 1093 '[--boot-disk-kms-project] can only be used when creating a new ' 1094 'boot disk.') 1095 1096 1097def ValidateCreateDiskFlags(args, 1098 enable_snapshots=False, 1099 enable_source_snapshot_csek=False, 1100 enable_image_csek=False, 1101 include_name=True): 1102 """Validates the values of create-disk related flags.""" 1103 require_csek_key_create = getattr(args, 'require_csek_key_create', None) 1104 csek_key_file = getattr(args, 'csek_key_file', None) 1105 resource_names = getattr(args, 'names', []) 1106 1107 for disk in getattr(args, 'create_disk', []) or []: 1108 disk_name = disk.get('name') 1109 if include_name and len(resource_names) > 1 and disk_name: 1110 raise exceptions.BadArgumentException( 1111 '--disk', 1112 'Cannot create a disk with [name]={} for more than one instance.' 1113 .format(disk_name)) 1114 if disk_name and require_csek_key_create and csek_key_file: 1115 raise exceptions.BadArgumentException( 1116 '--disk', 1117 'Cannot create a disk with customer supplied key when disk name ' 1118 'is not specified.') 1119 1120 mode_value = disk.get('mode') 1121 if mode_value and mode_value not in ('rw', 'ro'): 1122 raise exceptions.InvalidArgumentException( 1123 '--disk', 1124 'Value for [mode] in [--disk] must be [rw] or [ro], not [{0}].' 1125 .format(mode_value)) 1126 1127 image_value = disk.get('image') 1128 image_family_value = disk.get('image-family') 1129 source_snapshot = disk.get('source-snapshot') 1130 image_csek_file = disk.get('image_csek') 1131 source_snapshot_csek_file = disk.get('source_snapshot_csek_file') 1132 1133 disk_source = set() 1134 if image_value: 1135 disk_source.add(image_value) 1136 if image_family_value: 1137 disk_source.add(image_family_value) 1138 if source_snapshot: 1139 disk_source.add(source_snapshot) 1140 if image_csek_file: 1141 disk_source.add(image_csek_file) 1142 if source_snapshot_csek_file: 1143 disk_source.add(source_snapshot_csek_file) 1144 1145 mutex_attributes = ['[image]', '[image-family]'] 1146 if enable_image_csek: 1147 mutex_attributes.append('[image-csek-required]') 1148 if enable_snapshots: 1149 mutex_attributes.append('[source-snapshot]') 1150 if enable_source_snapshot_csek: 1151 mutex_attributes.append('[source-snapshot-csek-required]') 1152 formatted_attributes = '{}, or {}'.format(', '.join(mutex_attributes[:-1]), 1153 mutex_attributes[-1]) 1154 source_error_message = ( 1155 'Must specify exactly one of {} for a ' 1156 '[--create-disk]. These fields are mutually exclusive.'.format( 1157 formatted_attributes)) 1158 if len(disk_source) > 1: 1159 raise exceptions.ToolException(source_error_message) 1160 1161 1162def ValidateImageFlags(args): 1163 """Validates the image flags.""" 1164 if args.image_project and not (args.image or args.image_family): 1165 raise exceptions.ToolException( 1166 'Must specify either [--image] or [--image-family] when specifying ' 1167 '[--image-project] flag.') 1168 1169 1170def AddAddressArgs(parser, 1171 instances=True, 1172 multiple_network_interface_cards=True, 1173 support_network_interface_nic_type=False): 1174 """Adds address arguments for instances and instance-templates.""" 1175 addresses = parser.add_mutually_exclusive_group() 1176 AddNoAddressArg(addresses) 1177 if instances: 1178 address_help = """\ 1179 Assigns the given external address to the instance that is created. 1180 The address may be an IP address or the name or URI of an address 1181 resource. This option can only be used when creating a single instance. 1182 """ 1183 else: 1184 address_help = """\ 1185 Assigns the given external IP address to the instance that is created. 1186 This option can only be used when creating a single instance. 1187 """ 1188 addresses.add_argument('--address', help=address_help) 1189 multiple_network_interface_cards_spec = { 1190 'address': str, 1191 'network': str, 1192 'no-address': None, 1193 'subnet': str, 1194 } 1195 1196 multiple_network_interface_cards_spec['private-network-ip'] = str 1197 1198 def ValidateNetworkTier(network_tier_input): 1199 network_tier = network_tier_input.upper() 1200 if network_tier in constants.NETWORK_TIER_CHOICES_FOR_INSTANCE: 1201 return network_tier 1202 else: 1203 raise exceptions.InvalidArgumentException( 1204 '--network-interface', 'Invalid value for network-tier') 1205 1206 multiple_network_interface_cards_spec['network-tier'] = ValidateNetworkTier 1207 1208 def ValidateNetworkInterfaceNicType(nic_type_input): 1209 nic_type = nic_type_input.upper() 1210 if nic_type in constants.NETWORK_INTERFACE_NIC_TYPE_CHOICES: 1211 return nic_type 1212 else: 1213 raise exceptions.InvalidArgumentException( 1214 '--network-interface', 'Invalid value for nic-type [%s]' % nic_type) 1215 1216 if support_network_interface_nic_type: 1217 multiple_network_interface_cards_spec[ 1218 'nic-type'] = ValidateNetworkInterfaceNicType 1219 1220 if multiple_network_interface_cards: 1221 multiple_network_interface_cards_spec['aliases'] = str 1222 network_interface_help = """\ 1223 Adds a network interface to the instance. Mutually exclusive with any 1224 of these flags: *--address*, *--network*, *--network-tier*, *--subnet*, 1225 *--private-network-ip*. This flag can be repeated to specify multiple 1226 network interfaces. 1227 1228 The following keys are allowed: 1229 *address*::: Assigns the given external address to the instance that is 1230 created. Specifying an empty string will assign an ephemeral IP. 1231 Mutually exclusive with no-address. If neither key is present the 1232 instance will get an ephemeral IP. 1233 1234 *network*::: Specifies the network that the interface will be part of. 1235 If subnet is also specified it must be subnetwork of this network. If 1236 neither is specified, this defaults to the "default" network. 1237 1238 *no-address*::: If specified the interface will have no external IP. 1239 Mutually exclusive with address. If neither key is present the 1240 instance will get an ephemeral IP. 1241 """ 1242 network_interface_help += """ 1243 *network-tier*::: Specifies the network tier of the interface. 1244 ``NETWORK_TIER'' must be one of: `PREMIUM`, `STANDARD`. The default 1245 value is `PREMIUM`. 1246 """ 1247 1248 network_interface_help += """ 1249 *private-network-ip*::: Assigns the given RFC1918 IP address to the 1250 interface. 1251 """ 1252 network_interface_help += """ 1253 *subnet*::: Specifies the subnet that the interface will be part of. 1254 If network key is also specified this must be a subnetwork of the 1255 specified network. 1256 """ 1257 1258 if support_network_interface_nic_type: 1259 network_interface_help += """ 1260 *nic-type*::: Specifies the NIC type for the interface. 1261 ``NIC_TYPE'' must be one of: `GVNIC`, `VIRTIO_NET`. 1262 """ 1263 1264 network_interface_help += """ 1265 *aliases*::: Specifies the IP alias ranges to allocate for this 1266 interface. If there are multiple IP alias ranges, they are separated 1267 by semicolons. 1268 1269 For example: 1270 1271 --aliases="10.128.1.0/24;range1:/32" 1272 1273 """ 1274 if instances: 1275 network_interface_help += """ 1276 Each IP alias range consists of a range name and an IP range 1277 separated by a colon, or just the IP range. 1278 The range name is the name of the range within the network 1279 interface's subnet from which to allocate an IP alias range. If 1280 unspecified, it defaults to the primary IP range of the subnet. 1281 The IP range can be a CIDR range (e.g. `192.168.100.0/24`), a single 1282 IP address (e.g. `192.168.100.1`), or a netmask in CIDR format (e.g. 1283 `/24`). If the IP range is specified by CIDR range or single IP 1284 address, it must belong to the CIDR range specified by the range 1285 name on the subnet. If the IP range is specified by netmask, the 1286 IP allocator will pick an available range with the specified netmask 1287 and allocate it to this network interface.""" 1288 else: 1289 network_interface_help += """ 1290 Each IP alias range consists of a range name and an CIDR netmask 1291 (e.g. `/24`) separated by a colon, or just the netmask. 1292 The range name is the name of the range within the network 1293 interface's subnet from which to allocate an IP alias range. If 1294 unspecified, it defaults to the primary IP range of the subnet. 1295 The IP allocator will pick an available range with the specified 1296 netmask and allocate it to this network interface.""" 1297 1298 parser.add_argument( 1299 '--network-interface', 1300 type=arg_parsers.ArgDict( 1301 spec=multiple_network_interface_cards_spec, 1302 allow_key_only=True, 1303 ), 1304 action='append', # pylint:disable=protected-access 1305 metavar='PROPERTY=VALUE', 1306 help=network_interface_help) 1307 1308 1309def AddNoAddressArg(parser): 1310 parser.add_argument( 1311 '--no-address', 1312 action='store_true', 1313 help="""\ 1314 If provided, the instances are not assigned external IP 1315 addresses. To pull container images, you must configure private 1316 Google access if using Container Registry or configure Cloud NAT 1317 for instances to access container images directly. For more 1318 information, see: 1319 * https://cloud.google.com/vpc/docs/configure-private-google-access 1320 * https://cloud.google.com/nat/docs/using-nat 1321 """) 1322 1323 1324def AddMachineTypeArgs(parser, required=False, unspecified_help=None): 1325 if unspecified_help is None: 1326 unspecified_help = ' If unspecified, the default type is n1-standard-1.' 1327 parser.add_argument( 1328 '--machine-type', 1329 completer=compute_completers.MachineTypesCompleter, 1330 required=required, 1331 help="""\ 1332 Specifies the machine type used for the instances. To get a 1333 list of available machine types, run 'gcloud compute 1334 machine-types list'.{}""".format(unspecified_help)) 1335 1336 1337def AddMinCpuPlatformArgs(parser, track, required=False): 1338 parser.add_argument( 1339 '--min-cpu-platform', 1340 metavar='PLATFORM', 1341 required=required, 1342 help="""\ 1343 When specified, the VM will be scheduled on host with specified CPU 1344 architecture or a newer one. To list available CPU platforms in given 1345 zone, run: 1346 1347 $ gcloud {}compute zones describe ZONE --format="value(availableCpuPlatforms)" 1348 1349 Default setting is "AUTOMATIC". 1350 1351 CPU platform selection is available only in selected zones. 1352 1353 You can find more information on-line: 1354 [](https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform) 1355 """.format(track.prefix + ' ' if track.prefix else '')) 1356 1357 1358def AddMinNodeCpuArg(parser, is_update=False): 1359 parser.add_argument( 1360 '--min-node-cpu', 1361 help="""\ 1362 Minimum number of virtual CPUs this instance will consume when running on 1363 a sole-tenant node. 1364 """) 1365 if is_update: 1366 parser.add_argument( 1367 '--clear-min-node-cpu', 1368 action='store_true', 1369 help="""\ 1370 Removes the min-node-cpu field from the instance. If specified, the 1371 instance min-node-cpu will be cleared. The instance will not be 1372 overcommitted and utilize the full CPU count assigned. 1373 """) 1374 1375 1376def AddLocationHintArg(parser): 1377 parser.add_argument( 1378 '--location-hint', 1379 hidden=True, 1380 help="""\ 1381 Used by internal tools to control sub-zone location of the instance. 1382 """) 1383 1384 1385def AddPreemptibleVmArgs(parser): 1386 parser.add_argument( 1387 '--preemptible', 1388 action='store_true', 1389 default=False, 1390 help="""\ 1391 If provided, instances will be preemptible and time-limited. 1392 Instances may be preempted to free up resources for standard VM instances, 1393 and will only be able to run for a limited amount of time. Preemptible 1394 instances can not be restarted and will not migrate. 1395 """) 1396 1397 1398def AddNetworkArgs(parser): 1399 """Set arguments for choosing the network/subnetwork.""" 1400 parser.add_argument( 1401 '--network', 1402 help="""\ 1403 Specifies the network that the instances will be part of. If --subnet is 1404 also specified subnet must be a subnetwork of network specified by 1405 --network. If neither is specified, this defaults to the "default" 1406 network. 1407 """) 1408 1409 parser.add_argument( 1410 '--subnet', 1411 help="""\ 1412 Specifies the subnet that the instances will be part of. If --network is 1413 also specified subnet must be a subnetwork of network specified by 1414 --network. 1415 """) 1416 1417 1418def AddPrivateNetworkIpArgs(parser): 1419 """Set arguments for choosing the network IP address.""" 1420 parser.add_argument( 1421 '--private-network-ip', 1422 help="""\ 1423 Specifies the RFC1918 IP to assign to the instance. The IP should be in 1424 the subnet or legacy network IP range. 1425 """) 1426 1427 1428def AddServiceAccountAndScopeArgs(parser, 1429 instance_exists, 1430 extra_scopes_help=''): 1431 """Add args for configuring service account and scopes. 1432 1433 This should replace AddScopeArgs (b/30802231). 1434 1435 Args: 1436 parser: ArgumentParser, parser to which flags will be added. 1437 instance_exists: bool, If instance already exists and we are modifying it. 1438 extra_scopes_help: str, Extra help text for the scopes flag. 1439 """ 1440 service_account_group = parser.add_mutually_exclusive_group() 1441 service_account_group.add_argument( 1442 '--no-service-account', 1443 action='store_true', 1444 help='Remove service account from the instance' 1445 if instance_exists else 'Create instance without service account') 1446 1447 sa_exists = """You can explicitly specify the Compute Engine default service 1448 account using the 'default' alias. 1449 1450 If not provided, the instance will use the service account it currently has. 1451 """ 1452 1453 sa_not_exists = """ 1454 1455 If not provided, the instance will use the project\'s default service account. 1456 """ 1457 1458 service_account_help = """\ 1459 A service account is an identity attached to the instance. Its access tokens 1460 can be accessed through the instance metadata server and are used to 1461 authenticate applications on the instance. The account can be set using an 1462 email address corresponding to the required service account. {0} 1463 """.format(sa_exists if instance_exists else sa_not_exists) 1464 service_account_group.add_argument( 1465 '--service-account', help=service_account_help) 1466 1467 scopes_group = parser.add_mutually_exclusive_group() 1468 scopes_group.add_argument( 1469 '--no-scopes', 1470 action='store_true', 1471 help='Remove all scopes from the instance' 1472 if instance_exists else 'Create instance without scopes') 1473 scopes_exists = 'keep the scopes it currently has' 1474 scopes_not_exists = 'be assigned the default scopes, described below' 1475 scopes_help = """\ 1476If not provided, the instance will {exists}. {extra} 1477 1478{scopes_help} 1479""".format( 1480 exists=scopes_exists if instance_exists else scopes_not_exists, 1481 extra=extra_scopes_help, 1482 scopes_help=constants.ScopesHelp()) 1483 scopes_group.add_argument( 1484 '--scopes', type=arg_parsers.ArgList(), metavar='SCOPE', help=scopes_help) 1485 1486 1487def AddNetworkInterfaceArgs(parser): 1488 """Adds network interface flag to the argparse.""" 1489 1490 parser.add_argument( 1491 '--network-interface', 1492 default=constants.DEFAULT_NETWORK_INTERFACE, 1493 action=arg_parsers.StoreOnceAction, 1494 help="""\ 1495 Specifies the name of the network interface which contains the access 1496 configuration. If this is not provided, then "nic0" is used 1497 as the default. 1498 """) 1499 1500 1501def AddNetworkTierArgs(parser, instance=True, for_update=False): 1502 """Adds network tier flag to the argparse.""" 1503 1504 if for_update: 1505 parser.add_argument( 1506 '--network-tier', 1507 type=lambda x: x.upper(), 1508 help='Update the network tier of the access configuration. It does not allow' 1509 ' to change from `PREMIUM` to `STANDARD` and visa versa.') 1510 return 1511 1512 if instance: 1513 network_tier_help = """\ 1514 Specifies the network tier that will be used to configure the instance. 1515 ``NETWORK_TIER'' must be one of: `PREMIUM`, `STANDARD`. The default 1516 value is `PREMIUM`. 1517 """ 1518 else: 1519 network_tier_help = """\ 1520 Specifies the network tier of the access configuration. ``NETWORK_TIER'' 1521 must be one of: `PREMIUM`, `STANDARD`. The default value is `PREMIUM`. 1522 """ 1523 parser.add_argument( 1524 '--network-tier', type=lambda x: x.upper(), help=network_tier_help) 1525 1526 1527def AddDisplayDeviceArg(parser, is_update=False): 1528 """Adds public DNS arguments for instance or access configuration.""" 1529 display_help = 'Enable a display device on VM instances.' 1530 if not is_update: 1531 display_help += ' Disabled by default.' 1532 parser.add_argument( 1533 '--enable-display-device', 1534 action=arg_parsers.StoreTrueFalseAction if is_update else 'store_true', 1535 help=display_help) 1536 1537 1538def AddPublicDnsArgs(parser, instance=True): 1539 """Adds public DNS arguments for instance or access configuration.""" 1540 1541 public_dns_args = parser.add_mutually_exclusive_group() 1542 if instance: 1543 no_public_dns_help = """\ 1544 If provided, the instance will not be assigned a public DNS name. 1545 """ 1546 else: 1547 no_public_dns_help = """\ 1548 If provided, the external IP in the access configuration will not be 1549 assigned a public DNS name. 1550 """ 1551 public_dns_args.add_argument( 1552 '--no-public-dns', action='store_true', help=no_public_dns_help) 1553 1554 if instance: 1555 public_dns_help = """\ 1556 Assigns a public DNS name to the instance. 1557 """ 1558 else: 1559 public_dns_help = """\ 1560 Assigns a public DNS name to the external IP in the access 1561 configuration. This option can only be specified for the default 1562 network-interface, "nic0". 1563 """ 1564 public_dns_args.add_argument( 1565 '--public-dns', action='store_true', help=public_dns_help) 1566 1567 1568def AddPublicPtrArgs(parser, instance=True): 1569 """Adds public PTR arguments for instance or access configuration.""" 1570 1571 public_ptr_args = parser.add_mutually_exclusive_group() 1572 if instance: 1573 no_public_ptr_help = """\ 1574 If provided, no DNS PTR record is created for the external IP of the 1575 instance. Mutually exclusive with public-ptr-domain. 1576 """ 1577 else: 1578 no_public_ptr_help = """\ 1579 If provided, no DNS PTR record is created for the external IP in the 1580 access configuration. Mutually exclusive with public-ptr-domain. 1581 """ 1582 public_ptr_args.add_argument( 1583 '--no-public-ptr', action='store_true', help=no_public_ptr_help) 1584 1585 if instance: 1586 public_ptr_help = """\ 1587 Creates a DNS PTR record for the external IP of the instance. 1588 """ 1589 else: 1590 public_ptr_help = """\ 1591 Creates a DNS PTR record for the external IP in the access 1592 configuration. This option can only be specified for the default 1593 network-interface, "nic0".""" 1594 public_ptr_args.add_argument( 1595 '--public-ptr', action='store_true', help=public_ptr_help) 1596 1597 public_ptr_domain_args = parser.add_mutually_exclusive_group() 1598 if instance: 1599 no_public_ptr_domain_help = """\ 1600 If both this flag and --public-ptr are specified, creates a DNS PTR 1601 record for the external IP of the instance with the PTR domain name 1602 being the DNS name of the instance. 1603 """ 1604 else: 1605 no_public_ptr_domain_help = """\ 1606 If both this flag and --public-ptr are specified, creates a DNS PTR 1607 record for the external IP in the access configuration with the PTR 1608 domain name being the DNS name of the instance. 1609 """ 1610 public_ptr_domain_args.add_argument( 1611 '--no-public-ptr-domain', 1612 action='store_true', 1613 help=no_public_ptr_domain_help) 1614 1615 if instance: 1616 public_ptr_domain_help = """\ 1617 Assigns a custom PTR domain for the external IP of the instance. 1618 Mutually exclusive with no-public-ptr. 1619 """ 1620 else: 1621 public_ptr_domain_help = """\ 1622 Assigns a custom PTR domain for the external IP in the access 1623 configuration. Mutually exclusive with no-public-ptr. This option can 1624 only be specified for the default network-interface, "nic0". 1625 """ 1626 public_ptr_domain_args.add_argument( 1627 '--public-ptr-domain', help=public_ptr_domain_help) 1628 1629 1630def AddIpv6PublicPtrDomainArg(parser): 1631 """Adds IPv6 public PTR domain for IPv6 access configuration of instance.""" 1632 parser.add_argument( 1633 '--ipv6-public-ptr-domain', 1634 default=None, 1635 help="""\ 1636 Assigns a custom PTR domain for the external IPv6 in the IPv6 access 1637 configuration of instance. If its value is not specified, the default 1638 PTR record will be used. This option can only be specified for the default 1639 network interface, ``nic0''.""") 1640 1641 1642def AddIpv6PublicPtrArgs(parser): 1643 """Adds IPv6 public PTR arguments for ipv6 access configuration.""" 1644 1645 ipv6_public_ptr_args = parser.add_mutually_exclusive_group() 1646 no_ipv6_public_ptr_help = """\ 1647 If provided, the default DNS PTR record will replace the existing one 1648 for external IPv6 in the IPv6 access configuration. Mutually exclusive 1649 with ipv6-public-ptr-domain. 1650 """ 1651 ipv6_public_ptr_args.add_argument( 1652 '--no-ipv6-public-ptr', action='store_true', help=no_ipv6_public_ptr_help) 1653 1654 ipv6_public_ptr_domain_help = """\ 1655 Assigns a custom PTR domain for the external IPv6 in the access 1656 configuration. Mutually exclusive with no-ipv6-public-ptr. This option 1657 can only be specified for the default network interface, ``nic0''. 1658 """ 1659 ipv6_public_ptr_args.add_argument( 1660 '--ipv6-public-ptr-domain', help=ipv6_public_ptr_domain_help) 1661 1662 1663def ValidatePublicDnsFlags(args): 1664 """Validates the values of public DNS related flags.""" 1665 1666 network_interface = getattr(args, 'network_interface', None) 1667 public_dns = getattr(args, 'public_dns', None) 1668 if public_dns: 1669 if (network_interface is not None and 1670 network_interface != constants.DEFAULT_NETWORK_INTERFACE): 1671 raise exceptions.ToolException( 1672 'Public DNS can only be enabled for default network interface ' 1673 '\'{0}\' rather than \'{1}\'.'.format( 1674 constants.DEFAULT_NETWORK_INTERFACE, network_interface)) 1675 1676 1677def ValidatePublicPtrFlags(args): 1678 """Validates the values of public PTR related flags.""" 1679 1680 network_interface = getattr(args, 'network_interface', None) 1681 public_ptr = getattr(args, 'public_ptr', None) 1682 if public_ptr is True: # pylint:disable=g-bool-id-comparison 1683 if (network_interface is not None and 1684 network_interface != constants.DEFAULT_NETWORK_INTERFACE): 1685 raise exceptions.ToolException( 1686 'Public PTR can only be enabled for default network interface ' 1687 '\'{0}\' rather than \'{1}\'.'.format( 1688 constants.DEFAULT_NETWORK_INTERFACE, network_interface)) 1689 1690 if args.public_ptr_domain is not None and args.no_public_ptr is True: # pylint:disable=g-bool-id-comparison 1691 raise exceptions.ConflictingArgumentsException('--public-ptr-domain', 1692 '--no-public-ptr') 1693 1694 1695def ValidateIpv6PublicPtrFlags(args): 1696 """Validates the values of IPv6 public PTR related flags.""" 1697 1698 network_interface = getattr(args, 'network_interface', None) 1699 1700 if args.ipv6_public_ptr_domain is not None or args.no_ipv6_public_ptr: 1701 if (network_interface is not None and 1702 network_interface != constants.DEFAULT_NETWORK_INTERFACE): 1703 raise exceptions.ToolException( 1704 'IPv6 Public PTR can only be enabled for default network interface ' 1705 '\'{0}\' rather than \'{1}\'.'.format( 1706 constants.DEFAULT_NETWORK_INTERFACE, network_interface)) 1707 1708 if args.ipv6_public_ptr_domain is not None and args.no_ipv6_public_ptr: 1709 raise exceptions.ConflictingArgumentsException('--ipv6-public-ptr-domain', 1710 '--no-ipv6-public-ptr') 1711 1712 1713def ValidateServiceAccountAndScopeArgs(args): 1714 if args.no_service_account and not args.no_scopes: 1715 raise exceptions.RequiredArgumentException( 1716 '--no-scopes', 'required with argument ' 1717 '--no-service-account') 1718 # Reject empty scopes 1719 for scope in (args.scopes or []): 1720 if not scope: 1721 raise exceptions.InvalidArgumentException( 1722 '--scopes', 'Scope cannot be an empty string.') 1723 1724 1725def AddTagsArgs(parser): 1726 parser.add_argument( 1727 '--tags', 1728 type=arg_parsers.ArgList(min_length=1), 1729 metavar='TAG', 1730 help="""\ 1731 Specifies a list of tags to apply to the instance. These tags allow 1732 network firewall rules and routes to be applied to specified VM instances. 1733 See gcloud_compute_firewall-rules_create(1) for more details. 1734 1735 To read more about configuring network tags, read this guide: 1736 https://cloud.google.com/vpc/docs/add-remove-network-tags 1737 1738 To list instances with their respective status and tags, run: 1739 1740 $ gcloud compute instances list --format='table(name,status,tags.list())' 1741 1742 To list instances tagged with a specific tag, `tag1`, run: 1743 1744 $ gcloud compute instances list --filter='tags:tag1' 1745 """) 1746 1747 1748def AddNoRestartOnFailureArgs(parser): 1749 parser.add_argument( 1750 '--restart-on-failure', 1751 action='store_true', 1752 default=True, 1753 help="""\ 1754 The instances will be restarted if they are terminated by Compute Engine. 1755 This does not affect terminations performed by the user. 1756 """) 1757 1758 1759def AddMaintenancePolicyArgs(parser, deprecate=False): 1760 """Adds maintenance behavior related args.""" 1761 help_text = ('Specifies the behavior of the instances when their host ' 1762 'machines undergo maintenance. The default is MIGRATE.') 1763 flag_type = lambda x: x.upper() 1764 action = None 1765 if deprecate: 1766 # Use nested group to group the deprecated arg with the new one. 1767 parser = parser.add_mutually_exclusive_group('Maintenance Behavior.') 1768 parser.add_argument( 1769 '--on-host-maintenance', 1770 dest='maintenance_policy', 1771 choices=MIGRATION_OPTIONS, 1772 type=flag_type, 1773 help=help_text) 1774 action = actions.DeprecationAction( 1775 '--maintenance-policy', 1776 warn='The {flag_name} flag is now deprecated. Please use ' 1777 '`--on-host-maintenance` instead') 1778 parser.add_argument( 1779 '--maintenance-policy', 1780 action=action, 1781 choices=MIGRATION_OPTIONS, 1782 type=flag_type, 1783 help=help_text) 1784 1785 1786def AddAcceleratorArgs(parser): 1787 """Adds Accelerator-related args.""" 1788 # Attaches accelerators (e.g. GPUs) to the instances. e.g. --accelerator 1789 # type=nvidia-tesla-k80,count=4 1790 parser.add_argument( 1791 '--accelerator', 1792 type=arg_parsers.ArgDict(spec={ 1793 'type': str, 1794 'count': int, 1795 }), 1796 help="""\ 1797 Attaches accelerators (e.g. GPUs) to the instances. 1798 1799 *type*::: The specific type (e.g. nvidia-tesla-k80 for nVidia Tesla K80) 1800 of accelerator to attach to the instances. Use 'gcloud compute 1801 accelerator-types list' to learn about all available accelerator types. 1802 1803 *count*::: Number of accelerators to attach to each 1804 instance. The default value is 1. 1805 """) 1806 1807 1808def ValidateAcceleratorArgs(args): 1809 """Valiadates flags specifying accelerators (e.g. 1810 1811 GPUs). 1812 1813 Args: 1814 args: parsed comandline arguments. 1815 1816 Raises: 1817 InvalidArgumentException: when type is not specified in the accelerator 1818 config dictionary. 1819 """ 1820 accelerator_args = getattr(args, 'accelerator', None) 1821 if accelerator_args: 1822 accelerator_type_name = accelerator_args.get('type', '') 1823 if not accelerator_type_name: 1824 raise exceptions.InvalidArgumentException( 1825 '--accelerator', 'accelerator type must be specified. ' 1826 'e.g. --accelerator type=nvidia-tesla-k80,count=2') 1827 1828 1829def AddKonletArgs(parser): 1830 """Adds Konlet-related args.""" 1831 parser.add_argument( 1832 '--container-image', 1833 help="""\ 1834 Full container image name, which should be pulled onto VM instance, 1835 eg. `docker.io/tomcat`. 1836 """) 1837 1838 parser.add_argument( 1839 '--container-command', 1840 help="""\ 1841 Specifies what executable to run when the container starts (overrides 1842 default entrypoint), eg. `nc`. 1843 1844 Default: None (default container entrypoint is used) 1845 """) 1846 1847 parser.add_argument( 1848 '--container-arg', 1849 action='append', 1850 help="""\ 1851 Argument to append to container entrypoint or to override container CMD. 1852 Each argument must have a separate flag. Arguments are appended in the 1853 order of flags. Example: 1854 1855 Assuming the default entry point of the container (or an entry point 1856 overridden with --container-command flag) is a Bourne shell-compatible 1857 executable, in order to execute 'ls -l' command in the container, 1858 the user could use: 1859 1860 `--container-arg="-c" --container-arg="ls -l"` 1861 1862 Caveat: due to the nature of the argument parsing, it's impossible to 1863 provide the flag value that starts with a dash (`-`) without the `=` sign 1864 (that is, `--container-arg "-c"` will not work correctly). 1865 1866 Default: None. (no arguments appended) 1867 """) 1868 1869 parser.add_argument( 1870 '--container-privileged', 1871 action='store_true', 1872 help="""\ 1873 Specify whether to run container in privileged mode. 1874 1875 Default: `--no-container-privileged`. 1876 """) 1877 1878 _AddContainerMountHostPathFlag(parser) 1879 _AddContainerMountTmpfsFlag(parser) 1880 1881 parser.add_argument( 1882 '--container-env', 1883 type=arg_parsers.ArgDict(), 1884 action='append', 1885 metavar='KEY=VALUE, ...', 1886 help="""\ 1887 Declare environment variables KEY with value VALUE passed to container. 1888 Only the last value of KEY is taken when KEY is repeated more than once. 1889 1890 Values, declared with --container-env flag override those with the same 1891 KEY from file, provided in --container-env-file. 1892 """) 1893 1894 parser.add_argument( 1895 '--container-env-file', 1896 help="""\ 1897 Declare environment variables in a file. Values, declared with 1898 --container-env flag override those with the same KEY from file. 1899 1900 File with environment variables in format used by docker (almost). 1901 This means: 1902 - Lines are in format KEY=VALUE. 1903 - Values must contain equality signs. 1904 - Variables without values are not supported (this is different from 1905 docker format). 1906 - If `#` is first non-whitespace character in a line the line is ignored 1907 as a comment. 1908 - Lines with nothing but whitespace are ignored. 1909 """) 1910 1911 parser.add_argument( 1912 '--container-stdin', 1913 action='store_true', 1914 help="""\ 1915 Keep container STDIN open even if not attached. 1916 1917 Default: `--no-container-stdin`. 1918 """) 1919 1920 parser.add_argument( 1921 '--container-tty', 1922 action='store_true', 1923 help="""\ 1924 Allocate a pseudo-TTY for the container. 1925 1926 Default: `--no-container-tty`. 1927 """) 1928 1929 parser.add_argument( 1930 '--container-restart-policy', 1931 choices=['never', 'on-failure', 'always'], 1932 default='always', 1933 metavar='POLICY', 1934 type=lambda val: val.lower(), 1935 help="""\ 1936 Specify whether to restart a container on exit. 1937 """) 1938 1939 1940def ValidateKonletArgs(args): 1941 """Validates Konlet-related args.""" 1942 if not args.IsSpecified('container_image'): 1943 raise exceptions.RequiredArgumentException( 1944 '--container-image', 'You must provide container image') 1945 1946 1947def ValidateLocalSsdFlags(args): 1948 """Validate local ssd flags.""" 1949 for local_ssd in args.local_ssd or []: 1950 interface = local_ssd.get('interface') 1951 if interface and interface not in LOCAL_SSD_INTERFACES: 1952 raise exceptions.InvalidArgumentException( 1953 '--local-ssd:interface', 'Unexpected local SSD interface: [{given}]. ' 1954 'Legal values are [{ok}].'.format( 1955 given=interface, ok=', '.join(LOCAL_SSD_INTERFACES))) 1956 size = local_ssd.get('size') 1957 if size is not None and size % (375 * constants.BYTES_IN_ONE_GB) != 0: 1958 raise exceptions.InvalidArgumentException( 1959 '--local-ssd:size', 'Unexpected local SSD size: [{given}]. ' 1960 'Legal values are positive multiples of 375GB.'.format(given=size)) 1961 1962 1963def ValidateNicFlags(args): 1964 """Validates flags specifying network interface cards. 1965 1966 Args: 1967 args: parsed command line arguments. 1968 1969 Raises: 1970 InvalidArgumentException: when it finds --network-interface that has both 1971 address, and no-address keys. 1972 ConflictingArgumentsException: when it finds --network-interface and at 1973 least one of --address, --network, 1974 --private_network_ip, or --subnet. 1975 """ 1976 network_interface = getattr(args, 'network_interface', None) 1977 if network_interface is None: 1978 return 1979 for ni in network_interface: 1980 if 'address' in ni and 'no-address' in ni: 1981 raise exceptions.InvalidArgumentException( 1982 '--network-interface', 1983 'specifies both address and no-address for one interface') 1984 1985 conflicting_args = ['address', 'network', 'private_network_ip', 'subnet'] 1986 conflicting_args_present = [ 1987 arg for arg in conflicting_args if getattr(args, arg, None) 1988 ] 1989 if not conflicting_args_present: 1990 return 1991 conflicting_args = [ 1992 '--{0}'.format(arg.replace('_', '-')) for arg in conflicting_args_present 1993 ] 1994 raise exceptions.ConflictingArgumentsException( 1995 '--network-interface', 1996 'all of the following: ' + ', '.join(conflicting_args)) 1997 1998 1999def AddDiskScopeFlag(parser): 2000 """Adds --disk-scope flag.""" 2001 parser.add_argument( 2002 '--disk-scope', 2003 choices={ 2004 'zonal': 'The disk specified in --disk is interpreted as a ' 2005 'zonal disk in the same zone as the instance. ' 2006 'Ignored if a full URI is provided to the `--disk` flag.', 2007 'regional': 'The disk specified in --disk is interpreted as a ' 2008 'regional disk in the same region as the instance. ' 2009 'Ignored if a full URI is provided to the `--disk` flag.' 2010 }, 2011 help='The scope of the disk.', 2012 default='zonal') 2013 2014 2015def WarnForSourceInstanceTemplateLimitations(args): 2016 """Warn if --source-instance-template is mixed with unsupported flags. 2017 2018 Args: 2019 args: Argument namespace 2020 """ 2021 allowed_flags = [ 2022 '--project', '--zone', '--region', '--source-instance-template', 2023 'INSTANCE_NAMES:1', '--machine-type', '--custom-cpu', '--custom-memory', 2024 '--labels' 2025 ] 2026 2027 if args.IsSpecified('source_instance_template'): 2028 specified_args = args.GetSpecifiedArgNames() 2029 # TODO(b/62933344) - Improve flag collision detection 2030 for flag in allowed_flags: 2031 if flag in specified_args: 2032 specified_args.remove(flag) 2033 if specified_args: 2034 log.status.write('When a source instance template is used, additional ' 2035 'parameters other than --machine-type and --labels will ' 2036 'be ignored but provided by the source instance ' 2037 'template\n') 2038 2039 2040def ValidateNetworkTierArgs(args): 2041 if (args.network_tier and 2042 args.network_tier not in constants.NETWORK_TIER_CHOICES_FOR_INSTANCE): 2043 raise exceptions.InvalidArgumentException( 2044 '--network-tier', 2045 'Invalid network tier [{tier}]'.format(tier=args.network_tier)) 2046 2047 2048def AddDeletionProtectionFlag(parser, use_default_value=True): 2049 """Adds --deletion-protection Boolean flag. 2050 2051 Args: 2052 parser: ArgumentParser, parser to which flags will be added. 2053 use_default_value: Bool, if True, deletion protection flag will be given the 2054 default value False, else None. Update uses None as an indicator that no 2055 update needs to be done for deletion protection. 2056 """ 2057 help_text = ('Enables deletion protection for the instance.') 2058 action = ('store_true' 2059 if use_default_value else arg_parsers.StoreTrueFalseAction) 2060 parser.add_argument('--deletion-protection', help=help_text, action=action) 2061 2062 2063def AddShieldedInstanceConfigArgs(parser, 2064 use_default_value=True, 2065 for_update=False, 2066 for_container=False): 2067 """Adds flags for Shielded VM configuration. 2068 2069 Args: 2070 parser: ArgumentParser, parser to which flags will be added. 2071 use_default_value: Bool, if True, flag will be given the default value 2072 False, else None. Update uses None as an indicator that no update needs to 2073 be done for deletion protection. 2074 for_update: Bool, if True, flags are intended for an update operation. 2075 for_container: Bool, if True, flags intended for an instances with container 2076 operation. 2077 """ 2078 if use_default_value: 2079 default_action = 'store_true' 2080 action_kwargs = {'default': None} 2081 else: 2082 default_action = arg_parsers.StoreTrueFalseAction 2083 action_kwargs = {} 2084 2085 # --shielded-secure-boot 2086 secure_boot_help = """\ 2087 The instance boots with secure boot enabled. On Shielded VM instances, 2088 Secure Boot is not enabled by default. For information about how to modify 2089 Shielded VM options, see 2090 https://cloud.google.com/compute/docs/instances/modifying-shielded-vm. 2091 """ 2092 if for_update: 2093 secure_boot_help += """\ 2094 Changes to this setting with the update command only take effect 2095 after stopping and starting the instance. 2096 """ 2097 2098 parser.add_argument( 2099 '--shielded-secure-boot', 2100 help=secure_boot_help, 2101 dest='shielded_vm_secure_boot', 2102 action=default_action, 2103 **action_kwargs) 2104 2105 # --shielded-vtpm 2106 vtpm_help = """\ 2107 The instance boots with the TPM (Trusted Platform Module) enabled. 2108 A TPM is a hardware module that can be used for different security 2109 operations such as remote attestation, encryption, and sealing of keys. 2110 On Shielded VM instances, vTPM is enabled by default. For information 2111 about how to modify Shielded VM options, see 2112 https://cloud.google.com/compute/docs/instances/modifying-shielded-vm. 2113 """ 2114 if for_update: 2115 vtpm_help += """\ 2116 Changes to this setting with the update command only take effect 2117 after stopping and starting the instance. 2118 """ 2119 2120 parser.add_argument( 2121 '--shielded-vtpm', 2122 dest='shielded_vm_vtpm', 2123 help=vtpm_help, 2124 action=default_action, 2125 **action_kwargs) 2126 2127 # --shielded-integrity-monitoring 2128 integrity_monitoring_help_format = """\ 2129 Enables monitoring and attestation of the boot integrity of the 2130 instance. The attestation is performed against the integrity policy 2131 baseline. This baseline is initially derived from the implicitly 2132 trusted boot image when the instance is created. This baseline can be 2133 updated by using 2134 `gcloud compute instances {} --shielded-learn-integrity-policy`. On 2135 Shielded VM instances, integrity monitoring is enabled by default. For 2136 information about how to modify Shielded VM options, see 2137 https://cloud.google.com/compute/docs/instances/modifying-shielded-vm. 2138 For information about monitoring integrity on Shielded VM instances, see 2139 https://cloud.google.com/compute/docs/instances/integrity-monitoring." 2140 """ 2141 if for_container: 2142 update_command = 'update-container' 2143 else: 2144 update_command = 'update' 2145 integrity_monitoring_help = integrity_monitoring_help_format.format( 2146 update_command) 2147 if for_update: 2148 integrity_monitoring_help += """\ 2149 Changes to this setting with the update command only take effect 2150 after stopping and starting the instance. 2151 """ 2152 2153 parser.add_argument( 2154 '--shielded-integrity-monitoring', 2155 help=integrity_monitoring_help, 2156 dest='shielded_vm_integrity_monitoring', 2157 action=default_action, 2158 **action_kwargs) 2159 2160 2161def AddShieldedInstanceIntegrityPolicyArgs(parser): 2162 """Adds flags for shielded instance integrity policy settings.""" 2163 help_text = """\ 2164 Causes the instance to re-learn the integrity policy baseline using 2165 the current instance configuration. Use this flag after any planned 2166 boot-specific changes in the instance configuration, like kernel 2167 updates or kernel driver installation. 2168 """ 2169 default_action = 'store_true' 2170 parser.add_argument( 2171 '--shielded-learn-integrity-policy', 2172 dest='shielded_vm_learn_integrity_policy', 2173 action=default_action, 2174 default=None, 2175 help=help_text) 2176 2177 2178def AddConfidentialComputeArgs(parser): 2179 """Adds flags for confidential compute for instance.""" 2180 help_text = """\ 2181 The instance boots with Confidential Computing enabled. Confidential 2182 Computing is based on Secure Encrypted Virtualization (SEV), an AMD 2183 virtualization feature for running confidential instances. 2184 """ 2185 parser.add_argument( 2186 '--confidential-compute', 2187 dest='confidential_compute', 2188 action='store_true', 2189 default=None, 2190 help=help_text) 2191 2192 2193def AddHostnameArg(parser): 2194 """Adds flag for overriding hostname for instance.""" 2195 parser.add_argument( 2196 '--hostname', 2197 help="""\ 2198 Specify the hostname of the instance to be created. The specified 2199 hostname must be RFC1035 compliant. If hostname is not specified, the 2200 default hostname is [INSTANCE_NAME].c.[PROJECT_ID].internal when using 2201 the global DNS, and [INSTANCE_NAME].[ZONE].c.[PROJECT_ID].internal 2202 when using zonal DNS. 2203 """) 2204 2205 2206def AddReservationAffinityGroup(parser, group_text, affinity_text): 2207 """Adds the argument group to handle reservation affinity configurations.""" 2208 group = parser.add_group(help=group_text) 2209 group.add_argument( 2210 '--reservation-affinity', 2211 choices=['any', 'none', 'specific'], 2212 default='any', 2213 help=affinity_text) 2214 group.add_argument( 2215 '--reservation', 2216 help=""" 2217The name of the reservation, required when `--reservation-affinity=specific`. 2218""") 2219 2220 2221def ValidateReservationAffinityGroup(args): 2222 """Validates flags specifying reservation affinity.""" 2223 affinity = getattr(args, 'reservation_affinity', None) 2224 if affinity == 'specific': 2225 if not args.IsSpecified('reservation'): 2226 raise exceptions.InvalidArgumentException( 2227 '--reservation', 2228 'The name the specific reservation must be specified.') 2229 2230 2231def _GetContainerMountDescriptionAndNameDescription(for_update=False): 2232 """Get description text for --container-mount-disk flag.""" 2233 if for_update: 2234 description = ("""\ 2235Mounts a disk to the container by using mount-path or updates how the volume is 2236mounted if the same mount path has already been declared. The disk must already 2237be attached to the instance with a device-name that matches the disk name. 2238Multiple flags are allowed. 2239""") 2240 name_description = ("""\ 2241Name of the disk. Can be omitted if exactly one additional disk is attached to 2242the instance. The name of the single additional disk will be used by default. 2243""") 2244 return description, name_description 2245 else: 2246 description = ("""\ 2247Mounts a disk to the specified mount path in the container. Multiple ' 2248flags are allowed. Must be used with `--disk` or `--create-disk`. 2249""") 2250 name_description = ("""\ 2251Name of the disk. If exactly one additional disk is attached 2252to the instance using `--disk` or `--create-disk`, specifying disk 2253name here is optional. The name of the single additional disk will be 2254used by default. 2255""") 2256 return description, name_description 2257 2258 2259def ParseMountVolumeMode(argument_name, mode): 2260 """Parser for mode option in ArgDict specs.""" 2261 if not mode or mode == 'rw': 2262 return containers_utils.MountVolumeMode.READ_WRITE 2263 elif mode == 'ro': 2264 return containers_utils.MountVolumeMode.READ_ONLY 2265 else: 2266 raise exceptions.InvalidArgumentException(argument_name, 2267 'Mode can only be [ro] or [rw].') 2268 2269 2270def AddContainerMountDiskFlag(parser, for_update=False): 2271 """Add --container-mount-disk flag.""" 2272 description, name_description = ( 2273 _GetContainerMountDescriptionAndNameDescription(for_update=for_update)) 2274 help_text = ("""\ 2275{} 2276 2277*name*::: {} 2278 2279*mount-path*::: Path on container to mount to. Mount paths with spaces 2280 and commas (and other special characters) are not supported by this 2281 command. 2282 2283*partition*::: Optional. The partition of the disk to mount. Multiple 2284partitions of a disk may be mounted.{} 2285 2286*mode*::: Volume mount mode: `rw` (read/write) or `ro` (read-only). 2287Defaults to `rw`. Fails if the disk mode is `ro` and volume mount mode 2288is `rw`. 2289""".format(description, name_description, 2290 '' if for_update else ' May not be used with --create-disk.')) 2291 2292 spec = { 2293 'name': str, 2294 'mount-path': str, 2295 'partition': int, 2296 'mode': functools.partial(ParseMountVolumeMode, '--container-mount-disk') 2297 } 2298 parser.add_argument( 2299 '--container-mount-disk', 2300 type=arg_parsers.ArgDict(spec=spec, required_keys=['mount-path']), 2301 help=help_text, 2302 action='append') 2303 2304 2305def _GetMatchingDiskFromMessages(holder, mount_disk_name, disk, client=None): 2306 """Helper to match a mount disk's name to a disk message.""" 2307 if client is None: 2308 client = apis.GetClientClass('compute', 'alpha') 2309 if mount_disk_name is None and len(disk) == 1: 2310 return { 2311 'name': 2312 holder.resources.Parse(disk[0].source).Name(), 2313 'device_name': 2314 disk[0].deviceName, 2315 'ro': 2316 (disk[0].mode == 2317 client.MESSAGES_MODULE.AttachedDisk.ModeValueValuesEnum.READ_WRITE) 2318 }, False 2319 for disk_spec in disk: 2320 disk_name = holder.resources.Parse(disk_spec.source).Name() 2321 if disk_name == mount_disk_name: 2322 return { 2323 'name': 2324 disk_name, 2325 'device_name': 2326 disk_spec.deviceName, 2327 'ro': (disk_spec.mode == client.MESSAGES_MODULE.AttachedDisk 2328 .ModeValueValuesEnum.READ_WRITE) 2329 }, False 2330 return None, None 2331 2332 2333def _GetMatchingDiskFromFlags(mount_disk_name, disk, create_disk): 2334 """Helper to match a mount disk's name to a disk spec from a flag.""" 2335 2336 def _GetMatchingDiskFromSpec(spec): 2337 return { 2338 'name': spec.get('name'), 2339 'device_name': spec.get('device-name'), 2340 'ro': spec.get('mode') == 'ro' 2341 } 2342 2343 if mount_disk_name is None and len(disk + create_disk) == 1: 2344 disk_spec = (disk + create_disk)[0] 2345 return _GetMatchingDiskFromSpec(disk_spec), bool(create_disk) 2346 for disk_spec in disk: 2347 if disk_spec.get('name') == mount_disk_name: 2348 return _GetMatchingDiskFromSpec(disk_spec), False 2349 for disk_spec in create_disk: 2350 if disk_spec.get('name') == mount_disk_name: 2351 return _GetMatchingDiskFromSpec(disk_spec), True 2352 return None, None 2353 2354 2355def _CheckMode(name, mode_value, mount_disk, matching_disk, create): 2356 """Make sure the correct mode is specified for container mount disk.""" 2357 partition = mount_disk.get('partition') 2358 if (mode_value == containers_utils.MountVolumeMode.READ_WRITE and 2359 matching_disk.get('ro')): 2360 raise exceptions.InvalidArgumentException( 2361 '--container-mount-disk', 2362 'Value for [mode] in [--container-mount-disk] cannot be [rw] if the ' 2363 'disk is attached in [ro] mode: disk name [{}], partition [{}]'.format( 2364 name, partition)) 2365 if matching_disk.get('ro') and create: 2366 raise exceptions.InvalidArgumentException( 2367 '--container-mount-disk', 2368 'Cannot mount disk named [{}] to container: disk is created in [ro] ' 2369 'mode and thus cannot be formatted.'.format(name)) 2370 2371 2372def GetValidatedContainerMountDisk(holder, 2373 container_mount_disk, 2374 disk, 2375 create_disk, 2376 for_update=False, 2377 client=None): 2378 """Validate --container-mount-disk value.""" 2379 disk = disk or [] 2380 create_disk = create_disk or [] 2381 if not container_mount_disk: 2382 return 2383 if not (disk or create_disk or for_update): 2384 raise exceptions.InvalidArgumentException( 2385 '--container-mount-disk', 2386 'Must be used with `--disk` or `--create-disk`') 2387 2388 message = '' if for_update else ' using `--disk` or `--create-disk`.' 2389 validated_disks = [] 2390 for mount_disk in container_mount_disk: 2391 if for_update: 2392 matching_disk, create = _GetMatchingDiskFromMessages( 2393 holder, mount_disk.get('name'), disk, client=client) 2394 else: 2395 matching_disk, create = _GetMatchingDiskFromFlags( 2396 mount_disk.get('name'), disk, create_disk) 2397 if not mount_disk.get('name'): 2398 if len(disk + create_disk) != 1: 2399 raise exceptions.InvalidArgumentException( 2400 '--container-mount-disk', 2401 'Must specify the name of the disk to be mounted unless exactly ' 2402 'one disk is attached to the instance{}.'.format(message)) 2403 name = matching_disk.get('name') 2404 if not name: 2405 raise exceptions.InvalidArgumentException( 2406 '--container-mount-disk', 2407 'When attaching or creating a disk that is also being mounted to ' 2408 'a container, must specify the disk name.') 2409 else: 2410 name = mount_disk.get('name') 2411 if not matching_disk: 2412 raise exceptions.InvalidArgumentException( 2413 '--container-mount-disk', 2414 'Attempting to mount a disk that is not attached to the instance: ' 2415 'must attach a disk named [{}]{}'.format(name, message)) 2416 if (matching_disk and matching_disk.get('device_name') and 2417 matching_disk.get('device_name') != matching_disk.get('name')): 2418 raise exceptions.InvalidArgumentException( 2419 '--container-mount-disk', 2420 'Container mount disk cannot be used with a device whose device-name ' 2421 'is different from its name. The disk with name [{}] has the ' 2422 'device-name [{}].'.format( 2423 matching_disk.get('name'), matching_disk.get('device_name'))) 2424 2425 mode_value = mount_disk.get('mode') 2426 if matching_disk: 2427 _CheckMode(name, mode_value, mount_disk, matching_disk, create) 2428 if matching_disk and create and mount_disk.get('partition'): 2429 raise exceptions.InvalidArgumentException( 2430 '--container-mount-disk', 2431 'Container mount disk cannot specify a partition when the disk ' 2432 'is created with --create-disk: disk name [{}], partition [{}]' 2433 .format(name, mount_disk.get('partition'))) 2434 mount_disk = copy.deepcopy(mount_disk) 2435 mount_disk['name'] = mount_disk.get('name') or name 2436 validated_disks.append(mount_disk) 2437 return validated_disks 2438 2439 2440def NonEmptyString(parameter_name): 2441 2442 def Factory(string): 2443 if not string: 2444 raise exceptions.InvalidArgumentException(parameter_name, 2445 'Empty string is not allowed.') 2446 return string 2447 2448 return Factory 2449 2450 2451def _AddContainerEnvGroup(parser): 2452 """Add flags to update the container environment.""" 2453 2454 env_group = parser.add_argument_group() 2455 2456 env_group.add_argument( 2457 '--container-env', 2458 type=arg_parsers.ArgDict(), 2459 action='append', 2460 metavar='KEY=VALUE, ...', 2461 help="""\ 2462 Update environment variables `KEY` with value `VALUE` passed to 2463 container. 2464 - Sets `KEY` to the specified value. 2465 - Adds `KEY` = `VALUE`, if `KEY` is not yet declared. 2466 - Only the last value of `KEY` is taken when `KEY` is repeated more 2467 than once. 2468 2469 Values, declared with `--container-env` flag override those with the 2470 same `KEY` from file, provided in `--container-env-file`. 2471 """) 2472 2473 env_group.add_argument( 2474 '--container-env-file', 2475 help="""\ 2476 Update environment variables from a file. 2477 Same update rules as for `--container-env` apply. 2478 Values, declared with `--container-env` flag override those with the 2479 same `KEY` from file. 2480 2481 File with environment variables declarations in format used by docker 2482 (almost). This means: 2483 - Lines are in format KEY=VALUE 2484 - Values must contain equality signs. 2485 - Variables without values are not supported (this is different from 2486 docker format). 2487 - If # is first non-whitespace character in a line the line is ignored 2488 as a comment. 2489 """) 2490 2491 env_group.add_argument( 2492 '--remove-container-env', 2493 type=arg_parsers.ArgList(), 2494 action='append', 2495 metavar='KEY', 2496 help="""\ 2497 Removes environment variables `KEY` from container declaration Does 2498 nothing, if a variable is not present. 2499 """) 2500 2501 2502def _AddContainerArgGroup(parser): 2503 """Add flags to update the container arg.""" 2504 2505 arg_group = parser.add_mutually_exclusive_group() 2506 2507 arg_group.add_argument( 2508 '--container-arg', 2509 action='append', 2510 help="""\ 2511 Completely replaces the list of arguments with the new list. 2512 Each argument must have a separate --container-arg flag. 2513 Arguments are appended the new list in the order of flags. 2514 2515 Cannot be used in the same command with `--clear-container-arg`. 2516 """) 2517 2518 arg_group.add_argument( 2519 '--clear-container-args', 2520 action='store_true', 2521 default=None, 2522 help="""\ 2523 Removes the list of arguments from container declaration. 2524 2525 Cannot be used in the same command with `--container-arg`. 2526 """) 2527 2528 2529def _AddContainerCommandGroup(parser): 2530 """Add flags to update the command in the container declaration.""" 2531 command_group = parser.add_mutually_exclusive_group() 2532 2533 command_group.add_argument( 2534 '--container-command', 2535 type=NonEmptyString('--container-command'), 2536 help="""\ 2537 Sets command in the declaration to the specified value. 2538 Empty string is not allowed. 2539 2540 Cannot be used in the same command with `--clear-container-command`. 2541 """) 2542 2543 command_group.add_argument( 2544 '--clear-container-command', 2545 action='store_true', 2546 default=None, 2547 help="""\ 2548 Removes command from container declaration. 2549 2550 Cannot be used in the same command with `--container-command`. 2551 """) 2552 2553 2554def _AddContainerMountHostPathFlag(parser, for_update=False): 2555 """Helper to add --container-mount-host-path flag.""" 2556 if for_update: 2557 additional = """\ 2558 2559 - Adds a volume, if `mount-path` is not yet declared. 2560 - Replaces a volume, if `mount-path` is declared. 2561 All parameters (`host-path`, `mount-path`, `mode`) are completely 2562 replaced.""" 2563 else: 2564 additional = '' 2565 parser.add_argument( 2566 '--container-mount-host-path', 2567 metavar='host-path=HOSTPATH,mount-path=MOUNTPATH[,mode=MODE]', 2568 type=arg_parsers.ArgDict( 2569 spec={ 2570 'host-path': 2571 str, 2572 'mount-path': 2573 str, 2574 'mode': 2575 functools.partial(ParseMountVolumeMode, 2576 '--container-mount-host-path') 2577 }), 2578 action='append', 2579 help="""\ 2580 Mounts a volume by using host-path.{} 2581 2582 *host-path*::: Path on host to mount from. 2583 2584 *mount-path*::: Path on container to mount to. Mount paths with spaces 2585 and commas (and other special characters) are not supported by this 2586 command. 2587 2588 *mode*::: Volume mount mode: rw (read/write) or ro (read-only). 2589 2590 Default: rw. 2591 """.format(additional)) 2592 2593 2594def _AddContainerMountTmpfsFlag(parser): 2595 """Helper to add --container-mount-tmpfs flag.""" 2596 parser.add_argument( 2597 '--container-mount-tmpfs', 2598 metavar='mount-path=MOUNTPATH', 2599 type=arg_parsers.ArgDict(spec={'mount-path': str}), 2600 action='append', 2601 help="""\ 2602 Mounts empty tmpfs into container at MOUNTPATH. 2603 2604 *mount-path*::: Path on container to mount to. Mount paths with spaces 2605 and commas (and other special characters) are not supported by this 2606 command. 2607 """) 2608 2609 2610def _AddContainerMountGroup(parser, container_mount_disk_enabled=False): 2611 """Add flags to update what is mounted to the container.""" 2612 2613 mount_group = parser.add_argument_group() 2614 2615 _AddContainerMountHostPathFlag(mount_group, for_update=True) 2616 _AddContainerMountTmpfsFlag(mount_group) 2617 2618 if container_mount_disk_enabled: 2619 AddContainerMountDiskFlag(parser, for_update=True) 2620 2621 mount_types = ['`host-path`', '`tmpfs`'] 2622 if container_mount_disk_enabled: 2623 mount_types.append('`disk`') 2624 mount_group.add_argument( 2625 '--remove-container-mounts', 2626 type=arg_parsers.ArgList(), 2627 metavar='MOUNTPATH[,MOUNTPATH,...]', 2628 help="""\ 2629 Removes volume mounts ({}) with 2630 `mountPath: MOUNTPATH` from container declaration. 2631 2632 Does nothing, if a volume mount is not declared. 2633 """.format(', '.join(mount_types))) 2634 2635 2636def _AddContainerArgs(parser): 2637 """Add basic args for update-container.""" 2638 2639 parser.add_argument( 2640 '--container-image', 2641 type=NonEmptyString('--container-image'), 2642 help="""\ 2643 Sets container image in the declaration to the specified value. 2644 2645 Empty string is not allowed. 2646 """) 2647 2648 parser.add_argument( 2649 '--container-privileged', 2650 action='store_true', 2651 default=None, 2652 help="""\ 2653 Sets permission to run container to the specified value. 2654 """) 2655 2656 parser.add_argument( 2657 '--container-stdin', 2658 action='store_true', 2659 default=None, 2660 help="""\ 2661 Sets configuration whether to keep container `STDIN` always open to the 2662 specified value. 2663 """) 2664 2665 parser.add_argument( 2666 '--container-tty', 2667 action='store_true', 2668 default=None, 2669 help="""\ 2670 Sets configuration whether to allocate a pseudo-TTY for the container 2671 to the specified value. 2672 """) 2673 2674 parser.add_argument( 2675 '--container-restart-policy', 2676 choices=['never', 'on-failure', 'always'], 2677 metavar='POLICY', 2678 type=lambda val: val.lower(), 2679 help="""\ 2680 Sets container restart policy to the specified value. 2681 """) 2682 2683 2684def AddUpdateContainerArgs(parser, container_mount_disk_enabled=False): 2685 """Add all args to update the container environment.""" 2686 INSTANCE_ARG.AddArgument(parser, operation_type='update') 2687 _AddContainerCommandGroup(parser) 2688 _AddContainerEnvGroup(parser) 2689 _AddContainerArgGroup(parser) 2690 _AddContainerMountGroup( 2691 parser, container_mount_disk_enabled=container_mount_disk_enabled) 2692 _AddContainerArgs(parser) 2693 AddShieldedInstanceConfigArgs( 2694 parser, use_default_value=False, for_update=True, for_container=True) 2695 AddShieldedInstanceIntegrityPolicyArgs(parser) 2696 2697 2698def AddPostKeyRevocationActionTypeArgs(parser): 2699 """Helper to add --post-key-revocation-action-type flag.""" 2700 parser.add_argument( 2701 '--post-key-revocation-action-type', 2702 choices=['noop', 'shutdown'], 2703 metavar='POLICY', 2704 required=False, 2705 help="""\ 2706 The instance will be shut down when the KMS key of one of its disk is 2707 revoked, if set to `SHUTDOWN`. 2708 2709 Default setting is `NOOP`. 2710 """) 2711 2712 2713def AddBulkCreateArgs(parser): 2714 """Adds bulk creation specific arguments to parser.""" 2715 parser.add_argument( 2716 '--count', 2717 type=int, 2718 help=""" 2719 Number of Compute Engine virtual machines to create. If specified, and 2720 `--predefined-names` is specified, count must equal the amount of names 2721 provided to `--predefined-names`. If not specified, 2722 the number of virtual machines created will equal the number of names 2723 provided to `--predefined-names`. 2724 """) 2725 parser.add_argument( 2726 '--min-count', 2727 type=int, 2728 help=""" 2729 The minimum number of Compute Engine virtual machines that must be 2730 successfully created for the operation to be considered a success. If 2731 the operation successfully creates as many virtual machines as 2732 specified here they will be persisted, otherwise the operation rolls 2733 back and deletes all created virtual machines. If not specified, this 2734 value is equal to `--count`.""") 2735 2736 name_group = parser.add_group(mutex=True, required=True) 2737 name_group.add_argument( 2738 '--predefined-names', 2739 type=arg_parsers.ArgList(), 2740 metavar='INSTANCE_NAME', 2741 help=""" 2742 List of predefined names for the Compute Engine virtual machines being 2743 created. If `--count` is specified alongside this flag, provided count 2744 must equal the amount of names provided to this flag. If `--count` is 2745 not specified, the number of virtual machines 2746 created will equal the number of names provided. 2747 """) 2748 name_group.add_argument( 2749 '--name-pattern', 2750 help=""" 2751 Name pattern for generating instance names. Specify a pattern with a 2752 single sequence of hash (#) characters that will be replaced with 2753 generated sequential numbers of instances. E.g. name pattern of 2754 'instance-###' will generate instance names 'instance-001', 2755 'instance-002', and so on, until the number of virtual machines 2756 specified using `--count` is reached. If instances matching name pattern 2757 exist, the new instances will be assigned names to avoid clashing with 2758 the existing ones. E.g. if there exists `instance-123`, the new 2759 instances will start at `instance-124` and increment from there. 2760 """) 2761 location = parser.add_group(required=True, mutex=True) 2762 location.add_argument( 2763 '--region', 2764 help=""" 2765 Region in which to create the Compute Engine virtual machines. Compute 2766 Engine will select a zone in which to create all virtual machines. 2767 """) 2768 location.add_argument( 2769 '--zone', 2770 help=""" 2771 Zone in which to create the Compute Engine virtual machines. 2772 2773 A list of zones can be fetched by running: 2774 2775 $ gcloud compute zones list 2776 2777 To unset the property, run: 2778 2779 $ gcloud config unset compute/zone 2780 2781 Alternatively, the zone can be stored in the environment variable 2782 CLOUDSDK_COMPUTE_ZONE. 2783 """) 2784 parser.add_argument( 2785 '--location-policy', 2786 metavar='ZONE=POLICY', 2787 type=arg_parsers.ArgDict(), 2788 help=""" 2789 Policy for which zones to include or exclude during bulk instance creation 2790 within a region. Policy is defined as a list of key-value pairs, with the 2791 key being the zone name, and value being the applied policy. Available 2792 policies are `allow` and `deny`. Default for zones left unspecified is `allow`. 2793 2794 Example: 2795 2796 gcloud compute instances bulk create --name-pattern=example-### 2797 --count=5 --region=us-east1 2798 --location-policy=us-east1-b=allow,us-east1-c=deny 2799 """) 2800 2801 2802def ValidateBulkCreateArgs(args): 2803 if args.IsSpecified('name_pattern') and not args.IsSpecified('count'): 2804 raise exceptions.RequiredArgumentException( 2805 '--count', 2806 """The `--count` argument must be specified when the `--name-pattern` argument is specified.""" 2807 ) 2808 if args.IsSpecified('location_policy') and (args.IsSpecified('zone') or 2809 not args.IsSpecified('region')): 2810 raise exceptions.RequiredArgumentException( 2811 '--region', 2812 """The '--region' argument must be used alongside the '--location-policy' argument and not '--zone'.""" 2813 ) 2814 2815 2816def ValidateLocationPolicyArgs(args): 2817 """Validates args supplied to --location-policy.""" 2818 if args.IsSpecified('location_policy'): 2819 for zone, policy in args.location_policy.items(): 2820 zone_split = zone.split('-') 2821 if len(zone_split) != 3 or ( 2822 len(zone_split[2]) != 1 or 2823 not zone_split[2].isalpha()) or not zone_split[1][-1].isdigit(): 2824 raise exceptions.InvalidArgumentException( 2825 '--location-policy', 'Key [{}] must be a zone.'.format(zone)) 2826 2827 if policy not in ['allow', 'deny']: 2828 raise exceptions.InvalidArgumentException( 2829 '--location-policy', 2830 'Value [{}] must be one of [allow, deny]'.format(policy)) 2831 2832 2833def AddBulkCreateNetworkingArgs(parser): 2834 """Adds Networkign Args for Bulk Create Command.""" 2835 2836 multiple_network_interface_cards_spec = { 2837 'network': str, 2838 'subnet': str, 2839 } 2840 2841 def ValidateNetworkTier(network_tier_input): 2842 network_tier = network_tier_input.upper() 2843 if network_tier in constants.NETWORK_TIER_CHOICES_FOR_INSTANCE: 2844 return network_tier 2845 else: 2846 raise exceptions.InvalidArgumentException( 2847 '--network-interface', 'Invalid value for network-tier') 2848 2849 multiple_network_interface_cards_spec['network-tier'] = ValidateNetworkTier 2850 2851 network_interface_help = """\ 2852 Adds a network interface to the instance. Mutually exclusive with any 2853 of these flags: *--network*, *--network-tier*, *--subnet*. 2854 This flag can be repeated to specify multiple network interfaces. 2855 2856 *network*::: Specifies the network that the interface will be part of. 2857 If subnet is also specified it must be subnetwork of this network. If 2858 neither is specified, this defaults to the "default" network. 2859 2860 *network-tier*::: Specifies the network tier of the interface. 2861 ``NETWORK_TIER'' must be one of: `PREMIUM`, `STANDARD`. The default 2862 value is `PREMIUM`. 2863 2864 *subnet*::: Specifies the subnet that the interface will be part of. 2865 If network key is also specified this must be a subnetwork of the 2866 specified network. 2867 """ 2868 2869 parser.add_argument( 2870 '--network-interface', 2871 type=arg_parsers.ArgDict( 2872 spec=multiple_network_interface_cards_spec, 2873 allow_key_only=True, 2874 ), 2875 action='append', # pylint:disable=protected-access 2876 metavar='PROPERTY=VALUE', 2877 help=network_interface_help) 2878 2879 2880def AddNestedVirtualizationArgs(parser): 2881 parser.add_argument( 2882 '--enable-nested-virtualization', 2883 action=arg_parsers.StoreTrueFalseAction, 2884 help="""\ 2885 If set to true, enables nested virtualization for the instance. 2886 """) 2887 2888 2889def AddThreadsPerCoreArgs(parser): 2890 parser.add_argument( 2891 '--threads-per-core', 2892 type=int, 2893 help=""" 2894 The number of visible threads per physical core. To disable simultaneous 2895 multithreading (SMT) set this to 1. Valid values are currently: 0, 1, or 2. 2896 """) 2897 2898 2899def AddStackTypeArgs(parser): 2900 """Adds stack type arguments for instance.""" 2901 parser.add_argument( 2902 '--stack-type', 2903 choices={ 2904 'IPV4_ONLY': 2905 'The network interface will be assigned IPv4 addresses', 2906 'IPV4_IPV6': 2907 'The network interface can have both IPv4 and IPv6 addresses' 2908 }, 2909 type=arg_utils.ChoiceToEnumName, 2910 help=('The stack type for this network interface to identify whether the ' 2911 'IPv6 feature is enabled or not, only supports default NIC for ' 2912 'now. If not specified, IPV4_ONLY will be used.') 2913 ) 2914 2915 2916def AddIpv6NetworkTierArgs(parser): 2917 """Adds IPv6 network tier for network interface IPv6 access config.""" 2918 parser.add_argument( 2919 '--ipv6-network-tier', 2920 choices={ 2921 'PREMIUM': ('High quality, Google-grade network tier, support for ' 2922 'all networking products.'), 2923 'STANDARD': ('Public Internet quality, only limited support for ' 2924 'other networking products.') 2925 }, 2926 type=arg_utils.ChoiceToEnumName, 2927 help=('Specifies the IPv6 network tier that will be used to configure ' 2928 'the instance network interface IPv6 access config. Only `PREMIUM` ' 2929 'is supported for now.')) 2930 2931 2932def AddNetworkPerformanceConfigsArgs(parser): 2933 """Adds config flags for advanced networking bandwidth tiers.""" 2934 2935 network_perf_config_help = """\ 2936 Configures network performance settings for the instance. 2937 If this flag is not specified, the instance will be created 2938 with its default network performance configuration. 2939 2940 *total-egress-bandwidth-tier*::: Total egress bandwidth is the available 2941 outbound bandwidth from a VM, regardless of whether the traffic 2942 is going to internal IP or external IP destinations. 2943 The following tier values are allowed: [{tier_values}] 2944 2945 """.format(tier_values=','.join([ 2946 six.text_type(tier_val) 2947 for tier_val in constants.ADV_NETWORK_TIER_CHOICES 2948 ])) 2949 2950 spec = { 2951 'total-egress-bandwidth-tier': str 2952 } 2953 2954 parser.add_argument( 2955 '--network-performance-configs', 2956 type=arg_parsers.ArgDict(spec=spec), 2957 action='append', 2958 metavar='PROPERTY=VALUE', 2959 help=network_perf_config_help) 2960 2961 2962def ValidateNetworkPerformanceConfigsArgs(args): 2963 """Validates advanced networking bandwidth tier values.""" 2964 2965 for config in getattr(args, 'network_performance_configs', []) or []: 2966 total_tier = config.get('total-egress-bandwidth-tier', '').upper() 2967 if total_tier and \ 2968 total_tier not in constants.ADV_NETWORK_TIER_CHOICES: 2969 raise exceptions.InvalidArgumentException( 2970 '--network-performance-configs', 2971 """Invalid total-egress-bandwidth-tier tier value, "{tier}". 2972 Tier value must be on of the follwing {tier_values}""" 2973 .format(tier=total_tier, 2974 tier_values=','.join([ 2975 six.text_type(tier_val) 2976 for tier_val in constants.ADV_NETWORK_TIER_CHOICES 2977 ]))) 2978