diff --git a/PHPCI/Application.php b/PHPCI/Application.php index 77bd7170..3f20c3ed 100644 --- a/PHPCI/Application.php +++ b/PHPCI/Application.php @@ -10,6 +10,7 @@ namespace PHPCI; use b8; +use b8\Http\Response; use b8\Http\Response\RedirectResponse; use b8\View; @@ -19,52 +20,38 @@ use b8\View; */ class Application extends b8\Application { + public function init() + { + $request =& $this->request; + $route = '/:controller/:action'; + $opts = ['controller' => 'Home', 'action' => 'index']; + + $this->router->clearRoutes(); + $this->router->register($route, $opts, function (&$route, Response &$response) use (&$request) + { + $skipValidation = in_array($route['controller'], array('session', 'webhook', 'build-status')); + + if (!$skipValidation && !$this->validateSession()) { + if ($request->isAjax()) { + $response->setResponseCode(401); + $response->setContent(''); + } else { + $response = new RedirectResponse($response); + $response->setHeader('Location', PHPCI_URL.'session/login'); + } + + return false; + } + + return true; + }); + } /** * Handle an incoming web request. */ public function handleRequest() { - try { - $this->initRequest(); - - // Validate the user's session unless it is a login/logout action or a web hook: - $sessionAction = ($this->controllerName == 'Session' && in_array($this->action, array('login', 'logout'))); - $externalAction = in_array($this->controllerName, array('Bitbucket', 'Github', 'Gitlab', 'BuildStatus', 'Git')); - $skipValidation = ($externalAction || $sessionAction); - - if ($skipValidation || $this->validateSession()) { - parent::handleRequest(); - } - } catch (\Exception $ex) { - $content = '

There was a problem with this request

-

Please paste the details below into a - new bug report - so that we can investigate and fix it.

'; - - ob_start(); - var_dump(array( - 'message' => $ex->getMessage(), - 'file' => $ex->getFile(), - 'line' => $ex->getLine(), - 'trace' => $ex->getTraceAsString() - )); - var_dump(array( - 'PATH_INFO' => $_SERVER['PATH_INFO'], - 'REDIRECT_PATH_INFO' => $_SERVER['REDIRECT_PATH_INFO'], - 'REQUEST_URI' => $_SERVER['REQUEST_URI'], - 'PHP_SELF' => $_SERVER['PHP_SELF'], - 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], - 'DOCUMENT_ROOT' => $_SERVER['DOCUMENT_ROOT'], - 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'], - 'SERVER_SOFTWARE' => $_SERVER['SERVER_SOFTWARE'], - )); - $content .= ob_get_contents(); - ob_end_clean(); - - $this->response->setContent($content); - $this->response->disableLayout(); - } - + $this->response = parent::handleRequest(); if (View::exists('layout') && $this->response->hasLayout()) { $view = new View('layout'); @@ -91,14 +78,6 @@ class Application extends b8\Application unset($_SESSION['user_id']); } - if ($this->request->isAjax()) { - $this->response->setResponseCode(401); - $this->response->setContent(''); - } else { - $this->response = new RedirectResponse($this->response); - $this->response->setHeader('Location', PHPCI_URL.'session/login'); - } - return false; } } diff --git a/PHPCI/BuildFactory.php b/PHPCI/BuildFactory.php index 0dd53058..82ebc221 100644 --- a/PHPCI/BuildFactory.php +++ b/PHPCI/BuildFactory.php @@ -9,10 +9,8 @@ namespace PHPCI; +use b8\Store\Factory; use PHPCI\Model\Build; -use PHPCI\Model\Build\LocalBuild; -use PHPCI\Model\Build\GithubBuild; -use PHPCI\Model\Build\BitbucketBuild; /** * PHPCI Build Factory - Takes in a generic "Build" and returns a type-specific build model. @@ -20,9 +18,21 @@ use PHPCI\Model\Build\BitbucketBuild; */ class BuildFactory { + /** + * @param $buildId + * @return Build + */ + public static function getBuildById($buildId) + { + $build = Factory::getStore('Build')->getById($buildId); + + return self::getBuild($build); + } + /** * Takes a generic build and returns a type-specific build model. - * @return \PHPCI\Model\Build\LocalBuild|\PHPCI\Model\Build\GithubBuild|\PHPCI\Model\Build\BitbucketBuild + * @param Build $base The build from which to get a more specific build type. + * @return Build */ public static function getBuild(Build $base) { diff --git a/PHPCI/BuildLogger.php b/PHPCI/BuildLogger.php deleted file mode 100644 index ccb6288a..00000000 --- a/PHPCI/BuildLogger.php +++ /dev/null @@ -1,31 +0,0 @@ - */ -class Builder implements LoggerAwareInterface, BuildLogger +class Builder implements LoggerAwareInterface { /** * @var string @@ -75,12 +77,9 @@ class Builder implements LoggerAwareInterface, BuildLogger protected $lastOutput; /** - * An array of key => value pairs that will be used for - * interpolation and environment variables - * @var array - * @see setInterpolationVars() + * @var BuildInterpolator */ - protected $interpolation_vars = array(); + protected $interpolator; /** * @var \PHPCI\Store\BuildStore @@ -102,21 +101,34 @@ class Builder implements LoggerAwareInterface, BuildLogger */ protected $commandExecutor; + /** + * @var Logging\BuildLogger + */ + protected $buildLogger; + /** * Set up the builder. * @param \PHPCI\Model\Build $build * @param LoggerInterface $logger */ - public function __construct(Build $build, $logger = null) + public function __construct(Build $build, LoggerInterface $logger = null) { - if ($logger) { - $this->setLogger($logger); - } $this->build = $build; - $this->store = Store\Factory::getStore('Build'); - $this->pluginExecutor = new Plugin\Util\Executor($this->buildPluginFactory($build), $this); + $this->store = Factory::getStore('Build'); + + $this->buildLogger = new BuildLogger($logger, $build); + + $this->pluginExecutor = new Plugin\Util\Executor($this->buildPluginFactory($build), $this->buildLogger); + + $this->commandExecutor = new CommandExecutor( + $this->buildLogger, + PHPCI_DIR, + $this->quiet, + $this->verbose + ); + + $this->interpolator = new BuildInterpolator(); - $this->commandExecutor = new CommandExecutor($this, PHPCI_DIR, $this->quiet, $this->verbose); } /** @@ -195,15 +207,15 @@ class Builder implements LoggerAwareInterface, BuildLogger if ($this->success) { $this->pluginExecutor->executePlugins($this->config, 'success'); - $this->logSuccess('BUILD SUCCESSFUL!'); + $this->buildLogger->logSuccess('BUILD SUCCESSFUL!'); } else { $this->pluginExecutor->executePlugins($this->config, 'failure'); - $this->logFailure("BUILD FAILURE"); + $this->buildLogger->logFailure("BUILD FAILURE"); } // Clean up: - $this->log('Removing build.'); + $this->buildLogger->log('Removing build.'); shell_exec(sprintf('rm -Rf "%s"', $this->buildPath)); // Update the build in the database, ping any external services, etc. @@ -239,107 +251,14 @@ class Builder implements LoggerAwareInterface, BuildLogger } /** - * Add an entry to the build log. - * @param string|string[] $message - * @param string $level - * @param mixed[] $context - */ - public function log($message, $level = LogLevel::INFO, $context = array()) - { - // Skip if no logger has been loaded. - if (!$this->logger) { - return; - } - - if (!is_array($message)) { - $message = array($message); - } - - // The build is added to the context so the logger can use - // details from it if required. - $context['build'] = $this->build; - - foreach ($message as $item) { - $this->logger->log($level, $item, $context); - } - } - - /** - * Add a success-coloured message to the log. - * @param string - */ - public function logSuccess($message) - { - $this->log("\033[0;32m" . $message . "\033[0m"); - } - - /** - * Add a failure-coloured message to the log. - * @param string $message - * @param \Exception $exception The exception that caused the error. - */ - public function logFailure($message, \Exception $exception = null) - { - $context = array(); - - // The psr3 log interface stipulates that exceptions should be passed - // as the exception key in the context array. - if ($exception) { - $context['exception'] = $exception; - } - - $this->log( - "\033[0;31m" . $message . "\033[0m", - LogLevel::ERROR, - $context - ); - } - - /** - * Replace every occurance of the interpolation vars in the given string + * Replace every occurrence of the interpolation vars in the given string * Example: "This is build %PHPCI_BUILD%" => "This is build 182" * @param string $input * @return string */ public function interpolate($input) { - $keys = array_keys($this->interpolation_vars); - $values = array_values($this->interpolation_vars); - return str_replace($keys, $values, $input); - } - - /** - * Sets the variables that will be used for interpolation. This must be run - * from setupBuild() because prior to that, we don't know the buildPath - */ - protected function setInterpolationVars() - { - $this->interpolation_vars = array(); - $this->interpolation_vars['%PHPCI%'] = 1; - $this->interpolation_vars['%COMMIT%'] = $this->build->getCommitId(); - $this->interpolation_vars['%PROJECT%'] = $this->build->getProjectId(); - $this->interpolation_vars['%BUILD%'] = $this->build->getId(); - $this->interpolation_vars['%PROJECT_TITLE%'] = $this->getBuildProjectTitle( - ); - $this->interpolation_vars['%BUILD_PATH%'] = $this->buildPath; - $this->interpolation_vars['%BUILD_URI%'] = PHPCI_URL . "build/view/" . $this->build->getId( - ); - $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; - $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; - $this->interpolation_vars['%PHPCI_BUILD%'] = $this->interpolation_vars['%BUILD%']; - $this->interpolation_vars['%PHPCI_PROJECT_TITLE%'] = $this->interpolation_vars['%PROJECT_TITLE%']; - $this->interpolation_vars['%PHPCI_BUILD_PATH%'] = $this->interpolation_vars['%BUILD_PATH%']; - $this->interpolation_vars['%PHPCI_BUILD_URI%'] = $this->interpolation_vars['%BUILD_URI%']; - - putenv('PHPCI=1'); - putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']); - putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']); - putenv('PHPCI_BUILD=' . $this->interpolation_vars['%BUILD%']); - putenv( - 'PHPCI_PROJECT_TITLE=' . $this->interpolation_vars['%PROJECT_TITLE%'] - ); - putenv('PHPCI_BUILD_PATH=' . $this->interpolation_vars['%BUILD_PATH%']); - putenv('PHPCI_BUILD_URI=' . $this->interpolation_vars['%BUILD_URI%']); + return $this->interpolator->interpolate($input); } /** @@ -349,10 +268,15 @@ class Builder implements LoggerAwareInterface, BuildLogger { $buildId = 'project' . $this->build->getProject()->getId( ) . '-build' . $this->build->getId(); - $this->ciDir = dirname(__FILE__) . '/../'; + $this->ciDir = dirname(dirname(__FILE__) . '/../') . '/'; $this->buildPath = $this->ciDir . 'build/' . $buildId . '/'; + $this->build->currentBuildPath = $this->buildPath; - $this->setInterpolationVars(); + $this->interpolator->setupInterpolationVars( + $this->build, + $this->buildPath, + PHPCI_URL + ); // Create a working copy of the project: if (!$this->build->createWorkingCopy($this, $this->buildPath)) { @@ -369,7 +293,7 @@ class Builder implements LoggerAwareInterface, BuildLogger $this->ignore = $this->config['build_settings']['ignore']; } - $this->logSuccess('Working copy created: ' . $this->buildPath); + $this->buildLogger->logSuccess('Working copy created: ' . $this->buildPath); return true; } @@ -381,17 +305,31 @@ class Builder implements LoggerAwareInterface, BuildLogger */ public function setLogger(LoggerInterface $logger) { - $this->logger = $logger; + $this->buildLogger->setLogger($logger); + } + + public function log($message, $level = LogLevel::INFO, $context = array()) + { + $this->buildLogger->log($message, $level, $context); + } + + /** + * Add a success-coloured message to the log. + * @param string + */ + public function logSuccess($message) + { + $this->buildLogger->logSuccess($message); } /** - * returns the logger attached to this builder. - * - * @return LoggerInterface + * Add a failure-coloured message to the log. + * @param string $message + * @param \Exception $exception The exception that caused the error. */ - public function getLogger() + public function logFailure($message, \Exception $exception = null) { - return $this->logger; + $this->buildLogger->logFailure($message, $exception); } private function buildPluginFactory(Build $build) diff --git a/PHPCI/Command/CreateAdminCommand.php b/PHPCI/Command/CreateAdminCommand.php new file mode 100644 index 00000000..6122a108 --- /dev/null +++ b/PHPCI/Command/CreateAdminCommand.php @@ -0,0 +1,130 @@ +setName('phpci:create-admin') + ->setDescription('Create an admin user'); + } + + /** + * Creates an admin user in the existing PHPCI database + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + + require(PHPCI_DIR . 'bootstrap.php'); + + // Try to create a user account: + $adminEmail = $this->ask('Admin email address: ', true, FILTER_VALIDATE_EMAIL); + + if (empty($adminEmail)) { + return; + } + + $adminPass = $this->ask('Admin password: '); + $adminName = $this->ask('Admin name: '); + + try { + $user = new \PHPCI\Model\User(); + $user->setEmail($adminEmail); + $user->setName($adminName); + $user->setIsAdmin(1); + $user->setHash(password_hash($adminPass, PASSWORD_DEFAULT)); + + $store = \b8\Store\Factory::getStore('User'); + $store->save($user); + + print 'User account created!' . PHP_EOL; + } catch (\Exception $ex) { + print 'There was a problem creating your account. :(' . PHP_EOL; + print $ex->getMessage(); + print PHP_EOL; + } + } + + protected function ask($question, $emptyOk = false, $validationFilter = null) + { + print $question . ' '; + + $rtn = ''; + $stdin = fopen('php://stdin', 'r'); + $rtn = fgets($stdin); + fclose($stdin); + + $rtn = trim($rtn); + + if (!$emptyOk && empty($rtn)) { + $rtn = $this->ask($question, $emptyOk, $validationFilter); + } elseif ($validationFilter != null && ! empty($rtn)) { + if (! $this -> controlFormat($rtn, $validationFilter, $statusMessage)) { + print $statusMessage; + $rtn = $this->ask($question, $emptyOk, $validationFilter); + } + } + + return $rtn; + } + protected function controlFormat($valueToInspect, $filter, &$statusMessage) + { + $filters = !(is_array($filter))? array($filter) : $filter; + $statusMessage = ''; + $status = true; + $options = array(); + + foreach ($filters as $filter) { + if (! is_int($filter)) { + $regexp = $filter; + $filter = FILTER_VALIDATE_REGEXP; + $options = array( + 'options' => array( + 'regexp' => $regexp, + ) + ); + } + if (! filter_var($valueToInspect, $filter, $options)) { + $status = false; + + switch ($filter) + { + case FILTER_VALIDATE_URL: + $statusMessage = 'Incorrect url format.' . PHP_EOL; + break; + case FILTER_VALIDATE_EMAIL: + $statusMessage = 'Incorrect e-mail format.' . PHP_EOL; + break; + case FILTER_VALIDATE_REGEXP: + $statusMessage = 'Incorrect format.' . PHP_EOL; + break; + } + } + } + + return $status; + } +} diff --git a/PHPCI/Command/DaemoniseCommand.php b/PHPCI/Command/DaemoniseCommand.php index 3c490d75..5d2f003a 100644 --- a/PHPCI/Command/DaemoniseCommand.php +++ b/PHPCI/Command/DaemoniseCommand.php @@ -10,14 +10,13 @@ namespace PHPCI\Command; +use Monolog\Logger; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use b8\Store\Factory; -use PHPCI\Builder; -use PHPCI\BuildFactory; /** * Daemon that loops and call the run-command. @@ -27,6 +26,26 @@ use PHPCI\BuildFactory; */ class DaemoniseCommand extends Command { + /** + * @var Logger + */ + protected $logger; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + protected function configure() { $this @@ -43,14 +62,19 @@ class DaemoniseCommand extends Command $command = sprintf($cmd, getmypid(), PHPCI_DIR); exec($command); + $this->output = $output; $this->run = true; $this->sleep = 0; - $runner = new RunCommand; + $runner = new RunCommand($this->logger); + + $in = new ArgvInput(array()); while ($this->run) { + $buildCount = 0; + try { - $buildCount = $runner->execute($input, $output); + $buildCount = $runner->run($in, $output); } catch (\Exception $e) { var_dump($e); } diff --git a/PHPCI/Command/GenerateCommand.php b/PHPCI/Command/GenerateCommand.php index 3a97f70c..44a499de 100644 --- a/PHPCI/Command/GenerateCommand.php +++ b/PHPCI/Command/GenerateCommand.php @@ -37,7 +37,7 @@ class GenerateCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { - $gen = new CodeGenerator(Database::getConnection(), 'PHPCI', PHPCI_DIR . '/PHPCI/', false); + $gen = new CodeGenerator(Database::getConnection(), ['default' => 'PHPCI'], ['default' => PHPCI_DIR], false); $gen->generateModels(); $gen->generateStores(); } diff --git a/PHPCI/Command/RunCommand.php b/PHPCI/Command/RunCommand.php index d635b8a2..318f9247 100644 --- a/PHPCI/Command/RunCommand.php +++ b/PHPCI/Command/RunCommand.php @@ -10,9 +10,9 @@ namespace PHPCI\Command; use Monolog\Logger; -use PHPCI\Helper\BuildDBLogHandler; -use PHPCI\Helper\LoggedBuildContextTidier; -use PHPCI\Helper\OutputLogHandler; +use PHPCI\Logging\BuildDBLogHandler; +use PHPCI\Logging\LoggedBuildContextTidier; +use PHPCI\Logging\OutputLogHandler; use Psr\Log\LoggerAwareInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -22,6 +22,7 @@ use Symfony\Component\Console\Output\OutputInterface; use b8\Store\Factory; use PHPCI\Builder; use PHPCI\BuildFactory; +use PHPCI\Model\Build; /** * Run console command - Runs any pending builds. @@ -88,17 +89,24 @@ class RunCommand extends Command $build = BuildFactory::getBuild($build); - // Logging relevant to this build should be stored - // against the build itself. - $buildDbLog = new BuildDBLogHandler($build, Logger::INFO); - $this->logger->pushHandler($buildDbLog); + try { + // Logging relevant to this build should be stored + // against the build itself. + $buildDbLog = new BuildDBLogHandler($build, Logger::INFO); + $this->logger->pushHandler($buildDbLog); - $builder = new Builder($build, $this->logger); - $builder->execute(); + $builder = new Builder($build, $this->logger); + $builder->execute(); + + // After execution we no longer want to record the information + // back to this specific build so the handler should be removed. + $this->logger->popHandler($buildDbLog); + } catch (\Exception $ex) { + $build->setStatus(Build::STATUS_FAILED); + $build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage()); + $store->save($build); + } - // After execution we no longer want to record the information - // back to this specific build so the handler should be removed. - $this->logger->popHandler($buildDbLog); } $this->logger->addInfo("Finished processing builds"); diff --git a/PHPCI/Controller.php b/PHPCI/Controller.php index d95c24e0..442d9248 100644 --- a/PHPCI/Controller.php +++ b/PHPCI/Controller.php @@ -38,7 +38,7 @@ class Controller extends \b8\Controller if (View::exists($this->className)) { $this->controllerView = new View($this->className); } else { - $this->controllerView = new View\UserView('{@content}'); + $this->controllerView = new View\Template('{@content}'); } } diff --git a/PHPCI/Controller/BitbucketController.php b/PHPCI/Controller/BitbucketController.php deleted file mode 100644 index 783421a3..00000000 --- a/PHPCI/Controller/BitbucketController.php +++ /dev/null @@ -1,67 +0,0 @@ - -* @package PHPCI -* @subpackage Web -*/ -class BitbucketController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Bitbucket POST service. - */ - public function webhook($project) - { - $payload = json_decode($this->getParam('payload'), true); - $branches = array(); - $commits = array(); - - foreach ($payload['commits'] as $commit) { - if (!in_array($commit['branch'], $branches)) { - $branches[] = $commit['branch']; - $commits[$commit['branch']] = $commit['raw_node']; - } - } - - foreach ($branches as $branch) { - try { - - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($commits[$branch]); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch($branch); - $this->buildStore->save($build); - } catch (\Exception $ex) { - } - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/BuildController.php b/PHPCI/Controller/BuildController.php index c80cb7c8..ddcbc4ee 100644 --- a/PHPCI/Controller/BuildController.php +++ b/PHPCI/Controller/BuildController.php @@ -10,6 +10,7 @@ namespace PHPCI\Controller; use b8; +use PHPCI\BuildFactory; use PHPCI\Model\Build; /** @@ -35,7 +36,7 @@ class BuildController extends \PHPCI\Controller */ public function view($buildId) { - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); $this->view->plugins = $this->getUiPlugins(); $this->view->build = $build; $this->view->data = $this->getBuildData($build); @@ -63,7 +64,7 @@ class BuildController extends \PHPCI\Controller */ public function data($buildId) { - die($this->getBuildData($this->buildStore->getById($buildId))); + die($this->getBuildData(BuildFactory::getBuildById($buildId))); } /** @@ -71,7 +72,7 @@ class BuildController extends \PHPCI\Controller */ public function meta($buildId) { - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); $key = $this->getParam('key', null); $numBuilds = $this->getParam('num_builds', 1); $data = null; @@ -104,7 +105,7 @@ class BuildController extends \PHPCI\Controller */ public function rebuild($buildId) { - $copy = $this->buildStore->getById($buildId); + $copy = BuildFactory::getBuildById($buildId); $build = new Build(); $build->setProjectId($copy->getProjectId()); @@ -128,7 +129,7 @@ class BuildController extends \PHPCI\Controller throw new \Exception('You do not have permission to do that.'); } - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); if (!$build) { $this->response->setResponseCode(404); diff --git a/PHPCI/Controller/GitController.php b/PHPCI/Controller/GitController.php deleted file mode 100644 index b5a6cf87..00000000 --- a/PHPCI/Controller/GitController.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ -class GitController extends \PHPCI\Controller -{ - public function init() - { - $this->_buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by POSTing to /git/webhook/?branch=&commit= - * - * @param string $project - */ - public function webhook($project) - { - $branch = $this->getParam('branch'); - $commit = $this->getParam('commit'); - - try { - $build = new Build(); - $build->setProjectId($project); - - if ($branch !== null && trim($branch) !== '') { - $build->setBranch($branch); - } else { - $build->setBranch('master'); - } - - if ($commit !== null && trim($commit) !== '') { - $build->setCommitId($commit); - } - - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $this->_buildStore->save($build); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/GithubController.php b/PHPCI/Controller/GithubController.php deleted file mode 100644 index 4fe99756..00000000 --- a/PHPCI/Controller/GithubController.php +++ /dev/null @@ -1,77 +0,0 @@ - -* @package PHPCI -* @subpackage Web -*/ -class GithubController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Github Webhooks: - */ - public function webhook($project) - { - $payload = json_decode($this->getParam('payload'), true); - - // Github sends a payload when you close a pull request with a - // non-existant commit. We don't want this. - if ($payload['after'] === '0000000000000000000000000000000000000000') { - die('OK'); - } - - try { - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($payload['after']); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); - - if (!empty($payload['pusher']['email'])) { - $build->setCommitterEmail($payload['pusher']['email']); - } - - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $build = $this->buildStore->save($build); - $build->sendStatusPostback(); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/GitlabController.php b/PHPCI/Controller/GitlabController.php deleted file mode 100644 index a466f4dd..00000000 --- a/PHPCI/Controller/GitlabController.php +++ /dev/null @@ -1,66 +0,0 @@ -, Dan Cryer -* @package PHPCI -* @subpackage Web -*/ -class GitlabController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Gitlab Webhooks: - */ - public function webhook($project) - { - $payload = json_decode(file_get_contents("php://input"), true); - - try { - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($payload['after']); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $build = $this->buildStore->save($build); - $build->sendStatusPostback(); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/HomeController.php b/PHPCI/Controller/HomeController.php index 586f46f1..95676efc 100644 --- a/PHPCI/Controller/HomeController.php +++ b/PHPCI/Controller/HomeController.php @@ -10,6 +10,7 @@ namespace PHPCI\Controller; use b8; +use PHPCI\BuildFactory; /** * Home Controller - Displays the PHPCI Dashboard. @@ -73,6 +74,11 @@ class HomeController extends \PHPCI\Controller { $builds = $this->buildStore->getWhere(array(), 5, 0, array(), array('id' => 'DESC')); $view = new b8\View('BuildsTable'); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + $view->builds = $builds['items']; return $view->render(); diff --git a/PHPCI/Controller/ProjectController.php b/PHPCI/Controller/ProjectController.php index 7c0b75e7..19fb3149 100644 --- a/PHPCI/Controller/ProjectController.php +++ b/PHPCI/Controller/ProjectController.php @@ -9,6 +9,7 @@ namespace PHPCI\Controller; +use PHPCI\BuildFactory; use PHPCI\Model\Build; use PHPCI\Model\Project; use b8; @@ -113,6 +114,11 @@ class ProjectController extends \PHPCI\Controller $order = array('id' => 'DESC'); $builds = $this->buildStore->getWhere($criteria, 10, $start, array(), $order); $view = new b8\View('BuildsTable'); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + $view->builds = $builds['items']; return array($view->render(), $builds['count']); diff --git a/PHPCI/Controller/WebhookController.php b/PHPCI/Controller/WebhookController.php new file mode 100644 index 00000000..1b118440 --- /dev/null +++ b/PHPCI/Controller/WebhookController.php @@ -0,0 +1,206 @@ + + * @author Sami Tikka + * @author Alex Russell + * @package PHPCI + * @subpackage Web + */ +class WebhookController extends \PHPCI\Controller +{ + /** + * @var \PHPCI\Store\BuildStore + */ + protected $buildStore; + + public function init() + { + $this->buildStore = Store\Factory::getStore('Build'); + } + + /** + * Called by Bitbucket POST service. + */ + public function bitbucket($project) + { + $payload = json_decode($this->getParam('payload'), true); + + foreach ($payload['commits'] as $commit) { + try { + $email = $commit['raw_author']; + $email = substr($email, 0, strpos($email, '>')); + $email = substr($email, strpos($email, '<') + 1); + + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($commit['raw_node']); + $build->setCommitterEmail($email); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch($commit['branch']); + $build->setCommitMessage($commit['message']); + $this->buildStore->save($build); + } catch (\Exception $ex) { + } + } + + die('OK'); + } + + /** + * Called by POSTing to /git/webhook/?branch=&commit= + * + * @param string $project + */ + public function git($project) + { + $branch = $this->getParam('branch'); + $commit = $this->getParam('commit'); + + try { + $build = new Build(); + $build->setProjectId($project); + + if ($branch !== null && trim($branch) !== '') { + $build->setBranch($branch); + } else { + $build->setBranch('master'); + } + + if ($commit !== null && trim($commit) !== '') { + $build->setCommitId($commit); + } + + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + } catch (\Exception $ex) { + header('HTTP/1.1 400 Bad Request'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + try { + $this->_buildStore->save($build); + } catch (\Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + die('OK'); + } + + /** + * Called by Github Webhooks: + */ + public function github($project) + { + $payload = json_decode($this->getParam('payload'), true); + + // Github sends a payload when you close a pull request with a + // non-existant commit. We don't want this. + if ($payload['after'] === '0000000000000000000000000000000000000000') { + die('OK'); + } + + try { + + if (isset($payload['commits']) && is_array($payload['commits'])) { + // If we have a list of commits, then add them all as builds to be tested: + + foreach ($payload['commits'] as $commit) { + if (!$commit['distinct']) { + continue; + } + + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($commit['id']); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); + $build->setCommitterEmail($commit['committer']['email']); + $build->setCommitMessage($commit['message']); + $build = $this->buildStore->save($build); + $build->sendStatusPostback(); + } + } elseif (substr($payload['ref'], 0, 10) == 'refs/tags/') { + // If we don't, but we're dealing with a tag, add that instead: + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($payload['after']); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch(str_replace('refs/tags/', 'Tag: ', $payload['ref'])); + $build->setCommitterEmail($payload['pusher']['email']); + $build->setCommitMessage($payload['head_commit']['message']); + + $build = $this->buildStore->save($build); + $build->sendStatusPostback(); + } + + } catch (\Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + die('OK'); + } + + /** + * Called by Gitlab Webhooks: + */ + public function gitlab($project) + { + $payloadString = file_get_contents("php://input"); + $payload = json_decode($payloadString, true); + + try { + + if (isset($payload['commits']) && is_array($payload['commits'])) { + // If we have a list of commits, then add them all as builds to be tested: + + foreach ($payload['commits'] as $commit) { + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($commit['id']); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); + $build->setCommitterEmail($commit['author']['email']); + $build->setCommitMessage($commit['message']); + $build = $this->buildStore->save($build); + $build->sendStatusPostback(); + } + } + + } catch (\Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + die('OK'); + } +} diff --git a/PHPCI/Helper/BuildInterpolator.php b/PHPCI/Helper/BuildInterpolator.php new file mode 100644 index 00000000..4732607c --- /dev/null +++ b/PHPCI/Helper/BuildInterpolator.php @@ -0,0 +1,61 @@ + value pairs that will be used for + * interpolation and environment variables + * @var mixed[] + * @see setupInterpolationVars() + */ + protected $interpolation_vars = array(); + + /** + * Sets the variables that will be used for interpolation. + * @param Build $build + * @param string $buildPath + * @param string $phpCiUrl + */ + public function setupInterpolationVars(Build $build, $buildPath, $phpCiUrl) + { + $this->interpolation_vars = array(); + $this->interpolation_vars['%PHPCI%'] = 1; + $this->interpolation_vars['%COMMIT%'] = $build->getCommitId(); + $this->interpolation_vars['%PROJECT%'] = $build->getProjectId(); + $this->interpolation_vars['%BUILD%'] = $build->getId(); + $this->interpolation_vars['%PROJECT_TITLE%'] = $build->getProject()->getTitle(); + $this->interpolation_vars['%BUILD_PATH%'] = $buildPath; + $this->interpolation_vars['%BUILD_URI%'] = $phpCiUrl . "build/view/" . $build->getId(); + $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; + $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; + $this->interpolation_vars['%PHPCI_BUILD%'] = $this->interpolation_vars['%BUILD%']; + $this->interpolation_vars['%PHPCI_PROJECT_TITLE%'] = $this->interpolation_vars['%PROJECT_TITLE%']; + $this->interpolation_vars['%PHPCI_BUILD_PATH%'] = $this->interpolation_vars['%BUILD_PATH%']; + $this->interpolation_vars['%PHPCI_BUILD_URI%'] = $this->interpolation_vars['%BUILD_URI%']; + + putenv('PHPCI=1'); + putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']); + putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']); + putenv('PHPCI_BUILD=' . $this->interpolation_vars['%BUILD%']); + putenv('PHPCI_PROJECT_TITLE=' . $this->interpolation_vars['%PROJECT_TITLE%']); + putenv('PHPCI_BUILD_PATH=' . $this->interpolation_vars['%BUILD_PATH%']); + putenv('PHPCI_BUILD_URI=' . $this->interpolation_vars['%BUILD_URI%']); + } + + /** + * Replace every occurrence of the interpolation vars in the given string + * Example: "This is build %PHPCI_BUILD%" => "This is build 182" + * @param string $input + * @return string + */ + public function interpolate($input) + { + $keys = array_keys($this->interpolation_vars); + $values = array_values($this->interpolation_vars); + return str_replace($keys, $values, $input); + } +} \ No newline at end of file diff --git a/PHPCI/Helper/CommandExecutor.php b/PHPCI/Helper/CommandExecutor.php index 43813924..60061404 100644 --- a/PHPCI/Helper/CommandExecutor.php +++ b/PHPCI/Helper/CommandExecutor.php @@ -3,12 +3,12 @@ namespace PHPCI\Helper; -use PHPCI\BuildLogger; +use \PHPCI\Logging\BuildLogger; class CommandExecutor { /** - * @var \PHPCI\BuildLogger + * @var \PHPCI\Logging\BuildLogger */ protected $logger; diff --git a/PHPCI/Helper/BuildDBLogHandler.php b/PHPCI/Logging/BuildDBLogHandler.php similarity index 76% rename from PHPCI/Helper/BuildDBLogHandler.php rename to PHPCI/Logging/BuildDBLogHandler.php index 6ae34379..91196dc3 100644 --- a/PHPCI/Helper/BuildDBLogHandler.php +++ b/PHPCI/Logging/BuildDBLogHandler.php @@ -1,6 +1,6 @@ logValue .= (string)$record['formatted']; + $message = (string)$record['message']; + $message = str_replace($this->build->currentBuildPath, '/', $message); + + $this->logValue .= $message . PHP_EOL; $this->build->setLog($this->logValue); } } \ No newline at end of file diff --git a/PHPCI/Logging/BuildLogger.php b/PHPCI/Logging/BuildLogger.php new file mode 100644 index 00000000..f3f5ebce --- /dev/null +++ b/PHPCI/Logging/BuildLogger.php @@ -0,0 +1,96 @@ +logger = $logger; + $this->build = $build; + } + + /** + * Add an entry to the build log. + * @param string|string[] $message + * @param string $level + * @param mixed[] $context + */ + public function log($message, $level = LogLevel::INFO, $context = array()) + { + // Skip if no logger has been loaded. + if (!$this->logger) { + return; + } + + if (!is_array($message)) { + $message = array($message); + } + + // The build is added to the context so the logger can use + // details from it if required. + $context['build'] = $this->build; + + foreach ($message as $item) { + $this->logger->log($level, $item, $context); + } + } + + /** + * Add a success-coloured message to the log. + * @param string + */ + public function logSuccess($message) + { + $this->log("\033[0;32m" . $message . "\033[0m"); + } + + /** + * Add a failure-coloured message to the log. + * @param string $message + * @param \Exception $exception The exception that caused the error. + */ + public function logFailure($message, \Exception $exception = null) + { + $context = array(); + + // The psr3 log interface stipulates that exceptions should be passed + // as the exception key in the context array. + if ($exception) { + $context['exception'] = $exception; + } + + $this->log( + "\033[0;31m" . $message . "\033[0m", + LogLevel::ERROR, + $context + ); + } + + /** + * Sets a logger instance on the object + * + * @param LoggerInterface $logger + * @return null + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} \ No newline at end of file diff --git a/PHPCI/Helper/LoggedBuildContextTidier.php b/PHPCI/Logging/LoggedBuildContextTidier.php similarity index 96% rename from PHPCI/Helper/LoggedBuildContextTidier.php rename to PHPCI/Logging/LoggedBuildContextTidier.php index a67dd88d..aa852ae5 100644 --- a/PHPCI/Helper/LoggedBuildContextTidier.php +++ b/PHPCI/Logging/LoggedBuildContextTidier.php @@ -1,6 +1,6 @@ null, 'plugins' => null, 'committer_email' => null, + 'commit_message' => null, ); /** @@ -62,6 +63,7 @@ class BuildBase extends Model 'finished' => 'getFinished', 'plugins' => 'getPlugins', 'committer_email' => 'getCommitterEmail', + 'commit_message' => 'getCommitMessage', // Foreign key getters: 'Project' => 'getProject', @@ -83,6 +85,7 @@ class BuildBase extends Model 'finished' => 'setFinished', 'plugins' => 'setPlugins', 'committer_email' => 'setCommitterEmail', + 'commit_message' => 'setCommitMessage', // Foreign key setters: 'Project' => 'setProject', @@ -113,6 +116,7 @@ class BuildBase extends Model 'status' => array( 'type' => 'tinyint', 'length' => 4, + 'default' => null, ), 'log' => array( 'type' => 'longtext', @@ -150,6 +154,11 @@ class BuildBase extends Model 'nullable' => true, 'default' => null, ), + 'commit_message' => array( + 'type' => 'text', + 'nullable' => true, + 'default' => null, + ), ); /** @@ -318,6 +327,18 @@ class BuildBase extends Model return $rtn; } + /** + * Get the value of CommitMessage / commit_message. + * + * @return string + */ + public function getCommitMessage() + { + $rtn = $this->data['commit_message']; + + return $rtn; + } + /** * Set the value of Id / id. * @@ -524,6 +545,24 @@ class BuildBase extends Model $this->_setModified('committer_email'); } + /** + * Set the value of CommitMessage / commit_message. + * + * @param $value string + */ + public function setCommitMessage($value) + { + $this->_validateString('CommitMessage', $value); + + if ($this->data['commit_message'] === $value) { + return; + } + + $this->data['commit_message'] = $value; + + $this->_setModified('commit_message'); + } + /** * Get the Project model for this Build by Id. * @@ -543,7 +582,7 @@ class BuildBase extends Model $rtn = $this->cache->get($cacheKey, null); if (empty($rtn)) { - $rtn = Factory::getStore('Project')->getById($key); + $rtn = Factory::getStore('Project', 'PHPCI')->getById($key); $this->cache->set($cacheKey, $rtn); } @@ -590,6 +629,33 @@ class BuildBase extends Model */ public function getBuildBuildMetas() { - return Factory::getStore('BuildMeta')->getByBuildId($this->getId()); + return Factory::getStore('BuildMeta', 'PHPCI')->getByBuildId($this->getId()); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByProjectId($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getByProjectId($value, $limit, $useConnection); + } + + public static function getByStatus($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getByStatus($value, $limit, $useConnection); + } + + + } diff --git a/PHPCI/Model/Base/BuildMetaBase.php b/PHPCI/Model/Base/BuildMetaBase.php index 9ef0f457..83333102 100644 --- a/PHPCI/Model/Base/BuildMetaBase.php +++ b/PHPCI/Model/Base/BuildMetaBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -95,6 +95,7 @@ class BuildMetaBase extends Model 'meta_key' => array( 'type' => 'varchar', 'length' => 255, + 'default' => null, ), 'meta_value' => array( 'type' => 'text', @@ -299,7 +300,7 @@ class BuildMetaBase extends Model $rtn = $this->cache->get($cacheKey, null); if (empty($rtn)) { - $rtn = Factory::getStore('Build')->getById($key); + $rtn = Factory::getStore('Build', 'PHPCI')->getById($key); $this->cache->set($cacheKey, $rtn); } @@ -336,4 +337,26 @@ class BuildMetaBase extends Model { return $this->setBuildId($value->getId()); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('BuildMeta', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('BuildMeta', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByBuildId($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('BuildMeta', 'PHPCI')->getByBuildId($value, $limit, $useConnection); + } + + + } diff --git a/PHPCI/Model/Base/ProjectBase.php b/PHPCI/Model/Base/ProjectBase.php index c16d3ee5..df1c0a34 100644 --- a/PHPCI/Model/Base/ProjectBase.php +++ b/PHPCI/Model/Base/ProjectBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -91,10 +91,12 @@ class ProjectBase extends Model 'title' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'reference' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'git_key' => array( 'type' => 'text', @@ -215,19 +217,11 @@ class ProjectBase extends Model /** * Get the value of AccessInformation / access_information. * - * @param string|null $key Key of desired information - * * @return string */ - public function getAccessInformation($key = null) + public function getAccessInformation() { - if (is_null($key)) { - $rtn = $this->data['access_information']; - } else if (isset($this->data['access_information'][$key])) { - $rtn = $this->data['access_information'][$key]; - } else { - $rtn = null; - } + $rtn = $this->data['access_information']; return $rtn; } @@ -405,6 +399,28 @@ class ProjectBase extends Model */ public function getProjectBuilds() { - return Factory::getStore('Build')->getByProjectId($this->getId()); + return Factory::getStore('Build', 'PHPCI')->getByProjectId($this->getId()); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('Project', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('Project', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByTitle($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('Project', 'PHPCI')->getByTitle($value, $limit, $useConnection); + } + + + } diff --git a/PHPCI/Model/Base/UserBase.php b/PHPCI/Model/Base/UserBase.php index e8035660..f0b0e393 100644 --- a/PHPCI/Model/Base/UserBase.php +++ b/PHPCI/Model/Base/UserBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -82,14 +82,17 @@ class UserBase extends Model 'email' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'hash' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'is_admin' => array( 'type' => 'tinyint', 'length' => 1, + 'default' => null, ), 'name' => array( 'type' => 'varchar', @@ -270,4 +273,26 @@ class UserBase extends Model $this->_setModified('name'); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('User', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('User', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByEmail($value, $useConnection = 'read') + { + return Factory::getStore('User', 'PHPCI')->getByEmail($value, $useConnection); + } + + + } diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index bc814560..f17fcc74 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -26,6 +26,8 @@ class Build extends BuildBase const STATUS_SUCCESS = 2; const STATUS_FAILED = 3; + public $currentBuildPath = null; + /** * Get link to commit from another source (i.e. Github) */ diff --git a/PHPCI/Model/Build/RemoteGitBuild.php b/PHPCI/Model/Build/RemoteGitBuild.php index c4b03d2c..35aa8c90 100644 --- a/PHPCI/Model/Build/RemoteGitBuild.php +++ b/PHPCI/Model/Build/RemoteGitBuild.php @@ -35,8 +35,7 @@ class RemoteGitBuild extends Build public function createWorkingCopy(Builder $builder, $buildPath) { $yamlParser = new YamlParser(); - $success = true; - $key = trim($this->getProject()->getGitKey()); + $key = trim($this->getProject()->getGitKey()); if (!empty($key)) { $success = $this->cloneBySsh($builder, $buildPath); @@ -65,7 +64,9 @@ class RemoteGitBuild extends Build */ protected function cloneByHttp(Builder $builder, $cloneTo) { - return $builder->executeCommand('git clone -b %s %s "%s"', $this->getBranch(), $this->getCloneUrl(), $cloneTo); + $success = $builder->executeCommand('git clone -b %s %s "%s"', $this->getBranch(), $this->getCloneUrl(), $cloneTo); + $builder->executeCommand('cd "%s" && git checkout %s', $cloneTo, $this->getCommitId()); + return $success; } /** @@ -88,7 +89,13 @@ class RemoteGitBuild extends Build // Use the key file to do an SSH clone: $cmd = 'eval `ssh-agent -s` && ssh-add "%s" && git clone -b %s %s "%s" && ssh-agent -k'; $success = $builder->executeCommand($cmd, $keyFile, $this->getBranch(), $this->getCloneUrl(), $cloneTo); - + + $commit = $this->getCommitId(); + + if (!empty($commit) && $commit != 'Manual') { + $builder->executeCommand('cd "%s" && git checkout %s', $cloneTo, $this->getCommitId()); + } + // Remove the key file: unlink($keyFile); diff --git a/PHPCI/Plugin/Behat.php b/PHPCI/Plugin/Behat.php index 704d76cf..e4b72155 100644 --- a/PHPCI/Plugin/Behat.php +++ b/PHPCI/Plugin/Behat.php @@ -28,6 +28,12 @@ class Behat implements \PHPCI\Plugin $this->phpci = $phpci; $this->features = ''; + if (isset($options['executable'])) { + $this->executable = $options['executable']; + } else { + $this->executable = $this->phpci->findBinary('atoum'); + } + if (!empty($options['features'])) { $this->features = $options['features']; } @@ -41,7 +47,7 @@ class Behat implements \PHPCI\Plugin $curdir = getcwd(); chdir($this->phpci->buildPath); - $behat = $this->phpci->findBinary('behat'); + $behat = $this->executable; if (!$behat) { $this->phpci->logFailure('Could not find behat.'); diff --git a/PHPCI/Plugin/Codeception.php b/PHPCI/Plugin/Codeception.php index f2e580c7..bd168145 100644 --- a/PHPCI/Plugin/Codeception.php +++ b/PHPCI/Plugin/Codeception.php @@ -69,8 +69,8 @@ class Codeception implements \PHPCI\Plugin return false; } - $cmd = $codecept . ' run -c "%s"'; - $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $configPath); + $cmd = 'cd "%s" && ' . $codecept . ' run -c "%s"'; + $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $this->phpci->buildPath . $configPath); return $success; } diff --git a/PHPCI/Plugin/Email.php b/PHPCI/Plugin/Email.php index 81671e1c..05dbe4c0 100644 --- a/PHPCI/Plugin/Email.php +++ b/PHPCI/Plugin/Email.php @@ -9,6 +9,7 @@ namespace PHPCI\Plugin; +use b8\View; use PHPCI\Builder; use PHPCI\Model\Build; @@ -84,10 +85,16 @@ class Email implements \PHPCI\Plugin sprintf("Log Output:
%s
", $logText) ); } else { + $view = new View('Email/failed'); + $view->build = $this->build; + $view->project = $this->build->getProject(); + + $emailHtml = $view->render(); + $sendFailures = $this->sendSeparateEmails( $addresses, sprintf($subjectTemplate, $projectName, "Failing Build"), - sprintf("Log Output:
%s
", $logText) + $emailHtml ); } @@ -104,13 +111,18 @@ class Email implements \PHPCI\Plugin * @param string $body Email body * @return array Array of failed addresses */ - public function sendEmail($toAddresses, $subject, $body) + public function sendEmail($toAddresses, $ccList, $subject, $body) { $message = \Swift_Message::newInstance($subject) ->setFrom($this->fromAddress) ->setTo($toAddresses) ->setBody($body) ->setContentType("text/html"); + + if (is_array($ccList) && count($ccList)) { + $message->setCc($ccList); + } + $failedAddresses = array(); $this->mailer->send($message, $failedAddresses); @@ -120,8 +132,10 @@ class Email implements \PHPCI\Plugin public function sendSeparateEmails(array $toAddresses, $subject, $body) { $failures = array(); + $ccList = $this->getCcAddresses(); + foreach ($toAddresses as $address) { - $newFailures = $this->sendEmail($address, $subject, $body); + $newFailures = $this->sendEmail($address, $ccList, $subject, $body); foreach ($newFailures as $failure) { $failures[] = $failure; } @@ -150,4 +164,17 @@ class Email implements \PHPCI\Plugin } return $addresses; } + + protected function getCcAddresses() + { + $cc = array(); + + if (isset($this->options['cc'])) { + foreach ($this->options['cc'] as $address) { + $cc[] = $address; + } + } + + return $cc; + } } \ No newline at end of file diff --git a/PHPCI/Plugin/Util/Executor.php b/PHPCI/Plugin/Util/Executor.php index 5bec1820..ceb1de55 100644 --- a/PHPCI/Plugin/Util/Executor.php +++ b/PHPCI/Plugin/Util/Executor.php @@ -2,7 +2,7 @@ namespace PHPCI\Plugin\Util; -use PHPCI\BuildLogger; +use \PHPCI\Logging\BuildLogger; class Executor { @@ -17,7 +17,7 @@ class Executor */ protected $pluginFactory; - function __construct(Factory $pluginFactory, BuildLogger $logger) + function __construct(Factory $pluginFactory,BuildLogger $logger) { $this->pluginFactory = $pluginFactory; $this->logger = $logger; diff --git a/PHPCI/Store.php b/PHPCI/Store.php new file mode 100644 index 00000000..f0352680 --- /dev/null +++ b/PHPCI/Store.php @@ -0,0 +1,7 @@ +prepare($query); $stmt->bindValue(':id', $value); @@ -58,7 +58,7 @@ class BuildMetaStoreBase extends Store $count = null; - $query = 'SELECT * FROM build_meta WHERE build_id = :build_id' . $add; + $query = 'SELECT * FROM `build_meta` WHERE `build_id` = :build_id' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':build_id', $value); diff --git a/PHPCI/Store/Base/BuildStoreBase.php b/PHPCI/Store/Base/BuildStoreBase.php index f0e66085..b67d5f73 100644 --- a/PHPCI/Store/Base/BuildStoreBase.php +++ b/PHPCI/Store/Base/BuildStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\Build; /** @@ -31,7 +31,7 @@ class BuildStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM build WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `build` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -58,7 +58,7 @@ class BuildStoreBase extends Store $count = null; - $query = 'SELECT * FROM build WHERE project_id = :project_id' . $add; + $query = 'SELECT * FROM `build` WHERE `project_id` = :project_id' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':project_id', $value); @@ -90,7 +90,7 @@ class BuildStoreBase extends Store $count = null; - $query = 'SELECT * FROM build WHERE status = :status' . $add; + $query = 'SELECT * FROM `build` WHERE `status` = :status' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':status', $value); diff --git a/PHPCI/Store/Base/ProjectStoreBase.php b/PHPCI/Store/Base/ProjectStoreBase.php index 8b78c055..410a305e 100644 --- a/PHPCI/Store/Base/ProjectStoreBase.php +++ b/PHPCI/Store/Base/ProjectStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\Project; /** @@ -31,7 +31,7 @@ class ProjectStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM project WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `project` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -58,7 +58,7 @@ class ProjectStoreBase extends Store $count = null; - $query = 'SELECT * FROM project WHERE title = :title' . $add; + $query = 'SELECT * FROM `project` WHERE `title` = :title' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':title', $value); diff --git a/PHPCI/Store/Base/UserStoreBase.php b/PHPCI/Store/Base/UserStoreBase.php index c4268804..fd903d8e 100644 --- a/PHPCI/Store/Base/UserStoreBase.php +++ b/PHPCI/Store/Base/UserStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\User; /** @@ -31,7 +31,7 @@ class UserStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM user WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `user` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -50,7 +50,7 @@ class UserStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM user WHERE email = :email LIMIT 1'; + $query = 'SELECT * FROM `user` WHERE `email` = :email LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':email', $value); diff --git a/PHPCI/View/Build/view.phtml b/PHPCI/View/Build/view.phtml index aa18e591..1af884be 100644 --- a/PHPCI/View/Build/view.phtml +++ b/PHPCI/View/Build/view.phtml @@ -1,17 +1,22 @@
-

Build #getId(); ?>

-

Branch: getBranch(); ?> - getCommitId() == 'Manual' ? 'Manual Build' : 'Commit: ' . $build->getCommitId(); ?>

+

getProject()->getTitle(); ?> - Build #getId(); ?>

+ +
+ Branch: getBranch(); ?>
+ Commit ID: getCommitId() == 'Manual' ? 'HEAD' : $build->getCommitId(); ?>
+ Commit Message: getCommitMessage(); ?> +
Options