예제 #1
0
    def _PromptForScopeList(self, ambiguous_refs, attributes, resource_type,
                            choice_resources, raise_on_prompt_failure):
        """Prompt to resolve abiguous resources.  Either returns str or throws."""
        # targetInstances -> target instances
        resource_name = utils.CamelCaseToOutputFriendly(resource_type)
        # Resource names should be surrounded by brackets while choices should not
        names = ['[{0}]'.format(name) for name, _, _ in ambiguous_refs]
        # Print deprecation state for choices.
        choice_names = []
        choice_mapping = []
        for attribute in attributes:
            for choice_resource in choice_resources[attribute]:
                deprecated = choice_resource.deprecated
                if deprecated:
                    choice_name = '{0} ({1})'.format(choice_resource.name,
                                                     deprecated.state)
                else:
                    choice_name = choice_resource.name

                if len(attributes) > 1:
                    choice_name = '{0}: {1}'.format(attribute, choice_name)

                choice_mapping.append((attribute, choice_resource.name))
                choice_names.append(choice_name)

        title = utils.ConstructList(
            'For the following {0}:'.format(resource_name), names)
        idx = console_io.PromptChoice(options=choice_names,
                                      message='{0}choose a {1}:'.format(
                                          title, ' or '.join(attributes)))
        if idx is None:
            raise_on_prompt_failure()
        else:
            return choice_mapping[idx]
예제 #2
0
  def PromptIfDisksWithoutAutoDeleteWillBeDeleted(
      self, args, disks_to_warn_for):
    """Prompts if disks with False autoDelete will be deleted."""
    if not disks_to_warn_for:
      return

    prompt_list = []
    disk_refs = self.CreateZonalReferences(
        disks_to_warn_for, args.zone, resource_type='disks')
    for ref in disk_refs:
      prompt_list.append('[{0}] in [{1}]'.format(ref.Name(), ref.zone))
      prompt_message = utils.ConstructList(
          'The following disks are not configured to be automatically deleted '
          'with instance deletion, but they will be deleted as a result of '
          'this operation if they are not attached to any other instances:',
          prompt_list)
    if not console_io.PromptContinue(message=prompt_message):
      raise exceptions.ToolException('Deletion aborted by user.')
예제 #3
0
    def WarnForZonalCreation(self, resource_refs):
        """Warns the user if a zone has upcoming deprecation."""
        zones = self.GetZones(resource_refs)
        if not zones:
            return

        prompts = []
        zones_with_deprecated = []
        for zone in zones:
            if zone.deprecated:
                zones_with_deprecated.append(zone)

        if not zones_with_deprecated:
            return

        if zones_with_deprecated:
            phrases = []
            if len(zones_with_deprecated) == 1:
                phrases = ('zone is', 'this zone', 'the')
            else:
                phrases = ('zones are', 'these zones', 'their')
            title = ('\n'
                     'WARNING: The following selected {0} deprecated.'
                     ' All resources in {1} will be deleted after'
                     ' {2} turndown date.'.format(phrases[0], phrases[1],
                                                  phrases[2]))
            printable_deprecated_zones = []
            for zone in zones_with_deprecated:
                if zone.deprecated.deleted:
                    printable_deprecated_zones.append(
                        ('[{0}] {1}').format(zone.name,
                                             zone.deprecated.deleted))
                else:
                    printable_deprecated_zones.append('[{0}]'.format(
                        zone.name))
            prompts.append(
                utils.ConstructList(title, printable_deprecated_zones))

        final_message = ' '.join(prompts)
        if not console_io.PromptContinue(message=final_message):
            raise calliope_exceptions.ToolException(
                'Creation aborted by user.')
  def PromptIfDisksWithoutAutoDeleteWillBeDeleted(self, disks_to_warn_for):
    """Prompts if disks with False autoDelete will be deleted.

    Args:
      disks_to_warn_for: list of references to disk resources.
    """
    if not disks_to_warn_for:
      return

    prompt_list = []
    for ref in disks_to_warn_for:
      prompt_list.append('[{0}] in [{1}]'.format(ref.Name(), ref.zone))

    prompt_message = utils.ConstructList(
        'The following disks are not configured to be automatically deleted '
        'with instance deletion, but they will be deleted as a result of '
        'this operation if they are not attached to any other instances:',
        prompt_list)
    if not console_io.PromptContinue(message=prompt_message):
      raise compute_exceptions.AbortedError('Deletion aborted by user.')
예제 #5
0
    def WarnForRegionalCreation(self, resource_refs):
        """Warns the user if a region has upcoming deprecation."""
        regions = self.GetRegions(resource_refs)
        if not regions:
            return

        prompts = []
        regions_with_deprecated = []
        for region in regions:
            if region.deprecated:
                regions_with_deprecated.append(region)

        if not regions_with_deprecated:
            return

        if regions_with_deprecated:
            phrases = []
            if len(regions_with_deprecated) == 1:
                phrases = ('region is', 'this region', 'the')
            else:
                phrases = ('regions are', 'these regions', 'their')
            title = ('\n'
                     'WARNING: The following selected {0} deprecated.'
                     ' All resources in {1} will be deleted after'
                     ' {2} turndown date.'.format(phrases[0], phrases[1],
                                                  phrases[2]))
            printable_deprecated_regions = []
            for region in regions_with_deprecated:
                if region.deprecated.deleted:
                    printable_deprecated_regions.append(
                        ('[{0}] {1}').format(region.name,
                                             region.deprecated.deleted))
                else:
                    printable_deprecated_regions.append('[{0}]'.format(
                        region.name))
            prompts.append(
                utils.ConstructList(title, printable_deprecated_regions))

        final_message = ' '.join(prompts)
        if not console_io.PromptContinue(message=final_message):
            raise exceptions.AbortedError('Creation aborted by user.')
예제 #6
0
def MakeRequests(requests, http, batch_url, errors, progress_tracker=None):
    """Makes one or more requests to the API.

  Each request can be either a synchronous API call or an asynchronous
  one. For synchronous calls (e.g., get and list), the result from the
  server is yielded immediately. For asynchronous calls (e.g., calls
  that return operations like insert), this function waits until the
  operation reaches the DONE state and fetches the corresponding
  object and yields that object (nothing is yielded for deletions).

  Currently, a heterogenous set of synchronous calls can be made
  (e.g., get request to fetch a disk and instance), however, the
  asynchronous requests must be homogenous (e.g., they must all be the
  same verb on the same collection). In the future, heterogenous
  asynchronous requests will be supported. For now, it is up to the
  client to ensure that the asynchronous requests are
  homogenous. Synchronous and asynchronous requests can be mixed.

  Args:
    requests: A list of requests to make. Each element must be a 3-element
      tuple where the first element is the service, the second element is
      the string name of the method on the service, and the last element
      is a protocol buffer representing the request.
    http: An httplib2.Http-like object.
    batch_url: The handler for making batch requests.
    errors: A list for capturing errors. If any response contains an error,
      it is added to this list.
    progress_tracker: progress tracker to be ticked while waiting for operations
                      to finish.

  Yields:
    A response for each request. For deletion requests, no corresponding
    responses are returned.
  """
    if _RequestsAreListRequests(requests):
        for item in _List(requests=requests,
                          http=http,
                          batch_url=batch_url,
                          errors=errors):
            yield item
        return

    responses, new_errors = batch_helper.MakeRequests(requests=requests,
                                                      http=http,
                                                      batch_url=batch_url)
    errors.extend(new_errors)

    operation_service = None
    resource_service = None
    project = None

    # Collects all operation objects in a list so they can be waited on
    # and yields all non-operation objects since non-operation responses
    # cannot be waited on.
    operations_data = []

    for request, response in zip(requests, responses):
        if response is None:
            continue

        service, _, request_body = request
        if (isinstance(response, service.client.MESSAGES_MODULE.Operation)
                and service.__class__.__name__
                not in ('GlobalOperationsService', 'RegionOperationsService',
                        'ZoneOperationsService',
                        'GlobalAccountsOperationsService')):

            resource_service = service
            project = request_body.project

            if response.zone:
                operation_service = service.client.zoneOperations
            elif response.region:
                operation_service = service.client.regionOperations
            else:
                operation_service = service.client.globalOperations

            operations_data.append(
                waiters.OperationData(response, project, operation_service,
                                      resource_service))

        else:
            yield response

    if operations_data:
        warnings = []
        for response in waiters.WaitForOperations(
                operations_data=operations_data,
                http=http,
                batch_url=batch_url,
                warnings=warnings,
                progress_tracker=progress_tracker,
                errors=errors):
            yield response

        if warnings:
            log.warning(
                utils.ConstructList('Some requests generated warnings:',
                                    warnings))
예제 #7
0
def MakeRequests(requests, http, batch_url, errors):
    """Makes one or more requests to the API.

  Each request can be either a synchronous API call or an asynchronous
  one. For synchronous calls (e.g., get and list), the result from the
  server is yielded immediately. For asynchronous calls (e.g., calls
  that return operations like insert), this function waits until the
  operation reaches the DONE state and fetches the corresponding
  object and yields that object (nothing is yielded for deletions).

  Currently, a heterogenous set of synchronous calls can be made
  (e.g., get request to fetch a disk and instance), however, the
  asynchronous requests must be homogenous (e.g., they must all be the
  same verb on the same collection). In the future, heterogenous
  asynchronous requests will be supported. For now, it is up to the
  client to ensure that the asynchronous requests are
  homogenous. Synchronous and asynchronous requests can be mixed.

  Args:
    requests: A list of requests to make. Each element must be a 3-element
      tuple where the first element is the service, the second element is
      the string name of the method on the service, and the last element
      is a protocol buffer representing the request.
    http: An httplib2.Http-like object.
    batch_url: The handler for making batch requests.
    errors: A list for capturing errors. If any response contains an error,
      it is added to this list.

  Yields:
    A response for each request. For deletion requests, no corresponding
    responses are returned.
  """
    if _RequestsAreListRequests(requests):
        for item in _List(requests=requests,
                          http=http,
                          batch_url=batch_url,
                          errors=errors):
            yield item
        return

    # TODO(b/36057059): Delete the batch_helper module and move its logic
    # here. To do this, we also have to edit the lister module to depend
    # on this module instead of batch_helper.
    responses, new_errors = batch_helper.MakeRequests(requests=requests,
                                                      http=http,
                                                      batch_url=batch_url)
    errors.extend(new_errors)

    operation_service = None
    resource_service = None
    project = None

    # Collects all operation objects in a list so they can be waited on
    # and yields all non-operation objects since non-operation responses
    # cannot be waited on.
    operations = []

    for request, response in zip(requests, responses):
        if response is None:
            continue

        service, _, request_body = request
        if (isinstance(response, service.client.MESSAGES_MODULE.Operation)
                and service.__class__.__name__
                not in ('GlobalOperationsService', 'RegionOperationsService',
                        'ZoneOperationsService',
                        'GlobalAccountsOperationsService')):

            operations.append(response)

            # This logic assumes that all requests are homogenous, i.e.,
            # they all make calls to the same service and verb. This is
            # temporary. In the future, we will push this logic into the
            # function that does the actual waiting. For now, we have to
            # deal with existing interfaces to keep the scopes of the
            # refactoring CLs small.
            # TODO(b/32276307)
            if not operation_service:
                resource_service = service
                project = request_body.project

                if response.kind == 'clouduseraccounts#operation':
                    operation_service = service.client.globalAccountsOperations
                elif response.zone:
                    operation_service = service.client.zoneOperations
                elif response.region:
                    operation_service = service.client.regionOperations
                else:
                    operation_service = service.client.globalOperations

        else:
            yield response

    if operations:
        warnings = []
        for response in waiters.WaitForOperations(
                operations=operations,
                project=project,
                operation_service=operation_service,
                resource_service=resource_service,
                http=http,
                batch_url=batch_url,
                warnings=warnings,
                errors=errors):
            yield response

        if warnings:
            log.warn(
                utils.ConstructList('Some requests generated warnings:',
                                    warnings))
def SendInstancesRequestsAndPostProcessOutputs(
        api_holder,
        method_name,
        request_template,
        instances_holder_field,
        igm_ref,
        instances,
        per_instance_status_enabled=False):
    """Make *-instances requests and format output.

  Method resolves instance references, splits them to make batch of requests,
  adds to results statuses for unresolved instances, and yields all statuses
  raising errors, if any, in the end.

  Args:
    api_holder: Compute API holder.
    method_name: Name of the (region) instance groups managers service method to
      call.
    request_template: Partially filled *-instances request (no instances).
    instances_holder_field: Name of the field inside request holding instances
      field.
    igm_ref: URL to the target IGM.
    instances: A list of names of the instances to apply method to.
    per_instance_status_enabled: Enable functionality parsing resulting
      operation for graceful validation related warnings to allow per-instance
      status output. The plan is to gradually enable this for all per-instance
      commands in GA (even where graceful validation is not available / not
      used).

  Yields:
    A list of request statuses per instance. Requests status is a dictionary
    object with link to an instance keyed with 'selfLink', instance name keyed
    with 'instanceName', and status indicating if operation succeeded for
    instance keyed with 'status'. Status might be 'FAIL', 'SUCCESS', 'SKIPPED'
    in case of graceful validation, or 'MEMBER_NOT_FOUND' (in case of regional
    MIGs, when instance name cannot be resolved).
  """
    client = api_holder.client
    if igm_ref.Collection() == 'compute.instanceGroupManagers':
        service = client.apitools_client.instanceGroupManagers
    elif igm_ref.Collection() == 'compute.regionInstanceGroupManagers':
        service = client.apitools_client.regionInstanceGroupManagers
    else:
        raise ValueError('Unknown reference type {0}'.format(
            igm_ref.Collection()))

    instances_with_references = CreateInstanceReferences(
        api_holder.resources, client, igm_ref, instances)
    resolved_references = [
        instance.instance_reference for instance in instances_with_references
        if instance.instance_reference
    ]
    getattr(request_template,
            instances_holder_field).instances = resolved_references
    requests = SplitInstancesInRequest(request_template,
                                       instances_holder_field)
    request_tuples = GenerateRequestTuples(service, method_name, requests)

    errors_to_collect = []
    warnings_to_collect = []

    request_status_per_instance = []
    if per_instance_status_enabled:
        request_status_per_instance.extend(
            MakeRequestsAndGetStatusPerInstanceFromOperation(
                client, request_tuples, instances_holder_field,
                warnings_to_collect, errors_to_collect))
    else:
        request_status_per_instance.extend(
            MakeRequestsAndGetStatusPerInstance(client, request_tuples,
                                                instances_holder_field,
                                                errors_to_collect))

    unresolved_instance_names = [
        instance.instance_name for instance in instances_with_references
        if not instance.instance_reference
    ]
    request_status_per_instance.extend([
        dict(instanceName=name, status='MEMBER_NOT_FOUND')
        for name in unresolved_instance_names
    ])

    for status in request_status_per_instance:
        yield status

    if warnings_to_collect:
        log.warning(
            utils.ConstructList('Some requests generated warnings:',
                                warnings_to_collect))
    if errors_to_collect:
        raise utils.RaiseToolException(errors_to_collect)
def MakeRequests(requests,
                 http,
                 batch_url,
                 errors,
                 progress_tracker=None,
                 no_followup=False,
                 always_return_operation=False,
                 followup_overrides=None,
                 log_result=True,
                 log_warnings=True,
                 timeout=None):
    """Makes one or more requests to the API.

  Each request can be either a synchronous API call or an asynchronous
  one. For synchronous calls (e.g., get and list), the result from the
  server is yielded immediately. For asynchronous calls (e.g., calls
  that return operations like insert), this function waits until the
  operation reaches the DONE state and fetches the corresponding
  object and yields that object (nothing is yielded for deletions).

  Currently, a heterogenous set of synchronous calls can be made
  (e.g., get request to fetch a disk and instance), however, the
  asynchronous requests must be homogenous (e.g., they must all be the
  same verb on the same collection). In the future, heterogenous
  asynchronous requests will be supported. For now, it is up to the
  client to ensure that the asynchronous requests are
  homogenous. Synchronous and asynchronous requests can be mixed.

  Args:
    requests: A list of requests to make. Each element must be a 3-element tuple
      where the first element is the service, the second element is the string
      name of the method on the service, and the last element is a protocol
      buffer representing the request.
    http: An httplib2.Http-like object.
    batch_url: The handler for making batch requests.
    errors: A list for capturing errors. If any response contains an error, it
      is added to this list.
    progress_tracker: progress tracker to be ticked while waiting for operations
      to finish.
    no_followup: If True, do not followup operation with a GET request.
    always_return_operation: If True, return operation object even if operation
      fails.
    followup_overrides: A list of new resource names to GET once the operation
      finishes. Generally used in renaming calls.
    log_result: Whether the Operation Waiter should print the result in past
      tense of each request.
    log_warnings: Whether warnings for completed operation should be printed.
    timeout: The maximum amount of time, in seconds, to wait for the
      operations to reach the DONE state.

  Yields:
    A response for each request. For deletion requests, no corresponding
    responses are returned.
  """
    if _RequestsAreListRequests(requests):
        for item in _List(requests=requests,
                          http=http,
                          batch_url=batch_url,
                          errors=errors):
            yield item
        return
    responses, new_errors = batch_helper.MakeRequests(requests=requests,
                                                      http=http,
                                                      batch_url=batch_url)
    errors.extend(new_errors)

    operation_service = None
    resource_service = None

    # Collects all operation objects in a list so they can be waited on
    # and yields all non-operation objects since non-operation responses
    # cannot be waited on.
    operations_data = []

    if not followup_overrides:
        followup_overrides = [None for _ in requests]
    for request, response, followup_override in zip(requests, responses,
                                                    followup_overrides):
        if response is None:
            continue

        service, _, request_body = request
        if (isinstance(response, service.client.MESSAGES_MODULE.Operation)
                and not _IsEmptyOperation(response, service)
                and service.__class__.__name__
                not in ('GlobalOperationsService', 'RegionOperationsService',
                        'ZoneOperationsService',
                        'GlobalOrganizationOperationsService',
                        'GlobalAccountsOperationsService')):

            resource_service = service
            project = None
            if hasattr(request_body, 'project'):
                project = request_body.project
                if response.zone:
                    operation_service = service.client.zoneOperations
                elif response.region:
                    operation_service = service.client.regionOperations
                else:
                    operation_service = service.client.globalOperations
            else:
                operation_service = service.client.globalOrganizationOperations

            operations_data.append(
                waiters.OperationData(
                    response,
                    operation_service,
                    resource_service,
                    project=project,
                    no_followup=no_followup,
                    followup_override=followup_override,
                    always_return_operation=always_return_operation))

        else:
            yield response

    if operations_data:
        warnings = []
        for response in waiters.WaitForOperations(
                operations_data=operations_data,
                http=http,
                batch_url=batch_url,
                warnings=warnings,
                progress_tracker=progress_tracker,
                errors=errors,
                log_result=log_result,
                timeout=timeout):
            yield response

        if warnings and log_warnings:
            log.warning(
                utils.ConstructList('Some requests generated warnings:',
                                    warnings))
예제 #10
0
    def WarnForZonalCreation(self, resource_refs):
        """Warns the user if a zone has upcoming maintanence or deprecation."""
        zones = self.GetZones(resource_refs)
        if not zones:
            return

        prompts = []
        zones_with_upcoming_maintenance = []
        zones_with_deprecated = []
        for zone in zones:
            if zone.maintenanceWindows:
                zones_with_upcoming_maintenance.append(zone)
            if zone.deprecated:
                zones_with_deprecated.append(zone)

        if not zones_with_upcoming_maintenance and not zones_with_deprecated:
            return

        if zones_with_upcoming_maintenance:
            phrases = []
            if len(zones_with_upcoming_maintenance) == 1:
                phrases = ('a zone', 'window is')
            else:
                phrases = ('zones', 'windows are')
            title = ('You have selected {0} with upcoming '
                     'maintenance. During maintenance, resources are '
                     'temporarily unavailible. The next scheduled '
                     '{1} as follows:'.format(phrases[0], phrases[1]))
            printable_maintenance_zones = []
            for zone in zones_with_upcoming_maintenance:
                next_event = min(zone.maintenanceWindows,
                                 key=lambda x: x.beginTime)
                window = '[{0}]: {1} -- {2}'.format(zone.name,
                                                    next_event.beginTime,
                                                    next_event.endTime)
                printable_maintenance_zones.append(window)
            prompts.append(
                utils.ConstructList(title, printable_maintenance_zones))

        if zones_with_deprecated:
            phrases = []
            if len(zones_with_deprecated) == 1:
                phrases = ('zone is', 'this zone', 'the')
            else:
                phrases = ('zones are', 'these zones', 'their')
            title = ('\n'
                     'WARNING: The following selected {0} deprecated.'
                     ' All resources in {1} will be deleted after'
                     ' {2} turndown date.'.format(phrases[0], phrases[1],
                                                  phrases[2]))
            printable_deprecated_zones = []
            for zone in zones_with_deprecated:
                if zone.deprecated.deleted:
                    printable_deprecated_zones.append(
                        ('[{0}] {1}').format(zone.name,
                                             zone.deprecated.deleted))
                else:
                    printable_deprecated_zones.append('[{0}]'.format(
                        zone.name))
            prompts.append(
                utils.ConstructList(title, printable_deprecated_zones))

        final_message = ' '.join(prompts)
        if not console_io.PromptContinue(message=final_message):
            raise calliope_exceptions.ToolException(
                'Creation aborted by user.')