在线批量部署网站代码和数据库版本更新升级
2017-01-11 15:14
579 查看
【项目需求】
从SVN仓库自动checkout指定或全部代码文件,部署更新到指定应用服务器,并保存和比较每台应用服务器上的代码版本和SVN仓库代码版本。数据库实现版本化管理更新。
【项目疑难点】
部署客户端与SVN仓库、应用服务器的Ajax交互的性能、接口问题
【代码举例】
PhingDeploy.class.php Phing的方式部署类
DeploySource.class.php 部署
从SVN仓库自动checkout指定或全部代码文件,部署更新到指定应用服务器,并保存和比较每台应用服务器上的代码版本和SVN仓库代码版本。数据库实现版本化管理更新。
【项目疑难点】
部署客户端与SVN仓库、应用服务器的Ajax交互的性能、接口问题
【代码举例】
PhingDeploy.class.php Phing的方式部署类
<?php defined('IN_HEAVEN') or die('Hacking Attempt!'); /** * PhingDeploy * * PHP version 5 * * @category 4SWeb * @package Admin * @subpackage Custom * @author zhongyiwen * @version SVN: $Id: PhingDeploy.class.php 67 2014-11-25 15:12:29Z blin.z $ */ class PhingDeploy extends DeploySource{ protected $_phingCMD = 'phing'; protected $_buildConf = 'build.configs'; protected $_buildDir = 'phing'; protected $_buildFileMaps = array( 'ftp' => 'ftp_build.xml', //'svn' => 'svn_build.xml', 'copy' => 'copy_build.xml', 'db' => 'db_build.xml', 'ssh' => 'ssh_build.xml', 'scp' => 'scp_build.xml', 'ssh_query_db_revision' => 'ssh_query_db_revision_build.xml', 'api_query_db_revision' => 'api_query_db_revision_build.xml', 'backup_db' => 'backup_db_build.xml', 'http_request' => 'http_request_build.xml', 'svn' => 'svn_update_build.xml', ); protected $_runUser = array(); protected $_runTargets = array(); protected $_runEvent = array(); public function __construct($runder=null){ $this->_buildDir = DEPLOY_ROOT_DIR . '/phing'; parent::__construct($runder); $this->_makeBuildConf(); $phingCMD = DConfig::getConfig('deploy.phing_cmd'); if($phingCMD){ $this->_phingCMD = $phingCMD; } } public function supportedMethods($method=''){ return $method?array_key_exists($method, $this->_buildFileMaps):array_keys($this->_buildFileMaps); } public function getBuildConf(){ return $this->_getFullPath($this->_buildConf, $this->_buildDir); } public function getBuildFile($method='ftp'){ $file = $this->_buildFileMaps[$method]; if(!$file){ throw new __Exception("Not found build file, method={$method}", SCRIPT_ERR_CONFIG); } return $this->_getFullPath($file, $this->_buildDir); } protected function _makeBuildConf(){ $confContent = "#=========================================================#"; $confContent .= "\n# Auto Build Configurations for Phing"; $confContent .= "\n# Created on " . date ( 'Y/m/d H:i:s' ); $confContent .= "\n#=========================================================#"; // FTP Configuration $aFTPAttributes = array('host', 'username', 'password', 'port', 'passive', 'dir', 'level'); if (!empty ( $this->_ftpServerMaps )) { $confContent .= "\n\n#------------ FTP Server Configuration ----------#"; foreach ( $this->_ftpServerMaps as $server => $settings ) { $confContent .= "\n\n" . "# {$settings['host']} ftp server"; foreach ( $settings as $k => $v ) { if (in_array ( $k, $aFTPAttributes )) { $confContent .= "\n" . "ftp_{$server}.{$k} = {$v}"; } } } } // SSH Configuration $aSSHAttributes = array('method', 'host', 'username', 'password', 'port', 'tmpdir', 'level', 'pubkeyfile', 'privkeyfile', 'passphrase'); if (!empty ( $this->_sshServerMaps )) { $confContent .= "\n\n#------------ SSH Server Configuration ----------#"; foreach ( $this->_sshServerMaps as $server => $settings ) { $confContent .= "\n\n" . "# {$settings['host']} ssh server"; foreach ( $settings as $k => $v ) { if (in_array ( $k, $aSSHAttributes )) { $confContent .= "\n" . "ssh_{$server}.{$k} = {$v}"; } } } } // Api Configuration $aApiAttributes = array('url', 'authUser', 'authPassword', 'authScheme', 'responseRegex', 'observerEvents', 'method', 'verbose'); if (!empty ( $this->_apiServerMaps )) { $confContent .= "\n\n#------------ API Server Configuration ----------#"; foreach ( $this->_apiServerMaps as $server => $settings ) { $confContent .= "\n\n" . "# {$settings['url']} api server"; foreach ( $settings as $k => $v ) { if (in_array ( $k, $aApiAttributes )) { $confContent .= "\n" . "api_{$server}.{$k} = {$v}"; } } } } // Code Configuration $aCodeAttributes = array('host', 'username', 'password', 'port', 'passive', 'dir', 'level'); if (!empty ( $this->_targetCodeMaps )) { $confContent .= "\n\n#------------- Deploy Code Configuration -------------#"; foreach ( $this->_targetCodeMaps as $target => $settings ) { if(empty($settings['method'])){continue;} $confContent .= "\n\n" . "# {$settings['host']} {$settings['method']} code target"; foreach ( $settings as $k => $v ) { if (in_array ( $k, $aCodeAttributes )) { $confContent .= "\n" . "{$settings['method']}_{$target}.{$k} = {$v}"; } } } } // Database Configuration $aDatabaseAttributes = array('host', 'username', 'password', 'port', 'database', 'charset', 'mysql_cmd', 'mysqldump_cmd', 'gzip_cmd', 'backup_dir'); if (!empty ( $this->_targetDatabaseMaps )) { $confContent .= "\n\n#------------ Deploy Database Configuration ----------#"; foreach ( $this->_targetDatabaseMaps as $target => $settings ) { $confContent .= "\n\n" . "# {$settings['host']} {$settings['method']} database target"; foreach ( $settings as $k => $v ) { if (in_array ( $k, $aDatabaseAttributes )) { $confContent .= "\n" . "db_{$target}.{$k} = {$v}"; } } } } // Phing Configuration $confContent .= "\n\n" . "#-------------- Settings for phing --------------#"; // project basedir $confContent .= "\n" . "build.dir = " . $this->_buildDir; // save conf $confFile = $this->getBuildConf(); file_put_contents($confFile, $confContent); } protected function _phingCMD($type, $target='', $phingProperties=array()){ $method = '_' . $type . 'CMD'; if(method_exists($this, $method)){ $cmd = $this->$method($target, $phingProperties); }else{ $cmd = $this->_defaultCMD($type, $target, $phingProperties); } $cmd .= " " . DConfig::getConfig('deploy.phing_cmd_args'); return $cmd; } protected function _genPhingCMD($buildFile, $properties){ $cmd = $this->_phingCMD . " -f {$buildFile}"; $properties["build.token"] = $this->getToken(); foreach($properties as $p=>$v){ if(is_array($v) || is_object($v)){ $v = serialize($v); } $cmd .= " -D{$p}=\"" . addcslashes($v, '"'). "\""; } return $cmd; } protected function _codeCMD($target, $properties=array()) { if (! empty ( $this->_targetCodeMaps [$target] ['method'] )) { $method = $this->_targetCodeMaps [$target] ['method']; $server = $target; } else if (! empty ( $this->_targetCodeMaps [$target] ['auth'] )) { $targetAuth = $this->_targetCodeMaps [$target] ['auth']; $method = substr ( $targetAuth, 0, strpos ( $targetAuth, '.' ) ); $server = substr ( $targetAuth, strpos ( $targetAuth, '.' ) + 1 ); } else { $this->_setErrorMsg ( "Invalid Config for target: $target" ); return false; } $buildFile = $this->getBuildFile ( $method ); if (! $buildFile ) { $this->_setErrorMsg ( "Couldn't found build file for method: $method" ); return false; } $sourceDir = $this->getWorkingDir ('code'); $toDir = $this->_targetCodeMaps [$target]['dir']; $defaultProperties = array(); 4000 switch($method){ case 'ftp': $defaultProperties["ftpserver"] = "ftp_{$server}"; $defaultProperties['ftp.sourcedir'] = $sourceDir; $defaultProperties['ftp.toDir'] = $toDir; break; case 'scp': $defaultProperties["sshserver"] = "ssh_{$server}"; $defaultProperties['scp.sourceDir'] = $sourceDir; $defaultProperties['scp.toDir'] = $toDir; $defaultProperties['scp.autocreate'] = 'true'; break; case 'copy': $defaultProperties['copy.sourceDir'] = $sourceDir; $defaultProperties['copy.toDir'] = $toDir; break; default: throw new __Exception("Unknown method: $method", SCRIPT_ERR_CONFIG); } return $this->_genPhingCMD($buildFile, array_merge($defaultProperties, $properties)); } protected function _databaseCMD($target, $properties=array()) { $method = 'db'; if (! $this->supportedMethods ( $method )) { $this->_setErrorMsg ( "$method is not supported by phing for target $target" ); return false; } $buildFile = $this->getBuildFile ( $method ); $serverProperty = $method . "_" . $target; $workingDir = $this->getWorkingDir(); $dbDeltaDir = $this->getWorkingDir('deltas'); $dbScriptDir = DEPLOY_RUN_DIR . '/scripts'; $dbDeltaSet = DConfig::getConfig(array('deploy.target_database_maps', $target, 'database')); $dbTableName = DConfig::getConfig(array('deploy.target_database_maps', $target, 'changelog_table')); $dbDeltaSet = 'Main'; $defaultProperties = array(); $defaultProperties["dbserver"] = $serverProperty; $defaultProperties["db.delta_dir"] = $dbDeltaDir; $defaultProperties["db.script_dir"] =$dbScriptDir; $defaultProperties["db.delta_set"] = $dbDeltaSet; $defaultProperties["db.delta_revision"] = '999'; $defaultProperties["db.changelog_table"] = $dbTableName?$dbTableName:'changelog'; $defaultProperties["build.token"] = $this->getToken(); $targetAuth = $this->_targetDatabaseMaps[$target]['auth']; $authType = ''; if($targetAuth){ $authType = substr($targetAuth, 0, strpos($targetAuth, '.')); } if($authType=='ssh'){ $sshServer = substr($targetAuth, 3+1); $scp2Dir = DConfig::getConfig(array('deploy.ssh_server_maps', $sshServer, 'tmpdir')); if(!$scp2Dir){ throw new __Exception("Couldn't found ssh server tmpdir", SCRIPT_ERR_CONFIG); } $defaultProperties['sshserver'] = "ssh_{$sshServer}"; $defaultProperties['scp.toDir'] = $scp2Dir; $defaultProperties['db.change_logs'] = $workingDir . "/" . $target . "_db_revision.xml";; }elseif($authType=='api'){ $apiServer = substr($targetAuth, 3+1); $defaultProperties['apiserver'] = "api_{$apiServer}"; $defaultProperties['db.target_db'] = $this->_targetDatabaseMaps[$target]['auth.options']['getparameter']['db']; $defaultProperties['db.change_logs'] = $workingDir . "/" . $target . "_db_revision.json";; } return $this->_genPhingCMD($buildFile, array_merge($defaultProperties, $properties)); } protected function _defaultCMD($type, $target='', $properties=array()) { $buildFile = $this->getBuildFile ( $type ); $defaultProperties = array(); return $this->_genPhingCMD($buildFile, array_merge($defaultProperties, $properties)); } public function callPhing($cmd, $target, $properties=array()){ return $this->_callPhing($cmd, $target, $properties); } protected function _callPhing($type='code', $target='', $properties=array()){ /* if(!$target){ $this->_setErrorMsg( "Target can not be empty" ); return DeploySource::STATUS_STOP_ERROR; }*/ $logFile = $this->getLogFile($type); if(!isset($properties['build.logfile'])){ $properties['build.logfile'] = $logFile; } $cmd = $this->_phingCMD($type, $target, $properties); if(!$cmd){ return DeploySource::STATUS_STOP_ERROR; } // $cmd .= " -verbose"; if ($this->_enableLog && $logFile) { // $cmd .= " -logfile {$logFile}"; // use pipe to write log & errors $cmd .= " 1>>$logFile 2>&1"; } //die($cmd); $this->writeLog("\n\nSTART DEPLOY TARGET $target"); $this->writeLog("\nPhing CMD: $cmd \n"); // Note: must close log file resource before running phing $this->writeLog("", "", false); // escape command $cmd = escapeshellcmd($cmd); $cmdOutput = ''; $cmdStatus = ''; // execute command $cmdResult = exec($cmd, $cmdOutput, $cmdStatus); //echo "<br />CMD Result=" . print_r($cmdResult, true); //echo "<br />CMD Output=" . print_r($cmdOutput, true); //echo "<br />CMD Status=" . print_r($cmdStatus, true); // exit code returned by Phing is not credible, // let's check error ourself $aResultLines = tailFile($logFile, 20, true); //$this->writeLog("\ncmd Result: " . print_r($cmdResult, true)); //$this->writeLog("\nresult Lines: : " . print_r($aResultLines, true)); // print_r($aResultLines); /* Examples: Array ( [0] => [1] => BUILD FINISHED [2] => [3] => Total time: 3.2202 seconds [4] => ) Array ( [0] => [1] => BUILD FAILED [2] => E:\w3root\dev\websvn\deploy\phing\ftp_build.xml:9:33: Could not connect to FTP server ftp2.feet.com on port 21: Connection to host failed [3] => Total time: 0.3054 seconds [4] => ) Array ( [0] => BUILD FAILED [1] => E:\w3root\dev\websvn\deploy\phing\db_build.xml:35:12: E:\w3root\dev\websvn\deploy\phing\db_build.xml:53:20: E:\w3root\dev\websvn\deploy\phing\db_build.xml:62:34: Failed to upgrade to db revision: 3, Please check your delta do sql syntax! Mysql error message: ERROR 1050 (42S01) at line 6: Table 'export_setting_reg' already exists [2] => [3] => Total time: 5.6628 seconds [4] => ) */ //$buildCode = trim($aResultLines[0]); $isBuildSucc = true; $buildReason = ''; foreach($aResultLines as $i=>$line){ $line = trim($line); if($line && !strcasecmp($line, 'BUILD FAILED')){ $isBuildSucc = false; $buildReason = $aResultLines[$i+1]; }elseif($line && !strcasecmp($line, 'BUILD FINISHED')){ $isBuildSucc = true; $buildReason = ''; } } // succ: BUILD FINISHED // fail: BUILD FAILED // E:\w3root\dev\websvn\deploy\phing\ftp_build.xml:9:28: Could not login to FTP server ftp.feet.com on port 21 with username www2: Unable to login if(!$isBuildSucc){ $this->_setErrorMsg("[Target $target] Phing Error: $buildReason"); $this->_setErrorTarget($target, "部署失敗", DeploySource::STEP_PHING_DEPLOY, $buildReason); $this->writeLog("\n\nFailed deploy, Phing Error occurred: $buildReason"); return DeploySource::STATUS_STOP_ERROR; } // check phing exit code /* Exitcode Description -2 Environment not properly defined -1 Parameter error occured, printed help screen 0 Successful execution, no warnings, no errors 1 Successful execution, but warnings occured */ switch($cmdStatus){ case 0: $this->writeLog("\nSuccessful execute deploy by Phing, no errors, nor warnings."); $this->writeLog("\n\nEND DEPLOY TARGET $target"); return DeploySource::STATUS_DONE_SUCCESS; case 1: $this->writeLog("\nSuccessful execute deploy by Phing, but warnings occured."); $this->writeLog("\n\nEND DEPLOY TARGET $target"); return DeploySource::STATUS_DONE_WARNING; case -1: $statusText = "Parameter error occured, printed help screen"; $this->_setErrorMsg("[Target $target] Phing Error: $cmdStatus $statusText"); $this->_setErrorTarget($target, "部署失敗", DeploySource::STEP_PHING_DEPLOY, $statusText); $this->writeLog("\n\nFailed deploy files, Phing Error occurred: $cmdStatus $statusText"); return DeploySource::STATUS_STOP_ERROR; case -2: $statusText = "Environment not properly defined"; $this->_setErrorMsg("[Target $target] Phing Error: $cmdStatus $statusText"); $this->_setErrorTarget($target, "部署失敗", DeploySource::STEP_PHING_DEPLOY, $statusText); $this->writeLog("\n\nFailed deploy files, Phing Error occurred: $cmdStatus $statusText"); return DeploySource::STATUS_STOP_ERROR; default: throw new __Exception("Unknown phing exit code: {$cmdStatus}", SCRIPT_ERR_CONFIG); } } protected function _verifyFiles($type, $target){ $logFile = $this->getLogFile($type); $this->writeLog("\n\nTry to verify files of target $target"); $totalTargets = count($this->_runSettings['targets']); $currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1; $this->writeProgress("開始校驗是否全部文件部署到 {$currentTargetSeq}/{$totalTargets}: {$target}目标服務器", DeploySource::STEP_VERIFY_DEPLOY); if(empty($this->_runSettings['parsedLogs'])){ $errorMsg = "Verify Parsed Logs is empty"; $this->_setErrorMsg($errorMsg); $this->writeLog("\nSKIP VERIFY, " . $errorMsg); return DeploySource::STATUS_STOP_ERROR; } $startTag = "\n\nSTART DEPLOY TARGET $target"; $endTag = "\n\nEND DEPLOY TARGET $target"; $logContent = file_get_contents($logFile); if (($startPos = strrpos ( $logContent, $startTag )) === false) { $errorMsg = "Couldn't found verify start position: $startTag"; $this->_setErrorMsg ( $errorMsg ); $this->writeLog ( "\nSKIP VERIFY, " . $errorMsg ); return DeploySource::STATUS_STOP_ERROR; } else if (($endPos = strrpos ( $logContent, $endTag )) === false) { $errorMsg = "Couldn't found verify end position: $endTag"; $this->_setErrorMsg ( $errorMsg ); $this->writeLog ( "\nSKIP VERIFY, " . $errorMsg ); return DeploySource::STATUS_STOP_ERROR; } $this->writeLog("\nFound verify position [{$startPos} ~ {$endPos}]"); $length = $endPos - $startPos + strlen ( $endTag ); $tagContent = substr ( $logContent, $startPos, $length ); $aLogFiles = !empty($this->_runCodeRevisions)?$this->_runCodeRevisions:$this->_runSettings['parsedLogs']; $total = count($this->_runSettings['parsedLogs']); if (! empty ( $this->_targetCodeMaps [$target] ['method'] )) { $method = $this->_targetCodeMaps [$target] ['method']; } else if (! empty ( $this->_targetCodeMaps [$target] ['auth'] )) { $targetAuth = $this->_targetCodeMaps [$target] ['auth']; $method = substr ( $targetAuth, 0, strpos ( $targetAuth, '.' ) ); } $method = strtolower($method); switch($method){ case 'ftp': $aFailFiles = $this->_verify_ftp_log($target, $aLogFiles, $tagContent); break; case 'copy': $aFailFiles = $this->_verify_copy_log($target, $aLogFiles, $tagContent); break; default: $errorMsg = "Unknown deploy method: {$method}"; $this->_setErrorMsg($errorMsg); $this->writeLog("\nSKIP VERIFY, " . $errorMsg); return DeploySource::STATUS_STOP_ERROR; } $fail = $aFailFiles===true?0:count($aFailFiles); $this->writeLog ( "\nVerify Finished, total checked $total files, failed: {$fail} files." ); if ($aFailFiles!==true) { if (is_array ( $aFailFiles )) { foreach ( $aFailFiles as $file ) { $aUpdate = array (); $aUpdate ['file'] = $file; $aUpdate ['step'] = self::STEP_VERIFY_DEPLOY; $aUpdate ['target'] = $target; $aUpdate ['status'] = "Verify Error:文件未部署成功"; $aUpdate ['remark'] = "請重新部署該文件"; $aUpdate ['revision'] = $this->_runSettings ['revision']; $this->_setErrorFile ( $aUpdate ); } } $this->_setErrorMsg ( "有{$fail}個文件部署到{$target}未成功,請重新部署" ); return DeploySource::STATUS_DONE_WARNING; } else { return DeploySource::STATUS_DONE_SUCCESS; } } protected function _verify_ftp_log($target, $aLogFiles, $tagContent){ $aFailFiles = array(); foreach ( $aLogFiles as $log ) { $revision = $this->_runSettings ['revision']; if (is_array ( $log )) { if (! empty ( $log ['dumpFile'] )) { $matchLine = "[ftpdeploy] Will copy {$log['dumpFile']} to {$log['file']}"; } else { $matchLine = " to {$log['file']}"; } $file = $log ['file']; if (! empty ( $log ['revision'] )) { $revision = $log ['revision']; } } else { $file = $log; $matchLine = " to $file"; } if (($linePos = strstr ( $tagContent, $matchLine )) === false) { $aFailFiles[] = $file; $this->writeLog ( "\n[{$matchLine}] matched fail, please deploy this file again" ); } else { $this->writeLog ( "\n[{$matchLine}] matched success" ); } } return $aFailFiles?$aFailFiles:true; } protected function _verify_copy_log($target, $aLogFiles, $tagContent){ $total = count($aLogFiles); $matchLine = "[copy] Copying {$total} files to"; if (($linePos = strstr ( $tagContent, $matchLine )) === false) { return false; } else { return true; } } protected function _phingDeploy($type){ switch($type){ case 'code': return $this->_deployCode(); break; case 'database': return $this->_deployDatabase(); break; case 'svn': return $this->_deploySVN(); break; default : throw new __Exception("Unknown deploy type: $type" , SCRIPT_ERR_CONFIG); } } protected function _deployCode(){ $runOK = false; if(!$this->_runSettings['parsedLogs']){ $this->_runSettings['parsedLogs'] = $this->parseUpdateLogs($this->_runSettings['updateLogs']); } $totalFiles = count($this->_runSettings['parsedLogs']); $this->_runSettings['deploySteps'] = array(DeploySource::STEP_SVN_COPY, DeploySource::STEP_CHECK_REVISION, DeploySource::STEP_PHING_DEPLOY, DeploySource::STEP_VERIFY_DEPLOY); $returnStatus = DeploySource::STATUS_DONE_SUCCESS; $oEvent = new EventModel(); $oTarget = new TargetModel(); foreach ( $this->_runSettings ['deploySteps'] as $step ) { switch ($step) { case DeploySource::STEP_SVN_COPY : $this->writeProgress("開始去SVN倉庫獲取文件", self::STEP_SVN_COPY); $runOK = $this->dumpCodeFromSVN ( $this->_runSettings ['parsedLogs'], $this->_runSettings ['repname'], $this->_runSettings ['branch'], $this->_runSettings ['revision'] ); break; case DeploySource::STEP_CHECK_REVISION : $this->writeProgress("開始去SVN倉庫檢查文件版本", DeploySource::STEP_CHECK_REVISION); $runOK = $this->checkRevisionFromSVN( $this->_runSettings ['parsedLogs'], $this->_runSettings ['repname'], $this->_runSettings ['branch'], $this->_runSettings ['revision'] ); break; case DeploySource::STEP_PHING_DEPLOY : $totalTargets = count($this->_runSettings['targets']); foreach ( $this->_runSettings ['targets'] as $target ) { $currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1; $this->writeProgress("开始部署到第 {$currentTargetSeq}/{$totalTargets}: {$target} 目标", self::STEP_PHING_DEPLOY); $startTime = time(); $oEvent->startDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $this->_runUser, $totalFiles); $runOK = $this->_callPhing ( 'code', $target ); $endTime = time(); $elapsedSecs = $endTime - $startTime; $succFiles = 0; $isSucc = $runOK == DeploySource::STATUS_STOP_ERROR?false:true; // IGNORE phing stop error if (!$isSucc) { $this->writeProgress("部署到目标 {$target} 發生錯誤:" . $this->getErrorMsg(" "), self::STEP_PHING_DEPLOY); $returnStatus = DeploySource::STATUS_DONE_WARNING; }else{ // deploy succ or warning $this->writeProgress("成功部署到目标 {$target}", self::STEP_PHING_DEPLOY); $this->writeProgress("保存目标 {$target}代碼文件版本。。。", self::STEP_PHING_DEPLOY); $succFiles = $this->_saveTargetCodeRevisions($target); } // save deploy target status $oEvent->endDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $isSucc, $elapsedSecs, $succFiles, $this->getErrorMsg(" ")); $oTarget->lastDeploy($this- 1bb8c >_runTargets[$target]['targetId'], $this->_runEvent, $this->_runUser); } break; case DeploySource::STEP_VERIFY_DEPLOY : foreach ( $this->_runSettings ['targets'] as $target ) { $runOK = $this->_verifyFiles ('code', $target ); // IGNORE verify stop error if($runOK==DeploySource::STATUS_DONE_WARNING){ $returnStatus = DeploySource::STATUS_DONE_WARNING; } } break; default : throw new __Exception ( "Unknown deploying step $step", SCRIPT_ERR_CONFIG ); } if($runOK==DeploySource::STATUS_DONE_WARNING){ $returnStatus = DeploySource::STATUS_DONE_WARNING; } else if ($runOK == DeploySource::STATUS_STOP_ERROR) { $returnStatus = DeploySource::STATUS_STOP_ERROR; break; } } return $returnStatus; } protected function _deployDatabase(){ $runOK = false; if($this->_runSettings['backup']){ $this->_runSettings['deploySteps'] = array(DeploySource::STEP_CHECK_REVISION, DeploySource::STEP_SVN_COPY, DeploySource::STEP_BACKUP_BEFORE, DeploySource::STEP_PHING_DEPLOY); }else{ $this->_runSettings['deploySteps'] = array(DeploySource::STEP_CHECK_REVISION, DeploySource::STEP_SVN_COPY, DeploySource::STEP_PHING_DEPLOY); } $oEvent = new EventModel(); $oTarget = new TargetModel(); foreach ( $this->_runSettings ['deploySteps'] as $step ) { switch ($step) { case DeploySource::STEP_SVN_COPY : $saveDeltaDir = $this->getWorkingDir('deltas'); $this->writeLog("\n\nSTART dump dir from SVN repository " . $this->_runSettings ['repname'] . ", dir " . $this->_runSettings ['path']); $this->writeProgress("開始去SVN倉庫獲取文件", self::STEP_SVN_COPY); $dumpDeltaStatus = $this->dumpDirFromSVN ( $this->_runSettings ['repname'], $this->_runSettings ['path'], $this->_runSettings ['revision'] , $saveDeltaDir); if($dumpDeltaStatus){ $runOK = DeploySource::STATUS_DONE_SUCCESS; }else{ $runOK = DeploySource::STATUS_STOP_ERROR; } break; case DeploySource::STEP_CHECK_REVISION: $aTargetRevisions = $this->checkDBRevision ( $this->_runSettings ); if($aTargetRevisions){ $runOK = DeploySource::STATUS_DONE_SUCCESS; }else{ $runOK = DeploySource::STATUS_STOP_ERROR; } break; case DeploySource::STEP_BACKUP_BEFORE: $runOK = $this->backupDB($this->_runSettings); $isSucc = $runOK == DeploySource::STATUS_STOP_ERROR ? false : true; if (! $isSucc) { $this->writeProgress ( "備份發生錯誤:" . $this->getErrorMsg ( " " ), self::STEP_PHING_DEPLOY ); $returnStatus = DeploySource::STATUS_DONE_WARNING; } else { // deploy succ or warning $this->writeProgress ( "成功完成備份!", self::STEP_PHING_DEPLOY ); } break; case DeploySource::STEP_PHING_DEPLOY : $totalTargets = count($this->_runSettings['targets']); foreach ( $this->_runSettings ['targets'] as $target ) { $currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1; $this->writeProgress("开始部署到第 {$currentTargetSeq}/{$totalTargets}: {$target} 目标", self::STEP_PHING_DEPLOY); $currentDB = $this->_targetDatabaseMaps[$target]['database']; $currentDBRevision = $this->_runSettings['targetRevisions'][$target]['revision']; $upgradeDBRevision = $this->_runSettings['upgrade2Revision']; $dbScriptDir = DEPLOY_RUN_DIR . '/scripts'; $doSQLFile = 'deploy-' . $this->_runSettings['token'] . '-' . $currentDB . '.sql'; $undoSQLFile = 'undo-' . $this->_runSettings['token'] . '-' . $currentDB . '.sql'; $properties = array(); $properties['db.delta_revision'] = $upgradeDBRevision; $properties['db.script_dir'] =$dbScriptDir; $properties['build.deploy_file_name'] = $doSQLFile; $properties['build.deploy_undofile_name'] = $undoSQLFile; // force roll back $properties['build.roll_back'] = $this->_runSettings['rollback']; $this->writeLog("\nCurrent Target {$currentDB} DB Revision is {$currentDBRevision}, will upgrade to new revision {$upgradeDBRevision}."); $this->writeLog("\ndo SQL File: " . $dbScriptDir . '/' . $doSQLFile); $this->writeLog("\nundo SQL File: " . $dbScriptDir . '/' . $undoSQLFile); $targetAuth = $this->_targetDatabaseMaps[$target]['auth']; if($targetAuth){ $dotPos = strpos($targetAuth, '.'); $authType = substr($targetAuth, 0, $dotPos); switch ($authType){ case 'ssh': $authServer = substr($targetAuth, $dotPos+1); $properties['sshserver'] = "ssh_{$authServer}"; $this->writeLog("\nrun deploy process through " . $authType . " server: " . $authServer); break; case 'api': $authServer = substr($targetAuth, $dotPos+1); $properties['apiserver'] = "api_{$authServer}"; $this->writeLog("\nrun deploy process through " . $authType . " server: " . $authServer); break; default: throw new __Exception("Unknown auth type: $authType", SCRIPT_ERR_CONFIG); } } $startTime = time(); $oEvent->startDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $this->_runUser); $runOK = $this->_callPhing ( 'database', $target, $properties ); $endTime = time(); $elapsedSecs = $endTime - $startTime; $succFiles = 0; $isSucc = $runOK == DeploySource::STATUS_STOP_ERROR?false:true; $doSQL = file_get_contents($dbScriptDir . '/' . $doSQLFile); $undoSQL = file_get_contents($dbScriptDir . '/' . $undoSQLFile); $this->writeLog("\ndo SQL: \n" . $doSQL); $this->writeLog("\nundo SQL: \n" . $undoSQL); if (!$isSucc) { $this->writeProgress("部署到目标 {$target} 發生錯誤:" . $this->getErrorMsg(" "), self::STEP_PHING_DEPLOY); $returnStatus = DeploySource::STATUS_DONE_WARNING; }else{ // deploy succ or warning $this->writeProgress("成功部署到目标 {$target}", self::STEP_PHING_DEPLOY); $this->writeProgress("保存目标 {$target} 數據庫版本。。。", self::STEP_PHING_DEPLOY); $succFiles = $this->_saveTargetDatabaseRevisions($target, $currentDB, $upgradeDBRevision, $doSQL, $undoSQL); } // save deploy target status $oEvent->endDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $isSucc, $elapsedSecs, NULL, $this->getErrorMsg(" ")); $oTarget->lastDeploy($this->_runTargets[$target]['targetId'], $this->_runEvent, $this->_runUser); } break; default : throw new __Exception ( "Unknown deploying step $step", SCRIPT_ERR_CONFIG ); } if ($runOK == DeploySource::STATUS_STOP_ERROR) { break; } } return $runOK; } protected function _deploySVN(){ $runOK = false; $this->_runSettings['deploySteps'] = array(DeploySource::STEP_CHECK_REVISION, DeploySource::STEP_PHING_DEPLOY); $oEvent = new EventModel(); $oTarget = new TargetModel(); $logFile = $this->getLogFile($this->_runSettings['type']); foreach ( $this->_runSettings ['deploySteps'] as $step ) { switch ($step) { case DeploySource::STEP_CHECK_REVISION: $aTargetRevisions = $this->checkSVNRevision ( $this->_runSettings ); if($aTargetRevisions){ $runOK = DeploySource::STATUS_DONE_SUCCESS; }else{ $runOK = DeploySource::STATUS_STOP_ERROR; } break; case DeploySource::STEP_PHING_DEPLOY : $totalTargets = count($this->_runSettings['targets']); foreach ( $this->_runSettings ['targets'] as $target ) { $currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1; $this->writeProgress("开始部署到第 {$currentTargetSeq}/{$totalTargets}: {$target} 目标", self::STEP_PHING_DEPLOY); $currentSVN = $this->_targetSVNMaps[$target]['svn']; $currentSVNRevision = $this->_runSettings['targetRevisions'][$target]['revision']; $newRevision = $upgradeSVNRevision = $this->_runSettings['upgrade2Revision']; $this->writeLog("\nCurrent Target {$currentSVN} SVN Revision is {$currentSVNRevision}, will upgrade to new revision {$upgradeSVNRevision}.\n"); $targetAuth = $this->_targetSVNMaps[$target]['auth']; $targetRepository = $this->_targetSVNMaps[$target]['repository']; //$targetBranch = $this->_targetSVNMaps[$target]['branch']; $targetPath = $this->_targetSVNMaps[$target]['path']; if($targetAuth){ $dotPos = strpos($targetAuth, '.'); $authType = substr($targetAuth, 0, $dotPos); }else{ $authType = 'local'; } $properties = array(); $properties['build.token'] = $this->_runSettings['token']; $properties['svn.target'] = $currentSVN; $properties['svn.current_revision'] = $currentSVNRevision; $properties['svn.upgrade_revision'] = $upgradeSVNRevision; switch ($authType) { case 'local': /* global $config; $rep_conf = $config->findRepository($targetRepository); //$properties['svn.svnpath'] = $rep_conf->path . ($targetBranch?((($t=substr($rep_conf->path, -1))!='/' && $t!='\\')?'/':'') . $targetBranch : ''); $properties['svn.svnpath'] = $config->getSvnCommand(); $properties['svn.username'] = $rep_conf->username; $properties['svn.password'] = $rep_conf->password; $properties['svn.nocache'] = 'true'; $properties['svn.todir'] = $targetPath; $properties['svn.revision'] = $upgradeSVNRevision; $properties['svn.trustServerCert'] = $config->_svnTrustServerCert?'true':'false'; */ $startTag = "---START SVN UPDATE---"; $endTag = "----END SVN UPDATE----"; $this->writeLog($startTag."\n"); $this->writeLog("Check repository existence"); global $config; $cmdString = $config->getSvnCommand() . ' info "' . $targetPath . '" --xml 2>&1'; exec($cmdString, $cmdOutput, $cmdStatus); $cmdOutput = implode("", $cmdOutput); $cmdOutput = trim($cmdOutput); if(($pos=stripos($cmdOutput, "<?xml"))){ $error = substr($cmdOutput, 0, $pos); $notcopy = "is not a working copy"; if (stripos ( $error, $notcopy )) { // Checkout it $status = svncheckout($targetRepository, $targetPath, $upgradeSVNRevision, $this->_runSettings['branch'], $logFile); } else { $status = 0; $this->_setErrorMsg($error); } }else{ //$status = svnupdate($targetRepository, $targetPath, $upgradeSVNRevision, $logFile); $status = svnswitch($targetRepository, $targetPath, $upgradeSVNRevision, $this->_runSettings['branch'], $logFile); } $this->writeLog($endTag); $runOK = $status==0?DeploySource::STATUS_DONE_SUCCESS:DeploySource::STATUS_STOP_ERROR; $logContent = file_get_contents_utf8($logFile); $startPos = strrpos($logContent, $startTag); $endPos = strrpos($logContent, $endTag); if($startPos && $endPos && $endPos>$startPos){ $updateLog = substr($logContent, $startPos+strlen($startTag)+1, $endPos-$startPos-strlen($endTag)-strlen(PHP_EOL)); //debugLog($updateLog); // At revision 1095. // Updated to revision 1095. $pattern1 = '/At\srevision\s([0-9]+)\./'; $pattern2 = '/Updated\sto\srevision\s([0-9]+)\./'; $pattern3 = '/Checked\sout\srevision\s([0-9]+)\./'; if(preg_match($pattern1, $updateLog, $matches) || preg_match($pattern2, $updateLog, $matches) || preg_match($pattern2, $updateLog, $matches)){ //debugLog($maches); $newRevision = $matches[1]; } } break; case 'api' : $authServer = substr ( $targetAuth, $dotPos + 1 ); $properties ['apiserver'] = "api_{$authServer}"; $this->writeLog ( "\nrun deploy process through " . $authType . " server: " . $authServer ); break; default : throw new __Exception ( "Unknown auth type: $authType", SCRIPT_ERR_CONFIG ); } $startTime = time(); $oEvent->startDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $this->_runUser); //$runOK = $this->_callPhing ( 'svn', $target, $properties ); $endTime = time(); $elapsedSecs = $endTime - $startTime; $succFiles = 0; $isSucc = $runOK == DeploySource::STATUS_STOP_ERROR?false:true; if (!$isSucc) { $this->writeProgress("部署到目标 {$target} 發生錯誤:" . $this->getErrorMsg(" "), self::STEP_PHING_DEPLOY); $returnStatus = DeploySource::STATUS_DONE_WARNING; }else{ // deploy succ or warning $this->writeProgress("成功部署到目标 {$target}", self::STEP_PHING_DEPLOY); if(!strcasecmp('HEAD', $newRevision)){ $newRevision = 0; } $this->writeProgress("保存目标 {$target} SVN代码版本。。。", self::STEP_PHING_DEPLOY); $this->_saveTargetSVNRevisions($target, $currentSVN, $newRevision, $updateLog); } // save deploy target status $oEvent->endDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $isSucc, $elapsedSecs, NULL, $this->getErrorMsg(" ")); $oTarget->lastDeploy($this->_runTargets[$target]['targetId'], $this->_runEvent, $this->_runUser); } break; default : throw new __Exception ( "Unknown deploying step $step", SCRIPT_ERR_CONFIG ); } if ($runOK == DeploySource::STATUS_STOP_ERROR) { break; } } return $runOK; } public function runDeploy($aSettings){ $this->_runSettings($aSettings); $executor = $this->getExecutor(); $oEvent = new EventModel(); $oUser = new UserModel(); $serverHints = implode(" ", DeploySource::getTargetHints($this->_runSettings['targets'], $this->_runSettings['type'], true)); $endTime = $startTime = time(); $costTime = 0; // clear errors before deploy $this->clearErrors(); $this->writeLog("\n\nSTART DEPLOY"); $this->writeLog("\n[ $executor ] Try to deploy files to $serverHints on " . date('Y-m-d H:i:s', $startTime)); $this->writeLog("\nSource code is: repname=" . $this->_runSettings['repname'] . " branch=" . $this->_runSettings['branch'] . " revision=" . $this->_runSettings['revision']); // give me run more time if($this->_runSettings['maxExecuteTime']){ set_time_limit($this->_runSettings['maxExecuteTime']); $this->writeLog("\nMax execute time " . $this->_runSettings['maxExecuteTime'] . " seconds" ); } // 清空工作目錄 //if($this->_runSettings['type']=='code') $this->cleanWorkingDir(); $oEvent->startDeploy($this->_runEvent['eventId'], $this->_runUser, 0); $runOK = $this->_phingDeploy($this->_runSettings['type']); $endTime = time(); $costTime = $endTime - $startTime; $readableCostTime = time2readable($costTime); $this->writeLog("\n\nElapsed time: $costTime seconds"); switch ($runOK) { case DeploySource::STATUS_STOP_ERROR : $this->writeLog ( "\n\nSTOP DEPLOY, Error Occurred!" ); $this->writeProgress ( "發生錯誤,停止部署" ); $isSucc = false; break; case DeploySource::STATUS_DONE_WARNING: $this->writeLog ( "\n\nDEPLOY DONE, But Warnings Occurred!" ); $this->writeProgress ( "完成部署,有警告錯誤發生!" ); $isSucc = true; break; case DeploySource::STATUS_DONE_SUCCESS: $this->writeLog ( "\n\nSUCC DEPLOY!" ); $this->writeProgress ( "成功完成部署!耗時 $readableCostTime" ); $isSucc = true; break; default: throw new __Exception ( "Unknown deploying status $runOK", SCRIPT_ERR_CONFIG ); } // 部署结束 //$totalFiles = count($this->_runSettings ['parsedLogs']); $succFiles = $totalFiles = count($this->_runCodeRevisions); $oEvent->endDeploy($this->_runEvent['eventId'], $isSucc, $costTime, $succFiles, $this->getErrorMsg(" "), $totalFiles); // 记录部署日志 // 记录用户最后部署 $oUser->lastDeploy($this->_runUser['uid'], $this->_runEvent); // 清空工作目錄 if($this->_runSettings['type']=='code') $this->cleanWorkingDir (); return $runOK; } public function checkCodeRevision($aSettings){ $aR = array(); $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $this->writeLog("\n\nSTART check Code Revision"); $this->writeProgress("檢查代碼版本", DeploySource::STEP_CHECK_REVISION); if(!$this->_runSettings['parsedLogs']){ $this->writeProgress("分析代碼文件", DeploySource::STEP_CHECK_REVISION); $this->_runSettings['parsedLogs'] = $this->parseUpdateLogs($this->_runSettings['updateLogs']); $this->writeProgress("獲取SVN倉庫文件版本", DeploySource::STEP_CHECK_REVISION); $runOK = $this->checkRevisionFromSVN( $this->_runSettings ['parsedLogs'], $this->_runSettings ['repname'], $this->_runSettings ['branch'], $this->_runSettings ['revision'] ); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return false; } }else{ $this->_errorMsgs[] = "log files is empty"; return false; } $totalTargets = count($this->_runSettings['targets']); // Target Database Revision foreach($aSettings['targets'] as $target){ $currentTargetSeq = array_search($target, $aSettings['targets'])+1; $this->writeProgress("开始檢查第 {$currentTargetSeq}/{$totalTargets}: {$target} 代碼版本", DeploySource::STEP_CHECK_REVISION); $aRevision = $this->_checkTargetCodeRevision($target); $aR['targetRevisions'][$target] = $aRevision; // 同時保存到$this->_runSettings中 $this->_runSettings['targetRevisions'][$target] = $aRevision; } //$this->writeLog(print_r($this->_runSettings['targetRevisions'], true), 'debug'); return $aR; } protected function _checkTargetCodeRevision($target){ static $oRevision = null, $aFiles = array(); if(!$oRevision){ $oRevision = new CodeRevisionModel(); } if(!$aFiles){ $aFiles = array_keys($this->_runCodeRevisions); } $aReturn = array(); $aTargetRevisions = $oRevision->getFileRevisions($aFiles, $target); //$this->writeLog(print_r($aTargetRevisions, true), 'debug'); foreach($this->_runCodeRevisions as $file => $svnRev){ $aReturn[$file] = array( 'update' => array('revision' => $svnRev['revision'], 'author' => $svnRev['author'], 'date' => $svnRev['date']), 'target' => isset($aTargetRevisions[$file][$target]) ?array('revision' => $aTargetRevisions[$file][$target]['revision'], 'author' => $aTargetRevisions[$file][$target]['revAuthor'], 'date' => date('Y-m-d H:i:s', $aTargetRevisions[$file][$target]['revTime'])) :array(), ); $sRev = $aReturn[$file]['update']['revision']; $tRev = $aReturn[$file]['target']['revision']; if($sRev && $tRev){ $aReturn[$file]['compare'] = $sRev>$tRev?'older':($sRev<$tRev?'newer':'update'); }else{ $aReturn[$file]['compare'] = 'unknown'; } $aReturn[$file]['differ'] = $sRev - $tRev; } return $aReturn; } public function checkDBRevision($aSettings){ $aR = array(); $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $this->writeLog("\n\nSTART check DB Revision"); $this->writeProgress("檢查數據庫版本", DeploySource::STEP_CHECK_REVISION); $totalTargets = count($this->_runSettings['targets']); // Target Database Revision foreach($aSettings['targets'] as $target){ $targetAuth = $this->_targetDatabaseMaps[$target]['auth']; $targetDatabase = $this->_targetDatabaseMaps[$target]['database']; $currentTargetSeq = array_search($target, $aSettings['targets'])+1; $this->writeProgress("开始檢查第 {$currentTargetSeq}/{$totalTargets}: [{$target}] $targetDatabase 數據庫版本", DeploySource::STEP_CHECK_REVISION); $authType = ''; if($targetAuth){ $authType = substr($targetAuth, 0, strpos($targetAuth, '.')); } $func = '_checkTargetDBRevision' . ($authType?'_' . $authType : ''); $aRevision = $this->$func($target); if(!is_array($aRevision) && !in_array($aRevision, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ $this->writeLog("\nTarget[{$target}]: error occured while check $targetDatabase : " . $this->getErrorMsg()); return false; } $this->writeLog("\nTarget[{$target}]: $targetDatabase Rev is: " . $aRevision['revision']); $this->writeProgress("Target[{$target}]: $targetDatabase 數據庫版本為: " . $aRevision['revision'], DeploySource::STEP_CHECK_REVISION); $aR['targetRevisions'][$target] = $aRevision; // 同時保存到$this->_runSettings中 $this->_runSettings['targetRevisions'][$target] = $aRevision; } $this->writeLog("\n\nSucc End check DB Revision"); return $aR; } protected function _checkTargetDBRevision_ssh($target){ $targetDatabase = $this->_targetDatabaseMaps[$target]['database']; $targetAuth = $this->_targetDatabaseMaps[$target]['auth']; $sshServer = substr($targetAuth, 3+1); if(!$sshServer){ throw new __Exception("Couldn't found ssh server", SCRIPT_ERR_CONFIG); } $workingDir = $this->getWorkingDir(); // Gen SQL //$sql = "SELECT MAX( `change_number` ) AS revision FROM `changelog`;"; //$sql = "SELECT * FROM `changelog`;"; $change_table = DConfig::getConfig(array('deploy.target_database_maps', $target, 'changelog_table')); $sql = "SELECT * FROM `" . ($change_table?$change_table:'changelog') . "`"; $sqlFile = "qdbrev_" . $this->genToken() . ".sql"; file_put_contents($workingDir . '/' . $sqlFile, $sql); $scp2Dir = DConfig::getConfig(array('deploy.ssh_server_maps', $sshServer, 'tmpdir')); $scp2File = $scp2Dir . "/" . $sqlFile; if(!$scp2Dir){ throw new __Exception("Couldn't found ssh server tmpdir", SCRIPT_ERR_CONFIG); } $revisionFile = $workingDir . "/" . $target . "_db_revision.xml"; $properties = array(); $properties['sshserver'] = "ssh_{$sshServer}"; $properties['scp.sourceFile'] = $workingDir . "/" . $sqlFile; //$properties['scp.sourceDir'] = false; $properties['scp.toDir'] = $scp2Dir; //$properties['scp.autocreate'] = 'false'; //$properties['ssh.command'] = 'ls'; // SCP transfer sql file to server $runOK = $this->_callPhing('scp', $target, $properties); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return $runOK; } // Query DB Revision $properties = array(); $properties['sshserver'] = "ssh_{$sshServer}"; $properties['dbserver'] = "db_{$target}"; $properties['batch_sql_file'] = $scp2File; $properties['revision_file'] = $revisionFile; $runOK = $this->_callPhing('ssh_query_db_revision', $target, $properties); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return $runOK; } // Remove Server SQL File /* $properties = array(); $properties['sshserver'] = "ssh_{$sshServer}"; $properties['ssh.command'] = "rm -f $scp2File"; $runOK = $this->_callPhing('ssh', $target, $properties); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return $runOK; }*/ if(!file_exists($revisionFile)){ throw new __Exception("Revision file not exist: $revisionFile", SCRIPT_ERR_CONFIG); } $revision_result = file_get_contents($revisionFile); $dbUpdateLogs = mysqlxml2array($revision_result); $dbRevision = 0; $dbUpdateTime = 0; foreach($dbUpdateLogs as $log){ if($log['change_number']>$dbRevision){ $dbRevision = $log['change_number']; $dbUpdateTime = $log['complete_dt']; } } return array('revision' => $dbRevision, 'updated' => $dbUpdateTime, 'logs' => $dbUpdateLogs); } protected function _checkTargetDBRevision_api($target){ $targetDatabase = $this->_targetDatabaseMaps[$target]['database']; $targetAuth = $this->_targetDatabaseMaps[$target]['auth']; $apiServer = substr($targetAuth, 3+1); /* if(!$apiServer){ throw new __Exception("Couldn't found api server", SCRIPT_ERR_CONFIG); }*/ $workingDir = $this->getWorkingDir(); $revisionFile = $workingDir . "/" . $target . "_db_revision.json"; // Query DB Revision /* $properties = array(); $properties['apiserver'] = "api_{$apiServer}"; $properties['revision_file'] = $revisionFile; $runOK = $this->_callPhing('api_query_db_revision', $target, $properties); */ $properties = array(); if(!empty($apiServer) && !empty($this->_apiServerMaps[$apiServer])){ foreach($this->_apiServerMaps[$apiServer] as $p=>$v){ if(substr($p, 0, 12)!='httprequest.'){ $p = 'httprequest.' . $p; } $properties[$p] = $v; } } if(!empty($this->_targetDatabaseMaps[$target]['auth.options'])){ foreach($this->_targetDatabaseMaps[$target]['auth.options'] as $p=>$v){ if(substr($p, 0, 12)!='httprequest.'){ $p = 'httprequest.' . $p; } $properties[$p] = $v; } } //print_r($properties);exit; $properties['httprequest.url'] = url_append_param($properties['httprequest.url'], array('act'=>'check')); $properties['httprequest.responseContent'] = $revisionFile; $runOK = $this->_callPhing('http_request', $target, $properties); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return $runOK; } if(!file_exists($revisionFile)){ throw new __Exception("Revision file not exist: $revisionFile", SCRIPT_ERR_CONFIG); } $revision_result = file_get_contents($revisionFile); require_once 'Services/JSON.php'; $json = new Services_JSON (); $dbUpdateLogs = (array) $json->decode($revision_result); if(!$dbUpdateLogs){ $this->_setErrorMsg($revision_result); return false; } $dbUpdateLogs = $dbUpdateLogs?$dbUpdateLogs:array(); $dbRevision = 0; $dbUpdateTime = 0; foreach($dbUpdateLogs as $log){ $log = (array) $log; if($log['change_number']>$dbRevision){ $dbRevision = $log['change_number']; $dbUpdateTime = $log['complete_dt']; } } return array('revision' => $dbRevision, 'updated' => $dbUpdateTime, 'logs' => $dbUpdateLogs); } public function backupDB($aSettings){ $aR = array(); $this->setToken($aSettings['token']); $this->writeLog("\n\nSTART backup DB"); $this->writeProgress("備份數據庫", self::STEP_BACKUP_BEFORE); $totalTargets = count($this->_runSettings['targets']); // Target Database Revision foreach($aSettings['targets'] as $target){ $targetDatabase = $this->_targetDatabaseMaps[$target]['database']; $currentTargetSeq = array_search($target, $aSettings['targets'])+1; $this->writeProgress("开始備份第 {$currentTargetSeq}/{$totalTargets}: [{$target}] $targetDatabase 數據庫", self::STEP_BACKUP_BEFORE); $this->writeLog("\nTry to backup Target $target: $targetDatabase"); $runOK = $this->_backupDB($target); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ break; } } return $runOK; } protected function _backupDB($target){ $sshServer = ''; if (isset ( $this->_targetDatabaseMaps [$target] ['auth'] )) { $targetAuth = $this->_targetDatabaseMaps [$target] ['auth']; if(substr($targetAuth, 0, 4)=='ssh.'){ $sshServer = substr ( $targetAuth, 3 + 1 ); } } $properties = array(); $properties['dbserver'] = "db_{$target}"; if($sshServer){ $properties['sshserver'] = "ssh_{$sshServer}"; } // SCP transfer sql file to server $runOK = $this->_callPhing('backup_db', $target, $properties); return $runOK; } protected function _runSettings($aSettings){ global $_LOGIN_USER; $this->_runLED = true; $this->_runSettings = $aSettings; $this->_runUser = array(); $this->_runTargets = array(); $this->_runEvent = array(); $projectInfo = array(); $issueInfo = array(); // 添加用户 $oUser = new UserModel(); $userName = $this->getExecutor(); /* if($userName && !($userInfo=$oUser->userExists($userName))){ $userInfo = array(); $userInfo['user'] = $userName; $userInfo['password'] = md5('sandbox'); $userInfo['uid'] = $oUser->add($userInfo); }*/ $userInfo = $this->Runder->HS->getUser();; $this->_runUser = $userInfo; // 添加目标 $oTarget = new TargetModel(); foreach($aSettings['targets'] as $target){ if($target && !($targetInfo=$oTarget->targetExists($target))){ // 添加新target $configIndex = 'deploy.target_' . $aSettings['type'] . '_maps'; $targetInfo = array(); $targetInfo['targetName'] = DConfig::getConfig(array($configIndex, $target, 'desc')); $targetInfo['target'] = $target; $targetInfo['targetType'] = $aSettings['type']; $targetInfo['updateUid'] = $targetInfo['createUid'] = $userInfo['uid']; $targetInfo['updateTime'] = $targetInfo['createTime'] = time(); $targetInfo['targetId'] = $oTarget->add($targetInfo); } $this->_runTargets[$target] = $targetInfo; } // 添加事件 $oEvent = new EventModel(); $token = $this->_runSettings['token']; if($token && !($eventInfo=$oEvent->eventExists($token))){ $eventInfo = array(); $eventInfo['issueId'] = $issueInfo['issueId']; $eventInfo['projectId'] = $projectInfo['projectId']; $eventInfo['eventTitle'] = $aSettings['eventTitle']; $eventInfo['eventToken'] = $aSettings['token']; $eventInfo['eventType'] = $aSettings['type']; $eventInfo['sourceRepository'] = $aSettings['repname']; $eventInfo['sourceBranch'] = $aSettings['branch']; $eventInfo['sourcePath'] = $aSettings['path']; $eventInfo['deployStatus'] = DEPLOY_STATUS_WAIT; $eventInfo['issueCode'] = $aSettings['issueCode']; $eventInfo['fileLogs'] = $aSettings['updateLogs']; $eventInfo['updateUid'] = $eventInfo['createUid'] = $userInfo['uid']; $eventInfo['updateTime'] = $eventInfo['createTime'] = time(); $eventInfo['eventId'] = $oEvent->add($eventInfo); } // 添加目标事件 if($this->_runTargets && $eventInfo){ foreach($this->_runTargets as $targetInfo){ if(!($eventTargetInfo=$oEvent->targetExists($eventInfo['eventId'], $targetInfo['targetId']))){ $eventTargetInfo = array(); $eventTargetInfo['eventId'] = $eventInfo['eventId']; $eventTargetInfo['targetId'] = $targetInfo['targetId']; $eventTargetInfo['issueId'] = $issueInfo['issueId']; $eventTargetInfo['target'] = $targetInfo['target']; $eventTargetInfo['deployStatus'] = DEPLOY_STATUS_WAIT; $oEvent->addTarget($eventTargetInfo); } } } $this->_runEvent = $eventInfo; } protected function _saveTargetDatabaseRevisions($target, $database, $newRev, $doSQL=NULL, $undoSQL=NULL){ static $oRevision = null; $user = $this->getExecutor(); $nowTime = time(); if(!$oRevision){ $oRevision = new DatabaseRevisionModel(); } $this->writeLog("\n\nSTART save database revisions for target: $target"); $data = array( 'db' => $database, 'target' => $target, 'targetId' => $this->_runTargets[$target]['targetId'], 'revision' => $newRev, 'revAuthor' => $user, 'revTime' => $nowTime, 'updateUid' => $this->_runUser['uid'], 'updateTime' => $nowTime, 'lastEventId' => $this->_runEvent['eventId'], ); $aPrimKeys = array( 'db' => $database, 'target' => $target, ); $aOldRev = array(); if(!($aOldRev=$oRevision->revisionExists($aPrimKeys))){ $data['createUid'] = $data['updateUid']; $data['createTime'] = $data['updateTime']; $data['createRev'] = $data['revision']; $oRevision->add($data); }elseif ($aOldRev ['revision'] != $newRev) { $oRevision->save($data); } // save to log // log file with new revision if ($aOldRev ['revision'] != $newRev) { $logData = array ( 'eventId' => $this->_runEvent ['eventId'], 'targetId' => $this->_runTargets [$target] ['targetId'], 'uid' => $this->_runUser ['uid'], 'user' => $user, 'target' => $target, 'time' => $nowTime, 'db' => $database, 'revision' => $data['revision'], 'revAuthor' => $data['revAuthor'], 'revTime' => $data['revTime'], 'oldRev' => $aOldRev['revision'], 'doSql' => $doSQL, 'undoSql' => $undoSQL, ); $oRevision->addLog ( $logData ); $this->writeLog("\n database {$database} new revision is {$newRev}"); }else{ $this->writeLog("\n database {$database} has the same revision as old rev: {$aOldRev['revision']}"); } $this->writeLog("\n\nSucc saved database revisions for target: {$target}"); } protected function _saveTargetSVNRevisions($target, $svn, $newRev, $updateLog){ static $oRevision = null; $user = $this->getExecutor(); $nowTime = time(); if(!$oRevision){ $oRevision = new SVNRevisionModel(); } $this->writeLog("\n\nSTART save svn revisions for target: $target"); $data = array( 'svn' => $svn, 'target' => $target, 'targetId' => $this->_runTargets[$target]['targetId'], 'revision' => $newRev, 'revAuthor' => $user, 'revTime' => $nowTime, 'updateUid' => $this->_runUser['uid'], 'updateTime' => $nowTime, 'lastEventId' => $this->_runEvent['eventId'], ); $aPrimKeys = array( 'svn' => $svn, 'target' => $target, ); $aOldRev = array(); if(!($aOldRev=$oRevision->revisionExists($aPrimKeys))){ $data['createUid'] = $data['updateUid']; $data['createTime'] = $data['updateTime']; $data['createRev'] = $data['revision']; $oRevision->add($data); }elseif ($aOldRev ['revision'] != $newRev) { $oRevision->save($data); } // save to log // log file with new revision if ($aOldRev ['revision'] != $newRev) { $logData = array ( 'eventId' => $this->_runEvent ['eventId'], 'targetId' => $this->_runTargets [$target] ['targetId'], 'uid' => $this->_runUser ['uid'], 'user' => $user, 'target' => $target, 'time' => $nowTime, 'svn' => $svn, 'revision' => $data['revision'], 'revAuthor' => $data['revAuthor'], 'revTime' => $data['revTime'], 'oldRev' => $aOldRev['revision'], 'updateLog' => $updateLog, ); $oRevision->addLog ( $logData ); $this->writeLog("\n svn {$svn} new revision is {$newRev}"); }else{ $this->writeLog("\n svn {$svn} has the same revision as old rev: {$aOldRev['revision']}"); } $this->writeLog("\n\nSucc saved database revisions for target: {$target}"); } protected function _saveTargetCodeRevisions($target){ static $oRevision = null; $user = $this->getExecutor(); $nowTime = time(); if(!$oRevision){ $oRevision = new CodeRevisionModel(); } $this->writeLog("\n\nSTART save code files revisions for target: $target"); $totalFiles = 0; $newFiles = 0; $updateFiles = 0; $this->_writeLog(print_r($this->_runCodeRevisions, true), 'debug'); foreach($this->_runCodeRevisions as $file=>$revInfo){ $data = array( 'file' => $file, 'target' => $target, 'targetId' => $this->_runTargets[$target]['targetId'], 'revision' => $revInfo['revision'], 'revAuthor' => $revInfo['author'], 'revTime' => $revInfo['committime'], 'updateUid' => $this->_runUser['uid'], 'updateTime' => $nowTime, 'lastEventId' => $this->_runEvent['eventId'], ); $aPrimKeys = array( 'file' => $file, 'target' => $target, ); $aOldRev = array(); if(!($aOldRev=$oRevision->revisionExists($aPrimKeys))){ $data['createUid'] = $data['updateUid']; $data['createTime'] = $data['updateTime']; $data['createRev'] = $data['revision']; $oRevision->add($data); $newFiles++; }elseif ($aOldRev ['revision'] != $revInfo['revision']) { $oRevision->save($data); $updateFiles++; } // save to log // log file with new revision if ($aOldRev ['revision'] != $revInfo['revision']) { $logData = array ( 'eventId' => $this->_runEvent ['eventId'], 'targetId' => $this->_runTargets [$target] ['targetId'], 'uid' => $this->_runUser ['uid'], 'user' => $user, 'target' => $target, 'time' => $nowTime, 'file' => $file, 'revision' => $revInfo['revision'], 'revAuthor' => $revInfo['author'], 'revTime' => $revInfo['committime'], 'oldRev' => $aOldRev['revision'], ); $oRevision->addLog ( $logData ); $this->writeLog("\n file {$totalFiles}. {$file} new revision is {$revInfo['revision']}"); }else{ $this->writeLog("\n file {$totalFiles}. {$file} has the same revision as old file: {$aOldRev['revision']}"); } $totalFiles++; } $this->writeLog("\n\nSucc saved code files revisions for target: {$target}, total files: {$totalFiles}, new added {$newFiles}, old updated {$updateFiles}"); return $totalFiles; } public function checkSVNRevision($aSettings){ $aR = array(); $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $this->writeLog("\n\nSTART check SVN Revision"); $this->writeProgress("檢查SVN代码版本", DeploySource::STEP_CHECK_REVISION); $totalTargets = count($this->_runSettings['targets']); // Target SVN Revision foreach($aSettings['targets'] as $target){ $targetAuth = $this->_targetSVNMaps[$target]['auth']; $targetCopy = $this->_targetSVNMaps[$target]['svn']; $currentTargetSeq = array_search($target, $aSettings['targets'])+1; $this->writeProgress("开始檢查第 {$currentTargetSeq}/{$totalTargets}: [{$target}] $targetCopy SVN代码版本", DeploySource::STEP_CHECK_REVISION); $authType = ''; if($targetAuth){ $authType = substr($targetAuth, 0, strpos($targetAuth, '.')); } $func = '_checkTargetSVNRevision' . ($authType?'_' . $authType : ''); $aRevision = $this->$func($target); if(!is_array($aRevision) && !in_array($aRevision, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ $this->writeLog("\nTarget[{$target}]: error occured while check $targetCopy : " . $this->getErrorMsg()); return false; } $this->writeLog("\nTarget[{$target}]: $targetCopy Rev is: " . $aRevision['revision']); $this->writeProgress("Target[{$target}]: $targetCopy SVN代码版本为: " . $aRevision['revision'], DeploySource::STEP_CHECK_REVISION); $aR['targetRevisions'][$target] = $aRevision; // 同時保存到$this->_runSettings中 $this->_runSettings['targetRevisions'][$target] = $aRevision; } $this->writeLog("\n\nSucc End check SVN Revision"); return $aR; } protected function _checkTargetSVNRevision_api($target){ $targetSVN = $this->_targetSVNMaps[$target]['svn']; $targetAuth = $this->_targetSVNMaps[$target]['auth']; $apiServer = substr($targetAuth, 3+1); /* if(!$apiServer){ throw new __Exception("Couldn't found api server", SCRIPT_ERR_CONFIG); }*/ $workingDir = $this->getWorkingDir(); $revisionFile = $workingDir . "/" . $target . "_svn_revision.json"; // Query DB Revision /* $properties = array(); $properties['apiserver'] = "api_{$apiServer}"; $properties['revision_file'] = $revisionFile; $runOK = $this->_callPhing('api_query_db_revision', $target, $properties); */ $properties = array(); if(!empty($apiServer) && !empty($this->_apiServerMaps[$apiServer])){ foreach($this->_apiServerMaps[$apiServer] as $p=>$v){ if(substr($p, 0, 12)!='httprequest.'){ $p = 'httprequest.' . $p; } $properties[$p] = $v; } } if(!empty($this->_targetSVNMaps[$target]['auth.options'])){ foreach($this->_targetSVNMaps[$target]['auth.options'] as $p=>$v){ if(substr($p, 0, 12)!='httprequest.'){ $p = 'httprequest.' . $p; } $properties[$p] = $v; } } //print_r($properties);exit; $properties['httprequest.url'] = url_append_param($properties['httprequest.url'], array('act'=>'svn', 'cmd'=>'info')); $properties['httprequest.responseContent'] = $revisionFile; $runOK = $this->_callPhing('http_request', $target, $properties); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return $runOK; } if(!file_exists($revisionFile)){ throw new __Exception("Revision file not exist: $revisionFile", SCRIPT_ERR_CONFIG); } $revision_result = file_get_contents($revisionFile); require_once 'Services/JSON.php'; $json = new Services_JSON (); $svnRevision = (array) $json->decode($revision_result); //print_r($dbUpdateLogs);exit; if(!$svnRevision){ $this->_setErrorMsg($revision_result); return false; } return $svnRevision; } public function migrateVerison($aSettings){ $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $this->writeLog("\n\nSTART migrate version"); $this->writeProgress("迁移代码环境", DeploySource::STEP_MIGRATE_VERSION); $target = $aSettings['migrate']; $targetAuth = $this->_targetMigrateMaps[$target]['auth']; $authType = ''; if($targetAuth){ $authType = substr($targetAuth, 0, strpos($targetAuth, '.')); } $func = '_migrate' . ($authType?'_' . $authType : ''); $aR = $this->$func($target); if(!is_array($aR) && !in_array($aR, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ $this->writeLog("\nError occured while migrate $target : " . $this->getErrorMsg()); return false; } $this->writeLog("\nMigrate {$target} result: " . $aR['msg']); $this->writeProgress("迁移{$target}返回结果: " . $aR['msg'], DeploySource::STEP_MIGRATE_VERSION); $this->writeLog("\n\nSucc End migrate Version"); return $aR['msg']; } protected function _migrate_api($target){ $targetMigrate = $this->_targetMigrateMaps[$target]['migrate']; $targetAuth = $this->_targetMigrateMaps[$target]['auth']; $apiServer = substr($targetAuth, 3+1); /* if(!$apiServer){ throw new __Exception("Couldn't found api server", SCRIPT_ERR_CONFIG); }*/ $workingDir = $this->getWorkingDir(); $revisionFile = $workingDir . "/" . $target . "_migrate.json"; // Query DB Revision /* $properties = array(); $properties['apiserver'] = "api_{$apiServer}"; $properties['revision_file'] = $revisionFile; $runOK = $this->_callPhing('api_query_db_revision', $target, $properties); */ $properties = array(); if(!empty($apiServer) && !empty($this->_apiServerMaps[$apiServer])){ foreach($this->_apiServerMaps[$apiServer] as $p=>$v){ if(substr($p, 0, 12)!='httprequest.'){ $p = 'httprequest.' . $p; } $properties[$p] = $v; } } if(!empty($this->_targetMigrateMaps[$target]['auth.options'])){ foreach($this->_targetMigrateMaps[$target]['auth.options'] as $p=>$v){ if(substr($p, 0, 12)!='httprequest.'){ $p = 'httprequest.' . $p; } $properties[$p] = $v; } } //print_r($properties);exit; $properties['httprequest.url'] = url_append_param($properties['httprequest.url'], array('act'=>'migrate', 'migrate'=>$targetMigrate)); $properties['httprequest.responseContent'] = $revisionFile; $runOK = $this->_callPhing('http_request', $target, $properties); if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){ return $runOK; } if(!file_exists($revisionFile)){ throw new __Exception("JSON file not exist: $revisionFile", SCRIPT_ERR_CONFIG); } $revision_result = file_get_contents($revisionFile); require_once 'Services/JSON.php'; $json = new Services_JSON (); $svnRevision = (array) $json->decode($revision_result); //print_r($dbUpdateLogs);exit; if(!$svnRevision){ $this->_setErrorMsg($revision_result); return false; } return $svnRevision; } }
DeploySource.class.php 部署
<?php defined('IN_HEAVEN') or die('Hacking Attempt!'); /** * DeploySource * * PHP version 5 * * @category 4SWeb * @package Admin * @subpackage Custom * @author zhongyiwen * @version SVN: $Id: DeploySource.class.php 67 2014-11-25 15:12:29Z blin.z $ */ class DeploySource{ protected $_logDir; protected $_enableLog = true; protected $_targetCodeMaps = array(); protected $_targetDatabaseMaps = array(); protected $_targetSVNMaps = array(); protected $_targetMigrateMaps = array(); protected $_sshServerMaps = array(); protected $_ftpServerMaps = array(); protected $_errorMsgs=array(); protected $_errorFiles=array(); protected $_errorTargets=array(); protected $_finishTargets = array(); protected $_runLED = false; protected $_runSettings = array(); protected $_runCodeRevisions = array(); protected $_dumpDir; /** * STEP consts */ const STEP_SVN_COPY = 'SVN_COPY'; const STEP_CHECK_REVISION = 'CHECK_REVISION'; const STEP_PHING_DEPLOY = 'PHING_DEPLOY'; const STEP_VERIFY_DEPLOY = 'VERIFY_DEPLOY'; const STEP_BACKUP_BEFORE = 'BACKP_BEFORE'; const STEP_MIGRATE_VERSION = 'MIGRATE_VERSION'; /** * Status consts */ const STATUS_DONE_SUCCESS = 'DONE_SUCCESS'; const STATUS_DONE_WARNING = 'DONE_WARNING'; const STATUS_STOP_ERROR = 'STOP_ERROR'; public $Runder; /** * Regx patterns for parsing update logs * @var array */ protected $_attributePatterns = array( 'revision' => 'r\s*([0-9]+|HEAD)', ); public function __construct($runder=null){ $this->_logDir = DConfig::getConfig('deploy.log_dir'); $this->_dumpDir = DConfig::getConfig('deploy.dump_working_dir'); $this->_targetCodeMaps = DConfig::getConfig('deploy.target_code_maps'); $this->_targetDatabaseMaps = DConfig::getConfig('deploy.target_database_maps'); $this->_targetSVNMaps = DConfig::getConfig('deploy.target_svn_maps'); $this->_targetMigrateMaps = DConfig::getConfig('deploy.target_migrate_maps'); $this->_ftpServerMaps = DConfig::getConfig('deploy.ftp_server_maps'); $this->_sshServerMaps = DConfig::getConfig('deploy.ssh_server_maps'); $this->_apiServerMaps = DConfig::getConfig('deploy.api_server_maps'); $this->Runder = $runder; } public function getErrorMsg($delimiter=" "){ return ($this->_errorMsgs && is_array($this->_errorMsgs))? implode($delimiter , $this->_errorMsgs) : $this->_errorMsgs; } public function getErrorMsgs(){ return $this->_errorMsgs; } public function getErrorFiles(){ return $this->_errorFiles; } public function getErrorTargets(){ return $this->_errorTargets; } public function getFinishTargets(){ return $this->_finishTargets; } protected function _setErrorMsg($errorMsg){ $this->_errorMsgs[] = $errorMsg; } protected function _setErrorFile($file, $status='', $step='', $target='', $remark=''){ $aUpdates = array(); if(is_array($file)) $aUpdates = $file; else $aUpdates['file'] = $file; if($status) $aUpdates ['status'] = $status; if($step) $aUpdates ['step'] = $step; if($target) $aUpdates ['target'] = $target; if($remark) $aUpdates ['remark'] = $remark; $this->_errorFiles[] = $aUpdates; } protected function _setErrorTarget($target, $status='', $step='', $remark=''){ $aUpdates = array(); if(is_array($target)) $aUpdates = $target; else $aUpdates['target'] = $target; if($status) $aUpdates ['status'] = $status; if($step) $aUpdates ['step'] = $step; if($remark) $aUpdates ['remark'] = $remark; $this->_errorTargets[] = $aUpdates; } public function clearErrors(){ $this->clearErrorMsgs(); $this->clearErrorFiles(); $this->clearErrorTargets(); } public function clearErrorMsgs(){ $this->_errorMsgs = ''; } public function clearErrorFiles(){ $this->_errorFiles = array(); } public function clearErrorTargets(){ $this->_errorTargets = array(); } protected function _getFullPath($file, $dir=''){ if(!$dir){ $dir = DEPLOY_ROOT_DIR; } if(substr($file, 0, 1)=='/' || substr($file, 0, 1)=='\\' || preg_match('/^[a-zA-Z]\:/', $file)){ return $file; }else{ return $dir . "/" . $file; } } public function getLogFile($type=''){ $file = $this->getToken() . ($type?'_' . $type : '') . ".log"; return $this->_getFullPath($file, $this->_logDir); } protected function _writeLog($content, $type='', $keepOpen=true){ static $_fps=array(); $file = $this->getLogFile($type); if(!$this->_enableLog || !$file){ return false; } if (!isset($_fps)) $_fps = array(); $signature = serialize($file); if (!isset($_fps[$signature])) { $_fps[$signature] = fopen($file, 'a'); } $fp = $_fps[$signature]; if(!$fp){ throw new __Exception("Couldn't write log file: $file ", SCRIPT_ERR_CONFIG); } if (flock($fp, LOCK_EX)) { // do an exclusive lock fwrite($fp, $content); flock($fp, LOCK_UN); // release the lock } else { throw new __Exception("Couldn't lock the file: $file", SCRIPT_ERR_CONFIG); } if(!$keepOpen){ fclose($fp); unset($_fps[$signature]); } return true; } public function dumpDirFromSVN($repname, $dir, $revision='HEAD', $save2Dir='') { global $peg, $rep; $save2Dir = !empty($save2Dir)?$save2Dir:$this->getWorkingDir(); $dir = str_replace('\\', '/', $dir); $leadingChar = substr($dir, 0, 1); if($leadingChar!='/'){ $dir = '/' . $dir; } $svnrep = new SVNRepository ( $rep ); // 加--force //$revision .= " --force"; $result = $svnrep->exportRepositoryPath($dir, $save2Dir, $revision, $peg); return $result==0?true:false; } public function dumpFileFromSVN($repname, $file, $revision = 'HEAD', $save2File=false) { global $peg, $rep; static $svnrep = null; $file = str_replace('\\', '/', $file); $leadingChar = substr($file, 0, 1); if($leadingChar!='/'){ $file = '/' . $file; } if(!$svnrep){ $svnrep = new SVNRepository ( $rep ); } ob_start (); $svnrep->getFileContents ( $file, '', $revision, $peg, '', 'no' ); $content = ob_get_contents (); ob_end_clean (); $saveSucc = false; if($content && $save2File){ $saveSucc = file_put_contents($save2File, $content); } return !$save2File?$content:$saveSucc; } protected function _isFileLog($file){ $tag = substr($file, -1); return $tag!='/'; } public function dumpCodeFromSVN($aParsedLogs, $repname, $branch, $defaultRevision='HEAD') { global $peg, $rep; $aErrorLogs = array(); $workingDir = $this->getWorkingDir('code', true); $svnrep = new SVNRepository ( $rep ); $this->writeLog("\n\nSTART dump files from SVN repository $repname, branch $branch"); $total = 0; $succ = 0; $fail = 0; $fileCounter = 0; $totalFiles = count($aParsedLogs); foreach ( $aParsedLogs as $i=>$log ) { $rev = $defaultRevision; if (is_array ( $log )) { $file = $log ['file']; if ($log ['revision']) { $rev = $log ['revision']; } } else { $file = $log; } if (! $file) { continue; } $total++; $aUpdates = array (); $aUpdates ['revision'] = $rev; if ($this->_isFileLog ( $file )) { $aUpdates ['file'] = $file; $path = "/" . $branch . "/" . $file; $this->writeLog ( "\n[svndump] Will get file {$file} from repository {$repname}, revision {$rev}" ); $content = $this->dumpFileFromSVN ( $repname, $path, $rev, false ); $fileCounter ++; $this->writeProgress ( "從SVN倉庫獲取第 $fileCounter/$totalFiles 個文件", self::STEP_SVN_COPY ); if ($content) { $aUpdates ['dumpFile'] = $this->saveCodeFile ( $file, $content ); $this->writeLog ( ", Successfully save as {$aUpdates['dumpFile']}" ); $succ ++; } else { $aUpdates ['step'] = self::STEP_SVN_COPY; $aUpdates ['target'] = 'ALL'; $aUpdates ['status'] = "SVN Error:無法從SVN獲取該文件"; $aUpdates ['remark'] = "請檢查文件是否存在,文件名是否正確,文件版本是否存在(如高於SVN版本)"; $this->writeLog ( ", Failed: couldn't obtain this file" ); $fail ++; $this->writeLog ( "\nStop dump files from SVN, couldn't obtain $file, please check file name correct or file exists" ); $this->_setErrorMsg ( "SVN Error:無法從SVN獲取文件 {$file},請檢查文件是否存在,或者文件名是否正確" ); $this->_setErrorFile ( $aUpdates ); return DeploySource::STATUS_STOP_ERROR; } }else{ $aUpdates ['dir'] = $file; $path = "/" . $branch . "/" . $file; $this->writeLog ( "\n[svndump] Will get dir {$file} from repository {$repname}, revision {$rev}" ); $tmpFileDir = ''; if(substr($file, 0, 1)=='/'){ $tmpFileDir = substr($file, 1); }else{ $tmpFileDir = $file; } $fileFolder = substr($tmpFileDir, 0, strrpos($tmpFileDir, '/')); $save2Dir = $workingDir . "/" . $fileFolder; $retcode = $this->dumpDirFromSVN( $repname, $path, $rev, $save2Dir); $aUpdates['dumpFile'] = $save2Dir; $fileCounter ++; $this->writeProgress ( "從SVN倉庫獲取第 $fileCounter/$totalFiles 個文件", self::STEP_SVN_COPY ); if ($retcode) { $this->writeLog ( ", Successfully save as {$aUpdates['dumpFile']}" ); $succ ++; } else { $aUpdates ['step'] = self::STEP_SVN_COPY; $aUpdates ['target'] = 'ALL'; $aUpdates ['status'] = "SVN Error:無法從SVN獲取該目錄"; $aUpdates ['remark'] = "請檢查目錄是否存在,或者目錄名是否正確"; $this->writeLog ( ", Failed: couldn't obtain this file" ); $fail ++; $this->writeLog ( "\nStop dump files from SVN, couldn't obtain $file, please check directory name correct or directory exists" ); $this->_setErrorMsg ( "SVN Error:無法從SVN獲取目錄 {$file},請檢查目錄是否存在,或者目錄名是否正確" ); $this->_setErrorFile ( $aUpdates ); return DeploySource::STATUS_STOP_ERROR; } } } $this->writeLog("\nSUCC dump files from SVN, total:$total file(s) succ:$succ fail:$fail"); return DeploySource::STATUS_DONE_SUCCESS; } public function checkRevisionFromSVN($aParsedLogs, $repname, $branch, $defaultRevision='HEAD') { global $peg, $rep; $aErrorLogs = array(); $svnrep = new SVNRepository ( $rep ); $this->writeLog("\n\nSTART check files' revisions from SVN repository $repname, branch $branch"); $fileCounter = 0; $totalFiles = count($aParsedLogs); foreach ( $aParsedLogs as $i=>$log ) { $rev = $defaultRevision; if (is_array ( $log )) { $file = $log ['file']; if ($log ['revision']) { $rev = $log ['revision']; } } else { $file = $log; } if (! $file) { continue; } $path = "/" . $branch . "/" . $file; if ($this->_isFileLog ( $file )) { // store file revision $revisionInfo = $this->getSVNRevision ( $repname, $path, $rev, true ); if($revisionInfo){ $revisionInfo = (array) $revisionInfo; // 兼容parsedLogs $revisionInfo['file'] = $file; $revisionInfo['revision'] = $revisionInfo['rev']; $this->_runCodeRevisions [$file] = $revisionInfo; $this->writeLog ( "\n[checkrevision] file: {$file} new revision is {$revisionInfo['revision']}" ); $fileCounter ++; } } else { // store files revision $aFileLists = $this->getSVNLists ( $repname, $path ); foreach ( $aFileLists as $list ) { if ($list ['isDir'] || ! $list ['path'] || ! $list ['revision']) { continue; } $storeFile = str_replace ( '/' . $branch . '/', '', $list ['path'] ); $this->_runCodeRevisions [$storeFile] = $list; $this->writeLog ( "\n[checkrevision] file: {$storeFile} new revision is {$list['revision']}" ); $fileCounter ++; } } } $this->writeLog("\nSUCC check total {$fileCounter} files revisions"); return DeploySource::STATUS_DONE_SUCCESS; } /** * Parse Update Logs to array form * @param string $content * @return array */ public function parseUpdateLogs($content) { $aUpdateFiles = array(); $lines = explode("\n", $content); foreach ( $lines as $line ) { // Skip lines containing EOL only if (($line = trim ( $line )) == '') continue; // skipping update comments if (substr ( ltrim ( $line ), 0, 1 ) == '#') continue; // check extra attributes $aParsedAttributes = array(); if(($attrPos=strpos($line, '|'))!==false){ $attributes = substr($line, $attrPos+1); $aParsedAttributes = $this->_parseUpdateAttributes($attributes); $file = substr( $line, 0, $attrPos); $file = trim($file); }else{ $file = $line; } $file = str_replace('\\', '/', $file); // strip leading slash $firstChar = substr($file, 0, 1); if($firstChar=='/' || $firstChar=='\\'){ $file = substr($file, 1); } if(substr($file, -2)=='/*'){ $file = substr($file, 0, -1); } // replace backslash to slash $file = str_replace("\\", "/", $file); $fileIdx = md5($file); if($aParsedAttributes){ $aUpdateFiles[$fileIdx] = array_merge(array('file'=>$file), $aParsedAttributes); }else{ $aUpdateFiles[$fileIdx] = $file; } } return $aUpdateFiles; } /** * Parse Update Attributes by Regx * * @param string|array $attributes * @return boolean|array */ protected function _parseUpdateAttributes($attributes){ $aPatterns = $this->_attributePatterns; $aStrAttributes = is_array($attributes)?$attributes:explode('|', $attributes); if(!$aStrAttributes){ return false; } $aAttributes = array(); foreach($aStrAttributes as $attr){ $attr = trim($attr); if(!$attr){ continue; } foreach($aPatterns as $name=>$pattern){ if(!$pattern){ continue; } if(preg_match('/' . $pattern . '/', $attr, $matches)){ $aAttributes[$name] = $matches[1]; } } } return $aAttributes; } public function getWorkingDir($subFolder='', $autoCreate=false){ $workingFolder = $this->getToken(); $workingDir = $this->_dumpDir . "/" . $workingFolder; if(!file_exists($workingDir)){ mkdir($workingDir); } if($subFolder){ $workingDir = $this->_dumpDir . "/" . $workingFolder . "/" . $subFolder; if($autoCreate && !file_exists($workingDir)){ mkdir($workingDir); } } return $workingDir; } public function cleanWorkingDir(){ $workingDir = $this->getWorkingDir(); if(file_exists($workingDir)){ $this->writeLog("\nClean working dir $workingDir"); File_Func::removeDir($workingDir, true); } /* $progressFile = $this->getLogFile('progress'); if(file_exists($progressFile)){ $this->writeProgress('', '', false); unlink($progressFile); }*/ } public function saveCodeFile($file, $content){ $workingDir = $this->getWorkingDir('code'); $fileDir = dirname($file); Generator::genDir($workingDir, $fileDir); $dumpFile = $workingDir . "/" . $file; file_put_contents($dumpFile, $content); return $dumpFile; } public function getExecutor(){ //$authUser = !empty($_SERVER['PHP_AUTH_USER'])?$_SERVER['PHP_AUTH_USER']:(!empty($_SERVER['REMOTE_USER'])?$_SERVER['REMOTE_USER']:''); $authUser = $this->Runder->loadSession('user')->getUser('user'); return $authUser; } public function checkPerm($type){ return $this->Runder->loadSession('user')->checkPerm($type); } public function genToken(){ //$dateTime = date('YmdHi'); $dateTime = date('Ymd\_His'); $token = $this->getExecutor() . $dateTime; $token = str_replace(array('\\', '/', ':', '*', '?', '"', '<', '>', '|'), '', $token); return $token; } public function getToken(){ return $this->_runSettings['token']; } public function setToken($token){ return $this->_runSettings['token'] = $token; } public function downloadLog($settings=array()){ $this->_runSettings = $settings; //session_write_close(); // get deploy log $file = $this->getLogFile($this->_runSettings['type']); if (file_exists ( $file )) { header ( "Content-type: text/plain; charset=UTF-8" ); /* or whatever type */ header ( "Content-Disposition: attachment; filename=" . basename($file) ); header ( "Content-Length: " . filesize($file) ); header ( "Pragma: no-cache" ); header ( "Expires: 0" ); $file_contents = file_get_contents ( $file ); print ($file_contents) ; }else{ print "File not exists"; } } public function viewLog($settings=array()){ $this->_runSettings = $settings; //session_write_close(); // get deploy log $file = $this->getLogFile($this->_runSettings['type']); if (file_exists ( $file )) { $file_contents = file_get_contents ( $file ); $html = <<<EOT <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>View Deploy Log</title> </head> <body> EOT; $downloadParams = array('act'=>'download', 'type'=>$this->_runSettings['type'], 'token'=>$this->_runSettings['token']); $downloadLink = 'log.php?' . http_build_query($downloadParams); $html .= "<p>【<a href='{$downloadLink}'>下載日誌</a>】</p>"; $html .= "<pre>" . $file_contents . "</pre>" ; $html .= "</body></html>"; print $html; }else{ print "Log File not exists"; } } public function trackProgress($settings=array()){ $aProgress = array(); $aProgress['progress'] = ''; $aProgress['files'] = array(); $this->_runSettings = $settings; // get deploy log $file = $this->getLogFile($this->_runSettings['type']); if(file_exists($file)){ $aProgress['files'] = tailFile($file, 10, true); } $progressFile = $this->getLogFile('progress'); if(file_exists($progressFile)){ $aProgress['progress'] = file_get_contents($progressFile); } return $aProgress; } public function writeProgress($progress, $step='', $keepOpen=true){ static $_fps = array(); if($step && !empty($this->_runSettings['deploySteps'])){ $nowStep = array_search($step, $this->_runSettings['deploySteps'])+1; $totalSteps = count($this->_runSettings['deploySteps']); $content = "[Step $nowStep/$totalSteps] $progress"; }else{ $content = $progress; } $file = $this->getLogFile('progress'); if(!$this->_enableLog || !$file){ return false; } if (!isset($_fps)) $_fps = array(); $signature = serialize($file); if (!isset($_fps[$signature])) { $_fps[$signature] = fopen($file, 'w'); } $fp = $_fps[$signature]; if(!$fp){ throw new __Exception("Couldn't write progress file: $file ", SCRIPT_ERR_CONFIG); } if (flock($fp, LOCK_EX)) { // do an exclusive lock ftruncate($fp, 0); rewind($fp); fwrite($fp, $content); flock($fp, LOCK_UN); // release the lock } else { throw new __Exception("Couldn't lock the file: $file", SCRIPT_ERR_CONFIG); } if(!$keepOpen){ fclose($fp); unset($_fps[$signature]); } } public static function getTargetHints($targets, $type='code', $returnArray=false) { $aServerHints = array (); switch($type){ case 'code': $mapName = 'deploy.target_code_maps'; $hintName = 'dir'; break; case 'database': $mapName = 'deploy.target_database_maps'; $hintName = 'database'; break; case 'svn': $mapName = 'deploy.target_svn_maps'; $hintName = 'svn'; break; default: throw new __Exception("Unknown type: $type", SCRIPT_ERR_CONFIG); } foreach ( $targets as $idx => $target ) { if (! DConfig::issetConfig ( array ( $mapName, $target ) )) { $vars ['warning'] = "$target 目標服務器未配置"; break; } $toDir = DConfig::getConfig ( array ( $mapName, $target, $hintName ) ); if (! $toDir) { $vars ['warning'] = "$target 目標服務器未正確配置部署目錄"; break; } $aServerHints [] = ($idx + 1) . ". [" . $target . "] " . $toDir; } return $returnArray?$aServerHints:implode(" ", $aServerHints); } public function checkDeltaRevision($aSettings){ global $peg, $rep, $rev, $vars; $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $aR = array(); // Delta Files Revision $aR['deltaRevision'] = 0; $aR['deltaFiles'] = array(); $deltaRevPattern = '^([0-9]+)'; $this->writeLog("\n\nSTART check SQL Delta Revision"); $this->writeProgress("開始檢查SVN SQL版本", self::STEP_CHECK_REVISION); $svnrep = new SVNRepository ( $rep ); $aSVNList = $svnrep->getList("/" . $aSettings['path'], $rev, $peg); if(!$aSVNList){ $this->_setErrorMsg($vars['error']); return false; } foreach($aSVNList->entries as $e){ $f = $e->file; if($f && preg_match("/{$deltaRevPattern}/", $f, $m)){ $r = intval($m[1]); if($r){ $aR['deltaFiles'][] = array('file' => $f, 'revision' => $r, 'author' => $e->author, 'date' => $e->date); if($r > $aR['deltaRevision']){ $aR['deltaRevision'] = $r; } } } } return $aR; } public function checkCodeLogRevision($aSettings){ global $peg, $rep, $rev, $vars; $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $aR = array(); // Delta Files Revision $aR['logRevision'] = 0; $aR['logFiles'] = array(); $deltaRevPattern = '^([0-9]+)'; $this->writeLog("\n\nSTART check code log revision"); $this->writeProgress("开始检查代码更新日志", self::STEP_CHECK_REVISION); $svnrep = new SVNRepository ( $rep ); $aSVNList = $svnrep->getList("/" . $aSettings['path'], $rev, $peg); if(!$aSVNList){ $this->_setErrorMsg($vars['error']); return false; } foreach($aSVNList->entries as $e){ $f = $e->file; if($f && preg_match("/{$deltaRevPattern}/", $f, $m)){ $r = intval($m[1]); if($r){ $aR['logFiles'][] = array('file' => $f, 'revision' => $r); if($r > $aR['logRevision']){ $aR['logRevision'] = $r; } } } } return $aR; } public function checkSVNLogRevision($aSettings){ global $peg, $rep, $rev, $vars; $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $aR = array(); // Delta Files Revision $aR['headRevision'] = 0; $aR['revHistories'] = array(); $deltaRevPattern = '^([0-9]+)'; $this->writeLog("\n\nSTART check svn log revision"); $this->writeProgress("开始检查代码版本日志", self::STEP_CHECK_REVISION); $svnrep = new SVNRepository ( $rep ); // 分支 $aSVNList = $svnrep->getList("/", $rev, $peg); $aBranches = array(); foreach($aSVNList->entries as $e){ if($e->isdir){ $aBranches[] = array( 'rev' => $e->rev, 'author' => $e->author, 'date' => $e->date, 'file' => $e->file, ); } } usort($aBranches, create_function('$a,$b', 'return $a["rev"]==$b["rev"]?0:($a["rev"]>$b["rev"]?1:-1);')); $aR['svnBranches'] = $aBranches; $brev = ''; $erev = 1; $limit = 100; $aSVNList = $svnrep->getLog("/" . $aSettings['path'], $brev, $erev, false, $limit); if(!$aSVNList){ $this->_setErrorMsg($vars['error']); return false; } $title_length = 40; foreach($aSVNList->entries as $e){ $r = intval($e->rev); if($r){ $msg = ''; $title = ''; if($e->msg){ $msg = $e->msg; $msg = htmlspecialchars($msg); $msg = str_replace(array("\r", "\n"), " ", $msg); $msg = str_replace("\"", """, $msg); $msg = str_replace("'", "'", $msg); //$msg = mb_eregi_replace("\"", """, $msg); //$msg = mb_eregi_replace("'", "'", $msg); $title = $msg; } if($title && mb_strlen($title)>$title_length){ $title = mb_strimwidth($title, 0, $title_length, '...'); } $aR['revHistories'][] = array( 'rev' => $r, 'author' => $e->author, 'date' => $e->date, 'msg' => $msg, 'title' => $title, ); if($r > $aR['headRevision']){ $aR['headRevision'] = $r; } } } return $aR; } public function checkLogFileRevision($aSettings){ global $peg, $rep, $rev, $vars; $this->_runSettings = $aSettings; $this->setToken($aSettings['token']); $aR = array(); // Delta Files Revision $aR['logFile'] = $_REQUEST['file']; $aR['logContent'] = ''; $this->writeLog("\n\nSTART check log file revision"); $this->writeProgress("开始检查代码更新日志", self::STEP_CHECK_REVISION); if(!$aR['logFile']){ $this->_setErrorMsg("未指定代码更新Log文件"); return false; } //$svnrep = new SVNRepository ( $rep ); $aR['logContent'] = $this->dumpFileFromSVN($rep, "/" . $aSettings['path'] . "/" . $aR['logFile']); if(!$aR['logContent']){ $this->_setErrorMsg("未能获取到代码更新Log文件内容"); return false; } $aR['logTitle'] = $this->_matchUpdateLogContent($aR['logContent'], 'TITLE'); $aR['logIssueID'] = $this->_matchUpdateLogContent($aR['logContent'], 'ISSUEID'); $aR['logBugID'] = $this->_matchUpdateLogContent($aR['logContent'], 'BUGID'); $aR['logDesc'] = $this->_matchUpdateLogContent($aR['logContent'], 'DESC'); return $aR; } protected function _matchUpdateLogContent($content, $token){ $start = "[{$token}]"; $end = "[/{$token}]"; $qstr = "(?:" . preg_quote($start, '~') . "[^\[]*(?:\[[^\/]*)*" . preg_quote($end, '~') . ")"; $pattern = "~#({$qstr})#~"; if(preg_match($pattern, $content, $matches)){ $mt = $matches[1]; $mt = str_replace($start, '', $mt); $mt = str_replace($end, '', $mt); return trim($mt); }else{ return ''; } } protected function _checkTargetDBRevision($target) { $dbConf = DConfig::getConfig(array('deploy.target_database_maps', $target)); if(empty($dbConf)){ throw new __Exception("Target $target Database conf is empty", SCRIPT_ERR_CONFIG); } $dbRevision = 0; $dbUpdateTime = ''; $dbUpdateLogs = array(); // Connecting, selecting database $link = mysql_connect ( $dbConf['host'] . ($dbConf['port']?":".$dbConf['port']:""), $dbConf['username'], $dbConf['password'] ) or die ( 'Could not connect: ' . mysql_error () ); mysql_select_db ( $dbConf['database'] ) or die ( 'Could not select database' ); $change_table = DConfig::getConfig(array('deploy.target_database_maps', $target, 'changelog_table')); // Performing SQL query $query = 'SELECT * FROM `' . ($change_table?$change_table:'changelog') . '` ORDER BY change_number'; $result = mysql_query ( $query ) or die ( 'Query failed: ' . mysql_error () ); while ( $row = mysql_fetch_array ( $result, MYSQL_ASSOC ) ) { if($row['change_number'] > $dbRevision){ $dbRevision = $row['change_number']; $dbUpdateTime = $row['complete_dt']; } $dbUpdateLogs[] = $row; } // Free resultset mysql_free_result ( $result ); // Closing connection mysql_close ( $link ); return array('revision' => $dbRevision, 'updated' => $dbUpdateTime, 'logs' => $dbUpdateLogs); } protected function _showDirFiles($svnrep, $subs, $level, $limit, $rev, $peg, $listing, $index, $treeview = true) { global $rep, $peg, $passRevString; $path = $subs; // check if path is a file $pathIsFile = false; if(preg_match('/\.[a-zA-Z0-9]{1,6}$/', $path)){ $pathIsFile = true; } if (!$treeview) { $level = $limit; } // TODO: Fix node links to use the path and number of peg revision (if exists) // This applies to file detail, log, and RSS -- leave the download link as-is /* for ($n = 0; $n <= $level; $n++) { $path .= $subs[$n].'/'; }*/ // List each file in the current directory $loop = 0; $last_index = 0; $accessToThisDir = $rep->hasReadAccess($path, false); // If using flat view and not at the root, create a '..' entry at the top. /* if (!$treeview && count($subs) > 2) { $parentPath = $subs; unset($parentPath[count($parentPath) - 2]); $parentPath = implode('/', $parentPath); if ($rep->hasReadAccess($parentPath, false)) { $listing[$index]['rowparity'] = $index % 2; $listing[$index]['path'] = $parentPath; $listing[$index]['filetype'] = 'dir'; $listing[$index]['filename'] = '..'; $listing[$index]['level'] = 0; $listing[$index]['node'] = 0; // t-node $listing[$index]['revision'] = $rev; $index++; } }*/ $openDir = false; $logList = $svnrep->getList($path, $rev, $peg); if ($logList) { foreach ($logList->entries as $entry) { $isDir = $entry->isdir; /* if (!$isDir && $level != $limit) { continue; // Skip any files outside the current directory }*/ $file = $entry->file; // Only list files/directories that are not designated as off-limits $access = ($isDir) ? $rep->hasReadAccess($pathIsFile?$path:$path.$file, true) : $accessToThisDir; if ($access) { $listing[$index]['rowparity'] = $index % 2; if ($isDir) { $listing[$index]['filetype'] = ($openDir) ? 'diropen' : 'dir'; //$openDir = isset($subs[$level + 1]) && (!strcmp($subs[$level + 1].'/', $file) || !strcmp($subs[$level + 1], $file)); $openDir = true; } else { $listing[$index]['filetype'] = strtolower(strrchr($file, '.')); $openDir = false; } $listing[$index]['isDir'] = $isDir; $listing[$index]['openDir'] = $openDir; $listing[$index]['level'] = ($treeview) ? $level : 0; $listing[$index]['node'] = 0; // t-node $listing[$index]['path'] = $pathIsFile?$path:$path.$file; $listing[$index]['filename'] = $file; $listing[$index]['committime'] = $entry->committime; $listing[$index]['revision'] = $entry->rev; $listing[$index]['author'] = $entry->author; $listing[$index]['age'] = $entry->age; $listing[$index]['date'] = $entry->date; $loop++; $index++; $last_index = $index; //if ($isDir && ($level != $limit)) { if ($isDir) { // @todo remove the alternate check with htmlentities when assured that there are not side effects //if (isset($subs[$level + 1]) && (!strcmp($subs[$level + 1].'/', $file) || !strcmp(htmlentities($subs[$level + 1], ENT_QUOTES).'/', htmlentities($file)))) { if($openDir){ //$listing = $this->showDirFiles($svnrep, $subs, $level + 1, $limit, $rev, $peg, $listing, $index); $listing = $this->_showDirFiles($svnrep, $path.$file, $level + 1, $limit, $rev, $peg, $listing, $index); $index = count($listing); } } } } } // For an expanded tree, give the last entry an "L" node to close the grouping if ($treeview && $last_index != 0) { $listing[$last_index - 1]['node'] = 1; // l-node } return $listing; } protected function _showTreeDir($svnrep, $path, $rev, $peg, $listing) { $subs = explode('/', $path); // For directory, the last element in the subs is empty. // For file, the last element in the subs is the file name. // Therefore, it is always count($subs) - 2 $limit = count($subs) - 2; return $this->_showDirFiles($svnrep, $path, 0, $limit, $rev, $peg, $listing, 0, false); } public function getSVNLists($repname, $dir, $revision='HEAD'){ global $peg, $rep; static $svnrep = null; $dir = str_replace('\\', '/', $dir); $leadingChar = substr($dir, 0, 1); if($leadingChar!='/'){ $dir = '/' . $dir; } // check if path is a file $tailingChar = substr($dir, -1); if($tailingChar!='/' && !preg_match('/\.[a-zA-Z0-9]{1,6}$/', $dir)){ $dir .= '/'; } $aFileLists = array(); if(!$svnrep){ $svnrep = new SVNRepository ( $rep ); } return $this->_showTreeDir($svnrep, $dir, $revision, $peg, $aFileLists); } public function getSVNRevision($repname, $path, $revision='HEAD', $returnDetail=false){ global $peg, $rep; static $svnrep=null; $path = str_replace('\\', '/', $path); $leadingChar = substr($path, 0, 1); if($leadingChar!='/'){ $path = '/' . $path; } // check if path is a file $tailingChar = substr($path, -1); if($tailingChar!='/' && !preg_match('/\.[a-zA-Z0-9]{1,6}$/', $path)){ $path .= '/'; } if(!$svnrep){ $svnrep = new SVNRepository ( $rep ); } $headlog = $svnrep->getLog($path, $revision, '', true, 1); if(!$returnDetail){ $headrev = ($headlog && isset($headlog->entries[0])) ? $headlog->entries[0]->rev : 0; return $headrev; }else{ return $headlog->entries[0]; } } public function writeLog($content, $type='', $keepOpen=true){ return $this->_writeLog($content, $type?$type:$this->_runSettings['type'], $keepOpen); } protected function _checkTargetSVNRevision($target) { $conf = DConfig::getConfig(array('deploy.target_svn_maps', $target)); if(empty($conf)){ throw new __Exception("Target $target SVN conf is empty", SCRIPT_ERR_CONFIG); } global $config; $cmdString = $config->getSvnCommand() . ' info "' . $conf['path'] . '" --xml 2>&1'; exec($cmdString, $cmdOutput, $cmdStatus); $cmdOutput = implode("", $cmdOutput); $cmdOutput = trim($cmdOutput); if(($pos=stripos($cmdOutput, "<?xml"))){ $error = substr($cmdOutput, 0, $pos); //$xml = substr($output, $pos); $xml = ''; }else{ $error = false; $xml = $cmdOutput; } if (! $error) { $data = ( array ) simplexml_load_string ( $xml, 'SimpleXMLElement', LIBXML_NOCDATA ); $entry = ( array ) $data ['entry']; $repository = (array) $entry['repository']; $commit = ( array ) $entry ['commit']; $attributes = (array) $commit['@attributes']; $revision = $attributes ['revision']; $branch = str_replace($repository['root'], "", $entry['url']); $return = array ( 'revision' => $revision, 'author' => $commit ['author'], 'date' => $commit ['date'], 'branch' => $branch, ); }else{ $return = array( 'revision' => $error, 'author' => '', 'date' => '', 'branch' => '', ); } return $return; } }
相关文章推荐
- 能够可视化访问和查看H2数据库的工具
- 简单的Mysql数据库备份和同步脚本
- navicat data modeler的使用以及数据库设计的流程
- Oracle SQL语句解析过长问题
- MySQL 主从复制的原理和配置
- redis-cluster集群搭建
- MySQL Proxy 实现 MySQL 读写分离提高并发负载
- mysql操作命令梳理(4)-中文乱码问题
- mysqldump的权限说明
- Oracle中如何把A表一列的数据插入到B表的一列中
- Oracle 排序问题(null带来的)
- mysql的case then else end语句的使用,来显示多种结果之一
- SQL Server 数值四舍五入,小数点后保留2位
- Mysql单字段容量过大insert导致segfault问题。
- 数据库同步,配置两个数据源(mysql)
- 数据库基础及T-SQL语句
- 升级11g重复执行catupgrd.sql脚本,引起大量ORA-00001错误
- redis和memcache的比较
- Oracle学习笔记 --- 创建用户,权限
- Oracle 之分页查询