Index: sitescripts/extensions/bin/createNightlies.py |
=================================================================== |
--- a/sitescripts/extensions/bin/createNightlies.py |
+++ b/sitescripts/extensions/bin/createNightlies.py |
@@ -26,8 +26,10 @@ |
""" |
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 |
from datetime import datetime |
+from urllib import urlencode |
+from urllib2 import urlopen |
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 +288,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 +304,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 +394,57 @@ |
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(urlopen( |
+ 'https://accounts.google.com/o/oauth2/token', |
+ |
+ urlencode([ |
+ ('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'), |
+ ('assertion', jwt), |
+ ]) |
+ )) |
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
|
+ |
+ # upload a new version with the Chrome Web Store API |
+ # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng |
+ |
+ subprocess.check_call([ |
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
|
+ 'curl', 'https://www.googleapis.com/upload/chromewebstore/v1.1/items/' + self.config.galleryID |
+ |
+ '--header', 'Authorization: %s %s' % (response['token_type'], response['access_token']) |
+ '--header', 'x-goog-api-version: 2', |
+ '--request', 'PUT', |
+ '--upload-file', self.path, |
+ '--silent', |
+ ]) |
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.
|
+ |
def run(self): |
""" |
Run the nightly build process for one extension |
@@ -438,6 +491,9 @@ |
# update nightlies config |
self.config.latestRevision = self.revision |
+ |
+ if self.config.type == 'chrome': |
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.
|
+ self.uploadToChromeWebStore() |
finally: |
# clean up |
if self.tempdir: |