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