Added ability to merge in-database project config over in-repository

config instead of only overwrite. This commit solve issues: #14, #70,
#106, #121.
This commit is contained in:
Dmitry Khomutov 2018-04-15 15:48:50 +07:00
parent f2a1ab39a2
commit 069026bc2d
No known key found for this signature in database
GPG Key ID: EC19426474B37AAC
20 changed files with 277 additions and 130 deletions

View File

@ -127,13 +127,14 @@ class Builder implements LoggerAwareInterface
*
* @throws \Exception
*/
public function setConfigArray($config)
public function setConfig(array $config)
{
if (is_null($config) || !is_array($config)) {
throw new \Exception('This project does not contain a .php-censor.yml (.phpci.yml|phpci.yml) file, or it is empty.');
}
$this->logDebug('Config: ' . json_encode($config));
$this->config = $config;
}
@ -144,15 +145,16 @@ class Builder implements LoggerAwareInterface
*
* @return mixed
*/
public function getConfig($key)
public function getConfig($key = null)
{
$rtn = null;
if (isset($this->config[$key])) {
$rtn = $this->config[$key];
$value = null;
if (null === $key) {
$value = $this->config;
} elseif (isset($this->config[$key])) {
$value = $this->config[$key];
}
return $rtn;
return $value;
}
/**
@ -353,6 +355,8 @@ class Builder implements LoggerAwareInterface
$this->commandExecutor->setBuildPath($this->buildPath);
$this->build->handleConfigBeforeClone($this);
// Create a working copy of the project:
if (!$this->build->createWorkingCopy($this, $this->buildPath)) {
throw new \Exception('Could not create a working copy.');
@ -395,10 +399,20 @@ class Builder implements LoggerAwareInterface
$this->buildLogger->log($message, $level, $context);
}
/**
* Add a warning-coloured message to the log.
*
* @param string $message
*/
public function logWarning($message)
{
$this->buildLogger->logWarning($message);
}
/**
* Add a success-coloured message to the log.
*
* @param string
* @param string $message
*/
public function logSuccess($message)
{
@ -417,9 +431,9 @@ class Builder implements LoggerAwareInterface
}
/**
* Add a debug message to the log.
* Add a debug-coloured message to the log.
*
* @param string
* @param string $message
*/
public function logDebug($message)
{

View File

@ -311,18 +311,18 @@ class ProjectController extends WebController
$sshKey = new SshKey();
$key = $sshKey->generate();
$values['key'] = $key['private_key'];
$values['pubkey'] = $key['public_key'];
$values['ssh_private_key'] = $key['ssh_private_key'];
$values['ssh_public_key'] = $key['ssh_public_key'];
}
$form = $this->projectForm($values);
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
if ($method !== 'POST' || ($method === 'POST' && !$form->validate())) {
$view = new View('Project/edit');
$view->type = 'add';
$view->project = null;
$view->form = $form;
$view->key = $values['pubkey'];
$view->key = $values['ssh_public_key'];
return $view->render();
} else {
@ -331,14 +331,15 @@ class ProjectController extends WebController
$type = $this->getParam('type', null);
$options = [
'ssh_private_key' => $this->getParam('key', null),
'ssh_public_key' => $this->getParam('pubkey', null),
'build_config' => $this->getParam('build_config', null),
'allow_public_status' => (boolean)$this->getParam('allow_public_status', 0),
'branch' => $this->getParam('branch', null),
'default_branch_only' => (boolean)$this->getParam('default_branch_only', 0),
'group' => (integer)$this->getParam('group_id', null),
'environments' => $this->getParam('environments', null),
'ssh_private_key' => $this->getParam('ssh_private_key', null),
'ssh_public_key' => $this->getParam('ssh_public_key', null),
'overwrite_build_config' => (boolean)$this->getParam('overwrite_build_config', true),
'build_config' => $this->getParam('build_config', null),
'allow_public_status' => (boolean)$this->getParam('allow_public_status', false),
'branch' => $this->getParam('branch', null),
'default_branch_only' => (boolean)$this->getParam('default_branch_only', false),
'group' => (integer)$this->getParam('group_id', null),
'environments' => $this->getParam('environments', null),
];
/** @var PHPCensor\Model\User $user */
@ -370,8 +371,6 @@ class ProjectController extends WebController
$this->layout->subtitle = Lang::get('edit_project');
$values = $project->getDataArray();
$values['key'] = $values['ssh_private_key'];
$values['pubkey'] = $values['ssh_public_key'];
$values['environments'] = $project->getEnvironments();
if (in_array($values['type'], [
@ -391,18 +390,18 @@ class ProjectController extends WebController
}
}
if ($method == 'POST') {
if ($method === 'POST') {
$values = $this->getParams();
}
$form = $this->projectForm($values, 'edit/' . $projectId);
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
if ($method !== 'POST' || ($method === 'POST' && !$form->validate())) {
$view = new View('Project/edit');
$view->type = 'edit';
$view->project = $project;
$view->form = $form;
$view->key = $values['pubkey'];
$view->key = $values['ssh_public_key'];
return $view->render();
}
@ -412,15 +411,16 @@ class ProjectController extends WebController
$type = $this->getParam('type', null);
$options = [
'ssh_private_key' => $this->getParam('key', null),
'ssh_public_key' => $this->getParam('pubkey', null),
'build_config' => $this->getParam('build_config', null),
'allow_public_status' => (boolean)$this->getParam('allow_public_status', false),
'archived' => (boolean)$this->getParam('archived', false),
'branch' => $this->getParam('branch', null),
'default_branch_only' => (boolean)$this->getParam('default_branch_only', false),
'group' => (integer)$this->getParam('group_id', null),
'environments' => $this->getParam('environments', null),
'ssh_private_key' => $this->getParam('ssh_private_key', null),
'ssh_public_key' => $this->getParam('ssh_public_key', null),
'overwrite_build_config' => (boolean)$this->getParam('overwrite_build_config', false),
'build_config' => $this->getParam('build_config', null),
'allow_public_status' => (boolean)$this->getParam('allow_public_status', false),
'archived' => (boolean)$this->getParam('archived', false),
'branch' => $this->getParam('branch', null),
'default_branch_only' => (boolean)$this->getParam('default_branch_only', false),
'group' => (integer)$this->getParam('group_id', null),
'environments' => $this->getParam('environments', null),
];
$project = $this->projectService->updateProject($project, $title, $type, $reference, $options);
@ -442,7 +442,7 @@ class ProjectController extends WebController
$form->setAction(APP_URL . 'project/' . $type);
$form->addField(new Form\Element\Csrf('project_form'));
$form->addField(new Form\Element\Hidden('pubkey'));
$form->addField(new Form\Element\Hidden('ssh_public_key'));
$options = [
'choose' => Lang::get('select_repository_type'),
@ -457,8 +457,10 @@ class ProjectController extends WebController
Project::TYPE_SVN => 'Svn (Subversion)',
];
$sourcesPattern = sprintf('^(%s)', implode('|', Project::$allowedTypes));
$field = Form\Element\Select::create('type', Lang::get('where_hosted'), true);
$field->setPattern('^(github|bitbucket|bitbucket-hg|gitlab|gogs|git|local|hg|svn)');
$field->setPattern($sourcesPattern);
$field->setOptions($options);
$field->setClass('form-control')->setContainerClass('form-group');
$form->addField($field);
@ -486,11 +488,21 @@ class ProjectController extends WebController
$field->setValue(0);
$form->addField($field);
$field = Form\Element\TextArea::create('key', Lang::get('project_private_key'), false);
$field = Form\Element\TextArea::create('ssh_private_key', Lang::get('project_private_key'), false);
$field->setClass('form-control')->setContainerClass('form-group');
$field->setRows(6);
$form->addField($field);
$field = Form\Element\Checkbox::create(
'overwrite_build_config',
Lang::get('overwrite_build_config'),
false
);
$field->setContainerClass('form-group');
$field->setCheckedValue(1);
$field->setValue(1);
$form->addField($field);
$field = Form\Element\TextArea::create('build_config', Lang::get('build_config'), false);
$field->setClass('form-control')->setContainerClass('form-group');
$field->setRows(6);
@ -515,7 +527,11 @@ class ProjectController extends WebController
$field->setOptions($groups);
$form->addField($field);
$field = Form\Element\Checkbox::create('allow_public_status', Lang::get('allow_public_status'), false);
$field = Form\Element\Checkbox::create(
'allow_public_status',
Lang::get('allow_public_status'),
false
);
$field->setContainerClass('form-group');
$field->setCheckedValue(1);
$field->setValue(0);

View File

@ -23,7 +23,10 @@ class SshKey
mkdir($tempPath);
}
$return = ['private_key' => '', 'public_key' => ''];
$return = [
'ssh_private_key' => '',
'ssh_public_key' => ''
];
$sshStrength = Config::getInstance()->get('php-censor.ssh.strength', 2048);
$sshComment = Config::getInstance()->get('php-censor.ssh.comment', 'admin@php-censor');
@ -42,11 +45,11 @@ class SshKey
$prv = file_get_contents($keyFile);
if (!empty($pub)) {
$return['public_key'] = $pub;
$return['ssh_public_key'] = $pub;
}
if (!empty($prv)) {
$return['private_key'] = $prv;
$return['ssh_private_key'] = $prv;
}
}

View File

@ -119,6 +119,7 @@ PHP Censor',
(if you cannot add a .php-censor.yml (.phpci.yml|phpci.yml) file in the project repository)',
'default_branch' => 'Default branch name',
'default_branch_only' => 'Build default branch only',
'overwrite_build_config' => 'Overwrite in-repository file config by in-database config? If checkbox not checked then in-database config will be merged with file config.',
'allow_public_status' => 'Enable public status page and image for this project?',
'archived' => 'Archived',
'archived_menu' => 'Archived',

View File

@ -115,6 +115,7 @@ PHP Censor',
(если вы не добавили файл .php-censor.yml (.phpci.yml|phpci.yml) в репозиторий вашего проекта)',
'default_branch' => 'Ветка по умолчанию',
'default_branch_only' => 'Собирать только ветку по умолчанию',
'overwrite_build_config' => 'Заменить конфигурацию из файла в проекте конфигурацией из базы данных? Если чекбокс не отмечен, то конфигурации из файла и базы данных будут объединены.',
'allow_public_status' => 'Разрешить публичный статус и изображение (статуса) для проекта',
'archived' => 'Архивный',
'archived_menu' => 'Архив',

View File

@ -23,9 +23,8 @@ class BuildLogger implements LoggerAwareInterface
protected $build;
/**
* Set up the BuildLogger class.
* @param LoggerInterface $logger
* @param Build $build
* @param Build $build
*/
public function __construct(LoggerInterface $logger, Build $build)
{
@ -35,9 +34,10 @@ class BuildLogger implements LoggerAwareInterface
/**
* Add an entry to the build log.
*
* @param string|string[] $message
* @param string $level
* @param mixed[] $context
* @param string $level
* @param mixed[] $context
*/
public function log($message, $level = LogLevel::INFO, $context = [])
{
@ -59,9 +59,20 @@ class BuildLogger implements LoggerAwareInterface
}
}
/**
* Add a warning-coloured message to the log.
*
* @param string $message
*/
public function logWarning($message)
{
$this->log("\033[0;31m" . $message . "\033[0m", LogLevel::WARNING);
}
/**
* Add a success-coloured message to the log.
* @param string
*
* @param string $message
*/
public function logSuccess($message)
{
@ -70,7 +81,8 @@ class BuildLogger implements LoggerAwareInterface
/**
* Add a failure-coloured message to the log.
* @param string $message
*
* @param string $message
* @param \Exception $exception The exception that caused the error.
*/
public function logFailure($message, \Exception $exception = null)
@ -88,8 +100,9 @@ class BuildLogger implements LoggerAwareInterface
}
/**
* Add a debug message to the log.
* @param string
* Add a debug-coloured message to the log.
*
* @param string $message
*/
public function logDebug($message)
{
@ -105,7 +118,6 @@ class BuildLogger implements LoggerAwareInterface
* Sets a logger instance on the object
*
* @param LoggerInterface $logger
* @return null
*/
public function setLogger(LoggerInterface $logger)
{

View File

@ -0,0 +1,32 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddedOverwriteConfigFieldToProject extends AbstractMigration
{
public function up()
{
if ($this->hasTable('project')) {
$table = $this->table('project');
if (!$table->hasColumn('overwrite_build_config')) {
$table
->addColumn('overwrite_build_config', 'integer', ['default' => 1])
->save();
}
}
}
public function down()
{
if ($this->hasTable('project')) {
$table = $this->table('project');
if ($table->hasColumn('overwrite_build_config')) {
$table
->removeColumn('overwrite_build_config')
->save();
}
}
}
}

View File

@ -28,7 +28,7 @@ class Build extends Model
'commit_id' => null,
'status' => null,
'log' => null,
'branch' => null,
'branch' => 'master',
'tag' => null,
'create_date' => null,
'start_date' => null,

View File

@ -29,8 +29,8 @@ class BuildError extends Model
'severity' => null,
'message' => null,
'create_date' => null,
'hash' => null,
'is_new' => null,
'hash' => '',
'is_new' => 0,
];
/**

View File

@ -26,27 +26,28 @@ class Project extends Model
* @var array
*/
protected $data = [
'id' => null,
'title' => null,
'reference' => null,
'branch' => null,
'default_branch_only' => null,
'ssh_private_key' => null,
'ssh_public_key' => null,
'type' => null,
'access_information' => null,
'build_config' => null,
'allow_public_status' => null,
'archived' => null,
'group_id' => null,
'create_date' => null,
'user_id' => 0,
'id' => null,
'title' => null,
'reference' => null,
'branch' => null,
'default_branch_only' => 0,
'ssh_private_key' => null,
'ssh_public_key' => null,
'type' => null,
'access_information' => null,
'build_config' => null,
'overwrite_build_config' => 1,
'allow_public_status' => 0,
'archived' => 0,
'group_id' => 1,
'create_date' => null,
'user_id' => 0,
];
/**
* @var array
*/
protected $allowedTypes = [
public static $allowedTypes = [
self::TYPE_LOCAL,
self::TYPE_GIT,
self::TYPE_GITHUB,
@ -281,9 +282,9 @@ class Project extends Model
$this->validateNotNull('type', $value);
$this->validateString('type', $value);
if (!in_array($value, $this->allowedTypes, true)) {
if (!in_array($value, static::$allowedTypes, true)) {
throw new InvalidArgumentException(
'Column "type" must be one of: ' . join(', ', $this->allowedTypes) . '.'
'Column "type" must be one of: ' . join(', ', static::$allowedTypes) . '.'
);
}
@ -359,6 +360,33 @@ class Project extends Model
return $this->setModified('build_config');
}
/**
* @return boolean
*/
public function getOverwriteBuildConfig()
{
return (boolean)$this->data['overwrite_build_config'];
}
/**
* @param boolean $value
*
* @return boolean
*/
public function setOverwriteBuildConfig($value)
{
$this->validateNotNull('overwrite_build_config', $value);
$this->validateBoolean('overwrite_build_config', $value);
if ($this->data['overwrite_build_config'] === (integer)$value) {
return false;
}
$this->data['overwrite_build_config'] = (integer)$value;
return $this->setModified('overwrite_build_config');
}
/**
* @return boolean
*/

View File

@ -18,11 +18,11 @@ class User extends Model
'id' => null,
'email' => null,
'hash' => null,
'is_admin' => null,
'is_admin' => 0,
'name' => null,
'language' => null,
'per_page' => null,
'provider_key' => null,
'provider_key' => 'internal',
'provider_data' => null,
'remember_key' => null,
];

View File

@ -6,6 +6,7 @@ use PHPCensor\Builder;
use PHPCensor\Store\Factory;
use PHPCensor\Store\ProjectStore;
use PHPCensor\Store\BuildErrorStore;
use Psr\Log\LogLevel;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Yaml\Parser as YamlParser;
use PHPCensor\Model\Base\Build as BaseBuild;
@ -200,33 +201,70 @@ class Build extends BaseBuild
/**
* @param Builder $builder
* @param string $buildPath
*
* @return bool
*
* @throws \Exception
*/
protected function handleConfig(Builder $builder, $buildPath)
public function handleConfigBeforeClone(Builder $builder)
{
$buildConfig = $this->getProject()->getBuildConfig();
if (empty($buildConfig)) {
if (file_exists($buildPath . '/.php-censor.yml')) {
$buildConfig = file_get_contents($buildPath . '/.php-censor.yml');
} elseif (file_exists($buildPath . '/.phpci.yml')) {
$buildConfig = file_get_contents($buildPath . '/.phpci.yml');
} elseif (file_exists($buildPath . '/phpci.yml')) {
$buildConfig = file_get_contents($buildPath . '/phpci.yml');
} else {
$buildConfig = $this->getZeroConfigPlugins($builder);
if ($buildConfig) {
$yamlParser = new YamlParser();
$buildConfig = $yamlParser->parse($buildConfig);
if ($buildConfig && is_array($buildConfig)) {
$builder->setConfig($buildConfig);
}
}
// for YAML configs from files/DB
if (is_string($buildConfig)) {
$yamlParser = new YamlParser();
$buildConfig = $yamlParser->parse($buildConfig);
return true;
}
/**
* @param Builder $builder
* @param string $buildPath
*
* @return bool
*
* @throws \Exception
*/
protected function handleConfig(Builder $builder, $buildPath)
{
$yamlParser = new YamlParser();
$overwriteBuildConfig = $this->getProject()->getOverwriteBuildConfig();
$buildConfig = $builder->getConfig();
$repositoryConfig = $this->getZeroConfigPlugins($builder);
if (file_exists($buildPath . '/.php-censor.yml')) {
$repositoryConfig = $yamlParser->parse(
file_get_contents($buildPath . '/.php-censor.yml')
);
} elseif (file_exists($buildPath . '/.phpci.yml')) {
$repositoryConfig = $yamlParser->parse(
file_get_contents($buildPath . '/.phpci.yml')
);
} elseif (file_exists($buildPath . '/phpci.yml')) {
$repositoryConfig = $yamlParser->parse(
file_get_contents($buildPath . '/phpci.yml')
);
}
$builder->setConfigArray($buildConfig);
if (isset($repositoryConfig['build_settings']['clone_depth'])) {
$builder->logWarning(
'Option "build_settings.clone_depth" supported only in additional DB project config.' .
' Please move this option to DB config from your in-repository config file (".php-censor.yml").'
);
}
if (!$buildConfig) {
$buildConfig = $repositoryConfig;
} elseif ($buildConfig && !$overwriteBuildConfig) {
$buildConfig = array_replace_recursive($repositoryConfig, $buildConfig);
}
$builder->setConfig($buildConfig);
return true;
}

View File

@ -59,10 +59,10 @@ class GitBuild extends Build
foreach ($branches as $branch) {
$success = $builder->executeCommand($cmd, $buildPath, $branch);
if (!$success) {
$builder->log('Fail merge branch origin/'.$branch, LogLevel::ERROR);
$builder->log('Fail merge branch origin/' . $branch, LogLevel::ERROR);
return false;
}
$builder->log('Merged branch origin/'.$branch, LogLevel::INFO);
$builder->log('Merged branch origin/' . $branch, LogLevel::INFO);
}
}
return true;
@ -75,10 +75,9 @@ class GitBuild extends Build
{
$cmd = 'cd .. && git clone --recursive ';
$depth = $builder->getConfig('clone_depth');
if (!is_null($depth)) {
$cmd .= ' --depth ' . intval($depth) . ' ';
$buildSettings = $builder->getConfig('build_settings');
if ($buildSettings && isset($buildSettings['clone_depth'])) {
$cmd .= ' --depth ' . intval($buildSettings['clone_depth']) . ' ';
}
$cmd .= ' -b "%s" "%s" "%s"';
@ -102,10 +101,9 @@ class GitBuild extends Build
// Do the git clone:
$cmd = 'cd .. && git clone --recursive ';
$depth = $builder->getConfig('clone_depth');
if (!is_null($depth)) {
$cmd .= ' --depth ' . intval($depth) . ' ';
$buildSettings = $builder->getConfig('build_settings');
if ($buildSettings && isset($buildSettings['clone_depth'])) {
$cmd .= ' --depth ' . intval($buildSettings['clone_depth']) . ' ';
}
$cmd .= ' -b "%s" "%s" "%s"';

View File

@ -23,8 +23,11 @@ class LocalBuild extends Build
// If there's a /config file in the reference directory, it is probably a bare repository
// which we'll extract into our build path directly.
if (is_file($reference.'/config') && $this->handleBareRepository($builder, $reference, $buildPath) === true) {
return $this->handleConfig($builder, $buildPath) !== false;
if (
is_file($reference . '/config') &&
true === $this->handleBareRepository($builder, $reference, $buildPath)
) {
return $this->handleConfig($builder, $buildPath);
}
$configHandled = $this->handleConfig($builder, $reference);

View File

@ -50,14 +50,13 @@ class SvnBuild extends Build
$svn = $builder->getConfig('svn');
if ($svn) {
foreach ($svn as $key => $value) {
$cmd .= " --$key $value ";
$cmd .= " --${key} ${value} ";
}
}
$depth = $builder->getConfig('clone_depth');
if (!is_null($depth)) {
$cmd .= ' --depth ' . intval($depth) . ' ';
$buildSettings = $builder->getConfig('build_settings');
if ($buildSettings && isset($buildSettings['clone_depth'])) {
$cmd .= ' --depth ' . intval($buildSettings['clone_depth']) . ' ';
}
$this->svnCommand = $cmd;
@ -68,8 +67,6 @@ class SvnBuild extends Build
*/
public function createWorkingCopy(Builder $builder, $buildPath)
{
$this->handleConfig($builder, $buildPath);
$this->extendSvnCommandFromConfig($builder);
$key = trim($this->getProject()->getSshPrivateKey());

View File

@ -65,6 +65,7 @@ class ProjectService
$project->setReference($reference);
$project->setAllowPublicStatus(false);
$project->setDefaultBranchOnly(false);
$project->setOverwriteBuildConfig(true);
// Handle extra project options:
if (array_key_exists('ssh_private_key', $options)) {
@ -75,6 +76,10 @@ class ProjectService
$project->setSshPublicKey($options['ssh_public_key']);
}
if (array_key_exists('overwrite_build_config', $options)) {
$project->setOverwriteBuildConfig($options['overwrite_build_config']);
}
if (array_key_exists('build_config', $options)) {
$project->setBuildConfig($options['build_config']);
}

View File

@ -13,8 +13,6 @@
lineWrapping: true,
lineNumbers: true
});
setupProjectForm();
});
</script>

View File

@ -20,7 +20,7 @@ class BuildTest extends TestCase
'commit_id' => null,
'status' => null,
'log' => null,
'branch' => null,
'branch' => 'master',
'tag' => null,
'create_date' => null,
'start_date' => null,

View File

@ -15,21 +15,22 @@ class ProjectTest extends TestCase
self::assertInstanceOf('PHPCensor\Model\Base\Project', $project);
self::assertEquals([
'id' => null,
'title' => null,
'reference' => null,
'branch' => null,
'default_branch_only' => null,
'ssh_private_key' => null,
'ssh_public_key' => null,
'type' => null,
'access_information' => null,
'build_config' => null,
'allow_public_status' => null,
'archived' => null,
'group_id' => null,
'create_date' => null,
'user_id' => 0,
'id' => null,
'title' => null,
'reference' => null,
'branch' => null,
'default_branch_only' => 0,
'ssh_private_key' => null,
'ssh_public_key' => null,
'type' => null,
'access_information' => null,
'build_config' => null,
'overwrite_build_config' => 1,
'allow_public_status' => 0,
'archived' => 0,
'group_id' => 1,
'create_date' => null,
'user_id' => 0,
], $project->getDataArray());
}

View File

@ -18,11 +18,11 @@ class UserTest extends TestCase
'id' => null,
'email' => null,
'hash' => null,
'is_admin' => null,
'is_admin' => 0,
'name' => null,
'language' => null,
'per_page' => null,
'provider_key' => null,
'provider_key' => 'internal',
'provider_data' => null,
'remember_key' => null,
], $user->getDataArray());