blob: 4138b0902605851a6ed55fa518e50f6e11a647c3 [file] [log] [blame]
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07001# -*- coding: utf-8 -*-
Chris Sosa76e44b92013-01-31 12:11:38 -08002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
Frank Farzan37761d12011-12-01 14:29:08 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Gabe Black3b567202015-09-23 14:07:59 -07006"""Downloaders used to download artifacts and files from a given source."""
7
8from __future__ import print_function
9
Prashanth Ba06d2d22014-03-07 15:35:19 -080010import collections
Eric Carusoe76d1872019-02-21 11:17:45 -080011import errno
Gabe Black3b567202015-09-23 14:07:59 -070012import glob
Chris Sosa9164ca32012-03-28 11:04:50 -070013import os
Gabe Black3b567202015-09-23 14:07:59 -070014import re
15import shutil
Eric Carusoe76d1872019-02-21 11:17:45 -080016import subprocess
Gilad Arnold0b8c3f32012-09-19 14:35:44 -070017import threading
Prashanth Ba06d2d22014-03-07 15:35:19 -080018from datetime import datetime
Frank Farzan37761d12011-12-01 14:29:08 -080019
Achuith Bhandarkar46877ad2019-09-26 15:54:02 +020020try:
21 import android_build
22except ImportError:
23 # Ignore android_build import failure. This is to support devserver running
24 # inside a ChromeOS device triggered by cros flash. Most ChromeOS test images
25 # do not have google-api-python-client module and they don't need to support
26 # Android updating, therefore, ignore the import failure here.
27 android_build = None
28
Chris Sosa76e44b92013-01-31 12:11:38 -080029import build_artifact
Gilad Arnoldc65330c2012-09-20 15:17:48 -070030import common_util
31import log_util
Frank Farzan37761d12011-12-01 14:29:08 -080032
xixuan44b55452016-09-06 15:35:56 -070033# Make sure that chromite is available to import.
34import setup_chromite # pylint: disable=unused-import
35
36try:
37 from chromite.lib import gs
Achuith Bhandarkar46877ad2019-09-26 15:54:02 +020038except ImportError:
xixuan44b55452016-09-06 15:35:56 -070039 gs = None
40
Dan Shi6e50c722013-08-19 15:05:06 -070041class DownloaderException(Exception):
42 """Exception that aggregates all exceptions raised during async download.
43
44 Exceptions could be raised in artifact.Process method, and saved to files.
45 When caller calls IsStaged to check the downloading progress, devserver can
46 retrieve the persisted exceptions from the files, wrap them into a
47 DownloaderException, and raise it.
48 """
49 def __init__(self, exceptions):
50 """Initialize a DownloaderException instance with a list of exceptions.
51
Gabe Black3b567202015-09-23 14:07:59 -070052 Args:
53 exceptions: Exceptions raised when downloading artifacts.
Dan Shi6e50c722013-08-19 15:05:06 -070054 """
55 message = 'Exceptions were raised when downloading artifacts.'
56 Exception.__init__(self, message)
57 self.exceptions = exceptions
58
59 def __repr__(self):
60 return self.__str__()
61
62 def __str__(self):
63 """Return a custom exception message with all exceptions merged."""
64 return '--------\n'.join([str(exception) for exception in self.exceptions])
65
Gilad Arnoldc65330c2012-09-20 15:17:48 -070066class Downloader(log_util.Loggable):
Chris Sosa76e44b92013-01-31 12:11:38 -080067 """Downloader of images to the devsever.
Frank Farzan37761d12011-12-01 14:29:08 -080068
Gabe Black3b567202015-09-23 14:07:59 -070069 This is the base class for different types of downloaders, including
Dan Shi72b16132015-10-08 12:10:33 -070070 GoogleStorageDownloader, LocalDownloader and AndroidBuildDownloader.
Gabe Black3b567202015-09-23 14:07:59 -070071
Frank Farzan37761d12011-12-01 14:29:08 -080072 Given a URL to a build on the archive server:
Chris Sosa76e44b92013-01-31 12:11:38 -080073 - Caches that build and the given artifacts onto the devserver.
74 - May also initiate caching of related artifacts in the background.
Frank Farzan37761d12011-12-01 14:29:08 -080075
Chris Sosa76e44b92013-01-31 12:11:38 -080076 Private class members:
Chris Sosa76e44b92013-01-31 12:11:38 -080077 static_dir: local filesystem directory to store all artifacts.
78 build_dir: the local filesystem directory to store artifacts for the given
Gabe Black3b567202015-09-23 14:07:59 -070079 build based on the remote source.
80
81 Public methods must be overridden:
82 Wait: Verifies the local artifact exists and returns the appropriate names.
83 Fetch: Downloads artifact from given source to a local directory.
84 DescribeSource: Gets the source of the download, e.g., a url to GS.
Frank Farzan37761d12011-12-01 14:29:08 -080085 """
86
Alex Millera44d5022012-07-27 11:34:16 -070087 # This filename must be kept in sync with clean_staged_images.py
88 _TIMESTAMP_FILENAME = 'staged.timestamp'
Chris Masonea22d9382012-05-18 12:38:51 -070089
Gabe Black3b567202015-09-23 14:07:59 -070090 def __init__(self, static_dir, build_dir, build):
Chris Sosa76e44b92013-01-31 12:11:38 -080091 super(Downloader, self).__init__()
Frank Farzan37761d12011-12-01 14:29:08 -080092 self._static_dir = static_dir
Gabe Black3b567202015-09-23 14:07:59 -070093 self._build_dir = build_dir
94 self._build = build
Chris Masone816e38c2012-05-02 12:22:36 -070095
Gabe Black3b567202015-09-23 14:07:59 -070096 def GetBuildDir(self):
97 """Returns the path to where the artifacts will be staged."""
98 return self._build_dir
Simran Basi4243a862014-12-12 12:48:33 -080099
Gabe Black3b567202015-09-23 14:07:59 -0700100 def GetBuild(self):
101 """Returns the path to where the artifacts will be staged."""
102 return self._build
Frank Farzan37761d12011-12-01 14:29:08 -0800103
Chris Sosa9164ca32012-03-28 11:04:50 -0700104 @staticmethod
Simran Basief83d6a2014-08-28 14:32:01 -0700105 def TouchTimestampForStaged(directory_path):
Alex Millera44d5022012-07-27 11:34:16 -0700106 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
107 # Easiest python version of |touch file_name|
Achuith Bhandarkar46877ad2019-09-26 15:54:02 +0200108 with open(file_name, 'a'):
Alex Millera44d5022012-07-27 11:34:16 -0700109 os.utime(file_name, None)
110
Dan Shiba0e6742013-06-26 17:39:05 -0700111 @staticmethod
112 def _TryRemoveStageDir(directory_path):
Gilad Arnold02dc6552013-11-14 11:27:54 -0800113 """If download failed, try to remove the stage dir.
Dan Shiba0e6742013-06-26 17:39:05 -0700114
Gilad Arnold02dc6552013-11-14 11:27:54 -0800115 If the download attempt failed (ArtifactDownloadError) and staged.timestamp
116 is the only file in that directory. The build could be non-existing, and
117 the directory should be removed.
Dan Shiba0e6742013-06-26 17:39:05 -0700118
Gabe Black3b567202015-09-23 14:07:59 -0700119 Args:
120 directory_path: directory used to stage the image.
Dan Shiba0e6742013-06-26 17:39:05 -0700121 """
122 file_name = os.path.join(directory_path, Downloader._TIMESTAMP_FILENAME)
123 if os.path.exists(file_name) and len(os.listdir(directory_path)) == 1:
124 os.remove(file_name)
125 os.rmdir(directory_path)
126
Prashanth Ba06d2d22014-03-07 15:35:19 -0800127 def ListBuildDir(self):
128 """List the files in the build directory.
129
130 Only lists files a single level into the build directory. Includes
131 timestamp information in the listing.
132
133 Returns:
134 A string with information about the files in the build directory.
135 None if the build directory doesn't exist.
136
137 Raises:
138 build_artifact.ArtifactDownloadError: If the build_dir path exists
139 but is not a directory.
140 """
141 if not os.path.exists(self._build_dir):
142 return None
143 if not os.path.isdir(self._build_dir):
144 raise build_artifact.ArtifactDownloadError(
145 'Artifacts %s improperly staged to build_dir path %s. The path is '
146 'not a directory.' % (self._archive_url, self._build_dir))
147
148 ls_format = collections.namedtuple(
Gabe Black3b567202015-09-23 14:07:59 -0700149 'ls', ['name', 'accessed', 'modified', 'size'])
Prashanth Ba06d2d22014-03-07 15:35:19 -0800150 output_format = ('Name: %(name)s Accessed: %(accessed)s '
Gabe Black3b567202015-09-23 14:07:59 -0700151 'Modified: %(modified)s Size: %(size)s bytes.\n')
Prashanth Ba06d2d22014-03-07 15:35:19 -0800152
153 build_dir_info = 'Listing contents of :%s \n' % self._build_dir
154 for file_name in os.listdir(self._build_dir):
155 file_path = os.path.join(self._build_dir, file_name)
156 file_info = os.stat(file_path)
157 ls_info = ls_format(file_path,
158 datetime.fromtimestamp(file_info.st_atime),
159 datetime.fromtimestamp(file_info.st_mtime),
160 file_info.st_size)
161 build_dir_info += output_format % ls_info._asdict()
162 return build_dir_info
163
Gabe Black3b567202015-09-23 14:07:59 -0700164 def Download(self, factory, async=False):
Chris Sosa76e44b92013-01-31 12:11:38 -0800165 """Downloads and caches the |artifacts|.
Chris Sosa9164ca32012-03-28 11:04:50 -0700166
Gabe Black3b567202015-09-23 14:07:59 -0700167 Downloads and caches the |artifacts|. Returns once these are present on the
168 devserver. A call to this will attempt to cache non-specified artifacts in
169 the background following the principle of spatial locality.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700170
Chris Sosa75490802013-09-30 17:21:45 -0700171 Args:
Eric Carusoe76d1872019-02-21 11:17:45 -0800172 factory: The artifact factory.
173 async: If True, return without waiting for download to complete.
Chris Sosa75490802013-09-30 17:21:45 -0700174
175 Raises:
Gilad Arnold02dc6552013-11-14 11:27:54 -0800176 build_artifact.ArtifactDownloadError: If failed to download the artifact.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700177 """
Eric Carusoe76d1872019-02-21 11:17:45 -0800178 try:
179 common_util.MkDirP(self._build_dir)
180 except OSError as e:
181 if e.errno != errno.EACCES:
182 raise
183 self._Log('Could not create build dir due to permissions issue. '
184 'Attempting to fix permissions.')
185 subprocess.Popen(['sudo', 'chown', '-R',
186 '%s:%s' % (os.getuid(), os.getgid()),
187 self._static_dir]).wait()
188 # Then try to create the build dir again.
189 common_util.MkDirP(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700190
Chris Sosa76e44b92013-01-31 12:11:38 -0800191 # We are doing some work on this build -- let's touch it to indicate that
192 # we shouldn't be cleaning it up anytime soon.
Simran Basief83d6a2014-08-28 14:32:01 -0700193 Downloader.TouchTimestampForStaged(self._build_dir)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700194
Chris Sosa76e44b92013-01-31 12:11:38 -0800195 # Create factory to create build_artifacts from artifact names.
Chris Sosa76e44b92013-01-31 12:11:38 -0800196 background_artifacts = factory.OptionalArtifacts()
197 if background_artifacts:
198 self._DownloadArtifactsInBackground(background_artifacts)
Gilad Arnold6f99b982012-09-12 10:49:40 -0700199
Chris Sosa76e44b92013-01-31 12:11:38 -0800200 required_artifacts = factory.RequiredArtifacts()
201 str_repr = [str(a) for a in required_artifacts]
202 self._Log('Downloading artifacts %s.', ' '.join(str_repr))
Dan Shie37f8fe2013-08-09 16:10:29 -0700203
Dan Shi6e50c722013-08-19 15:05:06 -0700204 if async:
205 self._DownloadArtifactsInBackground(required_artifacts)
206 else:
207 self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
Chris Sosa76e44b92013-01-31 12:11:38 -0800208
Gabe Black3b567202015-09-23 14:07:59 -0700209 def IsStaged(self, factory):
Dan Shif8eb0d12013-08-01 17:52:06 -0700210 """Check if all artifacts have been downloaded.
211
Gabe Black3b567202015-09-23 14:07:59 -0700212 Args:
213 factory: An instance of BaseArtifactFactory to be used to check if desired
214 artifacts or files are staged.
215
216 Returns:
217 True if all artifacts are staged.
218
219 Raises:
220 DownloaderException: A wrapper for exceptions raised by any artifact when
221 calling Process.
Dan Shif8eb0d12013-08-01 17:52:06 -0700222 """
Dan Shif8eb0d12013-08-01 17:52:06 -0700223 required_artifacts = factory.RequiredArtifacts()
Dan Shi6e50c722013-08-19 15:05:06 -0700224 exceptions = [artifact.GetException() for artifact in required_artifacts if
225 artifact.GetException()]
226 if exceptions:
227 raise DownloaderException(exceptions)
228
Dan Shif8eb0d12013-08-01 17:52:06 -0700229 return all([artifact.ArtifactStaged() for artifact in required_artifacts])
230
Chris Sosa76e44b92013-01-31 12:11:38 -0800231 def _DownloadArtifactsSerially(self, artifacts, no_wait):
232 """Simple function to download all the given artifacts serially.
233
Chris Sosa75490802013-09-30 17:21:45 -0700234 Args:
235 artifacts: A list of build_artifact.BuildArtifact instances to
236 download.
237 no_wait: If True, don't block waiting for artifact to exist if we
238 fail to immediately find it.
239
240 Raises:
241 build_artifact.ArtifactDownloadError: If we failed to download the
242 artifact.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700243 """
Dan Shi6e50c722013-08-19 15:05:06 -0700244 try:
245 for artifact in artifacts:
Gabe Black3b567202015-09-23 14:07:59 -0700246 artifact.Process(self, no_wait)
Gilad Arnold02dc6552013-11-14 11:27:54 -0800247 except build_artifact.ArtifactDownloadError:
Dan Shi6e50c722013-08-19 15:05:06 -0700248 Downloader._TryRemoveStageDir(self._build_dir)
249 raise
Gilad Arnold6f99b982012-09-12 10:49:40 -0700250
Chris Sosa76e44b92013-01-31 12:11:38 -0800251 def _DownloadArtifactsInBackground(self, artifacts):
252 """Downloads |artifacts| in the background.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700253
Chris Sosa76e44b92013-01-31 12:11:38 -0800254 Downloads |artifacts| in the background. As these are backgrounded
255 artifacts, they are done best effort and may not exist.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700256
Chris Sosa76e44b92013-01-31 12:11:38 -0800257 Args:
258 artifacts: List of build_artifact.BuildArtifact instances to download.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700259 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800260 self._Log('Invoking background download of artifacts for %r', artifacts)
261 thread = threading.Thread(target=self._DownloadArtifactsSerially,
262 args=(artifacts, False))
263 thread.start()
Gabe Black3b567202015-09-23 14:07:59 -0700264
265 def Wait(self, name, is_regex_name, timeout):
266 """Waits for artifact to exist and returns the appropriate names.
267
268 Args:
269 name: Name to look at.
270 is_regex_name: True if the name is a regex pattern.
271 timeout: How long to wait for the artifact to become available.
272
273 Returns:
274 A list of names that match.
275 """
276 raise NotImplementedError()
277
278 def Fetch(self, remote_name, local_path):
279 """Downloads artifact from given source to a local directory.
280
281 Args:
282 remote_name: Remote name of the file to fetch.
283 local_path: Local path to the folder to store fetched file.
284
285 Returns:
286 The path to fetched file.
287 """
288 raise NotImplementedError()
289
290 def DescribeSource(self):
291 """Gets the source of the download, e.g., a url to GS."""
292 raise NotImplementedError()
293
294
295class GoogleStorageDownloader(Downloader):
296 """Downloader of images to the devserver from Google Storage.
297
298 Given a URL to a build on the archive server:
299 - Caches that build and the given artifacts onto the devserver.
300 - May also initiate caching of related artifacts in the background.
301
302 This is intended to be used with ChromeOS.
303
304 Private class members:
305 archive_url: Google Storage URL to download build artifacts from.
306 """
307
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700308 def __init__(self, static_dir, archive_url, build_id):
309 build = build_id.split('/')[-1]
310 build_dir = os.path.join(static_dir, build_id)
Gabe Black3b567202015-09-23 14:07:59 -0700311
312 super(GoogleStorageDownloader, self).__init__(static_dir, build_dir, build)
313
314 self._archive_url = archive_url
315
xixuan178263c2017-03-22 09:10:25 -0700316 if common_util.IsRunningOnMoblab():
317 self._ctx = gs.GSContext(cache_user='chronos') if gs else None
318 else:
319 self._ctx = gs.GSContext() if gs else None
xixuan44b55452016-09-06 15:35:56 -0700320
Gabe Black3b567202015-09-23 14:07:59 -0700321 def Wait(self, name, is_regex_name, timeout):
322 """Waits for artifact to exist and returns the appropriate names.
323
324 Args:
325 name: Name to look at.
326 is_regex_name: True if the name is a regex pattern.
327 timeout: How long to wait for the artifact to become available.
328
329 Returns:
330 A list of names that match.
331
332 Raises:
333 ArtifactDownloadError: An error occurred when obtaining artifact.
334 """
xixuan44b55452016-09-06 15:35:56 -0700335 names = self._ctx.GetGsNamesWithWait(
336 name, self._archive_url, timeout=timeout,
Gabe Black3b567202015-09-23 14:07:59 -0700337 is_regex_pattern=is_regex_name)
338 if not names:
339 raise build_artifact.ArtifactDownloadError(
340 'Could not find %s in Google Storage at %s' %
341 (name, self._archive_url))
342 return names
343
344 def Fetch(self, remote_name, local_path):
345 """Downloads artifact from Google Storage to a local directory."""
346 install_path = os.path.join(local_path, remote_name)
347 gs_path = '/'.join([self._archive_url, remote_name])
xixuan44b55452016-09-06 15:35:56 -0700348 self._ctx.Copy(gs_path, local_path)
Gabe Black3b567202015-09-23 14:07:59 -0700349 return install_path
350
351 def DescribeSource(self):
352 return self._archive_url
353
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700354 @staticmethod
355 def GetBuildIdFromArchiveURL(archive_url):
356 """Extracts the build ID from the archive URL.
357
358 The archive_url is of the form gs://server/[some_path/target]/...]/build
359 This function discards 'gs://server/' and extracts the [some_path/target]
360 as rel_path and the build as build.
361 """
362 sub_url = archive_url.partition('://')[2]
363 split_sub_url = sub_url.split('/')
364 return '/'.join(split_sub_url[1:])
365
Gabe Black3b567202015-09-23 14:07:59 -0700366
367class LocalDownloader(Downloader):
368 """Downloader of images to the devserver from local storage.
369
370 Given a local path:
371 - Caches that build and the given artifacts onto the devserver.
372 - May also initiate caching of related artifacts in the background.
373
374 Private class members:
375 archive_params: parameters for where to download build artifacts from.
376 """
377
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800378 def __init__(self, static_dir, source_path, delete_source=False):
379 """Initialize us.
380
381 Args:
382 static_dir: The directory where artifacts are to be staged.
383 source_path: The source path to copy artifacts from.
384 delete_source: If True, delete the source files. This mode is faster than
385 actually copying because it allows us to simply move the files.
386 """
Gabe Black3b567202015-09-23 14:07:59 -0700387 # The local path is of the form /{path to static dir}/{rel_path}/{build}.
388 # local_path must be a subpath of the static directory.
389 self.source_path = source_path
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800390 self._move_files = delete_source
Gabe Black3b567202015-09-23 14:07:59 -0700391 rel_path = os.path.basename(os.path.dirname(source_path))
392 build = os.path.basename(source_path)
393 build_dir = os.path.join(static_dir, rel_path, build)
394
395 super(LocalDownloader, self).__init__(static_dir, build_dir, build)
396
397 def Wait(self, name, is_regex_name, timeout):
398 """Verifies the local artifact exists and returns the appropriate names.
399
400 Args:
401 name: Name to look at.
402 is_regex_name: True if the name is a regex pattern.
403 timeout: How long to wait for the artifact to become available.
404
405 Returns:
406 A list of names that match.
407
408 Raises:
409 ArtifactDownloadError: An error occurred when obtaining artifact.
410 """
Gabe Black3b567202015-09-23 14:07:59 -0700411 if is_regex_name:
412 filter_re = re.compile(name)
Prathmesh Prabhubee63be2018-02-09 23:28:24 -0800413 artifacts = [f for f in os.listdir(self.source_path) if
414 filter_re.match(f)]
Gabe Black3b567202015-09-23 14:07:59 -0700415 else:
Prathmesh Prabhubee63be2018-02-09 23:28:24 -0800416 glob_search = glob.glob(os.path.join(self.source_path, name))
417 artifacts = [os.path.basename(g) for g in glob_search]
418
419 if not artifacts:
420 raise build_artifact.ArtifactDownloadError(
421 'Artifact %s not found at %s(regex_match: %s)'
422 % (name, self.source_path, is_regex_name))
423 return artifacts
Gabe Black3b567202015-09-23 14:07:59 -0700424
425 def Fetch(self, remote_name, local_path):
426 """Downloads artifact from Google Storage to a local directory."""
427 install_path = os.path.join(local_path, remote_name)
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800428 src_path = os.path.join(self.source_path, remote_name)
429 if self._move_files:
430 shutil.move(src_path, install_path)
431 else:
432 shutil.copyfile(src_path, install_path)
Gabe Black3b567202015-09-23 14:07:59 -0700433 return install_path
434
435 def DescribeSource(self):
436 return self.source_path
437
438
Dan Shi72b16132015-10-08 12:10:33 -0700439class AndroidBuildDownloader(Downloader):
440 """Downloader of images to the devserver from Android's build server."""
Gabe Black3b567202015-09-23 14:07:59 -0700441
Dan Shi72b16132015-10-08 12:10:33 -0700442 def __init__(self, static_dir, branch, build_id, target):
443 """Initialize AndroidBuildDownloader.
Gabe Black3b567202015-09-23 14:07:59 -0700444
445 Args:
446 static_dir: Root directory to store the build.
Dan Shi72b16132015-10-08 12:10:33 -0700447 branch: Branch for the build. Download will always verify if the given
448 build id is for the branch.
Gabe Black3b567202015-09-23 14:07:59 -0700449 build_id: Build id of the Android build, e.g., 2155602.
450 target: Target of the Android build, e.g., shamu-userdebug.
451 """
Dan Shi72b16132015-10-08 12:10:33 -0700452 build = '%s/%s/%s' % (branch, target, build_id)
Gabe Black3b567202015-09-23 14:07:59 -0700453 build_dir = os.path.join(static_dir, '', build)
454
Dan Shi72b16132015-10-08 12:10:33 -0700455 self.branch = branch
Gabe Black3b567202015-09-23 14:07:59 -0700456 self.build_id = build_id
457 self.target = target
458
Dan Shi72b16132015-10-08 12:10:33 -0700459 super(AndroidBuildDownloader, self).__init__(static_dir, build_dir, build)
Gabe Black3b567202015-09-23 14:07:59 -0700460
461 def Wait(self, name, is_regex_name, timeout):
462 """Verifies the local artifact exists and returns the appropriate names.
463
464 Args:
465 name: Name to look at.
466 is_regex_name: True if the name is a regex pattern.
467 timeout: How long to wait for the artifact to become available.
468
469 Returns:
470 A list of names that match.
471
472 Raises:
473 ArtifactDownloadError: An error occurred when obtaining artifact.
474 """
Dan Shi72b16132015-10-08 12:10:33 -0700475 artifacts = android_build.BuildAccessor.GetArtifacts(
476 branch=self.branch, build_id=self.build_id, target=self.target)
477
478 names = []
479 for artifact_name in [a['name'] for a in artifacts]:
480 match = (re.match(name, artifact_name) if is_regex_name
481 else name == artifact_name)
482 if match:
483 names.append(artifact_name)
484
485 if not names:
486 raise build_artifact.ArtifactDownloadError(
Dan Shi9ee5dc22017-06-27 11:53:07 -0700487 'No artifact found with given name: %s for %s-%s. All available '
488 'artifacts are: %s' %
489 (name, self.target, self.build_id,
490 ','.join([a['name'] for a in artifacts])))
Dan Shi72b16132015-10-08 12:10:33 -0700491
492 return names
Gabe Black3b567202015-09-23 14:07:59 -0700493
494 def Fetch(self, remote_name, local_path):
Dan Shi72b16132015-10-08 12:10:33 -0700495 """Downloads artifact from Android's build server to a local directory."""
496 dest_file = os.path.join(local_path, remote_name)
497 android_build.BuildAccessor.Download(
498 branch=self.branch, build_id=self.build_id, target=self.target,
499 resource_id=remote_name, dest_file=dest_file)
500 return dest_file
Gabe Black3b567202015-09-23 14:07:59 -0700501
502 def DescribeSource(self):
Dan Shi72b16132015-10-08 12:10:33 -0700503 return '%s/%s/%s/%s' % (android_build.DEFAULT_BUILDER, self.branch,
504 self.target, self.build_id)