From 3d07c07d740467ef89c29a96592ffe86828fee8a Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Mon, 10 Mar 2014 20:23:51 +0800 Subject: [PATCH] Push command can upload patches to the tracker --- lib/commands/push.py | 47 ++++++++++++++++++++++++++++++------------ lib/git.py | 12 +++++++++++ lib/jira.py | 36 ++++++++++++++++++++++++++++++++ lib/moodle.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++-- mdk.py | 4 +++- requirements.txt | 3 ++- 6 files changed, 143 insertions(+), 17 deletions(-) diff --git a/lib/commands/push.py b/lib/commands/push.py index 3bb07ce..d114456 100644 --- a/lib/commands/push.py +++ b/lib/commands/push.py @@ -59,6 +59,13 @@ class PushCommand(Command): } ), ( + ['-p', '--patch'], + { + 'action': 'store_true', + 'help': 'instead of pushing to a remote, this will upload a patch file to the tracker. Security issues use this by default. This option discards most other flags.' + } + ), + ( ['-t', '--update-tracker'], { 'const': True, @@ -138,23 +145,37 @@ class PushCommand(Command): print 'Exiting...' return - # Pushing current branch - logging.info('Pushing branch %s to remote %s...' % (branch, remote)) - result = M.git().push(remote, branch, force=args.force) - if result[0] != 0: - raise Exception(result[2]) + J = jira.Jira() + + # If the mode is not set to patch yet, and we can identify the MDL number. + if not args.patch and parsedbranch: + mdlIssue = 'MDL-%s' % (parsedbranch['issue']) + args.patch = J.isSecurityIssue(mdlIssue) + if args.patch: + logging.info('%s appears to be a security issue, attempting to upload a patch...' % (mdlIssue)) - # Update the tracker - if args.updatetracker != None: - ref = None if args.updatetracker == True else args.updatetracker - M.updateTrackerGitInfo(branch=branch, ref=ref) + if args.patch: + if not M.pushPatch(branch): + return - # Pushing stable branch - if args.includestable: - branch = M.get('stablebranch') + else: + # Pushing current branch logging.info('Pushing branch %s to remote %s...' % (branch, remote)) - result = M.git().push(remote, branch, force=args.forcestable) + result = M.git().push(remote, branch, force=args.force) if result[0] != 0: raise Exception(result[2]) + # Update the tracker + if args.updatetracker != None: + ref = None if args.updatetracker == True else args.updatetracker + M.updateTrackerGitInfo(branch=branch, ref=ref) + + # Pushing stable branch + if args.includestable: + branch = M.get('stablebranch') + logging.info('Pushing branch %s to remote %s...' % (branch, remote)) + result = M.git().push(remote, branch, force=args.forcestable) + if result[0] != 0: + raise Exception(result[2]) + logging.info('Done.') diff --git a/lib/git.py b/lib/git.py index 1bea328..f6add12 100644 --- a/lib/git.py +++ b/lib/git.py @@ -91,6 +91,18 @@ class Git(object): result = self.execute(cmd) return result[0] == 0 + def createPatch(self, ref, saveTo=None): + cmd = 'format-patch %s --stdout' % (ref) + result = self.execute(cmd) + if result[0] != 0: + return False + elif saveTo != None: + with open(saveTo, 'w') as f: + f.write(result[1]) + return True + return False + return result[1] + def currentBranch(self): cmd = 'symbolic-ref -q HEAD' result = self.execute(cmd) diff --git a/lib/jira.py b/lib/jira.py index 8028035..8d1a6be 100644 --- a/lib/jira.py +++ b/lib/jira.py @@ -33,6 +33,8 @@ import re import logging import os import httplib +import requests +import mimetypes try: import keyring except: @@ -62,6 +64,13 @@ class Jira(object): self.version = {} self._load() + def deleteAttachment(self, attachmentId): + """Deletes an attachment""" + resp = self.request('attachment/%s' % str(attachmentId), method='DELETE') + if resp['status'] != 204: + raise JiraException('Could not delete the attachment') + return True + def download(self, url, dest): """Download a URL to the destination while authenticating the user""" headers = {} @@ -130,6 +139,11 @@ class Jira(object): info[k] = v return info + def isSecurityIssue(self, key): + """Return whether or not the issue could be a security issue""" + resp = self.getIssue(key, fields='security') + return True if resp.get('fields', {}).get('security', None) != None else False + def _load(self): """Loads the information""" @@ -260,6 +274,28 @@ class Jira(object): return True + def upload(self, key, filepath): + """Uploads a new attachment to the issue""" + + uri = 'https://tracker.moodle.org' + self.uri + '/rest/api/' + str(self.apiversion) + '/issue/' + key + '/attachments' + + mimetype = mimetypes.guess_type(filepath)[0] + if not mimetype: + mimetype = 'application/octet-stream' + + files = { + 'file': (os.path.basename(filepath), open(filepath, 'rb'), mimetype) + } + + headers = { + 'X-Atlassian-Token': 'nocheck' + } + + resp = requests.post(uri, files=files, auth=requests.auth.HTTPBasicAuth(self.username, self.password), headers=headers) + if resp.status_code != 200: + raise JiraException('Could not upload file to Jira issue') + + return True class JiraException(Exception): pass diff --git a/lib/moodle.py b/lib/moodle.py index 6913a9b..6cf7fe3 100644 --- a/lib/moodle.py +++ b/lib/moodle.py @@ -26,13 +26,14 @@ import os import re import logging import shutil +from tempfile import gettempdir from tools import getMDLFromCommitMessage, mkdir, process, parseBranch from db import DB from config import Conf from git import Git, GitException from exceptions import InstallException, UpgradeNotAllowed -from jira import Jira +from jira import Jira, JiraException from scripts import Scripts C = Conf() @@ -579,6 +580,59 @@ class Moodle(object): except Exception: raise Exception('Error while purging cache!') + def pushPatch(self, branch=None): + """Push a patch on the tracker, and remove the previous one""" + + if branch == None: + branch = self.currentBranch() + if branch == 'HEAD': + raise Exception('Cannot create a patch from a detached branch') + + # Parsing the branch + parsedbranch = parseBranch(branch, C.get('wording.branchRegex')) + if not parsedbranch: + raise Exception('Could not extract issue number from %s' % branch) + issue = 'MDL-%s' % (parsedbranch['issue']) + headcommit = self.headcommit(branch) + + # Creating a patch file. + fileName = branch + '-mdk' + '.patch' + tmpPatchFile = os.path.join(gettempdir(), fileName) + if self.git().createPatch('%s...%s' % (headcommit, branch), saveTo=tmpPatchFile): + + J = Jira() + + # Checking if file with same name exists. + existingAttachmentId = None + existingAttachments = J.getIssue(issue, fields='attachment') + for existingAttachment in existingAttachments.get('fields', {}).get('attachment', {}): + if existingAttachment.get('filename') == fileName: + # Found an existing attachment with the same name, we keep track of it. + existingAttachmentId = existingAttachment.get('id') + break + + # Pushing patch to the tracker. + try: + logging.info('Uploading %s to the tracker' % (fileName)) + J.upload(issue, tmpPatchFile) + except JiraException: + logging.error('Error while uploading the patch to the tracker') + return False + else: + if existingAttachmentId != None: + # On success, deleting file that was there before. + try: + logging.info('Deleting older patch...') + J.deleteAttachment(existingAttachmentId) + except JiraException: + logging.info('Could not delete older attachment') + + else: + logging.error('Could not create a patch file') + return False + + return True + def reload(self): """Sets the value to be reloaded""" self._loaded = False @@ -713,7 +767,7 @@ class Moodle(object): if not (fieldrepositoryurl or fieldbranch or fielddiffurl): logging.error('Cannot set tracker fields for this version (%s). The field names are not set in the config file.', version) else: - logging.info('Setting tracker fields: \n\t%s: %s \n\t%s: %s \n\t%s: %s' % + logging.info('Setting tracker fields: \n %s: %s \n %s: %s \n %s: %s' % (fieldrepositoryurl, repositoryurl, fieldbranch, branch, fielddiffurl, diffurl)) J.setCustomFields(issue, {fieldrepositoryurl: repositoryurl, fieldbranch: branch, fielddiffurl: diffurl}) diff --git a/mdk.py b/mdk.py index ab8bf45..aee7c22 100755 --- a/mdk.py +++ b/mdk.py @@ -38,9 +38,11 @@ C = Conf() try: debuglevel = getattr(logging, C.get('debug').upper()) except AttributeError: - debuglevel = logging.WARNING + debuglevel = logging.INFO +# Set logging levels. logging.basicConfig(format='%(message)s', level=debuglevel) +logging.getLogger('requests').setLevel(logging.WARNING) # Reset logging level of 'requests' module. availaliases = [str(x) for x in C.get('aliases').keys()] choices = sorted(commandsList + availaliases) diff --git a/requirements.txt b/requirements.txt index 350bf3e..ff1af7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ keyring -watchdog +requests>=2.2.1 +watchdog \ No newline at end of file -- 2.11.0