Refactoring builds out into separate model types (Github, Bitbucket, Local) and builder to use build->createWorkingCopy() to make build directory. Fixes #29

This commit is contained in:
Dan Cryer 2013-05-15 23:47:37 +01:00
parent 1a92d1c619
commit be37a5e821
9 changed files with 304 additions and 150 deletions

32
PHPCI/BuildFactory.php Normal file
View file

@ -0,0 +1,32 @@
<?php
namespace PHPCI;
use PHPCI\Model\Build,
PHPCI\Model\Build\LocalBuild,
PHPCI\Model\Build\GithubBuild,
PHPCI\Model\Build\BitbucketBuild;
class BuildFactory
{
public static function getBuild(Build $base)
{
switch($base->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());
}
}

View file

@ -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;
}

View file

@ -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'));
}

View file

@ -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)
{
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* Build model for table: build
*/
namespace PHPCI\Model\Build;
use PHPCI\Model\Build;
use PHPCI\Model\Build\RemoteGitBuild;
/**
* Build Model
* @uses PHPCI\Model\Build
*/
class BitbucketBuild extends RemoteGitBuild
{
public function getCommitLink()
{
return 'https://bitbucket.org/' . $this->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';
}
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* Build model for table: build
*/
namespace PHPCI\Model\Build;
use PHPCI\Model\Build\RemoteGitBuild;
/**
* Build Model
* @uses PHPCI\Model\Build
*/
class GithubBuild extends RemoteGitBuild
{
public function getCommitLink()
{
return 'https://github.com/' . $this->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';
}
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* Build model for table: build
*/
namespace PHPCI\Model;
use PHPCI\Model\Build;
use Symfony\Component\Yaml\Parser as YamlParser;
/**
* Build Model
* @uses PHPCI\Model\Build
*/
class LocalBuild extends Build
{
public function createWorkingCopy(Builder $builder, $buildPath)
{
$reference = $this->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;
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* Build model for table: build
*/
namespace PHPCI\Model\Build;
use PHPCI\Model\Build;
use PHPCI\Builder;
use Symfony\Component\Yaml\Parser as YamlParser;
/**
* Build Model
* @uses PHPCI\Model\Build
*/
abstract class RemoteGitBuild extends Build
{
abstract protected function getCloneUrl();
public function createWorkingCopy(Builder $builder, $buildPath)
{
$yamlParser = new YamlParser();
$success = true;
$key = trim($this->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;
}
}

View file

@ -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';
}
}
}