zentaopms/module/job/model.php
2023-05-16 10:47:08 +08:00

669 lines
23 KiB
PHP

<?php
/**
* The model file of job module of ZenTaoCMS.
*
* @copyright Copyright 2009-2015 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Yidong Wang <yidong@cnezsoft.com>
* @package job
* @version $Id$
* @link http://www.zentao.net
*/
class jobModel extends model
{
/**
* Get by id.
*
* @param int $id
* @access public
* @return object
*/
public function getByID($id)
{
$job = $this->dao->select('*')->from(TABLE_JOB)->where('id')->eq($id)->fetch();
if(empty($job)) return new stdClass();
if(strtolower($job->engine) == 'gitlab')
{
$pipeline = json_decode($job->pipeline);
if(!isset($pipeline->reference)) return $job;
$job->project = $pipeline->project;
$job->reference = $pipeline->reference;
}
return $job;
}
/**
* Get job list.
*
* @param int $repoID
* @param string $orderBy
* @param object $pager
* @param string $engine
* @param string $pipeline
* @access public
* @return array
*/
public function getList($repoID = 0, $orderBy = 'id_desc', $pager = null, $engine = '', $pipeline = '')
{
return $this->dao->select('t1.*, DATE_FORMAT(t1.lastExec, "%m-%d %H:%i") AS lastExec, t2.name as repoName, t3.name as jenkinsName')->from(TABLE_JOB)->alias('t1')
->leftJoin(TABLE_REPO)->alias('t2')->on('t1.repo=t2.id')
->leftJoin(TABLE_PIPELINE)->alias('t3')->on('t1.server=t3.id')
->where('t1.deleted')->eq('0')
->beginIF($repoID)->andWhere('t1.repo')->eq($repoID)->fi()
->beginIF($engine)->andWhere('t1.engine')->eq($engine)->fi()
->beginIF($pipeline)->andWhere('t1.pipeline')->eq($pipeline)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id');
}
/**
* Get job list by RepoID.
*
* @param int $repoID
* @access public
* @return array
*/
public function getListByRepoID($repoID)
{
return $this->dao->select('id, name, lastStatus')->from(TABLE_JOB)
->where('deleted')->eq('0')
->andWhere('repo')->eq($repoID)
->orderBy('id_desc')
->fetchAll('id');
}
/**
* Get job pairs by RepoID.
*
* @param int $repoID
* @param string $engine gitlab|jenkins
* @access public
* @return array
*/
public function getPairs($repoID, $engine = '')
{
return $this->dao->select('id, name')->from(TABLE_JOB)
->where('deleted')->eq('0')
->andWhere('repo')->eq($repoID)
->beginIF($engine)->andWhere('engine')->eq($engine)->fi()
->orderBy('id_desc')
->fetchPairs();
}
/**
* Get list by triggerType field.
*
* @param string $triggerType
* @param array $repoIdList
* @access public
* @return array
*/
public function getListByTriggerType($triggerType, $repoIdList = array())
{
return $this->dao->select('*')->from(TABLE_JOB)
->where('deleted')->eq('0')
->andWhere('triggerType')->eq($triggerType)
->beginIF($repoIdList)->andWhere('repo')->in($repoIdList)->fi()
->fetchAll('id');
}
/**
* Get trigger config.
*
* @param object $job
* @access public
* @return string
*/
public function getTriggerConfig($job)
{
$triggerType = zget($this->lang->job->triggerTypeList, $job->triggerType);
if($job->triggerType == 'tag')
{
if(empty($job->svnDir)) return $triggerType;
$triggerType = $this->lang->job->dirChange;
return "{$triggerType}({$job->svnDir})";
}
if($job->triggerType == 'commit') return "{$triggerType}({$job->comment})";
if($job->triggerType == 'schedule')
{
$atDay = '';
foreach(explode(',', $job->atDay) as $day) $atDay .= zget($this->lang->datepicker->dayNames, trim($day), '') . ',';
$atDay = trim($atDay, ',');
return "{$triggerType}({$atDay}, {$job->atTime})";
}
}
/**
* Get trigger group.
*
* @param string $triggerType
* @param array $repoIdList
* @access public
* @return array
*/
public function getTriggerGroup($triggerType, $repoIdList)
{
$jobs = $this->getListByTriggerType($triggerType, $repoIdList);
$group = array();
foreach($jobs as $job) $group[$job->repo][$job->id] = $job;
return $group;
}
/**
* Create a job.
*
* @access public
* @return int|bool
*/
public function create()
{
$job = fixer::input('post')
->setDefault('atDay,projectKey', '')
->setDefault('sonarqubeServer', 0)
->add('createdBy', $this->app->user->account)
->add('createdDate', helper::now())
->remove('repoType,reference')
->get();
if($job->engine == 'jenkins')
{
$job->server = (int)zget($job, 'jkServer', 0);
$job->pipeline = zget($job, 'jkTask', '');
}
if(strtolower($job->engine) == 'gitlab')
{
$repo = $this->loadModel('repo')->getRepoByID($job->repo);
$project = zget($repo, 'project');
if(!empty($repo))
{
$pipeline = $this->loadModel('gitlab')->apiGetPipeline($repo->serviceHost, $repo->serviceProject, $this->post->reference);
if(!is_array($pipeline) or empty($pipeline))
{
dao::$errors['repo'] = $this->lang->job->engineTips->error;
return false;
}
}
$job->server = (int)zget($repo, 'serviceHost', 0);
$job->pipeline = json_encode(array('project' => $project, 'reference' => $this->post->reference));
}
unset($job->jkServer);
unset($job->jkTask);
unset($job->gitlabRepo);
/* SonarQube tool is only used if the engine is JenKins. */
if($job->engine != 'jenkins' and $job->frame == 'sonarqube')
{
dao::$errors[]['frame'] = $this->lang->job->mustUseJenkins;
return false;
}
if($job->repo > 0 and $job->frame == 'sonarqube')
{
$sonarqubeJob = $this->getSonarqubeByRepo(array($job->repo));
if(!empty($sonarqubeJob))
{
$message = sprintf($this->lang->job->repoExists, $sonarqubeJob[$job->repo]->id . '-' . $sonarqubeJob[$job->repo]->name);
dao::$errors[]['repo'] = $message;
return false;
}
}
if(!empty($job->projectKey) and $job->frame == 'sonarqube')
{
$projectList = $this->getJobBySonarqubeProject($job->sonarqubeServer, array($job->projectKey));
if(!empty($projectList))
{
$message = sprintf($this->lang->job->projectExists, $projectList[$job->projectKey]->id);
dao::$errors[]['projectKey'] = $message;
return false;
}
}
if($job->triggerType == 'schedule') $job->atDay = empty($_POST['atDay']) ? '' : join(',', $this->post->atDay);
$job->svnDir = '';
if($job->triggerType == 'tag' and $this->post->repoType == 'Subversion')
{
$job->svnDir = array_pop($_POST['svnDir']);
if($job->svnDir == '/' and $_POST['svnDir']) $job->svnDir = array_pop($_POST['svnDir']);
}
$customParam = array();
foreach($job->paramName as $key => $paramName)
{
$paramValue = zget($job->paramValue, $key, '');
if(empty($paramName) and !empty($paramValue))
{
dao::$errors[] = $this->lang->job->inputName;
return false;
}
if(!empty($paramName) and !validater::checkREG($paramName, '/^[A-Za-z_0-9]+$/'))
{
dao::$errors[] = $this->lang->job->invalidName;
return false;
}
if(!empty($paramName)) $customParam[$paramName] = $paramValue;
}
unset($job->paramName);
unset($job->paramValue);
unset($job->custom);
$job->customParam = json_encode($customParam);
$this->dao->insert(TABLE_JOB)->data($job)
->batchCheck($this->config->job->create->requiredFields, 'notempty')
->batchCheckIF($job->triggerType === 'schedule' and $job->atDay !== '0', "atDay", 'notempty')
->batchCheckIF($job->triggerType === 'schedule', "atTime", 'notempty')
->batchCheckIF($job->triggerType === 'commit', "comment", 'notempty')
->batchCheckIF(($this->post->repoType == 'Subversion' and $job->triggerType == 'tag'), "svnDir", 'notempty')
->batchCheckIF($job->frame === 'sonarqube', "sonarqubeServer,projectKey", 'notempty')
->autoCheck()
->exec();
if(dao::isError()) return false;
$id = $this->dao->lastInsertId();
if(strtolower($job->engine) == 'jenkins') $this->initJob($id, $job, $this->post->repoType);
return $id;
}
/**
* Update a job.
*
* @param int $id
* @access public
* @return bool
*/
public function update($id)
{
$job = fixer::input('post')
->setDefault('atDay', '')
->setIF($this->post->triggerType != 'commit', 'comment', '')
->setIF($this->post->triggerType != 'schedule', 'atDay', '')
->setIF($this->post->triggerType != 'schedule', 'atTime', '')
->setIF($this->post->triggerType != 'tag', 'lastTag', '')
->add('editedBy', $this->app->user->account)
->add('editedDate', helper::now())
->remove('repoType,reference')
->get();
if($job->engine == 'jenkins')
{
$job->server = (int)zget($job, 'jkServer', 0);
$job->pipeline = zget($job, 'jkTask', '');
}
if(strtolower($job->engine) == 'gitlab')
{
$repo = $this->loadModel('repo')->getRepoByID($job->gitlabRepo);
$project = zget($repo, 'project');
if(!empty($repo))
{
$pipeline = $this->loadModel('gitlab')->apiGetPipeline($repo->serviceHost, $repo->serviceProject, $this->post->reference);
if(!is_array($pipeline) or empty($pipeline))
{
dao::$errors['gitlabRepo'] = $this->lang->job->engineTips->error;
return false;
}
}
$job->repo = $job->gitlabRepo;
$job->server = (int)zget($repo, 'serviceHost', 0);
$job->pipeline = json_encode(array('project' => $project, 'reference' => $this->post->reference));
}
unset($job->jkServer);
unset($job->jkTask);
unset($job->gitlabRepo);
/* SonarQube tool is only used if the engine is JenKins. */
if($job->engine != 'jenkins' and $job->frame == 'sonarqube')
{
dao::$errors[] = $this->lang->job->mustUseJenkins;
return false;
}
if($job->repo > 0 and $job->frame == 'sonarqube')
{
$sonarqubeJob = $this->getSonarqubeByRepo(array($job->repo), $id);
if(!empty($sonarqubeJob))
{
$message = sprintf($this->lang->job->repoExists, $sonarqubeJob[$job->repo]->id . '-' . $sonarqubeJob[$job->repo]->name);
dao::$errors[]['repo'] = $message;
return false;
}
}
if(!empty($job->projectKey) and $job->frame == 'sonarqube')
{
$projectList = $this->getJobBySonarqubeProject($job->sonarqubeServer, array($job->projectKey));
if(!empty($projectList) && $projectList[$job->projectKey] != $id)
{
$message = sprintf($this->lang->job->projectExists, $projectList[$job->projectKey]);
dao::$errors[]['projectKey'] = $message;
return false;
}
}
if($job->triggerType == 'schedule') $job->atDay = empty($_POST['atDay']) ? '' : join(',', $this->post->atDay);
$job->svnDir = '';
if($job->triggerType == 'tag' and $this->post->repoType == 'Subversion')
{
$job->svnDir = array_pop($_POST['svnDir']);
if($job->svnDir == '/' and $_POST['svnDir']) $job->svnDir = array_pop($_POST['svnDir']);
}
$customParam = array();
foreach($job->paramName as $key => $paramName)
{
$paramValue = zget($job->paramValue, $key, '');
if(empty($paramName) and !empty($paramValue))
{
dao::$errors[] = $this->lang->job->inputName;
return false;
}
if(!empty($paramName) and !validater::checkREG($paramName, '/^[A-Za-z_0-9]+$/'))
{
dao::$errors[] = $this->lang->job->invalidName;
return false;
}
if(!empty($paramName)) $customParam[$paramName] = $paramValue;
}
unset($job->paramName);
unset($job->paramValue);
unset($job->custom);
$job->customParam = json_encode($customParam);
$this->dao->update(TABLE_JOB)->data($job)
->batchCheck($this->config->job->edit->requiredFields, 'notempty')
->batchCheckIF($job->triggerType === 'schedule' and $job->atDay !== '0', "atDay", 'notempty')
->batchCheckIF($job->triggerType === 'schedule', "atTime", 'notempty')
->batchCheckIF($job->triggerType === 'commit', "comment", 'notempty')
->batchCheckIF(($this->post->repoType == 'Subversion' and $job->triggerType == 'tag'), "svnDir", 'notempty')
->batchCheckIF($job->frame === 'sonarqube', "sonarqubeServer,projectKey", 'notempty')
->autoCheck()
->where('id')->eq($id)
->exec();
if(dao::isError()) return false;
$this->initJob($id, $job, $this->post->repoType);
return true;
}
/**
* Init when create or update job.
*
* @param int $id
* @param object $job
* @param string $repoType
* @access public
* @return bool
*/
public function initJob($id, $job, $repoType)
{
if(empty($id)) return false;
if($job->triggerType == 'schedule' and strpos($job->atDay, date('w')) !== false)
{
$compiles = $this->dao->select('*')->from(TABLE_COMPILE)->where('job')->eq($id)->andWhere('LEFT(createdDate, 10)')->eq(date('Y-m-d'))->fetchAll();
foreach($compiles as $compile)
{
if(!empty($compile->status)) continue;
$this->dao->delete()->from(TABLE_COMPILE)->where('id')->eq($compile->id)->exec();
}
$this->loadModel('compile')->createByJob($id, $job->atTime, 'atTime');
}
if($job->triggerType == 'tag')
{
$repo = $this->loadModel('repo')->getRepoByID($job->repo);
$lastTag = '';
if($repoType == 'Subversion')
{
$dirs = $this->loadModel('svn')->getRepoTags($repo, $job->svnDir);
end($dirs);
$lastTag = current($dirs);
}
else
{
$tags = $this->loadModel('git')->getRepoTags($repo);
end($tags);
$lastTag = current($tags);
}
$this->dao->update(TABLE_JOB)->set('lastTag')->eq($lastTag)->where('id')->eq($id)->exec();
}
return true;
}
/**
* Exec job.
*
* @param int $id
* @param array $extraParam
* @access public
* @return string|bool
*/
public function exec($id, $extraParam = array())
{
$job = $this->dao->select('t1.id,t1.name,t1.product,t1.repo,t1.server,t1.pipeline,t1.triggerType,t1.atTime,t1.customParam,t1.engine,t2.name as jenkinsName,t2.url,t2.account,t2.token,t2.password')
->from(TABLE_JOB)->alias('t1')
->leftJoin(TABLE_PIPELINE)->alias('t2')->on('t1.server=t2.id')
->where('t1.id')->eq($id)
->fetch();
if(!$job) return false;
$repo = $this->loadModel('repo')->getRepoById($job->repo);
$now = helper::now();
/* Save compile data. */
$build = new stdclass();
$build->job = $job->id;
$build->name = $job->name;
$build->createdBy = $this->app->user->account;
$build->createdDate = $now;
$build->updateDate = $now;
if($job->triggerType == 'schedule') $build->atTime = $job->atTime;
if($job->triggerType == 'tag')
{
$job->lastTag = $this->getLastTagByRepo($repo, $job);
if($job->lastTag)
{
$build->tag = $job->lastTag;
$this->dao->update(TABLE_JOB)->set('lastTag')->eq($job->lastTag)->where('id')->eq($job->id)->exec();
}
}
$this->dao->insert(TABLE_COMPILE)->data($build)->exec();
$compileID = $this->dao->lastInsertId();
if($job->engine == 'jenkins') $compile = $this->execJenkinsPipeline($job, $repo, $compileID, $extraParam);
if($job->engine == 'gitlab') $compile = $this->execGitlabPipeline($job);
$this->dao->update(TABLE_COMPILE)->data($compile)->where('id')->eq($compileID)->exec();
$this->dao->update(TABLE_JOB)
->set('lastExec')->eq($now)
->set('lastStatus')->eq($compile->status)
->where('id')->eq($job->id)
->exec();
return $compile;
}
/**
* Exec jenkins pipeline.
*
* @param object $job
* @param object $repo
* @param int $compileID
* @param array $extraParam
* @access public
* @return object
*/
public function execJenkinsPipeline($job, $repo, $compileID, $extraParam = array())
{
$pipeline = new stdclass();
$pipeline->PARAM_TAG = '';
$pipeline->ZENTAO_DATA = "compile={$compileID}";
if($job->triggerType == 'tag') $pipeline->PARAM_TAG = $job->lastTag;
/* Add custom parameters to the data. */
foreach(json_decode($job->customParam) as $paramName => $paramValue)
{
$paramValue = str_replace('$zentao_version', $this->config->version, $paramValue);
$paramValue = str_replace('$zentao_account', $this->app->user->account, $paramValue);
$paramValue = str_replace('$zentao_product', $job->product, $paramValue);
$paramValue = str_replace('$zentao_repopath', $repo->path, $paramValue);
$pipeline->$paramName = $paramValue;
}
foreach($extraParam as $paramName => $paramValue)
{
if(!isset($pipeline->$paramName)) $pipeline->$paramName = $paramValue;
}
$url = $this->loadModel('compile')->getBuildUrl($job);
$compile = new stdclass();
$compile->id = $compileID;
$compile->queue = $this->loadModel('ci')->sendRequest($url->url, $pipeline, $url->userPWD);
$compile->status = $compile->queue ? 'created' : 'create_fail';
return $compile;
}
/**
* Exec gitlab pipeline.
*
* @param object $job
* @access public
* @return void
*/
public function execGitlabPipeline($job)
{
$pipeline = json_decode($job->pipeline);
$pipelineParams = new stdclass;
$pipelineParams->ref = $pipeline->reference;
$customParams = json_decode($job->customParam);
$variables = array();
foreach($customParams as $paramName => $paramValue)
{
$variable = array();
$variable['key'] = $paramName;
$variable['value'] = $paramValue;
$variable['variable_type'] = "env_var";
$variables[] = $variable;
}
if(!empty($variables)) $pipelineParams->variables = $variables;
$compile = new stdclass;
$pipeline = $this->loadModel('gitlab')->apiCreatePipeline($job->server, $pipeline->project, $pipelineParams);
if(empty($pipeline->id)) $compile->status = 'create_fail';
if(!empty($pipeline->id))
{
$compile->queue = $pipeline->id;
$compile->status = zget($pipeline, 'status', 'create_fail');
}
return $compile;
}
/**
* Get last tag of one repo.
*
* @param object $repo
* @param object $job
* @access public
* @return void
*/
public function getLastTagByRepo($repo, $job)
{
if($repo->SCM == 'Subversion')
{
$dirs = $this->loadModel('svn')->getRepoTags($repo, $job->svnDir);
if($dirs)
{
end($dirs);
$lastTag = current($dirs);
return rtrim($repo->path , '/') . '/' . trim($job->svnDir, '/') . '/' . $lastTag;
}
}
else
{
$tags = $this->loadModel('git')->getRepoTags($repo);
if($tags)
{
end($tags);
return current($tags);
}
}
return '';
}
/**
* Get sonarqube by RepoID.
*
* @param array $repoIDList
* @param int $jobID
* @param bool $showDeleted
* @access public
* @return array
*/
public function getSonarqubeByRepo($repoIDList, $jobID = 0, $showDeleted = false)
{
return $this->dao->select('id,name,repo,deleted')->from(TABLE_JOB)
->where('frame')->eq('sonarqube')
->andWhere('repo')->in($repoIDList)
->beginIF(!$showDeleted)->andWhere('deleted')->eq('0')->fi()
->beginIF($jobID > 0)->andWhere('id')->ne($jobID)->fi()
->fetchAll('repo');
}
/**
* Get job pairs by sonarqube projectkeys.
*
* @param int $sonarqubeID
* @param array $projectKeys
* @param bool $emptyShowAll
* @param bool $showDeleted
* @access public
* @return array
*/
public function getJobBySonarqubeProject($sonarqubeID, $projectKeys = array(), $emptyShowAll = false, $showDeleted = false)
{
return $this->dao->select('projectKey,id')->from(TABLE_JOB)
->where('frame')->eq('sonarqube')
->andWhere('sonarqubeServer')->eq($sonarqubeID)
->beginIF(!$showDeleted)->andWhere('deleted')->eq('0')->fi()
->beginIF(!empty($projectKeys) or !$emptyShowAll)->andWhere('projectKey')->in($projectKeys)->fi()
->fetchPairs();
}
}