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