Example #1
0
def CreateService(service_name: str,
                  api_version: str) -> 'googleapiclient.discovery.Resource':
  """Creates an GCP API service.

  Args:
    service_name (str): Name of the GCP service to use.
    api_version (str): Version of the GCP service API to use.

  Returns:
    googleapiclient.discovery.Resource: API service resource.

  Raises:
    CredentialsConfigurationError: If Application Default Credentials could
        not be obtained
    RuntimeError: If service build times out.
  """

  try:
    credentials, _ = default()
  except DefaultCredentialsError as exception:
    raise errors.CredentialsConfigurationError(
        'Could not get application default credentials. Have you run $ gcloud '
        'auth application-default login?: {0!s}'.format_map(exception),
        __name__) from exception

  service_built = False
  for retry in range(RETRY_MAX):
    try:
      service = build(
          service_name,
          api_version,
          credentials=credentials,
          cache_discovery=False)
      service_built = True
    except socket.timeout:
      logger.warning(
          'Timeout trying to build service {0:s} (try {1:d} of {2:d})'.format(
              service_name, retry, RETRY_MAX))

    if service_built:
      break

  if not service_built:
    error_msg = (
        'Failures building service {0:s} caused by multiple '
        'timeouts').format(service_name)
    raise RuntimeError(error_msg)

  return service
Example #2
0
def ExecuteRequest(client: 'googleapiclient.discovery.Resource',
                   func: str,
                   kwargs: Dict[str, Any],
                   throttle: bool = False) -> List[Dict[str, Any]]:
    """Execute a request to the GCP API.

  Args:
    client (googleapiclient.discovery.Resource): A GCP client object.
    func (str): A GCP function to query from the client.
    kwargs (Dict): A dictionary of parameters for the function func.
    throttle (bool): A boolean indicating if requests should be throttled. This
        is necessary for some APIs (e.g. list logs) as there is an API rate
        limit. Default is False, i.e. requests are not throttled.

  Returns:
    List[Dict]: A List of dictionaries (responses from the request).

  Raises:
    CredentialsConfigurationError: If the request to the GCP API could not
        complete.
  """

    responses = []
    next_token = None
    while True:
        if throttle:
            time.sleep(1)
        if next_token:
            if 'body' in kwargs:
                kwargs['body']['pageToken'] = next_token
            else:
                kwargs['pageToken'] = next_token
        try:
            request = getattr(client, func)
            response = request(**kwargs).execute()
        except (RefreshError, DefaultCredentialsError) as exception:
            raise errors.CredentialsConfigurationError(
                ': {0!s}. Something is wrong with your Application Default '
                'Credentials. Try running: $ gcloud auth application-default '
                'login'.format(exception), __name__) from exception
        responses.append(response)
        next_token = response.get('nextPageToken')
        if not next_token:
            return responses
Example #3
0
def CreateDiskCopy(
    src_proj: str,
    dst_proj: str,
    zone: str,
    instance_name: Optional[str] = None,
    disk_name: Optional[str] = None,
    disk_type: Optional[str] = None) -> 'compute.GoogleComputeDisk':
  """Creates a copy of a Google Compute Disk.

  Args:
    src_proj (str): Name of project that holds the disk to be copied.
    dst_proj (str): Name of project to put the copied disk in.
    zone (str): Zone where the new disk is to be created.
    instance_name (str): Optional. Instance using the disk to be copied.
    disk_name (str): Optional. Name of the disk to copy. If None,
        instance_name must be specified and the boot disk will be copied.
    disk_type (str): Optional. URL of the disk type resource describing
        which disk type to use to create the disk. The default behavior is to
        use the same disk type as the source disk.

  Returns:
    GoogleComputeDisk: A Google Compute Disk object.

  Raises:
    ResourceNotFoundError: If the GCP resource is not found.
    CredentialsConfigurationError: If the library could not authenticate to GCP.
    RuntimeError: If an unknown HttpError is thrown.
    ResourceCreationError: If there are errors copying the disk.
    ValueError: If both instance_name and disk_name are missing.
  """

  if not instance_name and not disk_name:
    raise ValueError(
        'You must specify at least one of [instance_name, disk_name].')

  src_project = gcp_project.GoogleCloudProject(src_proj)
  dst_project = gcp_project.GoogleCloudProject(dst_proj, default_zone=zone)

  try:
    if disk_name:
      disk_to_copy = src_project.compute.GetDisk(disk_name)
    elif instance_name:
      instance = src_project.compute.GetInstance(instance_name)
      disk_to_copy = instance.GetBootDisk()

    if not disk_type:
      disk_type = disk_to_copy.GetDiskType()

    logger.info('Disk copy of {0:s} started...'.format(
        disk_to_copy.name))
    snapshot = disk_to_copy.Snapshot()
    logger.debug('Snapshot created: {0:s}'.format(snapshot.name))
    new_disk = dst_project.compute.CreateDiskFromSnapshot(
        snapshot, disk_name_prefix='evidence', disk_type=disk_type)
    logger.info(
        'Disk {0:s} successfully copied to {1:s}'.format(
            disk_to_copy.name, new_disk.name))
    snapshot.Delete()
    logger.debug('Snapshot {0:s} deleted.'.format(snapshot.name))

  except (RefreshError, DefaultCredentialsError) as exception:
    raise errors.CredentialsConfigurationError(
        'Something is wrong with your Application Default Credentials. Try '
        'running: $ gcloud auth application-default login: {0!s}'.format(
            exception), __name__) from exception
  except HttpError as exception:
    if exception.resp.status == 403:
      raise errors.CredentialsConfigurationError(
          'Make sure you have the appropriate permissions on the project',
          __name__) from exception
    if exception.resp.status == 404:
      raise errors.ResourceNotFoundError(
          'GCP resource not found. Maybe a typo in the project / instance / '
          'disk name?', __name__) from exception
    raise RuntimeError(exception) from exception
  except RuntimeError as exception:
    raise errors.ResourceCreationError(
        'Cannot copy disk "{0:s}": {1!s}'.format(disk_name, exception),
        __name__) from exception

  return new_disk
Example #4
0
def GetCredentials(
    profile_name: Optional[str] = None
) -> Tuple[str, ServicePrincipalCredentials]:
    # pylint: disable=line-too-long
    """Get Azure credentials.

  Args:
    profile_name (str): A name for the Azure account information to retrieve.
        If not provided, the default behavior is to look for Azure credential
        information in environment variables as explained in https://docs.microsoft.com/en-us/azure/developer/python/azure-sdk-authenticate
        If provided, then the library will look into
        ~/.azure/credentials.json for the account information linked to
        profile_name. The .json file should have the following format:

        {
          'profile_name': {
              'subscriptionId': xxx,
              'tenantId': xxx,
              'clientId': xxx,
              'clientSecret': xxx
          },
          'other_profile_name': {
              'subscriptionId': yyy,
              'tenantId': yyy,
              'clientId': yyy,
              'clientSecret': yyy
          },
          ...
        }

        Note that you can specify several profiles that use the same tenantId,
        clientId and clientSecret but a different subscriptionId.
        If you set the environment variable AZURE_CREDENTIALS_PATH to an
        absolute path to the credentials file, then the library will look
        there instead of in ~/.azure/credentials.json.

  Returns:
    Tuple[str, ServicePrincipalCredentials]: Subscription ID and
        corresponding Azure credentials.

  Raises:
    CredentialsConfigurationError: If there are environment variables that
        are not set or if the credentials file has missing entries/profiles.
    FileNotFoundError: If the credentials file is not found.
    InvalidFileFormatError: If the credentials file couldn't be parsed.
  """
    # pylint: enable=line-too-long
    if not profile_name:
        subscription_id = os.getenv('AZURE_SUBSCRIPTION_ID')
        client_id = os.getenv("AZURE_CLIENT_ID")
        secret = os.getenv("AZURE_CLIENT_SECRET")
        tenant = os.getenv("AZURE_TENANT_ID")
        if not (subscription_id and client_id and secret and tenant):
            raise errors.CredentialsConfigurationError(
                'Please make sure you defined the following environment variables: '
                '[AZURE_SUBSCRIPTION_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, '
                'AZURE_TENANT_ID].', __name__)
        return subscription_id, ServicePrincipalCredentials(client_id,
                                                            secret,
                                                            tenant=tenant)

    path = os.getenv('AZURE_CREDENTIALS_PATH')
    if not path:
        path = os.path.expanduser('~/.azure/credentials.json')

    if not os.path.exists(path):
        raise FileNotFoundError(
            'Credentials file not found. Please place it in '
            '"~/.azure/credentials.json" or specify an absolute path to it in '
            'the AZURE_CREDENTIALS_PATH environment variable.')

    with open(path) as profiles:
        try:
            account_info = json.load(profiles).get(profile_name)
        except ValueError as exception:
            raise errors.InvalidFileFormatError(
                'Could not decode JSON file. Please verify the file format:'
                ' {0!s}'.format(exception), __name__) from exception
        if not account_info:
            raise errors.CredentialsConfigurationError(
                'Profile name {0:s} not found in credentials file {1:s}'.
                format(profile_name, path), __name__)
        required_entries = [
            'subscriptionId', 'clientId', 'clientSecret', 'tenantId'
        ]
        if not all(account_info.get(entry) for entry in required_entries):
            raise errors.CredentialsConfigurationError(
                'Please make sure that your JSON file has the required entries. The '
                'file should contain at least the following: {0:s}'.format(
                    ', '.join(required_entries)), __name__)
        return account_info['subscriptionId'], ServicePrincipalCredentials(
            account_info['clientId'],
            account_info['clientSecret'],
            tenant=account_info['tenantId'])