Best Python code snippet using Testify_python
instance_cmds.py
Source:instance_cmds.py  
1# Copyright 2012 Google Inc. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Commands for interacting with Google Compute Engine VM instances."""15import logging16import subprocess17import sys18import time19from apiclient import errors20from google.apputils import app21from google.apputils import appcommands22import gflags as flags23from gcutil_lib import command_base24from gcutil_lib import gcutil_errors25from gcutil_lib import gcutil_logging26from gcutil_lib import image_cmds27from gcutil_lib import metadata28from gcutil_lib import scopes29from gcutil_lib import ssh_keys30from gcutil_lib import windows_password31from gcutil_lib import windows_user_name32FLAGS = flags.FLAGS33LOGGER = gcutil_logging.LOGGER34EPHEMERAL_ROOT_DISK_WARNING_MESSAGE = (35    'You appear to be running on a SCRATCH root disk '36    'so your data will not persist beyond the life '37    'of the instance. Consider using a persistent disk '38    'instead.\n'39    'For more information, see:\n'40    'https://developers.google.com/compute/docs/disks#persistentdisks'41)42def _DefineSchedulingFlags(flag_values):43  flags.DEFINE_enum('on_host_maintenance',44                    None,45                    ['terminate', 'migrate'],46                    'How the instance should behave when the host machine '47                    'undergoes maintenance that may temporarily impact '48                    'instance performance. Must be unspecified or '49                    '\'terminate\' for instances with scratch disk. If '50                    'unspecified, a default of \'terminate\' will be used for '51                    'instances with scratch disk, or \'migrate\' for instances '52                    'without scratch disk.',53                    flag_values=flag_values)54  flags.DEFINE_boolean('automatic_restart',55                       None,56                       'Whether the instance should be automatically '57                       'restarted whenever it is terminated by Compute '58                       'Engine (not terminated by user).',59                       flag_values=flag_values)60def _GetSchedulingFromFlags(flag_values):61  scheduling = {}62  if flag_values['on_host_maintenance'].present:63    scheduling['onHostMaintenance'] = flag_values.on_host_maintenance64  if flag_values['automatic_restart'].present:65    scheduling['automaticRestart'] = flag_values.automatic_restart66  return scheduling67class InstanceCommand(command_base.GoogleComputeCommand):68  """Base command for working with the instances collection."""69  print_spec = command_base.ResourcePrintSpec(70      summary=['name', 'zone', 'status', 'network-ip', 'external-ip'],71      field_mappings=(72          ('name', 'name'),73          ('machine-type', 'machineType'),74          ('image', 'image'),75          ('network', 'networkInterfaces.network'),76          ('network-ip', 'networkInterfaces.networkIP'),77          ('external-ip', 'networkInterfaces.accessConfigs.natIP'),78          ('disks', 'disks.source'),79          ('zone', 'zone'),80          ('status', 'status'),81          ('status-message', 'statusMessage')),82      detail=(83          ('name', 'name'),84          ('description', 'description'),85          ('creation-time', 'creationTimestamp'),86          ('machine', 'machineType'),87          ('image', 'image'),88          ('zone', 'zone'),89          ('tags-fingerprint', 'tags.fingerprint'),90          ('on-host-maintenance', 'scheduling.onHostMaintenance'),91          ('automatic-restart', 'scheduling.automaticRestart'),92          ('metadata-fingerprint', 'metadata.fingerprint'),93          ('status', 'status'),94          ('status-message', 'statusMessage')),95      sort_by='name')96  # A map from legal values for the disk "mode" option to the97  # corresponding API value. Keys in this map should be lowercase, as98  # we convert user provided values to lowercase prior to performing a99  # look-up.100  disk_modes = {101      'read_only': 'READ_ONLY',102      'ro': 'READ_ONLY',103      'read_write': 'READ_WRITE',104      'rw': 'READ_WRITE'}105  resource_collection_name = 'instances'106  # The default network interface name assigned by the service.107  DEFAULT_NETWORK_INTERFACE_NAME = 'nic0'108  # The default access config name109  DEFAULT_ACCESS_CONFIG_NAME = 'External NAT'110  # Currently, only access config type 'ONE_TO_ONE_NAT' is supported.111  ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE = 'ONE_TO_ONE_NAT'112  # Let the server select an ephemeral IP address.113  EPHEMERAL_ACCESS_CONFIG_NAT_IP = 'ephemeral'114  def __init__(self, name, flag_values):115    super(InstanceCommand, self).__init__(name, flag_values)116    flags.DEFINE_string('zone',117                        None,118                        '[Required] The zone for this request.',119                        flag_values=flag_values)120  def GetDetailRow(self, result):121    """Returns an associative list of items for display in a detail table.122    Args:123      result: A dict returned by the server.124    Returns:125      A list.126    """127    data = []128    # Add the disks129    for disk in result.get('disks', []):130      disk_info = [('type', disk['type'])]131      if 'mode' in disk:132        disk_info.append(('mode', disk['mode']))133      if 'deviceName' in disk:134        disk_info.append(('device-name', disk['deviceName']))135      if 'source' in disk:136        disk_info.append(('source', disk['source']))137      if 'boot' in disk:138        disk_info.append(('boot', disk['boot']))139      if 'autoDelete' in disk:140        disk_info.append(('autoDelete', disk['autoDelete']))141      if 'deleteOnTerminate' in disk:142        disk_info.append(('delete-on-terminate', disk['deleteOnTerminate']))143      data.append(('disk', disk_info))144    # Add the networks145    for network in result.get('networkInterfaces', []):146      network_info = [('network', network.get('network')),147                      ('ip', network.get('networkIP'))]148      for config in network.get('accessConfigs', []):149        network_info.append(('access-configuration', config.get('name')))150        network_info.append(('type', config.get('type')))151        network_info.append(('external-ip', config.get('natIP')))152      data.append(('network-interface', network_info))153    # Add the service accounts154    for service_account in result.get('serviceAccounts', []):155      account_info = [('service-account', service_account.get('email')),156                      ('scopes', service_account.get('scopes'))]157      data.append(('service-account', account_info))158    # Add metadata159    if result.get('metadata', []):160      metadata_container = result.get('metadata', {}).get('items', [])161      metadata_info = []162      for i in metadata_container:163        metadata_info.append((i.get('key'), i.get('value')))164      if metadata_info:165        data.append(('metadata', metadata_info))166        data.append(('metadata-fingerprint',167                     result.get('metadata', {}).get('fingerprint')))168    # Add tags169    if result.get('tags', []):170      tags_container = result.get('tags', {}).get('items', [])171      tags_info = []172      for tag in tags_container:173        tags_info.append(tag)174      if tags_info:175        data.append(('tags', tags_info))176        data.append(('tags-fingerprint',177                     result.get('tags', {}).get('fingerprint')))178    return data179  def _ExtractExternalIpFromInstanceRecord(self, instance_record):180    """Extract the external IP(s) from an instance record.181    Args:182      instance_record: An instance as returned by the Google Compute Engine API.183    Returns:184      A list of internet IP addresses associated with this VM.185    """186    external_ips = set()187    for network_interface in instance_record.get('networkInterfaces', []):188      for access_config in network_interface.get('accessConfigs', []):189        # At the moment, we only know how to translate 1-to-1 NAT190        if (access_config.get('type') == self.ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE191            and 'natIP' in access_config):192          external_ips.add(access_config['natIP'])193    return list(external_ips)194  def _AddAuthorizedUserKeyToProject(self, authorized_user_key):195    """Update the project to include the specified user/key pair.196    Args:197      authorized_user_key: A dictionary of a user/key pair for the user.198    Returns:199      True iff the ssh key was added to the project.200    Raises:201      gcutil_errors.CommandError: If the metadata update fails.202    """203    project = self.api.projects.get(project=self._project).execute()204    common_instance_metadata = project.get('commonInstanceMetadata', {})205    project_metadata = common_instance_metadata.get(206        'items', [])207    project_ssh_keys = ssh_keys.SshKeys.GetAuthorizedUserKeysFromMetadata(208        project_metadata)209    if authorized_user_key in project_ssh_keys:210      return False211    else:212      project_ssh_keys.append(authorized_user_key)213      ssh_keys.SshKeys.SetAuthorizedUserKeysInMetadata(214          project_metadata, project_ssh_keys)215      try:216        request = self.api.projects.setCommonInstanceMetadata(217            project=self._project,218            body={'kind': self._GetResourceApiKind('metadata'),219                  'items': project_metadata})220        request.execute()221      except errors.HttpError:222        # A failure to add the ssh key probably means that the project metadata223        # has exceeded the max size. The user needs to either manually224        # clean up their project metadata, or set the ssh keys manually for this225        # instance. Either way, trigger a usage error to let them know.226        raise gcutil_errors.CommandError(227            'Unable to add the local ssh key to the project. Either manually '228            'remove some entries from the commonInstanceMetadata field of the '229            'project, or explicitly set the authorized keys for this instance.')230      return True231  def _PrepareRequestArgs(self, instance_context,232                          **other_args):233    """Gets the dictionary of API method keyword arguments.234    Args:235      instance_context: A context dict for this instance.236      **other_args: Keyword arguments that should be included in the request.237    Returns:238      Dictionary of keyword arguments that should be passed in the API call,239      includes all keyword arguments passed in 'other_args' plus240      common keys such as the name of the resource and the project.241    """242    kwargs = {243        'project': instance_context['project'],244        'instance': instance_context['instance'],245        'zone': instance_context['zone']246    }247    for key, value in other_args.items():248      kwargs[key] = value249    return kwargs250  def _AddComputeKeyToProject(self):251    """Update the current project to include the user's public ssh key.252    Returns:253      True iff the ssh key was added to the project.254    """255    compute_key = ssh_keys.SshKeys.GetPublicKey()256    return self._AddAuthorizedUserKeyToProject(compute_key)257  def _AutoDetectZone(self):258    """Instruct this command to auto detect zone instead of prompting."""259    def _GetZoneContext(object_type, context):260      if self._flags.zone:261        return self.DenormalizeResourceName(self._flags.zone)262      if object_type == 'instances':263        return self.GetZoneForResource(self.api.instances,264                                       context['instance'],265                                       project=context['project'])266      elif object_type == 'disks':267        return self.GetZoneForResource(self.api.disks,268                                       context['disk'],269                                       project=context['project'])270    self._context_parser.context_prompt_fxns['zone'] = _GetZoneContext271  def _AutoDetectZoneForDisksOnly(self):272    """Instruct this command to auto detect zone for disks only."""273    def _GetZoneContext(object_type, context):274      if self._flags.zone:275        return self.DenormalizeResourceName(self._flags.zone)276      elif object_type == 'disks':277        return self.GetZoneForResource(self.api.disks,278                                       context['disk'],279                                       project=context['project'])280      elif object_type == 'instances':281        self._flags.zone = self._GetZone(None)282        return self._flags.zone283    self._context_parser.context_prompt_fxns['zone'] = _GetZoneContext284  def _BuildAttachedBootDisk(self, disk_name, source_image, instance_name,285                             disk_size_gb=None, auto_delete=False):286    disk = {287        'type': 'PERSISTENT',288        'mode': 'READ_WRITE',289        'boot': True,290        'deviceName': instance_name,291        'initializeParams': {292            'diskName': disk_name,293            'sourceImage': source_image,294        },295        'autoDelete': auto_delete,296    }297    if disk_size_gb is not None:298      disk['initializeParams']['diskSizeGb'] = disk_size_gb299    return disk300  def _BuildAttachedDisk(self, disk_arg):301    """Converts a disk argument into an AttachedDisk object."""302    # Start with the assumption that the argument only specifies the303    # name of the disk resource.304    disk_name = disk_arg305    device_name = disk_arg306    mode = 'READ_WRITE'307    boot = False308    disk_parts = disk_arg.split(',')309    if len(disk_parts) > 1:310      # The argument includes new-style decorators. The first part is311      # the disk resource name. The other parts are optional key/value312      # pairs.313      disk_name = disk_parts[0]314      device_name = disk_parts[0]315      for option in disk_parts[1:]:316        if option == 'boot':317          boot = True318          continue319        if '=' not in option:320          raise ValueError('Invalid disk option: %s' % option)321        key, value = option.split('=', 2)322        if key == 'deviceName':323          device_name = value324        elif key == 'mode':325          mode = self.disk_modes.get(value.lower())326          if not mode:327            raise ValueError('Invalid disk mode: %s' % value)328        else:329          raise ValueError('Invalid disk option: %s' % key)330    else:331      # The user didn't provide any options using the newer key/value332      # syntax, so check to see if they have used the old syntax where333      # the device name is delimited by a colon.334      disk_parts = disk_arg.split(':')335      if len(disk_parts) > 1:336        disk_name = disk_parts[0]337        device_name = disk_parts[1]338        LOGGER.info(339            'Please use new disk device naming syntax: --disk=%s,deviceName=%s',340            disk_name,341            device_name)342    disk_url = self._context_parser.NormalizeOrPrompt('disks', disk_name)343    disk = {344        'type': 'PERSISTENT',345        'source': disk_url,346        'mode': mode,347        'deviceName': device_name,348        'boot': boot,349    }350    if boot and self._flags.auto_delete_boot_disk:351      disk['autoDelete'] = True352    return disk353class AddInstance(InstanceCommand):354  """Create new virtual machine instances.355  Specify multiple instances as multiple arguments. Each instance will be356  created in parallel.357  """358  positional_args = '<instance-name-1> ... <instance-name-n>'359  status_field = 'status'360  _TERMINAL_STATUS = ['RUNNING', 'TERMINATED']361  _MAX_BOOT_DISK_NAME_LENGTH = 64362  def __init__(self, name, flag_values):363    super(AddInstance, self).__init__(name, flag_values)364    image_cmds.RegisterCommonImageFlags(flag_values)365    flags.DEFINE_string('description',366                        '',367                        'An optional description for this instance.',368                        flag_values=flag_values)369    flags.DEFINE_string('image',370                        None,371                        'Specifies the image to use for this '372                        'instance. For example, \'--image=debian-7\' or '373                        '\'--image=debian-7-wheezy-v20130723\'. '374                        'To get a list of images built by Google, run '375                        '\'gcutil listimages --project=projects/google\'. '376                        'To get a list of images you have built, run '377                        '\'gcutil --project=<project-id> listimages\'.',378                        flag_values=flag_values)379    flags.DEFINE_boolean('persistent_boot_disk',380                         None,381                         '[Deprecated] Creates a persistent boot disk for this '382                         'instance. If this is set, gcutil creates a new '383                         'disk named \'<instance-name>\' and copies the '384                         'contents of the image onto the new disk and uses '385                         'it for booting.',386                         flag_values=flag_values)387    flags.DEFINE_string('boot_disk_type',388                        'pd-standard',389                        'Specifies the disk type used to create the boot disk. '390                        'For example, \'--boot_disk_type=pd-standard\'. '391                        'To get a list of avaiable disk types, run '392                        '\'gcutil listdisktypes\'.',393                        flag_values=flag_values)394    flags.DEFINE_integer('boot_disk_size_gb',395                         None,396                         'Size of the persistent boot disk in GiB.',397                         flag_values=flag_values)398    flags.DEFINE_boolean('auto_delete_boot_disk',399                         False,400                         'Whether to auto-delete the boot disk when the '401                         'instance is deleted.',402                         flag_values=flag_values)403    flags.DEFINE_string('machine_type',404                        None,405                        '[Required] Specifies the machine type used for the '406                        'instance. For example, '407                        '\'--machine_type=machinetype-name\'. '408                        'To get a list of available machine types, run '409                        '\'gcutil listmachinetypes\'.',410                        flag_values=flag_values)411    flags.DEFINE_string('network',412                        'default',413                        'Specifies the network to use for this instance. If '414                        'not specified, the \'default\' network is used.',415                        flag_values=flag_values)416    flags.DEFINE_string('external_ip_address',417                        self.EPHEMERAL_ACCESS_CONFIG_NAT_IP,418                        'Specifies the external NAT IP of this new instance. '419                        'The default value is \'ephemeral\' and indicates '420                        'that the service should use any available ephemeral '421                        'IP. You can also specify "none" '422                        '(or an empty string) to indicate that no external '423                        'IP should be assigned to the instance. If you want '424                        'to explicitly use a certain IP, the IP must be '425                        'reserved by the project and not in use by '426                        'another resource.',427                        flag_values=flag_values)428    flags.DEFINE_multistring('disk',429                             [],430                             'Attaches a persistent disk to this instance. '431                             'You can also provide a list of comma-separated '432                             'name=value pairs options. Legal option names '433                             'are \'deviceName\', to specify the disk\'s '434                             'device name, and \'mode\', to indicate whether '435                             'the disk should be attached READ_WRITE '436                             '(the default) or READ_ONLY. You may also use '437                             'the \'boot\' flag to designate the disk as a '438                             'boot device. For example: '439                             '\'--disk=mydisk,deviceName=primarydisk,'440                             'mode=rw,boot\'',441                             flag_values=flag_values)442    flags.DEFINE_boolean('use_compute_key',443                         False,444                         'Specifies the Google Compute Engine ssh key as one '445                         'of the authorized ssh keys for the created '446                         'instance. This has the side effect of disabling '447                         'project-wide ssh key management for the instance.',448                         flag_values=flag_values)449    flags.DEFINE_boolean('add_compute_key_to_project',450                         None,451                         'Adds the default Google Compute Engine ssh key as '452                         'one of the authorized ssh keys for the project.'453                         'If the default key has already been added to the '454                         'project, then this will have no effect. '455                         'The default behavior is to add the key to the '456                         'project if no instance-specific keys are defined.',457                         flag_values=flag_values)458    flags.DEFINE_list('authorized_ssh_keys',459                      [],460                      'Updates the list of user/key-file pairs to the '461                      'specified entries, disabling project-wide key '462                      'management for this instance. These are specified as '463                      'a comma separated list of colon separated entries: '464                      'user1:keyfile1,user2:keyfile2,...',465                      flag_values=flag_values)466    flags.DEFINE_string('service_account',467                        'default',468                        'Specifies the service accounts for this instance. '469                        'Once set, the service account\'s credentials can '470                        'be used by the instance to make requests to other '471                        'services. The default account is \'default\'. You '472                        'can also set specific service account names. For '473                        'example, '474                        '\'123845678986@project.gserviceaccount.com\'',475                        flag_values=flag_values)476    flags.DEFINE_list('service_account_scopes',477                      [],478                      'Specifies the scope of credentials for the service '479                      'account(s) available to this instance. Specify '480                      'multiple scopes as comma-separated entries. You can '481                      'also use a set of supported scope aliases: %s'482                      % ', '.join(sorted(scopes.SCOPE_ALIASES.keys())),483                      flag_values=flag_values)484    flags.DEFINE_boolean('wait_until_running',485                         False,486                         'Specifies that gcutil should wait until the new '487                         'instance is running before returning.',488                         flag_values=flag_values)489    flags.DEFINE_list('tags',490                      [],491                      'Applies a set of tags to this instance. Tags can be '492                      'used for filtering and configuring network firewall '493                      'rules.',494                      flag_values=flag_values)495    flags.DEFINE_boolean('can_ip_forward',496                         False,497                         'Specifies that the newly-created instance is '498                         'allowed to send packets with a source IP address '499                         'that does not match its own and receive packets '500                         'whose destination IP address does not match its '501                         'own.',502                         flag_values=flag_values)503    _DefineSchedulingFlags(flag_values)504    self._metadata_flags_processor = metadata.MetadataFlagsProcessor(505        flag_values)506  def _GetErrorOperations(self, result_list):507    """Returns the list of operations that experienced a problem."""508    error_operations = []509    for result in result_list:510      error_list = result.get('error', {}).get('errors', [])511      if error_list:512        error_operations.append(result)513    return error_operations514  def _GetDefaultBootDiskName(self, instance_name):515    # The default boot disk name is the same as the instance name.516    return instance_name517  def _IsCreatingWindowsInstance(self, image_url, image_resource, disk_used):518    # If the boot disk is given, we get the disk and check its519    # license.520    if disk_used:521      disk_context = self._context_parser.ParseContextOrPrompt(522          'disks', disk_used['source'])523      disk_resource = self.api.disks.get(524          project=disk_context['project'],525          zone=disk_context['zone'],526          disk=disk_context['disk']).execute()527      return self._HasWindowsLicense(disk_resource.get('licenses', []))528    # If there is no boot disk, check the licenses in the image.529    if not image_resource:530      # If the image_resource is not given, we get it from server.531      image_context = self._context_parser.ParseContextOrPrompt(532          'images', image_url)533      image_resource = self.api.images.get(534          project=image_context['project'],535          image=image_context['image']).execute()536    return self._HasWindowsLicense(image_resource.get('licenses', []))537  def _HasWindowsLicense(self, license_urls):538    """Checks if a list of license urls contains a license for Windows.539        Currently, we consider a license is for Windows, if it540        comes from any of the Windows image provider projects.541    Args:542      license_urls: List of URLs for license resource.543    Returns:544      True if at least one license in the list is considered Windows license.545    """546    if not license_urls:547      return False548    for license_url in license_urls:549      license_context = self._context_parser.ParseContextOrPrompt(550          'licenses', license_url)551      if license_context['project'] in command_base.WINDOWS_IMAGE_PROJECTS:552        return True553    return False554  def Handle(self, *instance_names):555    """Add the specified instance.556    Args:557      *instance_names: A list of instance names to add.558    Returns:559      A tuple of (result, exceptions)560    Raises:561      UsageError: Improper arguments were passed to this command.562      UnsupportedCommand: Unsupported operation.563    """564    if not instance_names:565      raise app.UsageError('You must specify at least one instance name')566    if self._flags.persistent_boot_disk is False:567      raise gcutil_errors.UnsupportedCommand(568          'Persistent boot disk is required.')569    else:570      self._flags.persistent_boot_disk = True571    if len(instance_names) > 1 and self._flags.disk:572      raise gcutil_errors.CommandError(573          'Specifying a disk when starting multiple instances is not '574          'currently supported')575    if max([len(i) for i in instance_names]) > 32:576      LOGGER.warn('Hostnames longer than 32 characters trigger known issues '577                  'with some Linux distributions. If possible, you should '578                  'select a hostname that is shorter than 32 characters.')579    self._AutoDetectZoneForDisksOnly()580    instance_contexts = [self._context_parser.ParseContextOrPrompt(581        'instances', instance_name) for instance_name in instance_names]582    if not self._flags.machine_type:583      self._flags.machine_type = self._presenter.PromptForMachineType(584          self.api.machine_types)['name']585    # Processes the disks, so we can check for the presence of a boot586    # disk before prompting for image.587    disks = [self._BuildAttachedDisk(disk) for disk in self._flags.disk]588    boot_disk_used = self._GetBootDisk(disks)589    image_resource = command_base.ResolveImageTrackOrImageToResource(590        self.api.images, self._flags.project, self._flags.image,591        lambda image: self._presenter.PresentElement(image['selfLink']))592    self._flags.image = self._context_parser.NormalizeOrPrompt(593        'images',594        image_resource['selfLink'] if image_resource else self._flags.image)595    if not self._flags.image and not self._HasBootDisk(disks):596      image_resource = self._presenter.PromptForImage(597          self.api.images)598      self._flags.image = image_resource['selfLink']599    instance_metadata = self._metadata_flags_processor.GatherMetadata()600    is_windows_instance = self._IsCreatingWindowsInstance(601        self._flags.image, image_resource, boot_disk_used)602    if is_windows_instance:603      if (self._flags.authorized_ssh_keys or604          self._flags.add_compute_key_to_project is not None or605          self._flags.use_compute_key):606        LOGGER.warn('Creating Windows instance. '607                    'SSH key options such as authorized_ssh_keys, '608                    'add_compute_key_to_project and use_compute_key '609                    'are ignored.')610      # For Windows image, we need to set the user name and password611      # in metadata so the daemon on the machine can set up the initial612      # user account.613      windows_user = self._EnsureWindowsUserNameInMetadata(instance_metadata)614      self._EnsureWindowsPasswordInMetadata(instance_metadata, windows_user)615    else:616      # Do ssh related processing only for non-Windows image.617      if self._flags.authorized_ssh_keys or self._flags.use_compute_key:618        instance_metadata = self._AddSshKeysToMetadata(instance_metadata)619    # Instance names that we still want to create.620    instances_to_create = instance_contexts621    if not self._HasBootDisk(disks):622      # Determine the name of the boot disk that the create-instance call will623      # make on our behalf.624      for instance_context in instance_contexts:625        boot_disk_name = self._GetDefaultBootDiskName(626            instance_context['instance'])627        instance_context['boot_disk_name'] = boot_disk_name628    if not is_windows_instance and (629        self._flags.add_compute_key_to_project or (630            self._flags.add_compute_key_to_project is None and631            'sshKeys' not in [entry.get('key', '') for entry632                              in instance_metadata])):633      try:634        self._AddComputeKeyToProject()635      except ssh_keys.UserSetupError as e:636        LOGGER.warn('Could not generate compute ssh key: %s', e)637    self._ValidateFlags()638    wait_for_operations = (639        self._flags.wait_until_running or self._flags.synchronous_mode)640    requests = []641    for instance_context in instances_to_create:642      instance_disks = disks643      if 'boot_disk_name' in instance_context:644        boot_disk = self._BuildAttachedBootDisk(645            disk_name=instance_context['boot_disk_name'],646            source_image=self._flags.image,647            instance_name=instance_context['instance'],648            disk_size_gb=self._flags.boot_disk_size_gb,649            auto_delete=self._flags.auto_delete_boot_disk)650        boot_disk['initializeParams']['diskType'] = (651            self._context_parser.NormalizeOrPrompt(652                'diskTypes', self._flags.boot_disk_type))653        instance_disks = [boot_disk] + disks654      requests.append(self._BuildRequestWithMetadata(655          instance_context, instance_metadata, instance_disks))656    (results, exceptions) = self.ExecuteRequests(657        requests, wait_for_operations=wait_for_operations)658    if self._flags.wait_until_running:659      instances_to_wait = results660      results = []661      for result in instances_to_wait:662        if self.IsResultAnOperation(result):663          results.append(result)664        else:665          result_context = self._context_parser.ParseContextOrPrompt(666              'instances', result['selfLink'])667          kwargs = self._PrepareRequestArgs(result_context)668          get_request = self.api.instances.get(**kwargs)669          instance_result = get_request.execute()670          instance_result = self._WaitUntilInstanceIsRunning(671              instance_result, kwargs)672          results.append(instance_result)673      return (self.MakeListResult(results, 'instanceList'), exceptions)674    else:675      return (self.MakeListResult(results, 'operationList'), exceptions)676  def _WaitUntilInstanceIsRunning(self, result, kwargs):677    """Waits for the instance to start.678    Periodically polls the server for current instance status. Exits if the679    status of the instance is RUNNING or TERMINATED or the maximum waiting680    timeout has been reached. In both cases returns the last known instance681    details.682    Args:683      result: the current state of the instance.684      kwargs: keyword arguments to _instances_api.get()685    Returns:686      Json containing full instance information.687    """688    current_status = result[self.status_field]689    start_time = self._timer.time()690    instance_name = kwargs['instance']691    LOGGER.info('Ensuring %s is running.  Will wait to start for: %d seconds.',692                instance_name, self._flags.max_wait_time)693    while (self._timer.time() - start_time < self._flags.max_wait_time and694           current_status not in self._TERMINAL_STATUS):695      LOGGER.info(696          'Waiting for instance \'%s\' to start. '697          'Current status: %s. Sleeping for %ss.',698          instance_name,699          current_status, self._flags.sleep_between_polls)700      self._timer.sleep(self._flags.sleep_between_polls)701      result = self.api.instances.get(**kwargs).execute()702      current_status = result[self.status_field]703    if current_status not in self._TERMINAL_STATUS:704      LOGGER.warn('Timeout reached. Instance %s has not yet started.',705                  instance_name)706    return result707  def _AddSshKeysToMetadata(self, instance_metadata):708    instance_ssh_keys = ssh_keys.SshKeys.GetAuthorizedUserKeys(709        use_compute_key=self._flags.use_compute_key,710        authorized_ssh_keys=self._flags.authorized_ssh_keys)711    if instance_ssh_keys:712      new_value = ['%(user)s:%(key)s' % user_key713                   for user_key in instance_ssh_keys]714      # Have the new value extend the old value715      old_values = [entry['value'] for entry in instance_metadata716                    if entry['key'] == 'sshKeys']717      all_values = '\n'.join(old_values + new_value)718      instance_metadata = [entry for entry in instance_metadata719                           if entry['key'] != 'sshKeys']720      instance_metadata.append({'key': 'sshKeys', 'value': all_values})721    return instance_metadata722  def _HasBootDisk(self, disks):723    """Determines if any of the disks in a list is a boot disk."""724    return self._GetBootDisk(disks) is not None725  def _GetBootDisk(self, disks):726    """Gets the boot disk from a list of disks."""727    for disk in disks:728      if disk.get('boot', False):729        return disk730    return None731  def _ValidateFlags(self):732    """Validate flags coming in before we start building resources.733    Raises:734      app.UsageError: If service account explicitly given without scopes.735      gcutil_errors.CommandError: If scopes contains ' '.736    """737    if self._flags.service_account and self._flags.service_account_scopes:738      # Ensures that the user did not space-delimit his or her scopes739      # list.740      for scope in self._flags.service_account_scopes:741        if ' ' in scope:742          raise gcutil_errors.CommandError(743              'Scopes list must be comma-delimited, not space-delimited.')744    elif self._flags['service_account'].present:745      raise app.UsageError(746          '--service_account given without --service_account_scopes.')747  def _CreateDiskFromImageRequest(self, disk_name, disk_zone, disk_project):748    """Build a request that creates disk from source image.749    Args:750      disk_name: Name of the disk.751      disk_zone: Zone for the disk.752      disk_project: Project for the disk753    Returns:754      The prepared disk insert request.755    """756    disk_resource = {757        'kind': self._GetResourceApiKind('instance'),758        'name': disk_name,759        'description': 'Persistent boot disk created from %s.' % (760            self._flags.image),761    }762    source_image_url = self._context_parser.NormalizeOrPrompt(763        'images', self._flags.image)764    kwargs = {765        'project': disk_project,766        'body': disk_resource,767        'sourceImage': source_image_url,768        'zone': disk_zone,769    }770    return self.api.disks.insert(**kwargs)771  def _BuildRequestWithMetadata(772      self, instance_context, instance_metadata, disks):773    """Build a request to add the specified instance, given the ssh keys for it.774    Args:775      instance_context: Context dict for the instance that we are building the776        request for.777      instance_metadata: The metadata to be passed to the VM.  This is in the778        form of [{'key': <key>, 'value': <value>}] form, ready to be779        sent to the server.780      disks: Disks to attach to the instance.781    Returns:782      The prepared instance request.783    Raises:784      UsageError: Flags were not allowed in this service version.785    """786    instance_resource = {787        'kind': self._GetResourceApiKind('instance'),788        'name': instance_context['instance'],789        'description': self._flags.description,790        'networkInterfaces': [],791        'disks': disks,792        'metadata': [],793        }794    if self._flags.machine_type:795      instance_resource['machineType'] = self._context_parser.NormalizeOrPrompt(796          'machineTypes', self._flags.machine_type)797    if self._flags['can_ip_forward'].present:798      instance_resource['canIpForward'] = self._flags.can_ip_forward799    if self._flags.network:800      network_interface = {801          'network': self._context_parser.NormalizeOrPrompt(802              'networks', self._flags.network)803          }804      external_ip_address = self._flags.external_ip_address805      if external_ip_address and external_ip_address.lower() != 'none':806        access_config = {807            'name': self.DEFAULT_ACCESS_CONFIG_NAME,808            'type': self.ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE,809            }810        if external_ip_address.lower() != self.EPHEMERAL_ACCESS_CONFIG_NAT_IP:811          access_config['natIP'] = self._flags.external_ip_address812        network_interface['accessConfigs'] = [access_config]813      instance_resource['networkInterfaces'].append(network_interface)814    metadata_subresource = {815        'kind': self._GetResourceApiKind('metadata'),816        'items': []}817    metadata_subresource['items'].extend(instance_metadata)818    instance_resource['metadata'] = metadata_subresource819    if self._flags.service_account and (820        len(self._flags.service_account_scopes)):821      instance_resource['serviceAccounts'] = []822      expanded_scopes = scopes.ExpandScopeAliases(823          self._flags.service_account_scopes)824      instance_resource['serviceAccounts'].append({825          'email': self._flags.service_account,826          'scopes': expanded_scopes})827    instance_resource['tags'] = {'items': sorted(set(self._flags.tags))}828    scheduling = _GetSchedulingFromFlags(self._flags)829    # Only set the instance's scheduling if it is populated.830    if scheduling:831      instance_resource['scheduling'] = scheduling832    return self.api.instances.insert(833        project=instance_context['project'],834        zone=instance_context['zone'],835        body=instance_resource)836  def _IsImageFromWindowsProject(self, image_path):837    """Determines whether an image resource is from a Windows project.838    Currently, we check if the image comes from the public Google839    windows-cloud project.840    Args:841      image_path: Path to the image resource.842    Returns:843      True if the image is from public Windows project; False otherwise.844    """845    if not image_path:846      return False847    image_context = self._context_parser.ParseContextOrPrompt(848        'images', image_path)849    return image_context['project'] in command_base.WINDOWS_IMAGE_PROJECTS850  def _EnsureWindowsUserNameInMetadata(self, instance_metadata):851    """Ensures that the initial windows user account name is set in metadata.852    Args:853      instance_metadata: The instance metadata.854    Returns:855      The user account name.856    """857    windows_user = metadata.GetMetadataValue(858        instance_metadata,859        metadata.INITIAL_WINDOWS_USER_METADATA_NAME)860    if windows_user is None:861      windows_user = windows_user_name.GenerateLocalUserNameBasedOnProject(862          self._project, self.api)863      instance_metadata.append({864          'key': metadata.INITIAL_WINDOWS_USER_METADATA_NAME,865          'value': windows_user})866      LOGGER.info(867          'The initial Windows login user name is %s.', windows_user)868    else:869      windows_user_name.ValidateUserName(windows_user)870    return windows_user871  def _EnsureWindowsPasswordInMetadata(872      self, instance_metadata, user_account_name):873    """Ensures that the initial Windows password is set in the metadata.874    If the metadata does not contain password, a random password will be875    generated.876    Args:877      instance_metadata: The instance metadata.878      user_account_name: The user account name.879    Raises:880      CommandError: The password does not meet strong password requirement.881    """882    password = metadata.GetMetadataValue(883        instance_metadata,884        metadata.INITIAL_WINDOWS_PASSWORD_METADATA_NAME)885    if password is None:886      password = windows_password.GeneratePassword(user_account_name)887      instance_metadata.append({888          'key': metadata.INITIAL_WINDOWS_PASSWORD_METADATA_NAME,889          'value': password})890      LOGGER.info(891          'Generated password for user account %s. The password can be '892          'retrieved from %s key in instance metadata.' %893          (user_account_name, metadata.INITIAL_WINDOWS_PASSWORD_METADATA_NAME))894      # Print the password on screen.895      print 'Generated password is %s' % password896    else:897      windows_password.ValidateStrongPasswordRequirement(898          password, user_account_name)899class GetInstance(InstanceCommand):900  """Get a VM instance."""901  positional_args = '<instance-name>'902  def Handle(self, instance_name):903    """Get the specified instance.904    Args:905      instance_name: The name of the instance to get.906    Returns:907      The result of getting the instance.908    """909    self._AutoDetectZone()910    instance_context = self._context_parser.ParseContextOrPrompt('instances',911                                                                 instance_name)912    instance_request = self.api.instances.get(913        **self._PrepareRequestArgs(instance_context))914    return instance_request.execute()915class DeleteInstance(InstanceCommand):916  """Delete one or more VM instances.917  Specify multiple instances as multiple arguments. Instances will be deleted918  in parallel.919  """920  positional_args = '<instance-name-1> ... <instance-name-n>'921  safety_prompt = 'Delete instance'922  def __init__(self, name, flag_values):923    super(DeleteInstance, self).__init__(name, flag_values)924    flags.DEFINE_boolean('delete_boot_pd',925                         None,926                         'Delete the attached boot persistent disk.',927                         flag_values=flag_values)928  def _FindBootPersistentDisks(self, instance_context_list):929    """Find any persistent boot disks for the specified instances.930    Args:931      instance_context_list:  A list of instance contexts.932    Returns:933      The list of (instance, attached boot disk) pairs.934    """935    bootdisks = []936    for instance_context in instance_context_list:937      instance_list_info = (938          self.GetListResultForResourceWithZone(939              self.api.instances, instance_context['instance'],940              zone=instance_context['zone'],941              project=instance_context['project']))942      bootdisks.extend((instance_context, disk) for disk943                       in instance_list_info['disks']944                       if (disk['type'] == 'PERSISTENT' and 'boot' in disk945                           and disk['boot']))946    return bootdisks947  def Handle(self, *instance_names):948    """Delete the specified instances.949    Args:950      *instance_names: Names of the instances to delete.951    Returns:952      The result of deleting the instance.953    Raises:954      UsageError: Required flags were missing.955    """956    requests = []957    disk_requests = []958    autodelete_disk_requests = []959    bootdisks = []960    disk_names = []961    # Check to see if there is no instance, and just quit.962    if not instance_names:963      raise app.UsageError('You must specify an instance to delete.')964    self._AutoDetectZone()965    instance_contexts = [self._context_parser.ParseContextOrPrompt(966        'instances', name) for name in instance_names]967    bootdisks = self._FindBootPersistentDisks(instance_contexts)968    disk_names = [self.DenormalizeResourceName(disk['source'])969                  for (_, disk) in bootdisks]970    # Check to see which persistent disks are attached,971    # and also which are boot.972    if self._flags.delete_boot_pd is not False:973      # User didn't specify nodeleteboot_pd. Either:974      # - specified delete_boot_pd975      # - gave neither, so we should prompt976      if bootdisks:977        # If the -f flag is specified, then delete_boot_pd flag is required.978        if self._flags.force and self._flags.delete_boot_pd is None:979          raise app.UsageError(980              'Some of your instances have boot disks attached. To use the '981              '--force flag when deleting instances with boot disks, you '982              'must also specify the --[no]delete_boot_pd flag explicitly.')983        if self._flags.delete_boot_pd is None:984          # User didn't set delete_boot_pd, also didn't set985          # nodelete_boot_pd986          self._flags.delete_boot_pd = self._PresentSafetyPrompt(987              'Delete persistent boot disk ' + ', '.join(disk_names), False)988        for (instance_context, disk) in bootdisks:989          if self._flags.delete_boot_pd:990            if 'autoDelete' in disk and disk['autoDelete'] is True:991              LOGGER.info('Auto-delete on %s (%s) is already enabled.',992                          instance_context['instance'], disk['deviceName'])993            else:994              LOGGER.info('Enabling auto-delete on %s (%s).',995                          instance_context['instance'], disk['deviceName'])996              autodelete_disk_requests.append(997                  self.api.instances.setDiskAutoDelete(998                      **self._PrepareRequestArgs(999                          instance_context,1000                          deviceName=disk['deviceName'],1001                          autoDelete=True)))1002          else:1003            # User didn't set delete_boot_pd, but chose no in the prompt1004            # We must reset the autoDelete flag to false since the user does1005            # not want disks to be auto-deleted.1006            if 'autoDelete' in disk and disk['autoDelete'] is False:1007              LOGGER.info('Auto-delete on %s (%s) is already disabled.',1008                          instance_context['instance'], disk['deviceName'])1009            else:1010              LOGGER.info('INFO: Disabling auto-delete on %s (%s).',1011                          instance_context['instance'], disk['deviceName'])1012              autodelete_disk_requests.append(1013                  self.api.instances.setDiskAutoDelete(1014                      **self._PrepareRequestArgs(1015                          instance_context,1016                          deviceName=disk['deviceName'],1017                          autoDelete=False)))1018    elif bootdisks:1019      for (instance_context, disk) in bootdisks:1020        # We must reset the autoDelete flag to false since the user does1021        # not want disks to be auto-deleted.1022        if 'autoDelete' in disk and disk['autoDelete'] is False:1023          LOGGER.info('Auto-delete on %s (%s) is already disabled.',1024                      instance_context['instance'], disk['deviceName'])1025        else:1026          LOGGER.info('INFO: Disabling auto-delete on %s (%s).',1027                      instance_context['instance'], disk['deviceName'])1028          autodelete_disk_requests.append(1029              self.api.instances.setDiskAutoDelete(1030                  **self._PrepareRequestArgs(1031                      instance_context,1032                      deviceName=disk['deviceName'],1033                      autoDelete=False)))1034    (results, exceptions) = self.ExecuteRequests(1035        autodelete_disk_requests, wait_for_operations=True)1036    # Fail early if we got any errors when trying to set disk auto-delete.1037    have_operation_errors = False1038    for operation in results:1039      if 'error' in operation:1040        have_operation_errors = True1041    if have_operation_errors or exceptions:1042      return (self.MakeListResult(results, 'operationList'), exceptions)1043    # All of the instance deletions must go through first.1044    for instance_context in instance_contexts:1045      requests.append(self.api.instances.delete(1046          **self._PrepareRequestArgs(instance_context)))1047    wait_for_operations = self._flags.synchronous_mode or disk_requests1048    (results, exceptions) = self.ExecuteRequests(1049        requests, wait_for_operations=wait_for_operations)1050    # Now all of the disk deletions go through.1051    if disk_requests:1052      (disk_results, disk_exceptions) = self.ExecuteRequests(1053          disk_requests, collection_name='disks')1054      results.extend(disk_results)1055      exceptions.extend(disk_exceptions)1056    # If the user had boot disks that they didn't want to be deleted,1057    # remind them that they have newly orphaned boot PDs.1058    if disk_names and self._flags.delete_boot_pd is False:1059      print 'INFO:  The following boot persistent disks were orphaned:'1060      print ', '.join(disk_names)1061    return (self.MakeListResult(results, 'operationList'), exceptions)1062class ListInstances(InstanceCommand, command_base.GoogleComputeListCommand):1063  """List the VM instances for a project."""1064  def IsZoneLevelCollection(self):1065    return True1066  def IsGlobalLevelCollection(self):1067    return False1068  def ListFunc(self):1069    """Returns the function for listing instances."""1070    return self.api.instances.list1071  def ListZoneFunc(self):1072    """Returns the function for listing instances in a zone."""1073    return self.api.instances.list1074  def ListAggregatedFunc(self):1075    """Returns the function for listing instances across all zones."""1076    return self.api.instances.aggregatedList1077class AddAccessConfig(InstanceCommand):1078  """Create an access config for a VM instance's network interface."""1079  positional_args = '<instance-name>'1080  def __init__(self, name, flag_values):1081    super(AddAccessConfig, self).__init__(name, flag_values)1082    flags.DEFINE_string('network_interface_name',1083                        self.DEFAULT_NETWORK_INTERFACE_NAME,1084                        '[Required] Specifies the name of the instance\'s '1085                        'network interface to add the new access config.',1086                        flag_values=flag_values)1087    flags.DEFINE_string('access_config_name',1088                        self.DEFAULT_ACCESS_CONFIG_NAME,1089                        '[Required] Specifies the name of the new access '1090                        'config.',1091                        flag_values=flag_values)1092    flags.DEFINE_string('access_config_type',1093                        self.ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE,1094                        '[Required] Specifies the type of the new access '1095                        'config. Currently only type "ONE_TO_ONE_NAT" is '1096                        'supported.',1097                        flag_values=flag_values)1098    flags.DEFINE_string('access_config_nat_ip',1099                        self.EPHEMERAL_ACCESS_CONFIG_NAT_IP,1100                        'The external NAT IP of the new access config. The '1101                        'default value "ephemeral" indicates the service '1102                        'should choose any available ephemeral IP. If an '1103                        'explicit IP is given, that IP must be reserved '1104                        'by the project and not in use by another resource.',1105                        flag_values=flag_values)1106  def Handle(self, instance_name):1107    """Adds an access config to an instance's network interface.1108    Args:1109      instance_name: The instance name to which to add the new access config.1110    Returns:1111      An operation resource.1112    """1113    access_config_resource = {1114        'name': self._flags.access_config_name,1115        'type': self._flags.access_config_type,1116        }1117    if (self._flags.access_config_nat_ip.lower() !=1118        self.EPHEMERAL_ACCESS_CONFIG_NAT_IP):1119      access_config_resource['natIP'] = self._flags.access_config_nat_ip1120    kwargs = {'networkInterface': self._flags.network_interface_name}1121    self._AutoDetectZone()1122    instance_context = self._context_parser.ParseContextOrPrompt('instances',1123                                                                 instance_name)1124    add_access_config_request = self.api.instances.addAccessConfig(1125        **self._PrepareRequestArgs(1126            instance_context,1127            body=access_config_resource,1128            **kwargs))1129    return add_access_config_request.execute()1130class DeleteAccessConfig(InstanceCommand):1131  """Delete an access config from a VM instance's network interface."""1132  positional_args = '<instance-name>'1133  def __init__(self, name, flag_values):1134    super(DeleteAccessConfig, self).__init__(name, flag_values)1135    flags.DEFINE_string('network_interface_name',1136                        self.DEFAULT_NETWORK_INTERFACE_NAME,1137                        '[Required] The name of the instance\'s network '1138                        'interface from which to delete the access config.',1139                        flag_values=flag_values)1140    flags.DEFINE_string('access_config_name',1141                        self.DEFAULT_ACCESS_CONFIG_NAME,1142                        '[Required] The name of the access config to delete.',1143                        flag_values=flag_values)1144  def Handle(self, instance_name):1145    """Deletes an access config from an instance's network interface.1146    Args:1147      instance_name: The instance name from which to delete the access config.1148    Returns:1149      An operation resource.1150    """1151    kwargs = {'accessConfig': self._flags.access_config_name,1152              'networkInterface': self._flags.network_interface_name}1153    self._AutoDetectZone()1154    instance_context = self._context_parser.ParseContextOrPrompt('instances',1155                                                                 instance_name)1156    delete_access_config_request = self.api.instances.deleteAccessConfig(1157        **self._PrepareRequestArgs(instance_context, **kwargs))1158    return delete_access_config_request.execute()1159class SetScheduling(InstanceCommand):1160  """Set scheduling options for an instance."""1161  positional_args = '<instance-name>'1162  def __init__(self, name, flag_values):1163    super(SetScheduling, self).__init__(name, flag_values)1164    _DefineSchedulingFlags(flag_values)1165  def Handle(self, instance_name):1166    """Set scheduling options for an instance.1167    Args:1168      instance_name: The instance name for which to set scheduling options.1169    Returns:1170      An operation resource.1171    """1172    scheduling = _GetSchedulingFromFlags(self._flags)1173    self._AutoDetectZone()1174    instance_context = self._context_parser.ParseContextOrPrompt(1175        'instances', instance_name)1176    set_scheduling_request = self.api.instances.setScheduling(1177        **self._PrepareRequestArgs(instance_context, body=scheduling))1178    return set_scheduling_request.execute()1179class SshInstanceBase(InstanceCommand):1180  """Base class for SSH-based commands."""1181  # We want everything after 'ssh <instance>' to be passed on to the1182  # ssh command in question.  As such, all arguments to the utility1183  # must come before the 'ssh' command.1184  sort_args_and_flags = False1185  def __init__(self, name, flag_values):1186    super(SshInstanceBase, self).__init__(name, flag_values)1187    flags.DEFINE_integer(1188        'ssh_port',1189        22,1190        'TCP port to connect to',1191        flag_values=flag_values)1192    flags.DEFINE_multistring(1193        'ssh_arg',1194        [],1195        'Additional arguments to pass to ssh',1196        flag_values=flag_values)1197    flags.DEFINE_integer(1198        'ssh_key_push_wait_time',1199        10,  # 10 seconds1200        '[Deprecated] Number of seconds to wait for updates to project-wide '1201        'ssh keys to cascade to the instances within the project. This value '1202        'is no longer used. Instead, the instance is polled periodically '1203        'until it accepts SSH connections using the new key.',1204        flag_values=flag_values)1205    flags.DEFINE_integer(1206        'ssh_key_push_timeout',1207        60,  # 60 seconds1208        'Number of seconds to wait for a newly added SSH key to cascade '1209        'to the instance before timing out.',1210        flag_values=flag_values)1211  def PrintResult(self, _):1212    """Override the PrintResult to be a noop."""1213    pass1214  def _GetInstanceResource(self, instance_name):1215    """Get the instance resource. This is the dictionary returned by the API.1216    Args:1217      instance_name: The name of the instance to retrieve the ssh address for.1218    Returns:1219      The data for the instance resource as returned by the API.1220    Raises:1221      gcutil_errors.CommandError: If the instance does not exist.1222    """1223    self._AutoDetectZone()1224    instance_context = self._context_parser.ParseContextOrPrompt('instances',1225                                                                 instance_name)1226    request = self.api.instances.get(1227        **self._PrepareRequestArgs(instance_context))1228    result = request.execute()1229    if not result:1230      raise gcutil_errors.CommandError(1231          'Unable to find the instance %s.' % (instance_name))1232    return result1233  def _GetSshAddress(self, instance_resource):1234    """Retrieve the ssh address from the passed instance resource data.1235    Args:1236      instance_resource: The resource data of the instance for which1237        to retrieve the ssh address.1238    Returns:1239      The ssh address and port.1240    Raises:1241      gcutil_errors.CommandError: If the instance has no external address.1242    """1243    external_addresses = self._ExtractExternalIpFromInstanceRecord(1244        instance_resource)1245    if len(external_addresses) < 1:1246      raise gcutil_errors.CommandError(1247          'Cannot connect to an instance with no external address')1248    return (external_addresses[0], self._flags.ssh_port)1249  def _WaitForSshKeyPropagation(self, instance_resource):1250    command = self._BuildSshCmd(1251        instance_resource, 'ssh',1252        ['-A', '-p', '%(port)d', '%(user)s@%(host)s', 'true'])1253    deadline_sec = time.time() + self._flags.ssh_key_push_timeout1254    finished = False1255    while (not finished) and (time.time() < deadline_sec):1256      retval = subprocess.call(1257          command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)1258      finished = retval == 01259      if not finished:1260        time.sleep(5)1261    if not finished:1262      raise gcutil_errors.CommandError('SSH key failed to propagate to the VM')1263  def _EnsureSshable(self, instance_resource):1264    """Ensure that the user can ssh into the specified instance.1265    This method returns a context manager which checks if the instance has SSH1266    keys defined for it, and if it does not this makes sure the enclosing1267    project contains a metadata entry for the user's public ssh key.1268    If the project is updated to add the user's ssh key, then the entry point1269    will attempt to connect to the instance repeatedly until the ssh keys1270    have been propagated.1271    Args:1272      instance_resource: The resource data for the instance to which to connect.1273    Returns:1274      A context manager which takes care of adding ephemeral SSH keys to the1275      project and removing them later on.1276    Raises:1277      gcutil_errors.CommandError: If the instance is not in the RUNNING state.1278    """1279    class SshableContextManager(object):1280      """Context manager for an instance being sshable.1281      This ensures that an instance can be ssh'ed to on entry (including1282      adding an SSH key pair to the project if necessary).1283      """1284      def __init__(self, add_method, wait_method):1285        self.add_method = add_method1286        self.wait_method = wait_method1287      def __enter__(self):1288        instance_status = instance_resource.get('status')1289        if instance_status != 'RUNNING':1290          raise gcutil_errors.CommandError(1291              'Cannot connect to the instance since its current status is %s.'1292              % instance_status)1293        instance_metadata = instance_resource.get('metadata', {})1294        instance_ssh_key_entries = (1295            [entry for entry in instance_metadata.get('items', [])1296             if entry.get('key') == 'sshKeys'])1297        self.added_ssh_key = None1298        if not instance_ssh_key_entries:1299          self.added_ssh_key = self.add_method()1300        if self.added_ssh_key:1301          self.wait_method(instance_resource)1302        return self.added_ssh_key1303      def __exit__(self, *args, **kwargs):1304        pass1305    return SshableContextManager(1306        self._AddComputeKeyToProject,1307        self._WaitForSshKeyPropagation)1308  def _BuildSshCmd(self, instance_resource, command, args):1309    """Builds the given SSH-based command line with the given arguments.1310    A complete SSH-based command line is built from the given command,1311    any common arguments, and the arguments provided. The value of1312    each argument is formatted using a dictionary that contains the1313    following keys: host and port.1314    Args:1315      instance_resource: The resource data of the instance for which1316        to build the ssh command.1317      command: the ssh-based command to run (e.g. ssh or scp)1318      args: arguments for the command1319    Returns:1320      The command line used to perform the requested ssh operation.1321    Raises:1322      IOError: An error occured accessing SSH details.1323    """1324    (host, port) = self._GetSshAddress(instance_resource)1325    values = {'host': host,1326              'port': port,1327              'user': self._flags.ssh_user}1328    command_line = [1329        command,1330        '-o', 'UserKnownHostsFile=/dev/null',1331        '-o', 'CheckHostIP=no',1332        '-o', 'StrictHostKeyChecking=no',1333        '-i', self._flags.private_key_file,1334    ] + self._flags.ssh_arg1335    if LOGGER.level <= logging.DEBUG:1336      command_line.append('-v')1337    for arg in args:1338      command_line.append(arg % values)1339    return command_line1340  def _IsInstanceRootDiskPersistent(self, instance_resource):1341    """Determines if instance's root disk is persistent.1342    Args:1343      instance_resource: Dictionary result from a get instance json request.1344    Returns:1345      True if the root disk of the VM instance is persistent, otherwise False.1346    """1347    boot_disk_is_persistent = False1348    for disk in instance_resource.get('disks', []):1349      if disk.get('boot', False) and disk.get('type', '') == 'PERSISTENT':1350        boot_disk_is_persistent = True1351    return boot_disk_is_persistent1352  def _PrintEphemeralDiskWarning(self, instance_resource):1353    """Prints a warning message the instance is running on an ephemeral disk.1354    Args:1355      instance_resource: Dictionary result from a get instance json request.1356    """1357    if not self._IsInstanceRootDiskPersistent(instance_resource):1358      LOGGER.warn(EPHEMERAL_ROOT_DISK_WARNING_MESSAGE)1359  def _RunSshCmd(self, instance_name, command, args):1360    """Run the given SSH-based command line with the given arguments.1361    The specified SSH-base command is run for the arguments provided.1362    The value of each argument is formatted using a dictionary that1363    contains the following keys: host and port.1364    Args:1365      instance_name: The name of the instance for which to run the ssh command.1366      command: the ssh-based command to run (e.g. ssh or scp)1367      args: arguments for the command1368    Raises:1369      IOError: An error occured accessing SSH details.1370    """1371    instance_resource = self._GetInstanceResource(instance_name)1372    command_line = self._BuildSshCmd(instance_resource, command, args)1373    self._PrintEphemeralDiskWarning(instance_resource)1374    try:1375      proc = None1376      with self._EnsureSshable(instance_resource):1377        LOGGER.info('Running command line: %s', ' '.join(command_line))1378        proc = subprocess.Popen(command_line)1379      sys.exit(proc.wait())1380    except ssh_keys.UserSetupError as e:1381      LOGGER.warn('Could not generate compute ssh key: %s', e)1382      return1383    except OSError as e:1384      LOGGER.error('There was a problem executing the command: %s', e)1385class SshToInstance(SshInstanceBase):1386  """Connect to a VM instance with ssh."""1387  positional_args = '<instance-name> <ssh-args>'1388  def _GenerateSshArgs(self, *argv):1389    """Generates the command line arguments for the ssh command.1390    Args:1391      *argv: List of additional ssh command line args, if any.1392    Returns:1393      The complete ssh argument list.1394    """1395    ssh_args = ['-A', '-p', '%(port)d', '%(user)s@%(host)s', '--']1396    escaped_args = [a.replace('%', '%%') for a in argv]1397    ssh_args.extend(escaped_args)1398    return ssh_args1399  def Handle(self, instance_name, *argv):1400    """SSH into the instance.1401    Args:1402      instance_name: The name of the instance to ssh to.1403      *argv: The remaining unhandled arguments.1404    Returns:1405      The result of the ssh command1406    """1407    ssh_args = self._GenerateSshArgs(*argv)1408    self._RunSshCmd(instance_name, 'ssh', ssh_args)1409class PushToInstance(SshInstanceBase):1410  """Push one or more files to a VM instance."""1411  positional_args = '<instance-name> <file-1> ... <file-n> <destination>'1412  def _GenerateScpArgs(self, *argv):1413    """Generates the command line arguments for the scp command.1414    Args:1415      *argv: List of files to push and instance-relative destination.1416    Returns:1417      The scp argument list.1418    Raises:1419      gcutil_errors.CommandError: If an invalid number of arguments are passed1420          in.1421    """1422    if len(argv) < 2:1423      raise gcutil_errors.CommandError('Invalid number of arguments passed.')1424    scp_args = ['-r', '-P', '%(port)d', '--']1425    escaped_args = [a.replace('%', '%%') for a in argv]1426    scp_args.extend(escaped_args[0:-1])1427    scp_args.append('%(user)s@%(host)s:' + escaped_args[-1])1428    return scp_args1429  def Handle(self, instance_name, *argv):1430    """Pushes one or more files into the instance.1431    Args:1432      instance_name: The name of the instance to push files to.1433      *argv: The remaining unhandled arguments.1434    Returns:1435      The result of the scp command1436    Raises:1437      gcutil_errors.CommandError: If an invalid number of arguments are passed1438        in.1439    """1440    scp_args = self._GenerateScpArgs(*argv)1441    self._RunSshCmd(instance_name, 'scp', scp_args)1442class PullFromInstance(SshInstanceBase):1443  """Pull one or more files from a VM instance."""1444  positional_args = '<instance-name> <file-1> ... <file-n> <destination>'1445  def _GenerateScpArgs(self, *argv):1446    """Generates the command line arguments for the scp command.1447    Args:1448      *argv: List of files to pull and local-relative destination.1449    Returns:1450      The scp argument list.1451    Raises:1452      gcutil_errors.CommandError: If an invalid number of arguments are passed1453          in.1454    """1455    if len(argv) < 2:1456      raise gcutil_errors.CommandError('Invalid number of arguments passed.')1457    scp_args = ['-r', '-P', '%(port)d', '--']1458    escaped_args = [a.replace('%', '%%') for a in argv]1459    for arg in escaped_args[0:-1]:1460      scp_args.append('%(user)s@%(host)s:' + arg)1461    scp_args.append(escaped_args[-1])1462    return scp_args1463  def Handle(self, instance_name, *argv):1464    """Pulls one or more files from the instance.1465    Args:1466      instance_name: The name of the instance to pull files from.1467      *argv: The remaining unhandled arguments.1468    Returns:1469      The result of the scp command1470    Raises:1471      gcutil_errors.CommandError: If an invalid number of arguments are passed1472          in.1473    """1474    scp_args = self._GenerateScpArgs(*argv)1475    self._RunSshCmd(instance_name, 'scp', scp_args)1476class GetSerialPortOutput(InstanceCommand):1477  """Get the output of a VM instance's serial port."""1478  positional_args = '<instance-name>'1479  def Handle(self, instance_name):1480    """Get the specified instance's serial port output.1481    Args:1482      instance_name: The name of the instance.1483    Returns:1484      The output of the instance's serial port.1485    """1486    self._AutoDetectZone()1487    instance_context = self._context_parser.ParseContextOrPrompt('instances',1488                                                                 instance_name)1489    instance_request = self.api.instances.getSerialPortOutput(1490        **self._PrepareRequestArgs(instance_context))1491    return instance_request.execute()1492  def PrintResult(self, result):1493    """Override the PrintResult to be a noop."""1494    if self._flags.print_json:1495      super(GetSerialPortOutput, self).PrintResult(result)1496    else:1497      print result['contents'].encode('utf-8')1498class ResetInstance(InstanceCommand):1499  """Reset a VM instance, resulting in a reboot."""1500  positional_args = '<instance-name>'1501  def Handle(self, instance_name):1502    """Initiate a hard reset on the instance.1503    Args:1504      instance_name: The instance name to reset.1505    Returns:1506      An operation resource.1507    """1508    self._AutoDetectZone()1509    instance_context = self._context_parser.ParseContextOrPrompt('instances',1510                                                                 instance_name)1511    reset_request = self.api.instances.reset(1512        **self._PrepareRequestArgs(instance_context))1513    return reset_request.execute()1514class OptimisticallyLockedInstanceCommand(InstanceCommand):1515  """Base class for instance commands that require a fingerprint."""1516  def __init__(self, name, flag_values):1517    super(OptimisticallyLockedInstanceCommand, self).__init__(name, flag_values)1518    flags.DEFINE_string('fingerprint',1519                        None,1520                        '[Required] Fingerprint of the data to be '1521                        'overwritten. This fingerprint provides optimistic '1522                        'locking. Data will only be set if the given '1523                        'fingerprint matches the state of the data prior to '1524                        'this request.',1525                        flag_values=flag_values)1526  def Handle(self, instance_name):1527    """Invokes the HandleCommand method of the subclass."""1528    if not self._flags.fingerprint:1529      raise app.UsageError('You must provide a fingerprint with your request.')1530    return self.HandleCommand(instance_name)1531class SetMetadata(OptimisticallyLockedInstanceCommand):1532  """Set the metadata for a VM instance.1533  This method overwrites existing instance metadata with new metadata.1534  Common metadata (project-wide) is preserved.1535  For example, running:1536    gcutil --project=<project-name> setinstancemetadata my-instance \1537      --metadata="key1:value1" \1538      --fingerprint=<original-fingerprint>1539    ...1540    gcutil --project=<project-name> setinstancemetadata my-instance \1541      --metadata="key2:value2" \1542      --fingerprint=<new-fingerprint>1543  will result in 'my-instance' having 'key2:value2' as its metadata.1544  """1545  positional_args = '<instance-name>'1546  def __init__(self, name, flag_values):1547    super(SetMetadata, self).__init__(name, flag_values)1548    flags.DEFINE_bool('force',1549                      None,1550                      'Set new metadata even if the key \'sshKeys\' will '1551                      'no longer be present.',1552                      flag_values=flag_values,1553                      short_name='f')1554    self._metadata_flags_processor = metadata.MetadataFlagsProcessor(1555        flag_values)1556  def HandleCommand(self, instance_name):1557    """Set instance-specific metadata.1558    Args:1559      instance_name: The name of the instance scoping this request.1560    Returns:1561      An operation resource.1562    """1563    self._AutoDetectZone()1564    instance_context = self._context_parser.ParseContextOrPrompt('instances',1565                                                                 instance_name)1566    new_metadata = self._metadata_flags_processor.GatherMetadata()1567    if not self._flags.force:1568      new_keys = set([entry['key'] for entry in new_metadata])1569      get_project = self.api.projects.get(project=instance_context['project'])1570      project_resource = get_project.execute()1571      project_metadata = project_resource.get('commonInstanceMetadata', {})1572      project_metadata = project_metadata.get('items', [])1573      project_keys = set([entry['key'] for entry in project_metadata])1574      get_instance = self.api.instances.get(1575          **self._PrepareRequestArgs(instance_context))1576      instance_resource = get_instance.execute()1577      instance_metadata = instance_resource.get('metadata', {})1578      instance_metadata = instance_metadata.get('items', [])1579      instance_keys = set([entry['key'] for entry in instance_metadata])1580      if ('sshKeys' in instance_keys and 'sshKeys' not in new_keys1581          and 'sshKeys' not in project_keys):1582        raise gcutil_errors.CommandError(1583            'Discarding update that would have erased instance sshKeys.'1584            '\n\nRe-run with the -f flag to force the update.')1585    metadata_resource = {'kind': self._GetResourceApiKind('metadata'),1586                         'items': new_metadata,1587                         'fingerprint': self._flags.fingerprint}1588    set_metadata_request = self.api.instances.setMetadata(1589        **self._PrepareRequestArgs(instance_context, body=metadata_resource))1590    return set_metadata_request.execute()1591class SetTags(OptimisticallyLockedInstanceCommand):1592  """Set the tags for a VM instance.1593  This method overwrites existing instance tags.1594  For example, running:1595    gcutil --project=<project-name> setinstancetags my-instance \1596      --tags="tag-1" \1597      --fingerprint=<original-fingerprint>1598    ...1599    gcutil --project=<project-name> setinstancetags my-instance \1600      --tags="tag-2,tag-3" \1601      --fingerprint=<new-fingerprint>1602  will result in 'my-instance' having tags 'tag-2' and 'tag-3'.1603  """1604  def __init__(self, name, flag_values):1605    super(SetTags, self).__init__(name, flag_values)1606    flags.DEFINE_list('tags',1607                      [],1608                      '[Required] A set of tags applied to this instance. '1609                      'Used for filtering and to configure network firewall '1610                      'rules To specify multiple tags, provide them as '1611                      'comma-separated entries.',1612                      flag_values=flag_values)1613  def HandleCommand(self, instance_name):1614    """Set instance tags.1615    Args:1616      instance_name: The name of the instance scoping this request.1617    Returns:1618      An operation resource.1619    """1620    self._AutoDetectZone()1621    instance_context = self._context_parser.ParseContextOrPrompt('instances',1622                                                                 instance_name)1623    tag_resource = {'items': sorted(set(self._flags.tags)),1624                    'fingerprint': self._flags.fingerprint}1625    set_tags_request = self.api.instances.setTags(1626        **self._PrepareRequestArgs(instance_context, body=tag_resource))1627    return set_tags_request.execute()1628class AttachDisk(InstanceCommand):1629  """Attach a persistent disk to a VM instance."""1630  positional_args = '<instance-name>'1631  def __init__(self, name, flag_values):1632    super(AttachDisk, self).__init__(name, flag_values)1633    flags.DEFINE_multistring(1634        'disk',1635        '',1636        '[Required] The name of a disk to be attached to the '1637        'instance. The name may be followed by a '1638        'comma-separated list of name=value pairs '1639        'specifying options. Legal option names are '1640        '\'deviceName\', to specify the disk\'s device '1641        'name, and \'mode\', to indicate whether the disk '1642        'should be attached READ_WRITE (the default) or '1643        'READ_ONLY',1644        flag_values=flag_values)1645  def Handle(self, instance_name):1646    """Attach a persistent disk to the instance.1647    Args:1648      instance_name: The instance name to attach to.1649    Returns:1650      An operation resource.1651    """1652    self._AutoDetectZone()1653    instance_context = self._context_parser.ParseContextOrPrompt('instances',1654                                                                 instance_name)1655    disks = [self._BuildAttachedDisk(disk) for disk in self._flags.disk]1656    attach_requests = []1657    for disk in disks:1658      attach_requests.append(1659          self.api.instances.attachDisk(1660              **self._PrepareRequestArgs(1661                  instance_context,1662                  body=disk)))1663    return self.ExecuteRequests(attach_requests)1664class DetachDisk(InstanceCommand):1665  """Detach a persistent disk from a VM instance."""1666  positional_args = '<instance-name>'1667  def __init__(self, name, flag_values):1668    super(DetachDisk, self).__init__(name, flag_values)1669    flags.DEFINE_multistring(1670        'device_name',1671        '',1672        '[Required] The device name of a persistent disk to '1673        'detach from the instance. The device name is '1674        'specified at instance creation time and may not '1675        'be the same as the persistent disk name.',1676        flag_values=flag_values)1677  def Handle(self, instance_name):1678    """Detach a persistent disk from the instance.1679    Args:1680      instance_name: The instance name to detach from.1681    Returns:1682      An operation resource.1683    """1684    self._AutoDetectZone()1685    instance_context = self._context_parser.ParseContextOrPrompt('instances',1686                                                                 instance_name)1687    detach_requests = []1688    for device_name in self._flags.device_name:1689      detach_requests.append(1690          self.api.instances.detachDisk(1691              **self._PrepareRequestArgs(1692                  instance_context,1693                  deviceName=device_name)))1694    return self.ExecuteRequests(detach_requests)1695class SetInstanceDiskAutoDelete(InstanceCommand):1696  """Changes the auto-delete flag on a disk attached to instance."""1697  positional_args = '<instance-name>'1698  def __init__(self, name, flag_values):1699    super(SetInstanceDiskAutoDelete, self).__init__(name, flag_values)1700    flags.DEFINE_string(1701        'device_name',1702        None,1703        'The device name of a persistent disk to '1704        'update on the instance. If not specified, '1705        'this will default to the instance name.',1706        flag_values=flag_values)1707    flags.DEFINE_boolean(1708        'auto_delete',1709        None,1710        '[Required] The new value of auto-delete flag for the given disk.',1711        flag_values=flag_values)1712  def Handle(self, instance_name):1713    """Changes the auto-delete flag on a disk attached to instance.1714    Args:1715      instance_name: The instance name to detach from.1716    Returns:1717      An operation resource.1718    """1719    self._AutoDetectZone()1720    instance_context = self._context_parser.ParseContextOrPrompt('instances',1721                                                                 instance_name)1722    request = self.api.instances.setDiskAutoDelete(1723        **self._PrepareRequestArgs(1724            instance_context,1725            deviceName=self._flags.device_name or instance_name,1726            autoDelete=self._flags.auto_delete))1727    return request.execute()1728def AddCommands():1729  """Add all of the instance related commands."""1730  appcommands.AddCmd('addinstance', AddInstance)1731  appcommands.AddCmd('getinstance', GetInstance)1732  appcommands.AddCmd('deleteinstance', DeleteInstance)1733  appcommands.AddCmd('listinstances', ListInstances)1734  appcommands.AddCmd('addaccessconfig', AddAccessConfig)1735  appcommands.AddCmd('deleteaccessconfig', DeleteAccessConfig)1736  appcommands.AddCmd('setscheduling', SetScheduling)1737  appcommands.AddCmd('ssh', SshToInstance)1738  appcommands.AddCmd('push', PushToInstance)1739  appcommands.AddCmd('pull', PullFromInstance)1740  appcommands.AddCmd('getserialportoutput', GetSerialPortOutput)1741  appcommands.AddCmd('setinstancemetadata', SetMetadata)1742  appcommands.AddCmd('setinstancediskautodelete', SetInstanceDiskAutoDelete)1743  appcommands.AddCmd('setinstancetags', SetTags)1744  appcommands.AddCmd('attachdisk', AttachDisk)1745  appcommands.AddCmd('detachdisk', DetachDisk)...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
