xref: /aosp_15_r20/tools/treble/fetcher/fetcher_lib.py (revision 105f628577ac4ba0e277a494fbb614ed8c12a994)
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