Added simple implementation of the pre-checker
authorFrederic Massart <fred@moodle.com>
Mon, 22 Dec 2014 05:06:09 +0000 (13:06 +0800)
committerFrederic Massart <fred@moodle.com>
Mon, 22 Dec 2014 05:06:09 +0000 (13:06 +0800)
README.rst
extra/bash_completion
mdk/ci.py [new file with mode: 0644]
mdk/commands/__init__.py
mdk/commands/precheck.py [new file with mode: 0644]
mdk/config-dist.json
requirements.txt

index c34dfda..a5545b3 100644 (file)
@@ -344,6 +344,18 @@ Look for a plugin on moodle.org and downloads it into your instance.
     mdk plugin download repository_evernote
 
 
+precheck
+--------
+
+Pre-checks a patch on the CI server.
+
+**Example**
+
+::
+
+    mdk precheck
+
+
 purge
 -----
 
index 4ee74ca..d4e96df 100644 (file)
@@ -49,7 +49,7 @@ function _mdk() {
     if [[ "${COMP_CWORD}" == 1 ]]; then
         # List the commands and aliases.
         # Ignoring these commands on purpose: init
-        OPTS="alias backport backup behat config create css doctor fix info install js phpunit plugin purge pull push rebase remove run tracker uninstall update upgrade"
+        OPTS="alias backport backup behat config create css doctor fix info install js phpunit plugin precheck purge pull push rebase remove run tracker uninstall update upgrade"
         OPTS="$OPTS $($BIN alias list 2> /dev/null | cut -d ':' -f 1)"
     else
         # List of options according to the command.
@@ -176,6 +176,9 @@ function _mdk() {
                     OPTS="$OPTS $(_list_instances)"
                 fi
                 ;;
+            precheck)
+                OPTS="--branch --push"
+                ;;
             purge)
                 OPTS="--all --integration --stable --manual"
                 if [[ "$CUR" != -* ]]; then
diff --git a/mdk/ci.py b/mdk/ci.py
new file mode 100644 (file)
index 0000000..60b0133
--- /dev/null
+++ b/mdk/ci.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Moodle Development Kit
+
+Copyright (c) 2014 Frédéric Massart - FMCorz.net
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+http://github.com/FMCorz/mdk
+"""
+
+import logging
+from jenkinsapi import jenkins
+from jenkinsapi.custom_exceptions import JenkinsAPIException, TimeOut
+from .config import Conf
+
+C = Conf()
+
+
+class CI(object):
+    """Wrapper for Jenkins"""
+
+    _jenkins = None
+    url = None
+    token = None
+
+    def __init__(self, url=None, token=None, load=True):
+        self.url = url or C.get('ci.url')
+        self.token = token or C.get('ci.token')
+        if load:
+            self.load()
+
+    @property
+    def jenkins(self):
+        """The Jenkins object"""
+        return self._jenkins
+
+    def load(self):
+        """Loads the Jenkins object"""
+
+        # Resets the logging level.
+        logger = logging.getLogger('jenkinsapi.job')
+        logger.setLevel(logging.WARNING)
+        logger = logging.getLogger('jenkinsapi.build')
+        logger.setLevel(logging.WARNING)
+
+        # Loads the jenkins object.
+        self._jenkins = jenkins.Jenkins(self.url)
+
+    def precheckRemoteBranch(self, remote, branch, integrateto, issue=None):
+        """Runs the precheck job and returns the build object"""
+        params = {
+            'remote': remote,
+            'branch': branch,
+            'integrateto': integrateto
+        }
+        if issue:
+            params['issue'] = issue
+
+        job = self.jenkins.get_job('Precheck remote branch')
+
+        try:
+            invoke = job.invoke(build_params=params, securitytoken=self.token, invoke_pre_check_delay=0)
+            invoke.block_until_not_queued(60, 2)
+        except JenkinsAPIException:
+            raise CIException('Failed to invoke the build, check your permissions.')
+        except TimeOut:
+            raise CIException('The build has been in queue for more than 60s. Aborting, please refer to: %s' % job.baseurl)
+
+        build = invoke.get_build()
+        return build
+
+
+class CIException(Exception):
+    pass
index 3461bea..47dedfd 100644 (file)
@@ -44,6 +44,7 @@ commandsList = [
     'js',
     'phpunit',
     'plugin',
+    'precheck',
     'pull',
     'purge',
     'push',
diff --git a/mdk/commands/precheck.py b/mdk/commands/precheck.py
new file mode 100644 (file)
index 0000000..d724e4d
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Moodle Development Kit
+
+Copyright (c) 2014 Frédéric Massart - FMCorz.net
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+http://github.com/FMCorz/mdk
+"""
+
+import sys
+import logging
+from .. import tools, jira
+from ..ci import CI, CIException
+from ..command import Command
+
+
+class PrecheckCommand(Command):
+
+    _arguments = [
+        (
+            ['-b', '--branch'],
+            {
+                'metavar': 'branch',
+                'help': 'the branch to pre-check. Defaults to the current branch.'
+            }
+        ),
+        (
+            ['-p', '--push'],
+            {
+                'action': 'store_true',
+                'help': 'if set, the branch will be pushed to your default remote.'
+            }
+        ),
+        (
+            ['name'],
+            {
+                'default': None,
+                'help': 'name of the instance',
+                'metavar': 'name',
+                'nargs': '?'
+            }
+        )
+    ]
+    _description = 'Pre-checks a branch on the CI server'
+
+    FAILED = -1
+
+    def run(self, args):
+        M = self.Wp.resolve(args.name)
+        if not M:
+            raise Exception('This is not a Moodle instance')
+
+        against = M.get('stablebranch')
+        branch = args.branch or M.currentBranch()
+        if branch == 'HEAD':
+            raise Exception('Cannot pre-check the HEAD branch')
+        elif branch == against:
+            raise Exception('Cannot pre-check the stable branch')
+
+        parsedbranch = tools.parseBranch(branch)
+        if not parsedbranch:
+            raise Exception('Could not parse the branch')
+
+        issue = parsedbranch['issue']
+
+        if args.push:
+            J = jira.Jira()
+            if J.isSecurityIssue('MDL-%s' % (issue)):
+                raise Exception('Security issues cannot be pre-checked')
+
+            remote = self.C.get('myRemote')
+            logging.info('Pushing branch \'%s\' to remote \'%s\'', branch, remote)
+            result = M.git().push(remote, branch)
+            if result[0] != 0:
+                raise Exception('Could not push the branch:\n  %s' % result[2])
+
+        ci = CI()
+        try:
+            # TODO Remove that ugly hack to get the read-only remote.
+            logging.info('Invoking the build on the CI server...')
+            build = ci.precheckRemoteBranch(self.C.get('repositoryUrl'), branch, against, 'MDL-%s' % issue)
+        except CIException as e:
+            raise e
+
+        logging.info('Waiting for the build to complete, please wait...')
+        build.block_until_complete(3)
+
+        if build.is_good():
+            logging.info('Precheck passed, good work!')
+            sys.exit(0)
+        else:
+            logging.warning('Precheck failed, refer to:\n  %s', build.baseurl)
+            sys.exit(self.FAILED)
index e6dde96..9e426b0 100644 (file)
         "pgsql": "PostgreSQL"
     },
 
+    // CI Server related settings
+    "ci": {
+        "url": "http://integration.moodle.org",
+        "token": null
+    },
+
     // The information for integrating MDK with Jira
     "tracker": {
         "url": "https://tracker.moodle.org/",
     // This is used to populate the fields on the tracker issue.
     "diffUrlTemplate": "https://github.com/YourGitHub/moodle/compare/%headcommit%...%branch%",
 
-    // The public acccess URL of your repository. It is used to populate the fields on the tracker issue.
+    // The public acccess URL of your repository. It is used to populate the fields on the tracker issue,
+    // and as the common read-only URL of the remote 'myRemote'.
     "repositoryUrl": "git://github.com/YourGitHub/moodle.git",
 
     // Plugins related settings.
index 7b450c8..605bed3 100644 (file)
@@ -1,4 +1,5 @@
 keyring>=3.5
+jenkinsapi>=0.2.25
 MySQL-python>=1.2.3
 psycopg2>=2.4.5
 requests>=2.2.1