* @package product * @version $Id: $ * @link http://www.zentao.net */ class ciModel extends model { /** * Set menu. * * @param int $repoID * @access public * @return void */ public function setMenu($repoID = 0) { if($repoID) { if(!session_id()) session_start(); $this->session->set('repoID', $repoID); session_write_close(); } common::setMenuVars('devops', $this->session->repoID); if($this->session->repoID) { $repo = $this->loadModel('repo')->getRepoByID($this->session->repoID); if(!empty($repo) and !in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) unset($this->lang->devops->menu->mr); $tab = $this->app->tab; $repos = $this->repo->getRepoPairs($tab); if(count($repos) > 1) $this->lang->switcherMenu = $this->loadModel('repo')->getSwitcher($this->session->repoID); } } /** * Send a request to jenkins to check build status. * * @param int $compileID * @access public * @return void */ 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')->ne('success') ->andWhere('compile.status')->ne('failure') ->andWhere('compile.status')->ne('create_fail') ->andWhere('compile.status')->ne('timeout') ->andWhere('compile.status')->ne('canceled') ->beginIf($compileID)->andWhere('compile.id')->eq($compileID)->fi() ->andWhere('compile.createdDate')->gt(date(DT_DATETIME1, strtotime("-1 day"))) ->fetchAll(); $notCompileMR = $this->dao->select('id,jobID') ->from(TABLE_MR) ->where('jobID')->gt(0) ->andWhere('compileStatus')->eq('created') ->fetchPairs(); foreach($compiles as $compile) $this->syncCompileStatus($compile, $notCompileMR); } /** * Sync compile status. * * @param object $compile * @param array $notCompileMR * @access public * @return void */ public function syncCompileStatus($compile, $notCompileMR = array()) { $MRID = array_search($compile->job, $notCompileMR); /* Max retry times is: 3. */ if($compile->times >= 3) { $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; $jenkinsUser = $compile->account; $jenkinsPassword = $compile->token ? $compile->token : base64_decode($compile->password); $userPWD = "$jenkinsUser:$jenkinsPassword"; $queueUrl = sprintf('%s/queue/item/%s/api/json', $jenkinsServer, $compile->queue); $response = common::http($queueUrl, '', array(CURLOPT_USERPWD => $userPWD)); $result = ''; if($compile->engine != 'gitlab') $this->dao->update(TABLE_COMPILE)->set('times = times + 1')->where('id')->eq($compile->id)->exec(); if(strripos($response, "404") > -1) { $infoUrl = sprintf("%s/job/%s/api/xml?tree=builds[id,number,result,queueId]&xpath=//build[queueId=%s]", $jenkinsServer, $compile->pipeline, $compile->queue); $response = common::http($infoUrl, '', array(CURLOPT_USERPWD => $userPWD)); if($response) { $buildInfo = simplexml_load_string($response); $buildNumber = strtolower($buildInfo->number); if(empty($buildNumber)) return false; $result = strtolower($buildInfo->result); if(empty($result)) return false; $this->updateBuildStatus($compile, $result); $logUrl = sprintf('%s/job/%s/%s/consoleText', $jenkinsServer, $compile->pipeline, $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); if(!empty($queueInfo->executable)) { $buildUrl = $queueInfo->executable->url . 'api/json?pretty=true'; $response = common::http($buildUrl, '', array(CURLOPT_USERPWD => $userPWD)); $buildInfo = json_decode($response); if($buildInfo->building) { $this->updateBuildStatus($compile, 'building'); } else { $result = strtolower($buildInfo->result); if(empty($result)) return false; $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(); } } } if($MRID && in_array($result, array('success', 'failure'))) { $actionType = $result == 'success' ? 'compilepass' : 'compilefail'; $this->loadModel('message')->send('mr', $MRID, $actionType, 0); } } /** * Sync gitlab task status. * * @param object $compile * @access public * @return void */ public function syncGitlabTaskStatus($compile) { $this->loadModel('gitlab'); $now = helper::now(); /* The value of `$compile->pipeline` is like `'{"project":"46","reference":"master"}'` in current design. */ $pipeline = json_decode($compile->pipeline); $compile->project = isset($pipeline->project) ? $pipeline->project : $compile->pipeline; $pipeline = $this->gitlab->apiGetSinglePipeline($compile->server, $compile->project, $compile->queue); if(!isset($pipeline->id) or isset($pipeline->message)) /* The pipeline is not available. */ { $pipeline->status = 'create_fail'; /* Set the status to fail. */ $this->dao->update(TABLE_JOB)->set('lastExec')->eq($now)->set('lastStatus')->eq($pipeline->status)->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) or $job->duration == '') $job->duration = '-'; $data->logs .= ">>> Job: $job->name, Stage: $job->stage, Status: $job->status, Duration: $job->duration Sec\r\n "; $data->logs .= "Job URL: web_url\" target='_blank'>$job->web_url \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'); } } /** * Transform ansi text to html. * * @param string $text * @access public * @return string */ public function transformAnsiToHtml($text) { $text = preg_replace("/\x1B\[31;40m/", '', $text); $text = preg_replace("/\x1B\[32;1m/", '', $text); $text = preg_replace("/\x1B\[32;1m/", '', $text); $text = preg_replace("/\x1B\[36;1m/", '', $text); $text = preg_replace("/\x1B\[0;33m/", '', $text); $text = preg_replace("/\x1B\[1m/", '', $text); $text = preg_replace("/\x1B\[0;m/", '
', $text); $text = preg_replace("/\x1B\[0K/", '
', $text); return $text; } /** * Update ci build status. * * @param object $build * @param string $status * @access public * @return void */ public function updateBuildStatus($build, $status) { $this->dao->update(TABLE_COMPILE)->set('status')->eq($status)->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; $relateMR = $this->dao->select('*')->from(TABLE_MR)->where('compileID')->eq($build->id)->fetch(); if(empty($relateMR)) return; if(isset($relateMR->synced) and $relateMR->synced == '0' and $status == 'success') { $newMR = new stdclass(); $newMR->mergeStatus = 'can_be_merged'; $newMR->compileStatus = $status; /* Create a gitlab mr. */ $MRObject = new stdclass(); $MRObject->target_project_id = $relateMR->targetProject; $MRObject->source_branch = $relateMR->sourceBranch; $MRObject->target_branch = $relateMR->targetBranch; $MRObject->title = $relateMR->title; $MRObject->description = $relateMR->description; $MRObject->remove_source_branch = $relateMR->removeSourceBranch == '1' ? true : false; if($relateMR->assignee) { $gitlabAssignee = $this->gitlab->getUserIDByZentaoAccount($relateMR->gitlabID, $relateMR->assignee); if($gitlabAssignee) $MRObject->assignee_ids = $gitlabAssignee; } $rawMR = $this->loadModel('mr')->apiCreateMR($relateMR->gitlabID, $relateMR->sourceProject, $MRObject); /** * Another open merge request already exists for this source branch. * The type of variable `$rawMR->message` is array. */ if(isset($rawMR->message) and !isset($rawMR->iid)) { $errorMessage = $rawMR->message; $rawMR = $this->mr->apiGetSameOpened($relateMR->gitlabID, $relateMR->sourceProject, $MRObject->source_branch, $MRObject->target_project_id, $MRObject->target_branch); if(empty($rawMR) or !isset($rawMR->iid)) { $errorMessage = $this->mr->convertApiError($errorMessage); $newMR->syncError = sprintf($this->lang->mr->apiError->createMR, $errorMessage); } } elseif(!isset($rawMR->iid)) { $newMR->syncError = $this->lang->mr->createFailedFromAPI; } if(!empty($rawMR->iid)) { $newMR->mriid = $rawMR->iid; $newMR->status = $rawMR->state; $newMR->synced = '1'; $newMR->syncError = ''; } $this->dao->update(TABLE_MR)->data($newMR)->where('id')->eq($relateMR->id)->exec(); $this->mr->linkObjects($relateMR); } elseif($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(); } } /** * Send request. * * @param string $url * @param object $data * @param string $userPWD * @access public * @return 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/(.*)/!", $response, $matches)) return $matches[1]; return 0; } }