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

Side by Side Diff: sitescripts/extensions/bin/createNightlies.py

Issue 29374637: Issue 4549 - Implement the Windows Store API to upload development builds (Closed)
Patch Set: Issue 4549 - Implement the Windows Store API to upload development builds Created Feb. 6, 2017, 9:29 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | sitescripts/extensions/test/sitescripts.ini.template » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # This file is part of the Adblock Plus web scripts, 1 # This file is part of the Adblock Plus web scripts,
2 # Copyright (C) 2006-2016 Eyeo GmbH 2 # Copyright (C) 2006-2016 Eyeo GmbH
3 # 3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify 4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as 5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation. 6 # published by the Free Software Foundation.
7 # 7 #
8 # Adblock Plus is distributed in the hope that it will be useful, 8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
(...skipping 24 matching lines...) Expand all
35 import random 35 import random
36 import shutil 36 import shutil
37 import struct 37 import struct
38 import subprocess 38 import subprocess
39 import sys 39 import sys
40 import tempfile 40 import tempfile
41 import time 41 import time
42 from urllib import urlencode 42 from urllib import urlencode
43 import urllib2 43 import urllib2
44 import urlparse 44 import urlparse
45 import httplib
46 import zipfile
47
45 from xml.dom.minidom import parse as parseXml 48 from xml.dom.minidom import parse as parseXml
46 49
47 from sitescripts.extensions.utils import ( 50 from sitescripts.extensions.utils import (
48 compareVersions, Configuration, 51 compareVersions, Configuration,
49 writeAndroidUpdateManifest 52 writeAndroidUpdateManifest
50 ) 53 )
51 from sitescripts.utils import get_config, get_template 54 from sitescripts.utils import get_config, get_template
52 55
53 MAX_BUILDS = 50 56 MAX_BUILDS = 50
54 57
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after
225 metadata = packager.readMetadata(self.tempdir, self.config.type) 228 metadata = packager.readMetadata(self.tempdir, self.config.type)
226 certs = xarfile.read_certificates_and_key(self.config.keyFile)[0] 229 certs = xarfile.read_certificates_and_key(self.config.keyFile)[0]
227 230
228 self.certificateID = packager.get_developer_identifier(certs) 231 self.certificateID = packager.get_developer_identifier(certs)
229 self.version = packager.getBuildVersion(self.tempdir, metadata, False, 232 self.version = packager.getBuildVersion(self.tempdir, metadata, False,
230 self.buildNum) 233 self.buildNum)
231 self.shortVersion = metadata.get('general', 'version') 234 self.shortVersion = metadata.get('general', 'version')
232 self.basename = metadata.get('general', 'basename') 235 self.basename = metadata.get('general', 'basename')
233 self.updatedFromGallery = False 236 self.updatedFromGallery = False
234 237
238 def readEdgeMetadata(self):
239 """
240 Read Edge-specific metadata from metadata file.
241 """
242 import buildtools.packagerEdge as packagerEdge
243 # Now read metadata file
244 metadata = packagerEdge.packager.readMetadata(self.tempdir,
245 self.config.type)
246 self.version = packagerEdge.packager.getBuildVersion(self.tempdir,
247 metadata, False,
248 self.buildNum)
249 self.basename = metadata.get('general', 'basename')
250
251 self.compat = []
252
235 def writeUpdateManifest(self): 253 def writeUpdateManifest(self):
236 """ 254 """
237 Writes update manifest for the current build 255 Writes update manifest for the current build
238 """ 256 """
239 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) 257 baseDir = os.path.join(self.config.nightliesDirectory, self.basename)
240 if self.config.type == 'safari': 258 if self.config.type == 'safari':
241 manifestPath = os.path.join(baseDir, 'updates.plist') 259 manifestPath = os.path.join(baseDir, 'updates.plist')
242 templateName = 'safariUpdateManifest' 260 templateName = 'safariUpdateManifest'
243 autoescape = True 261 autoescape = True
244 elif self.config.type == 'android': 262 elif self.config.type == 'android':
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
331 os.remove(self.path) 349 os.remove(self.path)
332 raise 350 raise
333 else: 351 else:
334 env = os.environ 352 env = os.environ
335 spiderMonkeyBinary = self.config.spiderMonkeyBinary 353 spiderMonkeyBinary = self.config.spiderMonkeyBinary
336 if spiderMonkeyBinary: 354 if spiderMonkeyBinary:
337 env = dict(env, SPIDERMONKEY_BINARY=spiderMonkeyBinary) 355 env = dict(env, SPIDERMONKEY_BINARY=spiderMonkeyBinary)
338 356
339 command = [os.path.join(self.tempdir, 'build.py'), 357 command = [os.path.join(self.tempdir, 'build.py'),
340 '-t', self.config.type, 'build', '-b', self.buildNum] 358 '-t', self.config.type, 'build', '-b', self.buildNum]
341 if self.config.type not in {'gecko', 'gecko-webext'}: 359 if self.config.type not in {'gecko', 'gecko-webext', 'edge'}:
342 command.extend(['-k', self.config.keyFile]) 360 command.extend(['-k', self.config.keyFile])
343 command.append(self.path) 361 command.append(self.path)
344 subprocess.check_call(command, env=env) 362 subprocess.check_call(command, env=env)
345 363
346 if not os.path.exists(self.path): 364 if not os.path.exists(self.path):
347 raise Exception("Build failed, output file hasn't been created") 365 raise Exception("Build failed, output file hasn't been created")
348 366
349 linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffi x) 367 linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffi x)
350 if hasattr(os, 'symlink'): 368 if hasattr(os, 'symlink'):
351 if os.path.exists(linkPath): 369 if os.path.exists(linkPath):
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after
513 request.get_method = lambda: 'POST' 531 request.get_method = lambda: 'POST'
514 request.add_header('Authorization', auth_token) 532 request.add_header('Authorization', auth_token)
515 request.add_header('x-goog-api-version', '2') 533 request.add_header('x-goog-api-version', '2')
516 request.add_header('Content-Length', '0') 534 request.add_header('Content-Length', '0')
517 535
518 response = json.load(opener.open(request)) 536 response = json.load(opener.open(request))
519 537
520 if any(status not in ('OK', 'ITEM_PENDING_REVIEW') for status in respons e['status']): 538 if any(status not in ('OK', 'ITEM_PENDING_REVIEW') for status in respons e['status']):
521 raise Exception({'status': response['status'], 'statusDetail': respo nse['statusDetail']}) 539 raise Exception({'status': response['status'], 'statusDetail': respo nse['statusDetail']})
522 540
541 def uploadToWindowsStore(self):
542
543 class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler):
544 def http_error_default(self, req, fp, code, msg, hdrs):
545 raise urllib2.HTTPError(req.get_full_url(), code,
546 '%s\n%s' % (msg, fp.read()), hdrs, fp)
547
548 opener = urllib2.build_opener(HTTPErrorBodyHandler)
549
550 # use refresh token to obtain a valid access token
551 # https://docs.microsoft.com/en-us/azure/active-directory/active-directo ry-protocols-oauth-code#refreshing-the-access-tokens
552
553 response = json.load(opener.open(
554 'https://login.microsoftonline.com/{0}/oauth2/token'.format(
555 self.config.tenantID),
556 urlencode([
557 ('refresh_token', self.config.refreshToken),
558 ('client_id', self.config.clientID),
559 ('client_secret', self.config.clientSecret),
560 ('grant_type', 'refresh_token'),
561 ('resource', 'https://graph.windows.net')
562 ])
563 ))
564
565 auth_token = response['token_type'] + ' ' + response['access_token']
566
567 # Clone the previous submission for the new one. Largely based on code
568 # from https://msdn.microsoft.com/en-us/windows/uwp/monetize/python-code -examples-for-the-windows-store-submission-api#create-an-app-submission
569 headers = {'Authorization': auth_token,
570 'Content-type': 'application/json',
571 'User-Agent': 'Python'}
572
573 apiServer = 'manage.devcenter.microsoft.com'
574 ingestionConnection = httplib.HTTPSConnection(apiServer)
575
576 # Get application
577 appPath = '/v1.0/my/applications/%s' % self.config.devbuildGalleryID
578
579 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/get-an-add-on
580 ingestionConnection.request('GET', appPath, '', headers)
581 appResponse = ingestionConnection.getresponse()
582
583 # Delete existing in-progress submission
584 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/get-an-add-on
585 appObj = json.loads(appResponse.read().decode())
586
587 submissionsPath = appPath + '/submissions'
588 if 'pendingApplicationSubmission' in appObj:
589 removeId = appObj['pendingApplicationSubmission']['id']
590 ingestionConnection.request('DELETE',
591 '%s/%s' % (submissionsPath, removeId),
592 '', headers)
593 deleteSubmissionResponse = ingestionConnection.getresponse()
594 deleteSubmissionResponse.read()
595
596 # Create submission
597 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/create-an-app-su bmission
598 ingestionConnection.request('POST', submissionsPath, '', headers)
599 createSubmissionResponse = ingestionConnection.getresponse()
600
601 submission = json.loads(
602 createSubmissionResponse.read().decode()
603 )
604
605 submissionId = submission['id']
606 fileUploadUrl = submission['fileUploadUrl']
607
608 # Update submission
609 oldSubmission = submission['applicationPackages'][0]
610 oldSubmission['fileStatus'] = 'PendingDelete'
611 submission['applicationPackages'].append(
612 {'fileStatus': 'PendingUpload'})
613 addedSubmission = submission['applicationPackages'][1]
614 addedSubmission['fileName'] = os.path.basename(self.path)
615 addedSubmission['minimumSystemRam'] = oldSubmission['minimumSystemRam']
616
617 oldDirectXVersion = oldSubmission['minimumDirectXVersion']
618 addedSubmission['minimumDirectXVersion'] = oldDirectXVersion
619
620 newSubmissionPath = '%s/%s' % (submissionsPath, submissionId)
621 ingestionConnection.request('PUT', newSubmissionPath,
622 json.dumps(submission), headers)
623 ingestionConnection.getresponse().read()
624
625 # Add .appx file to a .zip file
626 zipPath = os.path.splitext(self.path)[0] + '.zip'
627 with zipfile.ZipFile(zipPath, 'w', zipfile.ZIP_DEFLATED) as zf:
628 zf.write(self.path, os.path.basename(self.path))
629
630 # Upload that .zip file
631 request = urllib2.Request(fileUploadUrl.replace('+', '%2B'))
632 request.get_method = lambda: 'PUT'
633 request.add_header('x-ms-blob-type', 'BlockBlob')
634
635 with open(zipPath, 'rb') as file:
636 fileSize = os.fstat(file.fileno()).st_size - file.tell()
637 request.add_header('Content-Length', fileSize)
638 request.add_data(file)
639 opener.open(request)
640
641 # Commit submission
642 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/commit-an-app-su bmission
643 ingestionConnection.request('POST',
644 newSubmissionPath + '/commit',
645 '', headers)
646 submission = json.loads(
647 ingestionConnection.getresponse().read().decode()
648 )
649
650 if submission['status'] != 'CommitStarted':
651 raise Exception({'status': submission['status'],
652 'statusDetails': submission['statusDetails']})
653 ingestionConnection.close()
654
523 def run(self): 655 def run(self):
524 """ 656 """
525 Run the nightly build process for one extension 657 Run the nightly build process for one extension
526 """ 658 """
527 try: 659 try:
528 if self.config.type == 'ie': 660 if self.config.type == 'ie':
529 # We cannot build IE builds, simply list the builds already in 661 # We cannot build IE builds, simply list the builds already in
530 # the directory. Basename has to be deduced from the repository name. 662 # the directory. Basename has to be deduced from the repository name.
531 self.basename = os.path.basename(self.config.repository) 663 self.basename = os.path.basename(self.config.repository)
532 else: 664 else:
533 # copy the repository into a temporary directory 665 # copy the repository into a temporary directory
534 self.copyRepository() 666 self.copyRepository()
535 self.buildNum = self.getCurrentBuild() 667 self.buildNum = self.getCurrentBuild()
536 668
537 # get meta data from the repository 669 # get meta data from the repository
538 if self.config.type == 'android': 670 if self.config.type == 'android':
539 self.readAndroidMetadata() 671 self.readAndroidMetadata()
540 elif self.config.type == 'chrome': 672 elif self.config.type == 'chrome':
541 self.readChromeMetadata() 673 self.readChromeMetadata()
542 elif self.config.type == 'safari': 674 elif self.config.type == 'safari':
543 self.readSafariMetadata() 675 self.readSafariMetadata()
544 elif self.config.type in {'gecko', 'gecko-webext'}: 676 elif self.config.type in {'gecko', 'gecko-webext'}:
545 self.readGeckoMetadata() 677 self.readGeckoMetadata()
678 elif self.config.type == 'edge':
679 self.readEdgeMetadata()
546 else: 680 else:
547 raise Exception('Unknown build type {}' % self.config.type) 681 raise Exception('Unknown build type {}' % self.config.type)
548 682
549 # create development build 683 # create development build
550 self.build() 684 self.build()
551 685
552 # write out changelog 686 # write out changelog
553 self.writeChangelog(self.getChanges()) 687 self.writeChangelog(self.getChanges())
554 688
555 # write update manifest 689 # write update manifest
(...skipping 11 matching lines...) Expand all
567 701
568 # update nightlies config 702 # update nightlies config
569 self.config.latestRevision = self.revision 703 self.config.latestRevision = self.revision
570 704
571 if (self.config.type in {'gecko', 'gecko-webext'} and 705 if (self.config.type in {'gecko', 'gecko-webext'} and
572 self.config.galleryID and 706 self.config.galleryID and
573 get_config().has_option('extensions', 'amo_key')): 707 get_config().has_option('extensions', 'amo_key')):
574 self.uploadToMozillaAddons() 708 self.uploadToMozillaAddons()
575 elif self.config.type == 'chrome' and self.config.clientID and self. config.clientSecret and self.config.refreshToken: 709 elif self.config.type == 'chrome' and self.config.clientID and self. config.clientSecret and self.config.refreshToken:
576 self.uploadToChromeWebStore() 710 self.uploadToChromeWebStore()
711 elif self.config.type == 'edge' and self.config.clientID and self.co nfig.clientSecret and self.config.refreshToken and self.config.tenantID:
712 self.uploadToWindowsStore()
713
577 finally: 714 finally:
578 # clean up 715 # clean up
579 if self.tempdir: 716 if self.tempdir:
580 shutil.rmtree(self.tempdir, ignore_errors=True) 717 shutil.rmtree(self.tempdir, ignore_errors=True)
581 718
582 719
583 def main(): 720 def main():
584 """ 721 """
585 main function for createNightlies.py 722 main function for createNightlies.py
586 """ 723 """
(...skipping 14 matching lines...) Expand all
601 except Exception as ex: 738 except Exception as ex:
602 logging.error('The build for %s failed:', repo) 739 logging.error('The build for %s failed:', repo)
603 logging.exception(ex) 740 logging.exception(ex)
604 741
605 file = open(nightlyConfigFile, 'wb') 742 file = open(nightlyConfigFile, 'wb')
606 nightlyConfig.write(file) 743 nightlyConfig.write(file)
607 744
608 745
609 if __name__ == '__main__': 746 if __name__ == '__main__':
610 main() 747 main()
OLDNEW
« no previous file with comments | « no previous file | sitescripts/extensions/test/sitescripts.ini.template » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld