1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import errno 6import os 7import stat 8 9from py_utils import cloud_storage 10 11from dependency_manager import exceptions 12 13class CloudStorageInfo(object): 14 def __init__(self, cs_bucket, cs_hash, download_path, cs_remote_path, 15 version_in_cs=None, archive_info=None): 16 """ Container for the information needed to download a dependency from 17 cloud storage. 18 19 Args: 20 cs_bucket: The cloud storage bucket the dependency is located in. 21 cs_hash: The hash of the file stored in cloud storage. 22 download_path: Where the file should be downloaded to. 23 cs_remote_path: Where the file is stored in the cloud storage bucket. 24 version_in_cs: The version of the file stored in cloud storage. 25 archive_info: An instance of ArchiveInfo if this dependency is an 26 archive. Else None. 27 """ 28 self._download_path = download_path 29 self._cs_remote_path = cs_remote_path 30 self._cs_bucket = cs_bucket 31 self._cs_hash = cs_hash 32 self._version_in_cs = version_in_cs 33 self._archive_info = archive_info 34 if not self._has_minimum_data: 35 raise ValueError( 36 'Not enough information specified to initialize a cloud storage info.' 37 ' %s' % self) 38 39 def DependencyExistsInCloudStorage(self): 40 return cloud_storage.Exists(self._cs_bucket, self._cs_remote_path) 41 42 def GetRemotePath(self): 43 """Gets the path to a downloaded version of the dependency. 44 45 May not download the file if it has already been downloaded. 46 Will unzip the downloaded file if a non-empty archive_info was passed in at 47 init. 48 49 Returns: A path to an executable that was stored in cloud_storage, or None 50 if not found. 51 52 Raises: 53 CredentialsError: If cloud_storage credentials aren't configured. 54 PermissionError: If cloud_storage credentials are configured, but not 55 with an account that has permission to download the needed file. 56 NotFoundError: If the needed file does not exist where expected in 57 cloud_storage or the downloaded zip file. 58 ServerError: If an internal server error is hit while downloading the 59 needed file. 60 CloudStorageError: If another error occured while downloading the remote 61 path. 62 FileNotFoundError: If the download was otherwise unsuccessful. 63 """ 64 if not self._has_minimum_data: 65 return None 66 67 download_dir = os.path.dirname(self._download_path) 68 if not os.path.exists(download_dir): 69 try: 70 os.makedirs(download_dir) 71 except OSError as e: 72 # The logic above is racy, and os.makedirs will raise an OSError if 73 # the directory exists. 74 if e.errno != errno.EEXIST: 75 raise 76 77 dependency_path = self._download_path 78 cloud_storage.GetIfHashChanged( 79 self._cs_remote_path, self._download_path, self._cs_bucket, 80 self._cs_hash) 81 if not os.path.exists(dependency_path): 82 raise exceptions.FileNotFoundError(dependency_path) 83 84 if self.has_archive_info: 85 dependency_path = self._archive_info.GetUnzippedPath() 86 else: 87 mode = os.stat(dependency_path).st_mode 88 os.chmod(dependency_path, mode | stat.S_IXUSR) 89 return os.path.abspath(dependency_path) 90 91 @property 92 def version_in_cs(self): 93 return self._version_in_cs 94 95 @property 96 def _has_minimum_data(self): 97 return all([self._cs_bucket, self._cs_remote_path, self._download_path, 98 self._cs_hash]) 99 100 101 @property 102 def has_archive_info(self): 103 return bool(self._archive_info) 104 105 def __repr__(self): 106 return ( 107 'CloudStorageInfo(download_path=%s, cs_remote_path=%s, cs_bucket=%s, ' 108 'cs_hash=%s, version_in_cs=%s, archive_info=%s)' % ( 109 self._download_path, self._cs_remote_path, self._cs_bucket, 110 self._cs_hash, self._version_in_cs, self._archive_info)) 111