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.
16 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), os
.pardir
, os
.pardir
,
19 from catapult_base
import cloud_storage
20 from telemetry
.core
import exceptions
22 # Remote target upload directory in cloud storage for extensions.
23 REMOTE_DIR
= 'extension_set'
26 ZIP_NAME
= 'extensions.zip'
29 def _DownloadCrxFromCws(ext_id
, dst
):
30 """Downloads CRX specified from Chrome Web Store.
32 Retrieves CRX (Chrome extension file) specified by ext_id from Chrome Web
33 Store, into directory specified by dst.
36 ext_id: id of extension to retrieve.
37 dst: directory to download CRX into
40 Returns local path to downloaded CRX.
41 If download fails, return None.
43 dst_path
= os
.path
.join(dst
, '%s.crx' % ext_id
)
44 cws_url
= ('https://clients2.google.com/service/update2/crx?response='
45 'redirect&prodversion=38.0&x=id%%3D%s%%26installsource%%3D'
46 'ondemand%%26uc' % ext_id
)
47 response
= urllib2
.urlopen(cws_url
)
48 if response
.getcode() is not 200:
50 with
open(dst_path
, 'w') as f
:
51 f
.write(response
.read())
55 def _UpdateExtensionsInCloud(local_extensions_dir
, extensions_csv
, remote_dir
):
56 """Updates set of extensions in Cloud Storage from a CSV of extension ids.
58 From well-formatted CSV file containing some set of extensions
59 (extensions_csv), download them, compress into archive, and update
60 the remote extension archive under REMOTE_DIR in CHROME-PARTNER-TELEMETRY
61 bucket. This script expects 2nd column of CSV file to contain extension ids.
64 local_extensions_dir: directory to download CRX files into.
65 extension_csv: CSV to pull extension_ids from.
66 remote_dir: remote directory to put extension archive in cloud storage.
69 Exception if a CRX download fails.
72 # Download CRX to temp files and compress into archive
73 zip_path
= os
.path
.join(local_extensions_dir
, ZIP_NAME
)
74 extension_zip
= zipfile
.ZipFile(zip_path
, 'w')
77 with
open(extensions_csv
, 'rb') as csv_file
:
78 reader
= csv
.reader(csv_file
)
79 # Stores comments (in case CSV needs to be updated/rewritten)
80 # and skips header line.
82 line
= ','.join(reader
.next())
83 while line
.startswith('#'):
85 line
= ','.join(reader
.next())
86 # Extract info from CSV.
89 'extension_name': row
[0],
95 print 'Fetching extension %s...' % extension_info
['id']
96 crx_path
= _DownloadCrxFromCws(extension_info
['id'], local_extensions_dir
)
98 raise exceptions
.Error('\tCould not fetch %s.\n\n'
99 'If this extension dl consistently fails, '
100 'remove this entry from %s.'
101 % (extension_info
['id'], extensions_csv
))
102 (new_hash
, new_version
) = _CrxHashIfChanged(crx_path
, extension_info
)
103 if new_hash
is not None:
105 extension_info
['hash'] = new_hash
106 extension_info
['version'] = new_version
107 extensions_info
.append(extension_info
)
108 extension_zip
.write(crx_path
, arcname
='%s.crx' % extension_info
['id'])
109 extension_zip
.close()
112 print 'Updating CSV...'
113 _UpdateCsv(comments
, extensions_csv
, extensions_info
)
115 print 'Uploading extensions to cloud...'
116 remote_zip_path
= os
.path
.join(remote_dir
, ZIP_NAME
)
117 cloud_storage
.Insert(cloud_storage
.PARTNER_BUCKET
, remote_zip_path
, zip_path
)
120 def _CrxHashIfChanged(crx_path
, extension_info
):
121 """Checks whether downloaded Crx has been altered.
123 Compares stored hash with hash of downloaded Crx. If different, alerts user
124 that CRX version has changed and will be updated in CSV file.
127 crx_path: Path to downloaded CRX.
128 extension_info: Info from CSV (including id and previous hash) about CRX.
131 New hash and version if extension differed. Otherwise, returns (None, None)
133 downloaded_hash
= _Base64Hash(crx_path
)
134 new_version
= _GetVersionFromCrx(crx_path
)
135 if downloaded_hash
!= extension_info
['hash']:
136 if new_version
!= extension_info
['version']:
137 ans
= raw_input('\tWarning: Extension %s version from Web Store differs '
138 'from CSV version.\n\tIf continued, script will write '
139 'new hash and version to CSV.\n\tContinue? (y/n) '
140 % extension_info
['id']).lower()
142 raise exceptions
.Error('Extension %s hash from Web Store differs from '
143 '\nhash stored in CSV, but versions are the same.')
144 if not ans
.startswith('y'):
145 sys
.exit('Web Store extension %s hash differs from hash in CSV.'
146 % extension_info
['id'])
147 return (downloaded_hash
, new_version
)
150 def _UpdateCsv(comments
, extensions_csv
, extensions_info
):
151 """Updates CSV with information in extensions_info.
153 Original CSV is overwritten with updated information about each extension.
154 Header comments from original CSV are preserved.
157 comments: List containing lines of comments found in header of original CSV.
158 extensions_csv: Path to CSV file.
159 extensions_info: List of extension info to write to CSV. Each entry is
160 a dict containing fields extension_name, id, hash, and version.
162 # Maintain pre-existing comments.
163 with
open(extensions_csv
, 'w') as csv_file
:
164 csv_file
.write('\n'.join(comments
))
166 with
open(extensions_csv
, 'a') as csv_file
:
167 writer
= csv
.DictWriter(
168 csv_file
, fieldnames
=['extension_name', 'id', 'hash', 'version'])
170 writer
.writerows(extensions_info
)
172 def _GetCsvFromArgs():
173 """Parse options to retrieve name of CSV file."""
174 parser
= optparse
.OptionParser()
175 parser
.add_option('-e', '--extension-csv', dest
='extension_csv',
176 help='CSV of extensions to load.')
177 (options
, _
) = parser
.parse_args()
178 if not options
.extension_csv
:
179 parser
.error('Must specify --extension-csv option.')
180 return options
.extension_csv
182 def _GetVersionFromCrx(crx_path
):
183 """Retrieves extension version from CRX archive.
186 crx_path: path to CRX archive to extract version from.
188 with zipfile
.ZipFile(crx_path
, 'r') as crx_zip
:
189 manifest_contents
= crx_zip
.read('manifest.json')
190 version
= json
.loads(manifest_contents
)['version']
193 def _Base64Hash(file_path
):
194 return base64
.b64encode(cloud_storage
.CalculateHash(file_path
))
197 extension_csv
= _GetCsvFromArgs()
198 local_extensions_dir
= tempfile
.mkdtemp()
200 _UpdateExtensionsInCloud(local_extensions_dir
,
201 extension_csv
, REMOTE_DIR
)
203 shutil
.rmtree(local_extensions_dir
)
205 if __name__
== '__main__':