From bdc21d134724468b304d8dacd9b956847c7c1acc Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Mon, 22 Dec 2014 13:06:09 +0800 Subject: [PATCH] Added simple implementation of the pre-checker --- README.rst | 12 ++++++ extra/bash_completion | 5 ++- mdk/ci.py | 88 ++++++++++++++++++++++++++++++++++++++ mdk/commands/__init__.py | 1 + mdk/commands/precheck.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++ mdk/config-dist.json | 9 +++- requirements.txt | 1 + 7 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 mdk/ci.py create mode 100644 mdk/commands/precheck.py diff --git a/README.rst b/README.rst index c34dfda..a5545b3 100644 --- a/README.rst +++ b/README.rst @@ -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 ----- diff --git a/extra/bash_completion b/extra/bash_completion index 4ee74ca..d4e96df 100644 --- a/extra/bash_completion +++ b/extra/bash_completion @@ -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 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://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 diff --git a/mdk/commands/__init__.py b/mdk/commands/__init__.py index 3461bea..47dedfd 100644 --- a/mdk/commands/__init__.py +++ b/mdk/commands/__init__.py @@ -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 index 0000000..d724e4d --- /dev/null +++ b/mdk/commands/precheck.py @@ -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://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) diff --git a/mdk/config-dist.json b/mdk/config-dist.json index e6dde96..9e426b0 100644 --- a/mdk/config-dist.json +++ b/mdk/config-dist.json @@ -80,6 +80,12 @@ "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/", @@ -127,7 +133,8 @@ // 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. diff --git a/requirements.txt b/requirements.txt index 7b450c8..605bed3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ keyring>=3.5 +jenkinsapi>=0.2.25 MySQL-python>=1.2.3 psycopg2>=2.4.5 requests>=2.2.1 -- 2.11.0