| """Provides helper functions for fetching artifacts.""" |
| |
| import io |
| import os |
| import re |
| import sys |
| import sysconfig |
| import time |
| |
| # This is a workaround to put '/usr/lib/python3.X' ahead of googleapiclient |
| # Using embedded_launcher won't work since py3-cmd doesn't contain _ssl module. |
| if sys.version_info.major == 3: |
| sys.path.insert(0, os.path.dirname(sysconfig.get_paths()['purelib'])) |
| |
| # pylint: disable=import-error,g-bad-import-order,g-import-not-at-top |
| import apiclient |
| from googleapiclient.discovery import build |
| from six.moves import http_client |
| |
| import httplib2 |
| from oauth2client.service_account import ServiceAccountCredentials |
| |
| _SCOPE_URL = 'https://www.googleapis.com/auth/androidbuild.internal' |
| _DEF_JSON_KEYFILE = '.config/gcloud/application_default_credentials.json' |
| |
| |
| # 20 MB default chunk size -- used in Buildbot |
| _DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024 |
| |
| # HTTP errors -- used in Builbot |
| _DEFAULT_MASKED_ERRORS = [404] |
| _DEFAULT_RETRIED_ERRORS = [503] |
| _DEFAULT_RETRIES = 10 |
| |
| |
| def _create_http_from_p12(robot_credentials_file, robot_username): |
| """Creates a credentialed HTTP object for requests. |
| |
| Args: |
| robot_credentials_file: The path to the robot credentials file. |
| robot_username: A string containing the username of the robot account. |
| |
| Returns: |
| An authorized httplib2.Http object. |
| """ |
| try: |
| credentials = ServiceAccountCredentials.from_p12_keyfile( |
| service_account_email=robot_username, |
| filename=robot_credentials_file, |
| scopes=_SCOPE_URL) |
| except AttributeError: |
| raise ValueError('Machine lacks openssl or pycrypto support') |
| http = httplib2.Http() |
| return credentials.authorize(http) |
| |
| |
| def _simple_execute(http_request, |
| masked_errors=None, |
| retried_errors=None, |
| retry_delay_seconds=5, |
| max_tries=_DEFAULT_RETRIES): |
| """Execute http request and return None on specified errors. |
| |
| Args: |
| http_request: the apiclient provided http request |
| masked_errors: list of errors to return None on |
| retried_errors: list of erros to retry the request on |
| retry_delay_seconds: how many seconds to sleep before retrying |
| max_tries: maximum number of attmpts to make request |
| |
| Returns: |
| The result on success or None on masked errors. |
| """ |
| if not masked_errors: |
| masked_errors = _DEFAULT_MASKED_ERRORS |
| if not retried_errors: |
| retried_errors = _DEFAULT_RETRIED_ERRORS |
| |
| last_error = None |
| for _ in range(max_tries): |
| try: |
| return http_request.execute() |
| except http_client.errors.HttpError as e: |
| last_error = e |
| if e.resp.status in masked_errors: |
| return None |
| elif e.resp.status in retried_errors: |
| time.sleep(retry_delay_seconds) |
| else: |
| # Server Error is server error |
| raise e |
| |
| # We've gone through the max_retries, raise the last error |
| raise last_error # pylint: disable=raising-bad-type |
| |
| |
| def create_client(http): |
| """Creates an Android build api client from an authorized http object. |
| |
| Args: |
| http: An authorized httplib2.Http object. |
| |
| Returns: |
| An authorized android build api client. |
| """ |
| return build(serviceName='androidbuildinternal', version='v2beta1', http=http, |
| static_discovery=False) |
| |
| |
| def create_client_from_json_keyfile(json_keyfile_name=None): |
| """Creates an Android build api client from a json keyfile. |
| |
| Args: |
| json_keyfile_name: The location of the keyfile, if None is provided use |
| default location. |
| |
| Returns: |
| An authorized android build api client. |
| """ |
| if not json_keyfile_name: |
| json_keyfile_name = os.path.join(os.getenv('HOME'), _DEF_JSON_KEYFILE) |
| |
| credentials = ServiceAccountCredentials.from_json_keyfile_name( |
| filename=json_keyfile_name, scopes=_SCOPE_URL) |
| http = httplib2.Http() |
| credentials.authorize(http) |
| return create_client(http) |
| |
| |
| def create_client_from_p12(robot_credentials_file, robot_username): |
| """Creates an Android build api client from a config file. |
| |
| Args: |
| robot_credentials_file: The path to the robot credentials file. |
| robot_username: A string containing the username of the robot account. |
| |
| Returns: |
| An authorized android build api client. |
| """ |
| http = _create_http_from_p12(robot_credentials_file, robot_username) |
| return create_client(http) |
| |
| |
| def fetch_artifact(client, build_id, target, resource_id, dest): |
| """Fetches an artifact. |
| |
| Args: |
| client: An authorized android build api client. |
| build_id: AB build id |
| target: the target name to download from |
| resource_id: the resource id of the artifact |
| dest: path to store the artifact |
| """ |
| out_dir = os.path.dirname(dest) |
| if not os.path.exists(out_dir): |
| os.makedirs(out_dir) |
| |
| dl_req = client.buildartifact().get_media( |
| buildId=build_id, |
| target=target, |
| attemptId='latest', |
| resourceId=resource_id) |
| |
| print('Fetching %s to %s...' % (resource_id, dest)) |
| with io.FileIO(dest, mode='wb') as fh: |
| downloader = apiclient.http.MediaIoBaseDownload( |
| fh, dl_req, chunksize=_DEFAULT_CHUNK_SIZE) |
| done = False |
| while not done: |
| status, done = downloader.next_chunk(num_retries=_DEFAULT_RETRIES) |
| print('Fetching...' + str(status.progress() * 100)) |
| |
| print('Done Fetching %s to %s' % (resource_id, dest)) |
| |
| |
| def get_build_list(client, **kwargs): |
| """Get a list of builds from the android build api that matches parameters. |
| |
| Args: |
| client: An authorized android build api client. |
| **kwargs: keyworded arguments to pass to build api. |
| |
| Returns: |
| Response from build api. |
| """ |
| build_request = client.build().list(**kwargs) |
| |
| return _simple_execute(build_request) |
| |
| |
| def list_artifacts(client, regex, **kwargs): |
| """List artifacts from the android build api that matches parameters. |
| |
| Args: |
| client: An authorized android build api client. |
| regex: Regular expression pattern to match artifact name. |
| **kwargs: keyworded arguments to pass to buildartifact.list api. |
| |
| Returns: |
| List of matching artifact names. |
| """ |
| matching_artifacts = [] |
| kwargs.setdefault('attemptId', 'latest') |
| regex = re.compile(regex) |
| req = client.buildartifact().list(**kwargs) |
| while req: |
| result = _simple_execute(req) |
| if result and 'artifacts' in result: |
| for a in result['artifacts']: |
| if regex.match(a['name']): |
| matching_artifacts.append(a['name']) |
| req = client.buildartifact().list_next(req, result) |
| return matching_artifacts |
| |
| |
| def fetch_artifacts(client, out_dir, target, pattern, build_id): |
| """Fetches target files artifacts matching patterns. |
| |
| Args: |
| client: An authorized instance of an android build api client for making |
| requests. |
| out_dir: The directory to store the fetched artifacts to. |
| target: The target name to download from. |
| pattern: A regex pattern to match to artifacts filename. |
| build_id: The Android Build id. |
| """ |
| if not os.path.exists(out_dir): |
| os.makedirs(out_dir) |
| |
| # Build a list of needed artifacts |
| artifacts = list_artifacts( |
| client=client, |
| regex=pattern, |
| buildId=build_id, |
| target=target) |
| |
| for artifact in artifacts: |
| fetch_artifact( |
| client=client, |
| build_id=build_id, |
| target=target, |
| resource_id=artifact, |
| dest=os.path.join(out_dir, artifact)) |
| |
| |
| def get_latest_build_id(client, branch, target): |
| """Get the latest build id. |
| |
| Args: |
| client: An authorized instance of an android build api client for making |
| requests. |
| branch: The branch to download from |
| target: The target name to download from. |
| Returns: |
| The build id. |
| """ |
| build_response = get_build_list( |
| client=client, |
| branch=branch, |
| target=target, |
| maxResults=1, |
| successful=True, |
| buildType='submitted') |
| |
| if not build_response: |
| raise ValueError('Unable to determine latest build ID!') |
| |
| return build_response['builds'][0]['buildId'] |
| |
| |
| def fetch_latest_artifacts(client, out_dir, target, pattern, branch): |
| """Fetches target files artifacts matching patterns from the latest build. |
| |
| Args: |
| client: An authorized instance of an android build api client for making |
| requests. |
| out_dir: The directory to store the fetched artifacts to. |
| target: The target name to download from. |
| pattern: A regex pattern to match to artifacts filename |
| branch: The branch to download from |
| """ |
| build_id = get_latest_build_id( |
| client=client, branch=branch, target=target) |
| |
| fetch_artifacts(client, out_dir, target, pattern, build_id) |