| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- <?php
- /**
- * The model file of ci module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
- * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Yanyi Cao <caoyanyi@easycorp.ltd>
- * @package ci
- * @link https://www.zentao.net
- */
- class ciModel extends model
- {
- /**
- * Set menu.
- *
- * @param int $repoID
- * @access public
- * @return void
- */
- public function setMenu($repoID = 0)
- {
- if($repoID) $this->session->set('repoID', $repoID);
- $homeMenuModule = array('gitlab', 'gogs', 'gitea', 'jenkins', 'sonarqube');
- if(!in_array("{$this->app->moduleName}", $homeMenuModule)) common::setMenuVars('devops', (int)$this->session->repoID);
- if($this->session->repoID)
- {
- $repo = $this->loadModel('repo')->getByID($this->session->repoID);
- if(!empty($repo) && !in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) unset($this->lang->devops->menu->mr);
- if(!$repo || !in_array($repo->SCM, $this->config->repo->notSyncSCM)) unset($this->lang->devops->menu->tag);
- if(!$repo || !in_array($repo->SCM, $this->config->repo->notSyncSCM)) unset($this->lang->devops->menu->branch);
- }
- }
- /**
- * 向jenkins发送请求以检查构建状态。
- * Send a request to jenkins to check build status.
- *
- * @param int $compileID
- * @access public
- * @return bool
- */
- public function checkCompileStatus($compileID = 0)
- {
- $compiles = $this->dao->select('compile.*, job.engine,job.pipeline, pipeline.name as jenkinsName,job.server,pipeline.url,pipeline.account,pipeline.token,pipeline.password')
- ->from(TABLE_COMPILE)->alias('compile')
- ->leftJoin(TABLE_JOB)->alias('job')->on('compile.job=job.id')
- ->leftJoin(TABLE_PIPELINE)->alias('pipeline')->on('job.server=pipeline.id')
- ->where('compile.status')->notIN('success, failure, create_fail, timeout, canceled')
- ->andWhere('compile.createdDate')->gt(date(DT_DATETIME1, strtotime("-1 day")))
- ->beginIf($compileID)->andWhere('compile.id')->eq($compileID)->fi()
- ->fetchAll();
- $notCompileMR = $this->dao->select('jobID,id')
- ->from(TABLE_MR)
- ->where('jobID')->gt(0)
- ->andWhere('compileStatus')->eq('created')
- ->fetchPairs();
- foreach($compiles as $compile)
- {
- $MRID = zget($notCompileMR, $compile->job, 0);
- $this->syncCompileStatus($compile, $MRID);
- }
- return !dao::isError();
- }
- /**
- * 根据编译ID获取编译信息。
- * Get compile by ID.
- *
- * @param int $compileID
- * @access public
- * @return object|false
- */
- public function getCompileByID($compileID)
- {
- return $this->dao->select('t1.*, t2.pipeline,t2.product,t2.frame,t2.server,t3.name as jenkinsName,t3.url,t3.account,t3.token,t3.password')->from(TABLE_COMPILE)->alias('t1')
- ->leftJoin(TABLE_JOB)->alias('t2')->on('t1.job=t2.id')
- ->leftJoin(TABLE_PIPELINE)->alias('t3')->on('t2.server=t3.id')
- ->where('t1.id')->eq($compileID)
- ->fetch();
- }
- /**
- * 保存编译信息到数据库。
- * Save compile info to database.
- *
- * @param string $response
- * @param object $compile
- * @param string $userPWD
- * @param string $jenkinsServer
- * @access public
- * @return bool
- */
- public function saveCompile($response, $compile, $userPWD, $jenkinsServer)
- {
- if(strripos($response, "404") > -1)
- {
- $jenkinsServer = strpos($compile->pipeline, '/job/') === 0 ? $jenkinsServer . $compile->pipeline : $jenkinsServer . '/job/' . $compile->pipeline;
- $infoUrl = sprintf("%s/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=%s]", $jenkinsServer, $compile->queue);
- $response = common::http($infoUrl, '', array(CURLOPT_USERPWD => $userPWD));
- if($response && strpos($response, "<") === 0)
- {
- $buildInfo = simplexml_load_string($response);
- if(empty($buildInfo)) return false;
- $buildNumber = strtolower($buildInfo->number->__toString());
- if(empty($buildNumber)) return false;
- $result = strtolower($buildInfo->result->__toString());
- if(empty($result)) return false;
- $this->updateBuildStatus($compile, $result);
- $logUrl = sprintf('%s/%s/consoleText', $jenkinsServer, $buildNumber);
- $response = common::http($logUrl, '', array(CURLOPT_USERPWD => $userPWD));
- $this->dao->update(TABLE_COMPILE)->set('logs')->eq($response)->where('id')->eq($compile->id)->exec();
- }
- }
- else
- {
- $queueInfo = @json_decode($response);
- $buildInfo = null;
- if($queueInfo && !empty($queueInfo->executable))
- {
- $buildUrl = $queueInfo->executable->url . 'api/json?pretty=true';
- $response = common::http($buildUrl, '', array(CURLOPT_USERPWD => $userPWD));
- $buildInfo = json_decode($response);
- }
- elseif(stripos($response, 'not found') !== false)
- {
- $job = strpos($compile->pipeline, '/job/') !== false ? $compile->pipeline : '/job/' . $compile->pipeline;
- $buildUrl = "{$compile->url}{$job}/api/json?depth=1";
- $response = json_decode(common::http($buildUrl, '', array(CURLOPT_USERPWD => $userPWD)));
- if($response)
- {
- foreach($response->builds as $build)
- {
- if($build->queueId == $compile->queue)
- {
- $buildInfo = $build;
- break;
- }
- }
- }
- }
- if($buildInfo)
- {
- if(!empty($buildInfo->building))
- {
- $this->updateBuildStatus($compile, 'building');
- }
- else
- {
- if(empty($buildInfo->result)) return false;
- $result = strtolower($buildInfo->result);
- $this->updateBuildStatus($compile, $result);
- $logUrl = $buildInfo->url . 'logText/progressiveText/api/json';
- $response = common::http($logUrl, '', array(CURLOPT_USERPWD => $userPWD));
- $this->dao->update(TABLE_COMPILE)->set('logs')->eq($response)->where('id')->eq($compile->id)->exec();
- }
- }
- }
- return !dao::isError();
- }
- /**
- * 同步构建状态。
- * Sync compile status.
- *
- * @param object $compile
- * @param int $MRID
- * @access public
- * @return bool
- */
- public function syncCompileStatus($compile, $MRID = 0)
- {
- /* Max retry times is: 5. */
- if($compile->times >= 5)
- {
- $this->updateBuildStatus($compile, 'failure');
- /* Added merge request result push to xuanxuan. */
- if($MRID) $this->loadModel('message')->send('mr', $MRID, 'compilefail', 0);
- return false;
- }
- if($compile->engine == 'gitlab') return $this->syncGitlabTaskStatus($compile);
- $jenkinsServer = $compile->url;
- $jenkinsPassword = $compile->token ? $compile->token : base64_decode($compile->password);
- $userPWD = "{$compile->account}:{$jenkinsPassword}";
- $queueUrl = sprintf('%s/queue/item/%s/api/json', $jenkinsServer, $compile->queue);
- $response = common::http($queueUrl, '', array(CURLOPT_USERPWD => $userPWD));
- $result = '';
- if($this->app->rawModule != 'mr') $this->dao->update(TABLE_COMPILE)->set('times = times + 1')->where('id')->eq($compile->id)->exec();
- $this->saveCompile($response, $compile, $userPWD, $jenkinsServer);
- if($MRID && in_array($result, array('success', 'failure')))
- {
- $actionType = $result == 'success' ? 'compilepass' : 'compilefail';
- $this->loadModel('message')->send('mr', $MRID, $actionType, 0);
- }
- return !dao::isError();
- }
- /**
- * 同步gitlab任务状态。
- * Sync gitlab task status.
- *
- * @param object $compile
- * @access public
- * @return bool
- */
- public function syncGitlabTaskStatus($compile)
- {
- /* The value of `$compile->pipeline` is like `'{"project":"46","reference":"master"}'` in current design. */
- $pipeline = json_decode($compile->pipeline);
- $compile->project = isset($pipeline->project) ? (int)$pipeline->project : (int)$compile->pipeline;
- $now = helper::now();
- $pipeline = $this->loadModel('gitlab')->apiGetSinglePipeline($compile->server, $compile->project, $compile->queue);
- if(!isset($pipeline->id) || isset($pipeline->message)) /* The pipeline is not available. */
- {
- $this->dao->update(TABLE_COMPILE)->set('status')->eq('failure')->where('id')->eq($compile->id)->exec();
- $this->dao->update(TABLE_JOB)->set('lastExec')->eq($now)->set('lastStatus')->eq('create_fail')->where('id')->eq($compile->job)->exec();
- return false;
- }
- $jobs = $this->gitlab->apiGetJobs($compile->server, $compile->project, $compile->queue);
- $data = new stdclass;
- $data->status = $pipeline->status;
- $data->updateDate = $now;
- $data->logs = '';
- foreach($jobs as $job)
- {
- if(empty($job->duration) || $job->duration == '') $job->duration = '-';
- $data->logs .= "<font style='font-weight:bold'>>>> Job: $job->name, Stage: $job->stage, Status: $job->status, Duration: $job->duration Sec\r\n </font>";
- $data->logs .= "Job URL: <a href=\"$job->web_url\" target='_blank'>$job->web_url</a> \r\n";
- $data->logs .= $this->transformAnsiToHtml($this->gitlab->apiGetJobLog($compile->server, $compile->project, $job->id));
- }
- $this->dao->update(TABLE_COMPILE)->data($data)->where('id')->eq($compile->id)->exec();
- $this->dao->update(TABLE_JOB)->set('lastExec')->eq($now)->set('lastStatus')->eq($pipeline->status)->where('id')->eq($compile->job)->exec();
- /* Send mr message by compile status. */
- $relateMR = $this->dao->select('*')->from(TABLE_MR)->where('compileID')->eq($compile->id)->fetch();
- if($relateMR)
- {
- if($data->status == 'success') $this->loadModel('action')->create('mr', $relateMR->id, 'compilePass');
- if($data->status == 'failed') $this->loadModel('action')->create('mr', $relateMR->id, 'compileFail');
- }
- return !dao::isError();
- }
- /**
- * 把ansi文本转换成html样式。
- * Transform ansi text to html.
- *
- * @param string $text
- * @access public
- * @return string
- */
- public function transformAnsiToHtml($text)
- {
- $text = preg_replace("/\x1B\[31;40m/", '<font style="color: red">', $text);
- $text = preg_replace("/\x1B\[32;1m/", '<font style="color: green">', $text);
- $text = preg_replace("/\x1B\[32;1m/", '<font style="color: green">', $text);
- $text = preg_replace("/\x1B\[36;1m/", '<font style="color: cyan">', $text);
- $text = preg_replace("/\x1B\[0;33m/", '<font style="color: yellow">', $text);
- $text = preg_replace("/\x1B\[1m/", '<font style="font-weight:bold">', $text);
- $text = preg_replace("/\x1B\[0;m/", '</font><br>', $text);
- return preg_replace("/\x1B\[0K/", '<br>', $text);
- }
- /**
- * 更新流水线构建状态。
- * Update ci build status.
- *
- * @param object $build
- * @param string $status
- * @access public
- * @return bool
- */
- public function updateBuildStatus($build, $status)
- {
- $this->dao->update(TABLE_COMPILE)->set('status')->eq($status)->set('updateDate')->eq(helper::now())->where('id')->eq($build->id)->exec();
- $this->dao->update(TABLE_JOB)->set('lastExec')->eq(helper::now())->set('lastStatus')->eq($status)->where('id')->eq($build->job)->exec();
- if($status == 'building') return false;
- $relateMR = $this->dao->select('*')->from(TABLE_MR)->where('compileID')->eq($build->id)->fetch();
- if(empty($relateMR)) return false;
- if($status != 'success')
- {
- $newMR = new stdclass();
- $newMR->status = 'closed';
- $newMR->mergeStatus = 'cannot_merge_by_fail';
- $newMR->compileStatus = $status;
- $this->dao->update(TABLE_MR)->data($newMR)->where('id')->eq($relateMR->id)->exec();
- }
- elseif(isset($relateMR->synced) && $relateMR->synced == '0')
- {
- $rawMR = $this->loadModel('mr')->apiCreateMR($relateMR->hostID, $relateMR);
- if(!empty($rawMR->iid))
- {
- $newMR = new stdclass();
- $newMR->mriid = $rawMR->iid;
- $newMR->status = $rawMR->state;
- $newMR->synced = '1';
- $this->dao->update(TABLE_MR)->data($newMR)->where('id')->eq($relateMR->id)->exec();
- $this->mr->linkObjects($relateMR);
- }
- }
- return !dao::isError();
- }
- /**
- * 发起一个请求。
- * Send request.
- *
- * @param string $url
- * @param object $data
- * @param string $userPWD
- * @access public
- * @return string|int
- */
- public function sendRequest($url, $data, $userPWD = '')
- {
- if(!empty($data->PARAM_TAG)) $data->PARAM_REVISION = '';
- $response = common::http($url, $data, array(CURLOPT_HEADER => true, CURLOPT_USERPWD => $userPWD));
- if(preg_match("!Location: .*item/(.*)/!i", $response, $matches)) return $matches[1];
- return 0;
- }
- /**
- * 根据ztf结果更新测试单。
- * Save test task for ztf.
- *
- * @param string $testType unit|func
- * @param int $productID
- * @param int $compileID
- * @param int $taskID
- * @param string $name
- * @access public
- * @return int|bool
- */
- public function saveTestTaskForZtf($testType = 'unit', $productID = 0, $compileID = 0, $taskID = 0, $name = '')
- {
- $this->loadModel('testtask');
- if(!empty($taskID))
- {
- $testtask = $this->testtask->getByID($taskID);
- if(!$testtask) return false;
- $this->dao->update(TABLE_TESTTASK)->set('auto')->eq(strtolower($testType))->where('id')->eq($taskID)->exec();
- }
- else
- {
- if(empty($productID)) return false;
- $lastProject = $this->dao->select('t2.id,t2.project')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
- ->where('t1.product')->eq($productID)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t2.project')->ne('0')
- ->orderBy('t2.id desc')
- ->limit(1)
- ->fetch();
- $testtask = new stdclass();
- $testtask->product = $productID;
- $testtask->name = !empty($name) ? $name : sprintf($this->lang->testtask->titleOfAuto, date('Y-m-d H:i:s'));
- $testtask->owner = $this->app->user->account;
- $testtask->project = $lastProject ? $lastProject->project : 0;
- $testtask->execution = $lastProject ? $lastProject->id : 0;
- $testtask->build = 'trunk';
- $testtask->auto = strtolower($testType);
- $testtask->begin = date('Y-m-d');
- $testtask->end = date('Y-m-d', time() + 24 * 3600);
- $testtask->status = 'done';
- $testtask->createdBy = $this->app->user->account;
- $testtask->createdDate = helper::now();
- $this->dao->insert(TABLE_TESTTASK)->data($testtask)->exec();
- $taskID = $this->dao->lastInsertId();
- $this->loadModel('action')->create('testtask', $taskID, 'opened');
- }
- if($compileID) $this->dao->update(TABLE_COMPILE)->set('testtask')->eq($taskID)->where('id')->eq($compileID)->exec();
- if(dao::isError()) return dao::isError();
- return $taskID;
- }
- }
|