1*105f6285SAndroid Build Coastguard Worker"""Provides helper functions for fetching artifacts.""" 2*105f6285SAndroid Build Coastguard Worker 3*105f6285SAndroid Build Coastguard Workerimport io 4*105f6285SAndroid Build Coastguard Workerimport os 5*105f6285SAndroid Build Coastguard Workerimport re 6*105f6285SAndroid Build Coastguard Workerimport sys 7*105f6285SAndroid Build Coastguard Workerimport sysconfig 8*105f6285SAndroid Build Coastguard Workerimport time 9*105f6285SAndroid Build Coastguard Worker 10*105f6285SAndroid Build Coastguard Worker# This is a workaround to put '/usr/lib/python3.X' ahead of googleapiclient 11*105f6285SAndroid Build Coastguard Worker# Using embedded_launcher won't work since py3-cmd doesn't contain _ssl module. 12*105f6285SAndroid Build Coastguard Workerif sys.version_info.major == 3: 13*105f6285SAndroid Build Coastguard Worker sys.path.insert(0, os.path.dirname(sysconfig.get_paths()['purelib'])) 14*105f6285SAndroid Build Coastguard Worker 15*105f6285SAndroid Build Coastguard Worker# pylint: disable=import-error,g-bad-import-order,g-import-not-at-top 16*105f6285SAndroid Build Coastguard Workerimport apiclient 17*105f6285SAndroid Build Coastguard Workerfrom googleapiclient.discovery import build 18*105f6285SAndroid Build Coastguard Workerfrom googleapiclient.errors import HttpError 19*105f6285SAndroid Build Coastguard Worker 20*105f6285SAndroid Build Coastguard Workerimport httplib2 21*105f6285SAndroid Build Coastguard Workerfrom oauth2client.service_account import ServiceAccountCredentials 22*105f6285SAndroid Build Coastguard Worker 23*105f6285SAndroid Build Coastguard Worker_SCOPE_URL = 'https://www.googleapis.com/auth/androidbuild.internal' 24*105f6285SAndroid Build Coastguard Worker_DEF_JSON_KEYFILE = '.config/gcloud/application_default_credentials.json' 25*105f6285SAndroid Build Coastguard Worker 26*105f6285SAndroid Build Coastguard Worker 27*105f6285SAndroid Build Coastguard Worker# 20 MB default chunk size -- used in Buildbot 28*105f6285SAndroid Build Coastguard Worker_DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024 29*105f6285SAndroid Build Coastguard Worker 30*105f6285SAndroid Build Coastguard Worker# HTTP errors -- used in Builbot 31*105f6285SAndroid Build Coastguard Worker_DEFAULT_MASKED_ERRORS = [404] 32*105f6285SAndroid Build Coastguard Worker_DEFAULT_RETRIED_ERRORS = [503] 33*105f6285SAndroid Build Coastguard Worker_DEFAULT_RETRIES = 10 34*105f6285SAndroid Build Coastguard Worker 35*105f6285SAndroid Build Coastguard Worker 36*105f6285SAndroid Build Coastguard Workerdef _create_http_from_p12(robot_credentials_file, robot_username): 37*105f6285SAndroid Build Coastguard Worker """Creates a credentialed HTTP object for requests. 38*105f6285SAndroid Build Coastguard Worker 39*105f6285SAndroid Build Coastguard Worker Args: 40*105f6285SAndroid Build Coastguard Worker robot_credentials_file: The path to the robot credentials file. 41*105f6285SAndroid Build Coastguard Worker robot_username: A string containing the username of the robot account. 42*105f6285SAndroid Build Coastguard Worker 43*105f6285SAndroid Build Coastguard Worker Returns: 44*105f6285SAndroid Build Coastguard Worker An authorized httplib2.Http object. 45*105f6285SAndroid Build Coastguard Worker """ 46*105f6285SAndroid Build Coastguard Worker try: 47*105f6285SAndroid Build Coastguard Worker credentials = ServiceAccountCredentials.from_p12_keyfile( 48*105f6285SAndroid Build Coastguard Worker service_account_email=robot_username, 49*105f6285SAndroid Build Coastguard Worker filename=robot_credentials_file, 50*105f6285SAndroid Build Coastguard Worker scopes=_SCOPE_URL) 51*105f6285SAndroid Build Coastguard Worker except AttributeError: 52*105f6285SAndroid Build Coastguard Worker raise ValueError('Machine lacks openssl or pycrypto support') 53*105f6285SAndroid Build Coastguard Worker http = httplib2.Http() 54*105f6285SAndroid Build Coastguard Worker return credentials.authorize(http) 55*105f6285SAndroid Build Coastguard Worker 56*105f6285SAndroid Build Coastguard Worker 57*105f6285SAndroid Build Coastguard Workerdef _simple_execute(http_request, 58*105f6285SAndroid Build Coastguard Worker masked_errors=None, 59*105f6285SAndroid Build Coastguard Worker retried_errors=None, 60*105f6285SAndroid Build Coastguard Worker retry_delay_seconds=5, 61*105f6285SAndroid Build Coastguard Worker max_tries=_DEFAULT_RETRIES): 62*105f6285SAndroid Build Coastguard Worker """Execute http request and return None on specified errors. 63*105f6285SAndroid Build Coastguard Worker 64*105f6285SAndroid Build Coastguard Worker Args: 65*105f6285SAndroid Build Coastguard Worker http_request: the apiclient provided http request 66*105f6285SAndroid Build Coastguard Worker masked_errors: list of errors to return None on 67*105f6285SAndroid Build Coastguard Worker retried_errors: list of erros to retry the request on 68*105f6285SAndroid Build Coastguard Worker retry_delay_seconds: how many seconds to sleep before retrying 69*105f6285SAndroid Build Coastguard Worker max_tries: maximum number of attmpts to make request 70*105f6285SAndroid Build Coastguard Worker 71*105f6285SAndroid Build Coastguard Worker Returns: 72*105f6285SAndroid Build Coastguard Worker The result on success or None on masked errors. 73*105f6285SAndroid Build Coastguard Worker """ 74*105f6285SAndroid Build Coastguard Worker if not masked_errors: 75*105f6285SAndroid Build Coastguard Worker masked_errors = _DEFAULT_MASKED_ERRORS 76*105f6285SAndroid Build Coastguard Worker if not retried_errors: 77*105f6285SAndroid Build Coastguard Worker retried_errors = _DEFAULT_RETRIED_ERRORS 78*105f6285SAndroid Build Coastguard Worker 79*105f6285SAndroid Build Coastguard Worker last_error = None 80*105f6285SAndroid Build Coastguard Worker for _ in range(max_tries): 81*105f6285SAndroid Build Coastguard Worker try: 82*105f6285SAndroid Build Coastguard Worker return http_request.execute() 83*105f6285SAndroid Build Coastguard Worker except HttpError as e: 84*105f6285SAndroid Build Coastguard Worker last_error = e 85*105f6285SAndroid Build Coastguard Worker if e.resp.status in masked_errors: 86*105f6285SAndroid Build Coastguard Worker return None 87*105f6285SAndroid Build Coastguard Worker elif e.resp.status in retried_errors: 88*105f6285SAndroid Build Coastguard Worker time.sleep(retry_delay_seconds) 89*105f6285SAndroid Build Coastguard Worker else: 90*105f6285SAndroid Build Coastguard Worker # Server Error is server error 91*105f6285SAndroid Build Coastguard Worker raise e 92*105f6285SAndroid Build Coastguard Worker 93*105f6285SAndroid Build Coastguard Worker # We've gone through the max_retries, raise the last error 94*105f6285SAndroid Build Coastguard Worker raise last_error # pylint: disable=raising-bad-type 95*105f6285SAndroid Build Coastguard Worker 96*105f6285SAndroid Build Coastguard Worker 97*105f6285SAndroid Build Coastguard Workerdef create_client(http): 98*105f6285SAndroid Build Coastguard Worker """Creates an Android build api client from an authorized http object. 99*105f6285SAndroid Build Coastguard Worker 100*105f6285SAndroid Build Coastguard Worker Args: 101*105f6285SAndroid Build Coastguard Worker http: An authorized httplib2.Http object. 102*105f6285SAndroid Build Coastguard Worker 103*105f6285SAndroid Build Coastguard Worker Returns: 104*105f6285SAndroid Build Coastguard Worker An authorized android build api client. 105*105f6285SAndroid Build Coastguard Worker """ 106*105f6285SAndroid Build Coastguard Worker return build(serviceName='androidbuildinternal', version='v3', http=http, 107*105f6285SAndroid Build Coastguard Worker static_discovery=False) 108*105f6285SAndroid Build Coastguard Worker 109*105f6285SAndroid Build Coastguard Worker 110*105f6285SAndroid Build Coastguard Workerdef create_client_from_json_keyfile(json_keyfile_name=None): 111*105f6285SAndroid Build Coastguard Worker """Creates an Android build api client from a json keyfile. 112*105f6285SAndroid Build Coastguard Worker 113*105f6285SAndroid Build Coastguard Worker Args: 114*105f6285SAndroid Build Coastguard Worker json_keyfile_name: The location of the keyfile, if None is provided use 115*105f6285SAndroid Build Coastguard Worker default location. 116*105f6285SAndroid Build Coastguard Worker 117*105f6285SAndroid Build Coastguard Worker Returns: 118*105f6285SAndroid Build Coastguard Worker An authorized android build api client. 119*105f6285SAndroid Build Coastguard Worker """ 120*105f6285SAndroid Build Coastguard Worker if not json_keyfile_name: 121*105f6285SAndroid Build Coastguard Worker json_keyfile_name = os.path.join(os.getenv('HOME'), _DEF_JSON_KEYFILE) 122*105f6285SAndroid Build Coastguard Worker 123*105f6285SAndroid Build Coastguard Worker credentials = ServiceAccountCredentials.from_json_keyfile_name( 124*105f6285SAndroid Build Coastguard Worker filename=json_keyfile_name, scopes=_SCOPE_URL) 125*105f6285SAndroid Build Coastguard Worker http = httplib2.Http() 126*105f6285SAndroid Build Coastguard Worker credentials.authorize(http) 127*105f6285SAndroid Build Coastguard Worker return create_client(http) 128*105f6285SAndroid Build Coastguard Worker 129*105f6285SAndroid Build Coastguard Worker 130*105f6285SAndroid Build Coastguard Workerdef create_client_from_p12(robot_credentials_file, robot_username): 131*105f6285SAndroid Build Coastguard Worker """Creates an Android build api client from a config file. 132*105f6285SAndroid Build Coastguard Worker 133*105f6285SAndroid Build Coastguard Worker Args: 134*105f6285SAndroid Build Coastguard Worker robot_credentials_file: The path to the robot credentials file. 135*105f6285SAndroid Build Coastguard Worker robot_username: A string containing the username of the robot account. 136*105f6285SAndroid Build Coastguard Worker 137*105f6285SAndroid Build Coastguard Worker Returns: 138*105f6285SAndroid Build Coastguard Worker An authorized android build api client. 139*105f6285SAndroid Build Coastguard Worker """ 140*105f6285SAndroid Build Coastguard Worker http = _create_http_from_p12(robot_credentials_file, robot_username) 141*105f6285SAndroid Build Coastguard Worker return create_client(http) 142*105f6285SAndroid Build Coastguard Worker 143*105f6285SAndroid Build Coastguard Worker 144*105f6285SAndroid Build Coastguard Workerdef fetch_artifact(client, build_id, target, resource_id, dest): 145*105f6285SAndroid Build Coastguard Worker """Fetches an artifact. 146*105f6285SAndroid Build Coastguard Worker 147*105f6285SAndroid Build Coastguard Worker Args: 148*105f6285SAndroid Build Coastguard Worker client: An authorized android build api client. 149*105f6285SAndroid Build Coastguard Worker build_id: AB build id 150*105f6285SAndroid Build Coastguard Worker target: the target name to download from 151*105f6285SAndroid Build Coastguard Worker resource_id: the resource id of the artifact 152*105f6285SAndroid Build Coastguard Worker dest: path to store the artifact 153*105f6285SAndroid Build Coastguard Worker """ 154*105f6285SAndroid Build Coastguard Worker out_dir = os.path.dirname(dest) 155*105f6285SAndroid Build Coastguard Worker if not os.path.exists(out_dir): 156*105f6285SAndroid Build Coastguard Worker os.makedirs(out_dir) 157*105f6285SAndroid Build Coastguard Worker 158*105f6285SAndroid Build Coastguard Worker dl_req = client.buildartifact().get_media( 159*105f6285SAndroid Build Coastguard Worker buildId=build_id, 160*105f6285SAndroid Build Coastguard Worker target=target, 161*105f6285SAndroid Build Coastguard Worker attemptId='latest', 162*105f6285SAndroid Build Coastguard Worker resourceId=resource_id) 163*105f6285SAndroid Build Coastguard Worker 164*105f6285SAndroid Build Coastguard Worker print('Fetching %s to %s...' % (resource_id, dest)) 165*105f6285SAndroid Build Coastguard Worker with io.FileIO(dest, mode='wb') as fh: 166*105f6285SAndroid Build Coastguard Worker downloader = apiclient.http.MediaIoBaseDownload( 167*105f6285SAndroid Build Coastguard Worker fh, dl_req, chunksize=_DEFAULT_CHUNK_SIZE) 168*105f6285SAndroid Build Coastguard Worker done = False 169*105f6285SAndroid Build Coastguard Worker while not done: 170*105f6285SAndroid Build Coastguard Worker status, done = downloader.next_chunk(num_retries=_DEFAULT_RETRIES) 171*105f6285SAndroid Build Coastguard Worker print('Fetching...' + str(status.progress() * 100)) 172*105f6285SAndroid Build Coastguard Worker 173*105f6285SAndroid Build Coastguard Worker print('Done Fetching %s to %s' % (resource_id, dest)) 174*105f6285SAndroid Build Coastguard Worker 175*105f6285SAndroid Build Coastguard Worker 176*105f6285SAndroid Build Coastguard Workerdef get_build_list(client, **kwargs): 177*105f6285SAndroid Build Coastguard Worker """Get a list of builds from the android build api that matches parameters. 178*105f6285SAndroid Build Coastguard Worker 179*105f6285SAndroid Build Coastguard Worker Args: 180*105f6285SAndroid Build Coastguard Worker client: An authorized android build api client. 181*105f6285SAndroid Build Coastguard Worker **kwargs: keyworded arguments to pass to build api. 182*105f6285SAndroid Build Coastguard Worker 183*105f6285SAndroid Build Coastguard Worker Returns: 184*105f6285SAndroid Build Coastguard Worker Response from build api. 185*105f6285SAndroid Build Coastguard Worker """ 186*105f6285SAndroid Build Coastguard Worker build_request = client.build().list(**kwargs) 187*105f6285SAndroid Build Coastguard Worker 188*105f6285SAndroid Build Coastguard Worker return _simple_execute(build_request) 189*105f6285SAndroid Build Coastguard Worker 190*105f6285SAndroid Build Coastguard Worker 191*105f6285SAndroid Build Coastguard Workerdef list_artifacts(client, regex, **kwargs): 192*105f6285SAndroid Build Coastguard Worker """List artifacts from the android build api that matches parameters. 193*105f6285SAndroid Build Coastguard Worker 194*105f6285SAndroid Build Coastguard Worker Args: 195*105f6285SAndroid Build Coastguard Worker client: An authorized android build api client. 196*105f6285SAndroid Build Coastguard Worker regex: Regular expression pattern to match artifact name. 197*105f6285SAndroid Build Coastguard Worker **kwargs: keyworded arguments to pass to buildartifact.list api. 198*105f6285SAndroid Build Coastguard Worker 199*105f6285SAndroid Build Coastguard Worker Returns: 200*105f6285SAndroid Build Coastguard Worker List of matching artifact names. 201*105f6285SAndroid Build Coastguard Worker """ 202*105f6285SAndroid Build Coastguard Worker matching_artifacts = [] 203*105f6285SAndroid Build Coastguard Worker kwargs.setdefault('attemptId', 'latest') 204*105f6285SAndroid Build Coastguard Worker req = client.buildartifact().list(nameRegexp=regex, **kwargs) 205*105f6285SAndroid Build Coastguard Worker while req: 206*105f6285SAndroid Build Coastguard Worker result = _simple_execute(req) 207*105f6285SAndroid Build Coastguard Worker if result and 'artifacts' in result: 208*105f6285SAndroid Build Coastguard Worker for a in result['artifacts']: 209*105f6285SAndroid Build Coastguard Worker matching_artifacts.append(a['name']) 210*105f6285SAndroid Build Coastguard Worker req = client.buildartifact().list_next(req, result) 211*105f6285SAndroid Build Coastguard Worker return matching_artifacts 212*105f6285SAndroid Build Coastguard Worker 213*105f6285SAndroid Build Coastguard Worker 214*105f6285SAndroid Build Coastguard Workerdef fetch_artifacts(client, out_dir, target, pattern, build_id): 215*105f6285SAndroid Build Coastguard Worker """Fetches target files artifacts matching patterns. 216*105f6285SAndroid Build Coastguard Worker 217*105f6285SAndroid Build Coastguard Worker Args: 218*105f6285SAndroid Build Coastguard Worker client: An authorized instance of an android build api client for making 219*105f6285SAndroid Build Coastguard Worker requests. 220*105f6285SAndroid Build Coastguard Worker out_dir: The directory to store the fetched artifacts to. 221*105f6285SAndroid Build Coastguard Worker target: The target name to download from. 222*105f6285SAndroid Build Coastguard Worker pattern: A regex pattern to match to artifacts filename. 223*105f6285SAndroid Build Coastguard Worker build_id: The Android Build id. 224*105f6285SAndroid Build Coastguard Worker """ 225*105f6285SAndroid Build Coastguard Worker if not os.path.exists(out_dir): 226*105f6285SAndroid Build Coastguard Worker os.makedirs(out_dir) 227*105f6285SAndroid Build Coastguard Worker 228*105f6285SAndroid Build Coastguard Worker # Build a list of needed artifacts 229*105f6285SAndroid Build Coastguard Worker artifacts = list_artifacts( 230*105f6285SAndroid Build Coastguard Worker client=client, 231*105f6285SAndroid Build Coastguard Worker regex=pattern, 232*105f6285SAndroid Build Coastguard Worker buildId=build_id, 233*105f6285SAndroid Build Coastguard Worker target=target) 234*105f6285SAndroid Build Coastguard Worker 235*105f6285SAndroid Build Coastguard Worker for artifact in artifacts: 236*105f6285SAndroid Build Coastguard Worker fetch_artifact( 237*105f6285SAndroid Build Coastguard Worker client=client, 238*105f6285SAndroid Build Coastguard Worker build_id=build_id, 239*105f6285SAndroid Build Coastguard Worker target=target, 240*105f6285SAndroid Build Coastguard Worker resource_id=artifact, 241*105f6285SAndroid Build Coastguard Worker dest=os.path.join(out_dir, artifact)) 242*105f6285SAndroid Build Coastguard Worker 243*105f6285SAndroid Build Coastguard Worker 244*105f6285SAndroid Build Coastguard Workerdef get_latest_build_id(client, branch, target): 245*105f6285SAndroid Build Coastguard Worker """Get the latest build id. 246*105f6285SAndroid Build Coastguard Worker 247*105f6285SAndroid Build Coastguard Worker Args: 248*105f6285SAndroid Build Coastguard Worker client: An authorized instance of an android build api client for making 249*105f6285SAndroid Build Coastguard Worker requests. 250*105f6285SAndroid Build Coastguard Worker branch: The branch to download from 251*105f6285SAndroid Build Coastguard Worker target: The target name to download from. 252*105f6285SAndroid Build Coastguard Worker Returns: 253*105f6285SAndroid Build Coastguard Worker The build id. 254*105f6285SAndroid Build Coastguard Worker """ 255*105f6285SAndroid Build Coastguard Worker build_response = get_build_list( 256*105f6285SAndroid Build Coastguard Worker client=client, 257*105f6285SAndroid Build Coastguard Worker branch=branch, 258*105f6285SAndroid Build Coastguard Worker target=target, 259*105f6285SAndroid Build Coastguard Worker maxResults=1, 260*105f6285SAndroid Build Coastguard Worker successful=True, 261*105f6285SAndroid Build Coastguard Worker buildType='submitted') 262*105f6285SAndroid Build Coastguard Worker 263*105f6285SAndroid Build Coastguard Worker if not build_response: 264*105f6285SAndroid Build Coastguard Worker raise ValueError('Unable to determine latest build ID!') 265*105f6285SAndroid Build Coastguard Worker 266*105f6285SAndroid Build Coastguard Worker return build_response['builds'][0]['buildId'] 267*105f6285SAndroid Build Coastguard Worker 268*105f6285SAndroid Build Coastguard Worker 269*105f6285SAndroid Build Coastguard Workerdef fetch_latest_artifacts(client, out_dir, target, pattern, branch): 270*105f6285SAndroid Build Coastguard Worker """Fetches target files artifacts matching patterns from the latest build. 271*105f6285SAndroid Build Coastguard Worker 272*105f6285SAndroid Build Coastguard Worker Args: 273*105f6285SAndroid Build Coastguard Worker client: An authorized instance of an android build api client for making 274*105f6285SAndroid Build Coastguard Worker requests. 275*105f6285SAndroid Build Coastguard Worker out_dir: The directory to store the fetched artifacts to. 276*105f6285SAndroid Build Coastguard Worker target: The target name to download from. 277*105f6285SAndroid Build Coastguard Worker pattern: A regex pattern to match to artifacts filename 278*105f6285SAndroid Build Coastguard Worker branch: The branch to download from 279*105f6285SAndroid Build Coastguard Worker """ 280*105f6285SAndroid Build Coastguard Worker build_id = get_latest_build_id( 281*105f6285SAndroid Build Coastguard Worker client=client, branch=branch, target=target) 282*105f6285SAndroid Build Coastguard Worker 283*105f6285SAndroid Build Coastguard Worker fetch_artifacts(client, out_dir, target, pattern, build_id) 284