| Index: sitescripts/extensions/bin/createNightlies.py |
| =================================================================== |
| --- a/sitescripts/extensions/bin/createNightlies.py |
| +++ b/sitescripts/extensions/bin/createNightlies.py |
| @@ -26,8 +26,9 @@ |
| """ |
| import sys, os, os.path, codecs, subprocess, ConfigParser, traceback, json, hashlib |
| -import tempfile, re, shutil, urlparse, pipes |
| +import tempfile, re, shutil, urlparse, pipes, time, base64, urllib2 |
| from datetime import datetime |
| +from urllib import urlencode |
| from xml.dom.minidom import parse as parseXml |
| from sitescripts.utils import get_config, setupStderr, get_template |
| from sitescripts.extensions.utils import compareVersions, Configuration |
| @@ -286,11 +287,11 @@ |
| if not os.path.exists(baseDir): |
| os.makedirs(baseDir) |
| outputFile = "%s-%s%s" % (self.basename, self.version, self.config.packageSuffix) |
| - outputPath = os.path.join(baseDir, outputFile) |
| + self.path = os.path.join(baseDir, outputFile) |
| self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + '/' + outputFile + '?update') |
| if self.config.type == 'android': |
| - apkFile = open(outputPath, 'wb') |
| + apkFile = open(self.path, 'wb') |
| try: |
| try: |
| @@ -302,29 +303,29 @@ |
| subprocess.check_call(buildCommand, stdout=apkFile, close_fds=True) |
| except: |
| # clear broken output if any |
| - if os.path.exists(outputPath): |
| - os.remove(outputPath) |
| + if os.path.exists(self.path): |
| + os.remove(self.path) |
| raise |
| elif self.config.type == 'chrome' or self.config.type == 'opera': |
| import buildtools.packagerChrome as packager |
| - packager.createBuild(self.tempdir, type=self.config.type, outFile=outputPath, buildNum=self.revision, keyFile=self.config.keyFile, experimentalAPI=self.config.experimental) |
| + packager.createBuild(self.tempdir, type=self.config.type, outFile=self.path, buildNum=self.revision, keyFile=self.config.keyFile, experimentalAPI=self.config.experimental) |
| elif self.config.type == 'safari': |
| import buildtools.packagerSafari as packager |
| - packager.createBuild(self.tempdir, type=self.config.type, outFile=outputPath, buildNum=self.revision, keyFile=self.config.keyFile) |
| + packager.createBuild(self.tempdir, type=self.config.type, outFile=self.path, buildNum=self.revision, keyFile=self.config.keyFile) |
| else: |
| import buildtools.packagerGecko as packager |
| - packager.createBuild(self.tempdir, outFile=outputPath, buildNum=self.revision, keyFile=self.config.keyFile) |
| + packager.createBuild(self.tempdir, outFile=self.path, buildNum=self.revision, keyFile=self.config.keyFile) |
| - if not os.path.exists(outputPath): |
| + if not os.path.exists(self.path): |
| raise Exception("Build failed, output file hasn't been created") |
| linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffix) |
| if hasattr(os, 'symlink'): |
| if os.path.exists(linkPath): |
| os.remove(linkPath) |
| - os.symlink(os.path.basename(outputPath), linkPath) |
| + os.symlink(os.path.basename(self.path), linkPath) |
| else: |
| - shutil.copyfile(outputPath, linkPath) |
| + shutil.copyfile(self.path, linkPath) |
| def retireBuilds(self): |
| """ |
| @@ -392,6 +393,61 @@ |
| finally: |
| shutil.rmtree(docsdir, ignore_errors=True) |
| + def uploadToChromeWebStore(self): |
| + import M2Crypto |
| + |
| + # log in with the Google Developer Service Account |
| + # https://developers.google.com/accounts/docs/OAuth2ServiceAccount#creatingjwt |
| + |
| + base64encode = lambda data: base64.urlsafe_b64encode(data).rstrip('=') |
| + now = int(time.mktime(time.gmtime())) |
| + |
| + jwt = '%s.%s' % ( |
| + base64encode(json.dumps({ |
| + 'alg': 'RS256', |
| + 'typ': 'JWT', |
| + })), |
| + base64encode(json.dumps({ |
| + 'iss': self.config.serviceAccountEmail, |
| + 'scope': 'https://www.googleapis.com/auth/chromewebstore', |
| + 'aud': 'https://accounts.google.com/o/oauth2/token', |
| + 'exp': now + 3600, # fails if less than about an hour |
| + 'iat': now, |
| + })), |
| + ) |
| + |
| + jwt = '%s.%s' % (jwt, base64encode( |
| + M2Crypto.RSA.load_key(self.config.serviceAccountKey).sign( |
| + hashlib.sha256(jwt).digest(), 'sha256' |
| + ) |
| + )) |
| + |
| + response = json.load(urllib2.urlopen( |
| + 'https://accounts.google.com/o/oauth2/token', |
| + |
| + urlencode([ |
| + ('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'), |
| + ('assertion', jwt), |
| + ]) |
| + )) |
| + |
| + # upload a new version with the Chrome Web Store API |
| + # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng |
| + |
| + request = urllib2.Request('https://www.googleapis.com/upload/chromewebstore/v1.1/items/' + self.config.galleryID) |
| + request.get_method = lambda: 'PUT' |
| + request.add_header('Authorization', '%s %s' % (response['token_type'], response['access_token'])) |
| + request.add_header('x-goog-api-version', '2') |
| + |
| + with open(self.path, 'rb') as file: |
| + request.add_header('Content-Length', '%d' % os.fstat(file.fileno()).st_size) |
| + request.add_data(file) |
| + |
| + response = json.load(urllib2.urlopen(request)) |
| + |
| + if response['uploadState'] == 'FAILURE': |
| + raise Exception(response['itemError']) |
| + |
| def run(self): |
| """ |
| Run the nightly build process for one extension |
| @@ -438,6 +494,9 @@ |
| # update nightlies config |
| self.config.latestRevision = self.revision |
| + |
| + if self.config.serviceAccountEmail and self.config.serviceAccountKey: |
| + self.uploadToChromeWebStore() |
| finally: |
| # clean up |
| if self.tempdir: |