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
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
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
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'])