| Index: sitescripts/extensions/bin/createNightlies.py |
| =================================================================== |
| --- a/sitescripts/extensions/bin/createNightlies.py |
| +++ b/sitescripts/extensions/bin/createNightlies.py |
| @@ -42,6 +42,9 @@ |
| from urllib import urlencode |
| import urllib2 |
| import urlparse |
| +import httplib |
| +import zipfile |
| + |
| from xml.dom.minidom import parse as parseXml |
| from sitescripts.extensions.utils import ( |
| @@ -232,6 +235,21 @@ |
| self.basename = metadata.get('general', 'basename') |
| self.updatedFromGallery = False |
| + def readEdgeMetadata(self): |
| + """ |
| + Read Edge-specific metadata from metadata file. |
| + """ |
| + import buildtools.packagerEdge as packagerEdge |
|
Vasily Kuznetsov
2017/02/07 12:38:15
You don't really need the `import ... as ...` synt
Oleksandr
2017/02/09 00:57:14
Done.
|
| + # Now read metadata file |
| + metadata = packagerEdge.packager.readMetadata(self.tempdir, |
|
Vasily Kuznetsov
2017/02/07 12:38:15
This `packagerEdge.packager` is actually `buildtoo
Oleksandr
2017/02/09 00:57:15
Done.
|
| + self.config.type) |
| + self.version = packagerEdge.packager.getBuildVersion(self.tempdir, |
| + metadata, False, |
| + self.buildNum) |
| + self.basename = metadata.get('general', 'basename') |
| + |
| + self.compat = [] |
| + |
| def writeUpdateManifest(self): |
| """ |
| Writes update manifest for the current build |
| @@ -338,7 +356,7 @@ |
| command = [os.path.join(self.tempdir, 'build.py'), |
| '-t', self.config.type, 'build', '-b', self.buildNum] |
| - if self.config.type not in {'gecko', 'gecko-webext'}: |
| + if self.config.type not in {'gecko', 'gecko-webext', 'edge'}: |
| command.extend(['-k', self.config.keyFile]) |
| command.append(self.path) |
| subprocess.check_call(command, env=env) |
| @@ -520,6 +538,120 @@ |
| if any(status not in ('OK', 'ITEM_PENDING_REVIEW') for status in response['status']): |
| raise Exception({'status': response['status'], 'statusDetail': response['statusDetail']}) |
| + def uploadToWindowsStore(self): |
|
Vasily Kuznetsov
2017/02/07 12:38:16
This method is quite long. It seems like it basica
Sebastian Noack
2017/02/07 13:54:43
Also new methods (and variables) should use unders
Oleksandr
2017/02/09 00:57:15
Done.
|
| + |
|
Vasily Kuznetsov
2017/02/07 12:38:16
Also the ingestionConnection-related code seems to
Oleksandr
2017/02/09 00:57:15
Done.
|
| + class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler): |
| + def http_error_default(self, req, fp, code, msg, hdrs): |
| + raise urllib2.HTTPError(req.get_full_url(), code, |
| + '%s\n%s' % (msg, fp.read()), hdrs, fp) |
|
Vasily Kuznetsov
2017/02/07 12:38:16
You're reading the content of `fp` and at the same
Vasily Kuznetsov
2017/02/07 12:38:16
Our style guide now recommends using `'{} bla {}'.
Sebastian Noack
2017/02/07 13:54:42
The thing is HttpError objects are both exceptions
Oleksandr
2017/02/09 00:57:15
I have moved all the networking code to just httpl
|
| + |
| + opener = urllib2.build_opener(HTTPErrorBodyHandler) |
| + |
| + # use refresh token to obtain a valid access token |
| + # https://docs.microsoft.com/en-us/azure/active-directory/active-directory-protocols-oauth-code#refreshing-the-access-tokens |
| + |
| + response = json.load(opener.open( |
|
Sebastian Noack
2017/02/07 13:54:42
The response object isn't closed here. The canonic
|
| + 'https://login.microsoftonline.com/{0}/oauth2/token'.format( |
|
Sebastian Noack
2017/02/07 13:54:43
Thanks for using the format() method here, but the
|
| + self.config.tenantID), |
| + urlencode([ |
| + ('refresh_token', self.config.refreshToken), |
| + ('client_id', self.config.clientID), |
| + ('client_secret', self.config.clientSecret), |
| + ('grant_type', 'refresh_token'), |
| + ('resource', 'https://graph.windows.net') |
| + ]) |
| + )) |
| + |
| + auth_token = response['token_type'] + ' ' + response['access_token'] |
|
Sebastian Noack
2017/02/07 13:54:42
As agreed on in our coding practices, we use the f
Oleksandr
2017/02/09 00:57:16
Done.
|
| + |
| + # Clone the previous submission for the new one. Largely based on code |
| + # from https://msdn.microsoft.com/en-us/windows/uwp/monetize/python-code-examples-for-the-windows-store-submission-api#create-an-app-submission |
|
Vasily Kuznetsov
2017/02/07 12:38:15
This is a very long line. I wonder if we could use
Sebastian Noack
2017/02/07 13:54:42
I'd rather not obfuscate URLs in the source code (
Vasily Kuznetsov
2017/02/07 15:05:26
Yeah, good point. I also don't see an ideal soluti
Oleksandr
2017/02/09 00:57:15
Acknowledged.
|
| + headers = {'Authorization': auth_token, |
| + 'Content-type': 'application/json', |
| + 'User-Agent': 'Python'} |
|
Sebastian Noack
2017/02/07 13:54:42
Is it even necessary to set a User-Agent here?
Oleksandr
2017/02/09 00:57:15
It is not. Removed.
|
| + |
| + apiServer = 'manage.devcenter.microsoft.com' |
|
Vasily Kuznetsov
2017/02/07 12:38:15
PEP8 (and therefore our style guide) recommends lo
Sebastian Noack
2017/02/07 13:54:43
As I said above, I think we should also avoid came
Oleksandr
2017/02/09 00:57:15
Done.
|
| + ingestionConnection = httplib.HTTPSConnection(apiServer) |
|
Sebastian Noack
2017/02/07 13:54:42
"ingestion" seems like a weird term to use here, i
Oleksandr
2017/02/09 00:57:15
Done.
|
| + |
| + # Get application |
| + appPath = '/v1.0/my/applications/%s' % self.config.devbuildGalleryID |
| + |
| + # https://msdn.microsoft.com/en-us/windows/uwp/monetize/get-an-add-on |
|
Vasily Kuznetsov
2017/02/07 12:38:15
Is this URL relevant to what we're doing here? The
Oleksandr
2017/02/09 00:57:16
Fixed, and also same for URL below.
|
| + ingestionConnection.request('GET', appPath, '', headers) |
| + appResponse = ingestionConnection.getresponse() |
| + |
| + # Delete existing in-progress submission |
| + # https://msdn.microsoft.com/en-us/windows/uwp/monetize/get-an-add-on |
| + appObj = json.loads(appResponse.read().decode()) |
| + |
| + submissionsPath = appPath + '/submissions' |
| + if 'pendingApplicationSubmission' in appObj: |
| + removeId = appObj['pendingApplicationSubmission']['id'] |
| + ingestionConnection.request('DELETE', |
| + '%s/%s' % (submissionsPath, removeId), |
| + '', headers) |
| + deleteSubmissionResponse = ingestionConnection.getresponse() |
| + deleteSubmissionResponse.read() |
|
Vasily Kuznetsov
2017/02/07 12:38:15
Should we do some error checking here? Or will we
Sebastian Noack
2017/02/07 13:54:42
Also what about closing the response?
Oleksandr
2017/02/09 00:57:14
If successful, the response would have an empty bo
Oleksandr
2017/02/09 00:57:15
I don't think we have to close the response. We wi
|
| + |
| + # Create submission |
| + # https://msdn.microsoft.com/en-us/windows/uwp/monetize/create-an-app-submission |
| + ingestionConnection.request('POST', submissionsPath, '', headers) |
| + createSubmissionResponse = ingestionConnection.getresponse() |
| + |
| + submission = json.loads( |
| + createSubmissionResponse.read().decode() |
| + ) |
| + |
| + submissionId = submission['id'] |
| + fileUploadUrl = submission['fileUploadUrl'] |
| + |
| + # Update submission |
| + oldSubmission = submission['applicationPackages'][0] |
| + oldSubmission['fileStatus'] = 'PendingDelete' |
| + submission['applicationPackages'].append( |
| + {'fileStatus': 'PendingUpload'}) |
| + addedSubmission = submission['applicationPackages'][1] |
| + addedSubmission['fileName'] = os.path.basename(self.path) |
| + addedSubmission['minimumSystemRam'] = oldSubmission['minimumSystemRam'] |
| + |
| + oldDirectXVersion = oldSubmission['minimumDirectXVersion'] |
| + addedSubmission['minimumDirectXVersion'] = oldDirectXVersion |
| + |
| + newSubmissionPath = '%s/%s' % (submissionsPath, submissionId) |
| + ingestionConnection.request('PUT', newSubmissionPath, |
| + json.dumps(submission), headers) |
| + ingestionConnection.getresponse().read() |
| + |
| + # Add .appx file to a .zip file |
| + zipPath = os.path.splitext(self.path)[0] + '.zip' |
| + with zipfile.ZipFile(zipPath, 'w', zipfile.ZIP_DEFLATED) as zf: |
| + zf.write(self.path, os.path.basename(self.path)) |
| + |
| + # Upload that .zip file |
| + request = urllib2.Request(fileUploadUrl.replace('+', '%2B')) |
| + request.get_method = lambda: 'PUT' |
| + request.add_header('x-ms-blob-type', 'BlockBlob') |
| + |
| + with open(zipPath, 'rb') as file: |
| + fileSize = os.fstat(file.fileno()).st_size - file.tell() |
| + request.add_header('Content-Length', fileSize) |
| + request.add_data(file) |
| + opener.open(request) |
| + |
| + # Commit submission |
| + # https://msdn.microsoft.com/en-us/windows/uwp/monetize/commit-an-app-submission |
| + ingestionConnection.request('POST', |
| + newSubmissionPath + '/commit', |
| + '', headers) |
| + submission = json.loads( |
|
Sebastian Noack
2017/02/07 13:54:42
Couldn't you simply use json.load(ingestionConnect
|
| + ingestionConnection.getresponse().read().decode() |
| + ) |
| + |
| + if submission['status'] != 'CommitStarted': |
| + raise Exception({'status': submission['status'], |
| + 'statusDetails': submission['statusDetails']}) |
|
Sebastian Noack
2017/02/07 13:54:43
The indentation here seems off. The keys should be
Oleksandr
2017/02/09 00:57:14
Done.
|
| + ingestionConnection.close() |
|
Sebastian Noack
2017/02/07 13:54:42
It would be better to use the with-statement (or t
Oleksandr
2017/02/09 00:57:15
Done.
|
| + |
| def run(self): |
| """ |
| Run the nightly build process for one extension |
| @@ -543,6 +675,8 @@ |
| self.readSafariMetadata() |
| elif self.config.type in {'gecko', 'gecko-webext'}: |
| self.readGeckoMetadata() |
| + elif self.config.type == 'edge': |
| + self.readEdgeMetadata() |
| else: |
| raise Exception('Unknown build type {}' % self.config.type) |
| @@ -574,6 +708,9 @@ |
| self.uploadToMozillaAddons() |
| elif self.config.type == 'chrome' and self.config.clientID and self.config.clientSecret and self.config.refreshToken: |
| self.uploadToChromeWebStore() |
| + elif self.config.type == 'edge' and self.config.clientID and self.config.clientSecret and self.config.refreshToken and self.config.tenantID: |
|
Vasily Kuznetsov
2017/02/07 12:38:15
What happens if we don't have `config.clientID` or
Sebastian Noack
2017/02/07 13:54:42
At least this seems consistent with the equivalane
Vasily Kuznetsov
2017/02/07 15:05:26
Acknowledged.
|
| + self.uploadToWindowsStore() |
| + |
| finally: |
| # clean up |
| if self.tempdir: |