Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: sitescripts/extensions/bin/createNightlies.py

Issue 5092904657747968: Issue 356 - Update devbuild in the Chrome Web Store (Closed)
Left Patch Set: Created April 19, 2014, 4:12 p.m.
Right Patch Set: Addressed comments Created April 21, 2014, 8:45 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « .sitescripts.example ('k') | sitescripts/extensions/utils.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # coding: utf-8 1 # coding: utf-8
2 2
3 # This file is part of the Adblock Plus web scripts, 3 # This file is part of the Adblock Plus web scripts,
4 # Copyright (C) 2006-2013 Eyeo GmbH 4 # Copyright (C) 2006-2013 Eyeo GmbH
5 # 5 #
6 # Adblock Plus is free software: you can redistribute it and/or modify 6 # Adblock Plus is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License version 3 as 7 # it under the terms of the GNU General Public License version 3 as
8 # published by the Free Software Foundation. 8 # published by the Free Software Foundation.
9 # 9 #
10 # Adblock Plus is distributed in the hope that it will be useful, 10 # Adblock Plus is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details. 13 # GNU General Public License for more details.
14 # 14 #
15 # You should have received a copy of the GNU General Public License 15 # You should have received a copy of the GNU General Public License
16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
17 17
18 """ 18 """
19 19
20 Nightly builds generation script 20 Nightly builds generation script
21 ================================ 21 ================================
22 22
23 This script generates nightly builds of extensions, together 23 This script generates nightly builds of extensions, together
24 with changelogs and documentation. 24 with changelogs and documentation.
25 25
26 """ 26 """
27 27
28 import sys, os, os.path, codecs, subprocess, ConfigParser, traceback, json, hash lib 28 import sys, os, os.path, codecs, subprocess, ConfigParser, traceback, json, hash lib
29 import tempfile, re, shutil, urlparse, pipes, time, base64 29 import tempfile, re, shutil, urlparse, pipes, time, urllib2, struct
30 from datetime import datetime 30 from datetime import datetime
31 from urllib import urlencode 31 from urllib import urlencode
32 from urllib2 import urlopen
33 from xml.dom.minidom import parse as parseXml 32 from xml.dom.minidom import parse as parseXml
34 from sitescripts.utils import get_config, setupStderr, get_template 33 from sitescripts.utils import get_config, setupStderr, get_template
35 from sitescripts.extensions.utils import compareVersions, Configuration 34 from sitescripts.extensions.utils import compareVersions, Configuration
36 35
37 MAX_BUILDS = 50 36 MAX_BUILDS = 50
38 37
39 38
40 class NightlyBuild(object): 39 class NightlyBuild(object):
41 """ 40 """
42 Performs the build process for an extension, 41 Performs the build process for an extension,
(...skipping 345 matching lines...) Expand 10 before | Expand all | Expand 10 after
388 subprocess.check_call(command) 387 subprocess.check_call(command)
389 388
390 try: 389 try:
391 import buildtools.build as build 390 import buildtools.build as build
392 outputPath = os.path.join(self.config.docsDirectory, self.basename) 391 outputPath = os.path.join(self.config.docsDirectory, self.basename)
393 build.generateDocs(self.tempdir, None, [("-t", docsdir), ("-q", "")], [out putPath], self.config.type) 392 build.generateDocs(self.tempdir, None, [("-t", docsdir), ("-q", "")], [out putPath], self.config.type)
394 finally: 393 finally:
395 shutil.rmtree(docsdir, ignore_errors=True) 394 shutil.rmtree(docsdir, ignore_errors=True)
396 395
397 def uploadToChromeWebStore(self): 396 def uploadToChromeWebStore(self):
398 import M2Crypto 397 # use refresh token to obtain a valid access token
399 398 # https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
400 # log in with the Google Developer Service Account 399
401 # https://developers.google.com/accounts/docs/OAuth2ServiceAccount#creatingj wt 400 response = json.load(urllib2.urlopen(
402
403 base64encode = lambda data: base64.urlsafe_b64encode(data).rstrip('=')
404 now = int(time.mktime(time.gmtime()))
405
406 jwt = '%s.%s' % (
407 base64encode(json.dumps({
408 'alg': 'RS256',
409 'typ': 'JWT',
410 })),
411 base64encode(json.dumps({
412 'iss': self.config.serviceAccountEmail,
413 'scope': 'https://www.googleapis.com/auth/chromewebstore',
414 'aud':'https://accounts.google.com/o/oauth2/token',
415 'exp': now + 3600, # fails if less than about an hour
416 'iat': now,
417 })),
418 )
419
420 jwt = '%s.%s' % (jwt, base64encode(
421 M2Crypto.RSA.load_key(self.config.serviceAccountKey).sign(
422 hashlib.sha256(jwt).digest(), 'sha256'
423 )
424 ))
425
426 response = json.load(urlopen(
427 'https://accounts.google.com/o/oauth2/token', 401 'https://accounts.google.com/o/oauth2/token',
428 402
429 urlencode([ 403 urlencode([
430 ('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'), 404 ('refresh_token', self.config.refreshToken),
431 ('assertion', jwt), 405 ('client_id', self.config.clientID),
406 ('client_secret', self.config.clientSecret),
407 ('grant_type', 'refresh_token'),
432 ]) 408 ])
433 )) 409 ))
Wladimir Palant 2014/04/20 06:47:53 Check for error responses?
Sebastian Noack 2014/04/20 16:03:08 This API uses standard HTTP error codes, which ar
434 410
435 # upload a new version with the Chrome Web Store API 411 # upload a new version with the Chrome Web Store API
436 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng 412 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng
437 413
438 subprocess.check_call([ 414 request = urllib2.Request('https://www.googleapis.com/upload/chromewebstore/ v1.1/items/' + self.config.galleryID)
Sebastian Noack 2014/04/19 16:15:34 I use curl instead of urllib here, because urllib
Wladimir Palant 2014/04/20 06:47:53 We do file uploads with urllib in buildtools/local
Sebastian Noack 2014/04/20 16:03:08 It was actually easier to use urllib2 as I thought
439 'curl', 'https://www.googleapis.com/upload/chromewebstore/v1.1/items/' + s elf.config.galleryID 415 request.get_method = lambda: 'PUT'
440 416 request.add_header('Authorization', '%s %s' % (response['token_type'], respo nse['access_token']))
441 '--header', 'Authorization: %s %s' % (response['token_type'], response['ac cess_token']) 417 request.add_header('x-goog-api-version', '2')
442 '--header', 'x-goog-api-version: 2', 418
443 '--request', 'PUT', 419 with open(self.path, 'rb') as file:
444 '--upload-file', self.path, 420 if file.read(8) != 'Cr24\x02\x00\x00\x00':
445 '--silent', 421 raise Exception('not a chrome extension or unknown CRX version')
446 ]) 422
Wladimir Palant 2014/04/20 06:47:53 Here as well - please check for error responses, i
Sebastian Noack 2014/04/20 16:03:08 Done.
423 # skip public key and signature
424 file.seek(sum(struct.unpack('<II', file.read(8))), os.SEEK_CUR)
425
426 request.add_header('Content-Length', os.fstat(file.fileno()).st_size - fil e.tell())
427 request.add_data(file)
428
429 response = json.load(urllib2.urlopen(request))
430
431 if response['uploadState'] == 'FAILURE':
432 raise Exception(response['itemError'])
447 433
448 def run(self): 434 def run(self):
449 """ 435 """
450 Run the nightly build process for one extension 436 Run the nightly build process for one extension
451 """ 437 """
452 try: 438 try:
453 if self.config.type == 'ie': 439 if self.config.type == 'ie':
454 # We cannot build IE builds, simply list the builds already in 440 # We cannot build IE builds, simply list the builds already in
455 # the directory. Basename has to be deduced from the repository name. 441 # the directory. Basename has to be deduced from the repository name.
456 self.basename = os.path.basename(self.config.repository) 442 self.basename = os.path.basename(self.config.repository)
(...skipping 28 matching lines...) Expand all
485 471
486 if self.config.type == 'ie': 472 if self.config.type == 'ie':
487 self.writeIEUpdateManifest(versions) 473 self.writeIEUpdateManifest(versions)
488 474
489 # update index page 475 # update index page
490 self.updateIndex(versions) 476 self.updateIndex(versions)
491 477
492 # update nightlies config 478 # update nightlies config
493 self.config.latestRevision = self.revision 479 self.config.latestRevision = self.revision
494 480
495 if self.config.type == 'chrome': 481 if self.type == 'chrome' and self.config.clientID and self.config.clientSe cret and self.config.refreshToken:
Wladimir Palant 2014/04/20 06:47:53 This step needs to be optional, only if the necess
Sebastian Noack 2014/04/20 16:03:08 Done.
496 self.uploadToChromeWebStore() 482 self.uploadToChromeWebStore()
497 finally: 483 finally:
498 # clean up 484 # clean up
499 if self.tempdir: 485 if self.tempdir:
500 shutil.rmtree(self.tempdir, ignore_errors=True) 486 shutil.rmtree(self.tempdir, ignore_errors=True)
501 487
502 488
503 def main(): 489 def main():
504 """ 490 """
505 main function for createNightlies.py 491 main function for createNightlies.py
(...skipping 17 matching lines...) Expand all
523 except Exception, ex: 509 except Exception, ex:
524 print >>sys.stderr, "The build for %s failed:" % repo 510 print >>sys.stderr, "The build for %s failed:" % repo
525 traceback.print_exc() 511 traceback.print_exc()
526 512
527 file = open(nightlyConfigFile, 'wb') 513 file = open(nightlyConfigFile, 'wb')
528 nightlyConfig.write(file) 514 nightlyConfig.write(file)
529 515
530 516
531 if __name__ == '__main__': 517 if __name__ == '__main__':
532 main() 518 main()
LEFTRIGHT

Powered by Google App Engine
This is Rietveld