OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # |
| 3 # Copyright 2016 Google Inc. |
| 4 # |
| 5 # Use of this source code is governed by a BSD-style license that can be |
| 6 # found in the LICENSE file. |
| 7 |
| 8 |
| 9 """Utilities for managing assets.""" |
| 10 |
| 11 |
| 12 import argparse |
| 13 import os |
| 14 import shlex |
| 15 import shutil |
| 16 import subprocess |
| 17 import sys |
| 18 |
| 19 SKIA_DIR = os.path.abspath(os.path.realpath(os.path.join( |
| 20 os.path.dirname(os.path.abspath(__file__)), |
| 21 os.pardir, os.pardir, os.pardir))) |
| 22 INFRA_BOTS_DIR = os.path.join(SKIA_DIR, 'infra', 'bots') |
| 23 sys.path.insert(0, INFRA_BOTS_DIR) |
| 24 import utils |
| 25 import zip_utils |
| 26 |
| 27 |
| 28 ASSETS_DIR = os.path.join(INFRA_BOTS_DIR, 'assets') |
| 29 DEFAULT_GS_BUCKET = 'skia-buildbots' |
| 30 GS_SUBDIR_TMPL = 'gs://%s/assets/%s' |
| 31 GS_PATH_TMPL = '%s/%s.zip' |
| 32 VERSION_FILENAME = 'VERSION' |
| 33 ZIP_BLACKLIST = ['.git', '.svn', '*.pyc', '.DS_STORE'] |
| 34 |
| 35 |
| 36 class _GSWrapper(object): |
| 37 """Wrapper object for interacting with Google Storage.""" |
| 38 def __init__(self, gsutil): |
| 39 gsutil = os.path.abspath(gsutil) if gsutil else 'gsutil' |
| 40 self._gsutil = [gsutil] |
| 41 if gsutil.endswith('.py'): |
| 42 self._gsutil = ['python', gsutil] |
| 43 |
| 44 def copy(self, src, dst): |
| 45 """Copy src to dst.""" |
| 46 subprocess.check_call(self._gsutil + ['cp', src, dst]) |
| 47 |
| 48 def list(self, path): |
| 49 """List objects in the given path.""" |
| 50 try: |
| 51 return subprocess.check_output(self._gsutil + ['ls', path]).splitlines() |
| 52 except subprocess.CalledProcessError: |
| 53 # If the prefix does not exist, we'll get an error, which is okay. |
| 54 return [] |
| 55 |
| 56 |
| 57 def _prompt(prompt): |
| 58 """Prompt for input, return result.""" |
| 59 return raw_input(prompt) |
| 60 |
| 61 |
| 62 class Asset(object): |
| 63 def __init__(self, name, gs_bucket=DEFAULT_GS_BUCKET, gsutil=None): |
| 64 self._gs = _GSWrapper(gsutil) |
| 65 self._gs_subdir = GS_SUBDIR_TMPL % (gs_bucket, name) |
| 66 self._name = name |
| 67 self._dir = os.path.join(ASSETS_DIR, self._name) |
| 68 |
| 69 @property |
| 70 def version_file(self): |
| 71 """Return the path to the version file for this asset.""" |
| 72 return os.path.join(self._dir, VERSION_FILENAME) |
| 73 |
| 74 def get_current_version(self): |
| 75 """Obtain the current version of the asset.""" |
| 76 if not os.path.isfile(self.version_file): |
| 77 return -1 |
| 78 with open(self.version_file) as f: |
| 79 return int(f.read()) |
| 80 |
| 81 def get_available_versions(self): |
| 82 """Return the existing version numbers for this asset.""" |
| 83 files = self._gs.list(self._gs_subdir) |
| 84 bnames = [os.path.basename(f) for f in files] |
| 85 suffix = '.zip' |
| 86 versions = [int(f[:-len(suffix)]) for f in bnames if f.endswith(suffix)] |
| 87 versions.sort() |
| 88 return versions |
| 89 |
| 90 def get_next_version(self): |
| 91 """Find the next available version number for the asset.""" |
| 92 versions = self.get_available_versions() |
| 93 if len(versions) == 0: |
| 94 return 0 |
| 95 return versions[-1] + 1 |
| 96 |
| 97 def download_version(self, version, target_dir): |
| 98 """Download the specified version of the asset.""" |
| 99 gs_path = GS_PATH_TMPL % (self._gs_subdir, str(version)) |
| 100 target_dir = os.path.abspath(target_dir) |
| 101 with utils.tmp_dir(): |
| 102 zip_file = os.path.join(os.getcwd(), '%d.zip' % version) |
| 103 self._gs.copy(gs_path, zip_file) |
| 104 zip_utils.unzip(zip_file, target_dir) |
| 105 |
| 106 def download_current_version(self, target_dir): |
| 107 """Download the version of the asset specified in its version file.""" |
| 108 v = self.get_current_version() |
| 109 self.download_version(v, target_dir) |
| 110 |
| 111 def upload_new_version(self, target_dir, commit=False): |
| 112 """Upload a new version and update the version file for the asset.""" |
| 113 version = self.get_next_version() |
| 114 target_dir = os.path.abspath(target_dir) |
| 115 with utils.tmp_dir(): |
| 116 zip_file = os.path.join(os.getcwd(), '%d.zip' % version) |
| 117 zip_utils.zip(target_dir, zip_file, blacklist=ZIP_BLACKLIST) |
| 118 gs_path = GS_PATH_TMPL % (self._gs_subdir, str(version)) |
| 119 self._gs.copy(zip_file, gs_path) |
| 120 |
| 121 def _write_version(): |
| 122 with open(self.version_file, 'w') as f: |
| 123 f.write(str(version)) |
| 124 subprocess.check_call([utils.GIT, 'add', self.version_file]) |
| 125 |
| 126 with utils.chdir(SKIA_DIR): |
| 127 if commit: |
| 128 with utils.git_branch(): |
| 129 _write_version() |
| 130 subprocess.check_call([ |
| 131 utils.GIT, 'commit', '-m', 'Update %s version' % self._name]) |
| 132 subprocess.check_call([utils.GIT, 'cl', 'upload', '--bypass-hooks']) |
| 133 else: |
| 134 _write_version() |
| 135 |
| 136 @classmethod |
| 137 def add(cls, name, gs_bucket=DEFAULT_GS_BUCKET, gsutil=None): |
| 138 """Add an asset.""" |
| 139 asset = cls(name, gs_bucket=gs_bucket, gsutil=gsutil) |
| 140 if os.path.isdir(asset._dir): |
| 141 raise Exception('Asset %s already exists!' % asset._name) |
| 142 |
| 143 print 'Creating asset in %s' % asset._dir |
| 144 os.mkdir(asset._dir) |
| 145 def copy_script(script): |
| 146 src = os.path.join(ASSETS_DIR, 'scripts', script) |
| 147 dst = os.path.join(asset._dir, script) |
| 148 print 'Creating %s' % dst |
| 149 shutil.copy(src, dst) |
| 150 subprocess.check_call([utils.GIT, 'add', dst]) |
| 151 |
| 152 for script in ('download.py', 'upload.py', 'common.py'): |
| 153 copy_script(script) |
| 154 resp = _prompt('Add script to automate creation of this asset? (y/n) ') |
| 155 if resp == 'y': |
| 156 copy_script('create.py') |
| 157 copy_script('create_and_upload.py') |
| 158 print 'You will need to add implementation to the creation script.' |
| 159 print 'Successfully created asset %s.' % asset._name |
| 160 return asset |
| 161 |
| 162 def remove(self): |
| 163 """Remove this asset.""" |
| 164 # Ensure that the asset exists. |
| 165 if not os.path.isdir(self._dir): |
| 166 raise Exception('Asset %s does not exist!' % self._name) |
| 167 |
| 168 # Remove the asset. |
| 169 subprocess.check_call([utils.GIT, 'rm', '-rf', self._dir]) |
| 170 if os.path.isdir(self._dir): |
| 171 shutil.rmtree(self._dir) |
| 172 |
| 173 # We *could* remove all uploaded versions of the asset in Google Storage but |
| 174 # we choose not to be that destructive. |
OLD | NEW |