From be37a5e821a00858675b3552c5e7108239cd5937 Mon Sep 17 00:00:00 2001 From: Dan Cryer Date: Wed, 15 May 2013 23:47:37 +0100 Subject: [PATCH] Refactoring builds out into separate model types (Github, Bitbucket, Local) and builder to use build->createWorkingCopy() to make build directory. Fixes #29 --- PHPCI/BuildFactory.php | 32 ++++++++ PHPCI/Builder.php | 112 ++++++--------------------- PHPCI/Command/RunCommand.php | 5 +- PHPCI/Model/Build.php | 51 ++---------- PHPCI/Model/Build/BitbucketBuild.php | 38 +++++++++ PHPCI/Model/Build/GithubBuild.php | 76 ++++++++++++++++++ PHPCI/Model/Build/LocalBuild.php | 52 +++++++++++++ PHPCI/Model/Build/RemoteGitBuild.php | 69 +++++++++++++++++ PHPCI/Model/Project.php | 19 ----- 9 files changed, 304 insertions(+), 150 deletions(-) create mode 100644 PHPCI/BuildFactory.php create mode 100644 PHPCI/Model/Build/BitbucketBuild.php create mode 100644 PHPCI/Model/Build/GithubBuild.php create mode 100644 PHPCI/Model/Build/LocalBuild.php create mode 100644 PHPCI/Model/Build/RemoteGitBuild.php diff --git a/PHPCI/BuildFactory.php b/PHPCI/BuildFactory.php new file mode 100644 index 00000000..d9dccf5f --- /dev/null +++ b/PHPCI/BuildFactory.php @@ -0,0 +1,32 @@ +getProject()->getType()) + { + case 'local': + $type = 'LocalBuild'; + break; + + case 'github': + $type = 'GithubBuild'; + break; + + case 'bitbucket': + $type = 'BitbucketBuild'; + break; + } + + $type = '\\PHPCI\\Model\\Build\\' . $type; + + return new $type($base->getDataArray()); + } +} \ No newline at end of file diff --git a/PHPCI/Builder.php b/PHPCI/Builder.php index b1aec937..651e6489 100644 --- a/PHPCI/Builder.php +++ b/PHPCI/Builder.php @@ -18,6 +18,7 @@ class Builder protected $plugins = array(); protected $build; protected $logCallback; + protected $config; public function __construct(Build $build, $logCallback = null) { @@ -30,11 +31,21 @@ class Builder } } + public function setConfigArray(array $config) + { + $this->config = $config; + } + + public function getConfig($key) + { + return isset($this->config[$key]) ? $this->config[$key] : null; + } + public function execute() { $this->build->setStatus(1); $this->build->setStarted(new \DateTime()); - $this->build = $this->store->save($this->build); + $this->store->save($this->build); $this->build->sendStatusPostback(); if($this->setupBuild()) @@ -91,7 +102,7 @@ class Builder return ($status == 0) ? true : false; } - protected function log($message, $prefix = '') + public function log($message, $prefix = '') { if(is_array($message)) @@ -123,15 +134,15 @@ class Builder $this->build->setLog($this->log); $this->build->setPlugins(json_encode($this->plugins)); - $this->build = $this->store->save($this->build); + $this->store->save($this->build); } - protected function logSuccess($message) + public function logSuccess($message) { $this->log("\033[0;32m" . $message . "\033[0m"); } - protected function logFailure($message) + public function logFailure($message) { $this->log("\033[0;31m" . $message . "\033[0m"); } @@ -139,102 +150,29 @@ class Builder protected function setupBuild() { $commitId = $this->build->getCommitId(); - $url = $this->build->getProject()->getGitUrl(); - $key = $this->build->getProject()->getGitKey(); - $type = $this->build->getProject()->getType(); - $reference = $this->build->getProject()->getReference(); - $reference = substr($reference, -1) == '/' ? substr($reference, 0, -1) : $reference; $buildId = 'project' . $this->build->getProject()->getId() . '-build' . $this->build->getId(); - $yamlParser = new YamlParser(); - $this->ciDir = realpath(dirname(__FILE__) . '/../') . '/'; $this->buildPath = $this->ciDir . 'build/' . $buildId . '/'; - switch ($type) - { - case 'local': - $this->buildPath = substr($this->buildPath, 0, -1); - - if(!is_file($reference . '/phpci.yml')) - { - $this->logFailure('Project does not contain a phpci.yml file.'); - return false; - } - - $yamlFile = file_get_contents($reference . '/phpci.yml'); - $this->config = $yamlParser->parse($yamlFile); - - if(array_key_exists('build_settings', $this->config) - && is_array($this->config['build_settings']) - && array_key_exists('prefer_symlink', $this->config['build_settings']) - && true === $this->config['build_settings']['prefer_symlink']) - { - if(is_link($this->buildPath) && is_file($this->buildPath)) - { - unlink($this->buildPath); - } - - $this->log(sprintf('Symlinking: %s to %s',$reference, $this->buildPath)); - if ( !symlink($reference, $this->buildPath) ) - { - $this->logFailure('Failed to symlink.'); - return false; - } - } - else - { - $this->executeCommand(sprintf("cp -Rf %s %s/", $reference, $this->buildPath)); - } - - $this->buildPath .= '/'; - break; - - case 'github': - case 'bitbucket': - mkdir($this->buildPath, 0777, true); - - if(!empty($key)) - { - // Do an SSH clone: - $keyFile = $this->ciDir . 'build/' . $buildId . '.key'; - file_put_contents($keyFile, $key); - chmod($keyFile, 0600); - $this->executeCommand('ssh-agent ssh-add '.$keyFile.' && git clone -b ' .$this->build->getBranch() . ' ' .$url.' '.$this->buildPath.' && ssh-agent -k'); - unlink($keyFile); - } - else - { - // Do an HTTP clone: - $this->executeCommand('git clone -b ' .$this->build->getBranch() . ' ' .$url.' '.$this->buildPath); - } - - if(!is_file($this->buildPath . 'phpci.yml')) - { - $this->logFailure('Project does not contain a phpci.yml file.'); - return false; - } - - $yamlFile = file_get_contents($this->buildPath . 'phpci.yml'); - $this->config = $yamlParser->parse($yamlFile); - break; + // Create a working copy of the project: + if(!$this->build->createWorkingCopy($this, $this->buildPath)) { + return false; } - if(!isset($this->config['build_settings']['verbose']) || !$this->config['build_settings']['verbose']) - { + // Does the project's phpci.yml request verbose mode? + if(!isset($this->config['build_settings']['verbose']) || !$this->config['build_settings']['verbose']) { $this->verbose = false; } - else - { + else { $this->verbose = true; } - if(isset($this->config['build_settings']['ignore'])) - { + // Does the project have any paths it wants plugins to ignore? + if(isset($this->config['build_settings']['ignore'])) { $this->ignore = $this->config['build_settings']['ignore']; } - $this->log('Set up build: ' . $this->buildPath); - + $this->logSuccess('Working copy created: ' . $this->buildPath); return true; } diff --git a/PHPCI/Command/RunCommand.php b/PHPCI/Command/RunCommand.php index 94334269..d7ae67b7 100644 --- a/PHPCI/Command/RunCommand.php +++ b/PHPCI/Command/RunCommand.php @@ -8,7 +8,8 @@ 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\Builder, + PHPCI\BuildFactory; class RunCommand extends Command { @@ -28,6 +29,8 @@ class RunCommand extends Command foreach($result['items'] as $build) { + $build = BuildFactory::getBuild($build); + if ($input->getOption('verbose')) { $builder = new Builder($build, array($this, 'logCallback')); } diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index 56b14010..69f02022 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -8,7 +8,8 @@ namespace PHPCI\Model; require_once(APPLICATION_PATH . 'PHPCI/Model/Base/BuildBase.php'); -use PHPCI\Model\Base\BuildBase; +use PHPCI\Model\Base\BuildBase, + PHPCI\Builder; /** * Build Model @@ -18,56 +19,20 @@ class Build extends BuildBase { public function getCommitLink() { - switch($this->getProject()->getType()) - { - case 'github': - return 'https://github.com/' . $this->getProject()->getReference() . '/commit/' . $this->getCommitId(); - } + return '#'; } public function getBranchLink() { - switch($this->getProject()->getType()) - { - case 'github': - return 'https://github.com/' . $this->getProject()->getReference() . '/tree/' . $this->getBranch(); - } + return '#'; } public function sendStatusPostback() { - $project = $this->getProject(); + return; + } - if($project->getType() == 'github' && $project->getToken()) - { - $url = 'https://api.github.com/repos/'.$project->getReference().'/statuses/'.$this->getCommitId(); - $http = new \b8\HttpClient(); - - switch($this->getStatus()) - { - case 0: - case 1: - $status = 'pending'; - break; - - case 2: - $status = 'success'; - break; - - case 3: - $status = 'failure'; - break; - - default: - $status = 'error'; - break; - } - - $params = array( 'state' => $status, - 'target_url' => \b8\Registry::getInstance()->get('install_url') . '/build/view/' . $this->getId()); - - $http->setHeaders(array('Authorization: token ' . $project->getToken())); - $http->request('POST', $url, json_encode($params)); - } + public function createWorkingCopy(Builder $builder, $buildPath) + { } } diff --git a/PHPCI/Model/Build/BitbucketBuild.php b/PHPCI/Model/Build/BitbucketBuild.php new file mode 100644 index 00000000..cdfc49b5 --- /dev/null +++ b/PHPCI/Model/Build/BitbucketBuild.php @@ -0,0 +1,38 @@ +getProject()->getReference() . '/commits/' . $this->getCommitId(); + } + + public function getBranchLink() + { + return 'https://bitbucket.org/' . $this->getProject()->getReference() . '/src/?at=' . $this->getBranch(); + } + + protected function getCloneUrl() + { + $key = trim($this->getProject()->getGitKey()); + + if(!empty($key)) { + return 'git@bitbucket.org:' . $this->getProject()->getReference() . '.git'; + } + else { + return 'https://bitbucket.org/' . $this->getProject()->getReference() . '.git'; + } + } +} diff --git a/PHPCI/Model/Build/GithubBuild.php b/PHPCI/Model/Build/GithubBuild.php new file mode 100644 index 00000000..c1cf42cb --- /dev/null +++ b/PHPCI/Model/Build/GithubBuild.php @@ -0,0 +1,76 @@ +getProject()->getReference() . '/commit/' . $this->getCommitId(); + } + + public function getBranchLink() + { + return 'https://github.com/' . $this->getProject()->getReference() . '/tree/' . $this->getBranch(); + } + + public function sendStatusPostback() + { + $project = $this->getProject(); + + // The postback will only work if we have an access token. + if(!$project->getToken()) { + return; + } + + $url = 'https://api.github.com/repos/'.$project->getReference().'/statuses/'.$this->getCommitId(); + $http = new \b8\HttpClient(); + + switch($this->getStatus()) + { + case 0: + case 1: + $status = 'pending'; + break; + + case 2: + $status = 'success'; + break; + + case 3: + $status = 'failure'; + break; + + default: + $status = 'error'; + break; + } + + $params = array( 'state' => $status, + 'target_url' => \b8\Registry::getInstance()->get('install_url') . '/build/view/' . $this->getId()); + + $http->setHeaders(array('Authorization: token ' . $project->getToken())); + $http->request('POST', $url, json_encode($params)); + } + + protected function getCloneUrl() + { + $key = trim($this->getProject()->getGitKey()); + + if(!empty($key)) { + return 'git@github.com:' . $this->getProject()->getReference() . '.git'; + } + else { + return 'https://github.com/' . $this->getProject()->getReference() . '.git'; + } + } +} diff --git a/PHPCI/Model/Build/LocalBuild.php b/PHPCI/Model/Build/LocalBuild.php new file mode 100644 index 00000000..6edac398 --- /dev/null +++ b/PHPCI/Model/Build/LocalBuild.php @@ -0,0 +1,52 @@ +getProject()->getReference(); + $reference = substr($reference, -1) == '/' ? substr($reference, 0, -1) : $reference; + $buildPath = substr($buildPath, 0, -1); + $yamlParser = new YamlParser(); + + if(!is_file($reference . '/phpci.yml')) { + $builder->logFailure('Project does not contain a phpci.yml file.'); + return false; + } + + $yamlFile = file_get_contents($reference . '/phpci.yml'); + $builder->setConfigArray($yamlParser->parse($yamlFile)); + + $buildSettings = $builder->getConfig('build_settings'); + + if(isset($buildSettings['prefer_symlink']) && $buildSettings['prefer_symlink'] === true) { + if(is_link($buildPath) && is_file($buildPath)) { + unlink($buildPath); + } + + $builder->log(sprintf('Symlinking: %s to %s',$reference, $buildPath)); + + if(!symlink($reference, $buildPath)) { + $builder->logFailure('Failed to symlink.'); + return false; + } + } + else { + $builder->executeCommand(sprintf("cp -Rf %s %s/", $reference, $buildPath)); + } + + return true; + } +} diff --git a/PHPCI/Model/Build/RemoteGitBuild.php b/PHPCI/Model/Build/RemoteGitBuild.php new file mode 100644 index 00000000..7df16b08 --- /dev/null +++ b/PHPCI/Model/Build/RemoteGitBuild.php @@ -0,0 +1,69 @@ +getProject()->getGitKey()); + + if(!empty($key)) { + $success = $this->cloneBySsh($builder, $buildPath); + } + else { + $success = $this->cloneByHttp($builder, $buildPath); + } + + if(!$success) { + $builder->logFailure('Failed to clone remote git repository.'); + return false; + } + + if(!is_file($buildPath . 'phpci.yml')) { + $builder->logFailure('Project does not contain a phpci.yml file.'); + return false; + } + + $yamlFile = file_get_contents($buildPath . 'phpci.yml'); + $builder->setConfigArray($yamlParser->parse($yamlFile)); + + return true; + } + + protected function cloneByHttp(Builder $builder, $to) + { + return $builder->executeCommand('git clone -b ' .$this->getBranch() . ' ' .$this->getCloneUrl().' '.$to); + } + + protected function cloneBySsh(Builder $builder, $to) + { + // Copy the project's keyfile to disk: + $keyFile = realpath($to) . '.key'; + file_put_contents($keyFile, $this->getProject()->getGitKey()); + chmod($keyFile, 0600); + + // Use the key file to do an SSH clone: + $success = $builder->executeCommand('ssh-agent ssh-add '.$keyFile.' && git clone -b ' .$build->getBranch() . ' ' .$this->getCloneUrl().' '.$to.' && ssh-agent -k'); + + // Remove the key file: + unlink($keyFile); + + return $success; + } +} diff --git a/PHPCI/Model/Project.php b/PHPCI/Model/Project.php index 2f35aa0c..ea552106 100644 --- a/PHPCI/Model/Project.php +++ b/PHPCI/Model/Project.php @@ -16,23 +16,4 @@ use PHPCI\Model\Base\ProjectBase; */ class Project extends ProjectBase { - public function getGitUrl() - { - $key = $this->getGitKey(); - - switch($this->getType() . '.' . (!empty($key) ? 'ssh' : 'http')) - { - case 'github.ssh': - return 'git@github.com:' . $this->getReference() . '.git'; - - case 'github.http': - return 'https://github.com/' . $this->getReference() . '.git'; - - case 'bitbucket.ssh': - return 'git@bitbucket.org:' . $this->getReference() . '.git'; - - case 'bitbucket.http': - return 'https://bitbucket.org/' . $this->getReference() . '.git'; - } - } }