/** * 更新到指定commit版本 * * @param string $commit * @return bool */ public function updateToVersion($task) { $copy = GlobalHelper::str2arr($task->file_list); $fileAndVersion = []; foreach ($copy as $file) { $fileAndVersion[] = StringHelper::explode($file, " ", true, true); } $branch = $task->branch == 'trunk' ? $task->branch : ($this->getConfig()->repo_mode == Project::REPO_BRANCH ? 'branches/' : 'tags/') . $task->branch; // 先更新 $versionSvnDir = sprintf('%s-svn', rtrim(Project::getDeployWorkspace($task->link_id), '/')); $cmd[] = sprintf('cd %s ', $versionSvnDir); $cmd[] = $this->_getSvnCmd(sprintf('svn checkout %s/%s .', $this->getConfig()->repo_url, $branch)); // 更新指定文件到指定版本,并复制到同步目录 foreach ($fileAndVersion as $assign) { if (in_array($assign[0], ['.', '..'])) { continue; } $cmd[] = $this->_getSvnCmd(sprintf('svn up %s %s', $assign[0], empty($assign[1]) ? '' : ' -r ' . $assign[1])); // 多层目录需要先新建父目录,否则复制失败 if (strpos($assign[0], '/') !== false) { $cmd[] = sprintf('mkdir -p %s/%s', Project::getDeployWorkspace($task->link_id), dirname($assign[0])); } $cmd[] = sprintf('cp -rf %s %s/%s', rtrim($assign[0], '/'), Project::getDeployWorkspace($task->link_id), dirname($assign[0])); } $command = join(' && ', $cmd); return $this->runLocalCommand($command); }
/** * 获取远程服务器要操作的任务命令 * * @param $task string * @param $version string * @return string string */ public static function getRemoteTaskCommand($task, $version) { $tasks = GlobalHelper::str2arr($task); if (empty($tasks)) { return ''; } $cmd = []; $workspace = rtrim(Project::getDeployWorkspace($version), '/'); $version = Project::getReleaseVersionDir($version); $pattern = ['#{WORKSPACE}#', '#{VERSION}#']; $replace = [$workspace, $version]; foreach ($tasks as $task) { $cmd[] = preg_replace($pattern, $replace, $task); } return join(' && ', $cmd); }
/** * 同步代码之后触发任务 * 所有目标机器都部署完毕之后,做一些清理工作,如删除缓存、重启服务(nginx、php、task) * * @return bool */ public function postRelease($version) { $tasks = GlobalHelper::str2arr($this->getConfig()->post_release); if (empty($tasks)) { return true; } $cmd = []; $workspace = rtrim(Project::getDeployWorkspace($version), '/'); $version = Project::getReleaseVersionDir($version); $pattern = ['#{WORKSPACE}#', '#{VERSION}#']; $replace = [$workspace, $version]; foreach ($tasks as $task) { $cmd[] = preg_replace($pattern, $replace, $task); } $command = join(' && ', $cmd); return $this->runRemoteCommand($command); }
protected final function runRemoteCommand($command) { $this->log = ''; $needs_tty = ''; foreach (GlobalHelper::str2arr($this->getConfig()->hosts) as $remoteHost) { $localCommand = 'ssh ' . $needs_tty . ' -p ' . $this->getHostPort($remoteHost) . ' -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' . $this->getConfig()->release_user . '@' . $this->getHostName($remoteHost); $remoteCommand = str_replace('"', '\\"', trim($command)); $localCommand .= ' "sh -c \\"' . $remoteCommand . '\\"" '; static::log('Run remote command ' . $remoteCommand); $log = $this->log; $this->status = $this->runLocalCommand($localCommand); $this->log = $log . (($log ? PHP_EOL : '') . $remoteHost . ' : ' . $this->log); if (!$this->status) { return false; } } return true; }
/** * 获取远程服务器要操作的任务命令 * * @param $task string * @param $version string * @return string string */ public static function getRemoteTaskCommand($task, $version) { $tasks = GlobalHelper::str2arr($task); if (empty($tasks)) { return ''; } // 可能要做一些依赖环境变量的命令操作 $cmd = ['source /etc/profile']; $workspace = Project::getTargetWorkspace(); $version = Project::getReleaseVersionDir($version); $pattern = ['#{WORKSPACE}#', '#{VERSION}#']; $replace = [$workspace, $version]; // 简化用户切换目录,直接切换到当前的版本目录:{release_library}/{project}/{version} $cmd[] = "cd {$version}"; foreach ($tasks as $task) { $cmd[] = preg_replace($pattern, $replace, $task); } return join(' && ', $cmd); }
/** * 将多个文件/目录通过ansible传输到指定的多个目标机 * * ansible 不支持 rsync模块, 改用宿主机 tar 打包, ansible 并发传输到目标机临时目录, 目标机解压 * * @param $version * @param string $files 相对仓库文件/目录路径, 空格分割 * @param array $remoteHosts */ public function copyFiles($version, $files = '*', $remoteHosts = []) { // 1. 打包 $excludes = GlobalHelper::str2arr($this->getConfig()->excludes); $packagePath = Project::getDeployPackagePath($version); $packageCommand = sprintf('cd %s && tar -p %s -cz -f %s %s', escapeshellarg(rtrim(Project::getDeployWorkspace($version), '/') . '/'), $this->excludes($excludes), escapeshellarg($packagePath), $files); $ret = $this->runLocalCommand($packageCommand); if (!$ret) { return false; } // 2. 传输文件 $releasePackage = Project::getReleaseVersionPackage($version); $ret = $this->copyFilesByAnsibleCopy($packagePath, $releasePackage); if (!$ret) { return false; } // 3. 解压 $releasePath = Project::getReleaseVersionDir($version); $unpackageCommand = sprintf('cd %1$s && tar --no-same-owner -pm -C %1$s -xz -f %2$s', $releasePath, $releasePackage); $ret = $this->runRemoteCommandByAnsibleShell($unpackageCommand); return $ret; }
public function diffWithOnline($file) { $localSourceDir = $this->config->getDeployFromDir(); $remoteSourceDir = $this->config->getTargetWorkspace(); $cmd[] = "cat {$remoteSourceDir}/{$file}"; $command = implode(' && ', $cmd); $host = GlobalHelper::str2arr($this->config->hosts)[0]; if ($this->runRemoteCommand($command, 0, $host)) { $contentOld = explode(PHP_EOL, substr($this->log, strlen($host . ' : '))); array_walk($contentOld, function (&$line) { $line = rtrim($line, "\r\n"); }); } else { $contentOld = []; } $contentNew = file("{$localSourceDir}/{$file}"); array_walk($contentNew, function (&$line) { $line = rtrim($line, "\r\n"); }); $diff = new \Diff($contentOld, $contentNew); return $diff->render(new \Diff_Renderer_Html_Array()); }
/** * 获取当前进程配置的目标机器host列表 */ public static function getHosts() { return GlobalHelper::str2arr(static::$CONF->hosts); }
/** * rsync 同步文件 * * @param $remoteHost 远程host,格式:host 、host:port * @return bool */ public function syncFiles($remoteHost, $version) { $excludes = GlobalHelper::str2arr($this->getConfig()->excludes); $command = sprintf('rsync -avzq --rsh="ssh -p %s" %s %s %s%s:%s', $this->getHostPort($remoteHost), $this->excludes($excludes), rtrim(Project::getDeployWorkspace($version), '/') . '/', $this->getConfig()->release_user . '@', $this->getHostName($remoteHost), Project::getReleaseVersionDir($version)); return $this->runLocalCommand($command); }
/** * @param Project $project * @param TaskModel $task * @return bool * @throws \Exception */ protected function _packageFiles(Project $project, TaskModel $task) { $version = $task->link_id; $files = $task->getCommandFiles(); $excludes = GlobalHelper::str2arr($project->excludes); $packagePath = Project::getDeployPackagePath($version); $packageCommand = sprintf('cd %s && tar -p %s -cz -f %s %s', escapeshellarg(rtrim(Project::getDeployWorkspace($version), '/') . '/'), $this->excludes($excludes), escapeshellarg($packagePath), $files); $ret = $this->runLocalCommand($packageCommand); if (!$ret) { throw new \Exception(yii::t('walle', 'package error')); } return true; }
/** * 获取文件和版本号列表 * * @param TaskModel $task * @return array */ public function getFileAndVersionList(TaskModel $task) { $fileList = GlobalHelper::str2arr($task->file_list); $fileAndVersion = []; foreach ($fileList as $file) { list($file, $version) = array_pad(StringHelper::explode($file, ' ', true, true), 2, null); $fileAndVersion[] = ['file' => $file, 'version' => $version]; } return $fileAndVersion; }
/** * 获取要发布的文件列表 * * @return array|string */ public function getCommandFiles() { if ($this->file_transmission_mode == static::FILE_TRANSMISSION_MODE_FULL) { return '.'; } elseif ($this->file_transmission_mode == static::FILE_TRANSMISSION_MODE_PART && $this->file_list) { $fileList = GlobalHelper::str2arr($this->file_list); $commandFiles = join(' ', $fileList); return trim($commandFiles); } else { throw new \InvalidArgumentException('file list empty'); } }
/** * 获取两个版本的差异 * * @ return array */ public function getVersionDiff($old, $new) { $destination = Project::getDeployFromDir(); $cmd[] = sprintf('cd %s ', $destination); $cmd[] = sprintf('/usr/bin/env git reset -q --hard %s', $new); $cmd[] = sprintf('/usr/bin/env git diff %s %s --name-status', $old, $new); $command = implode(' && ', $cmd); $result = $this->runLocalCommand($command); $output = explode(PHP_EOL, $this->getExeLog()); $diff = []; $excludes = GlobalHelper::str2arr($this->config->excludes); array_push($excludes, '*.git*'); array_push($excludes, '.svn*'); array_walk($excludes, function (&$item) { $item = '~^([A-Z])\\s+' . str_replace('\\*', '.*', preg_quote($item)) . '~Ui'; }); foreach ($output as $line) { if (!preg_filter($excludes, 'exclude', $line) && preg_match('~^([A-Z])\\s+(\\S+)$~', $line, $match)) { if ($file = realpath("{$destination}/{$match[2]}")) { $diff[$file] = ['status' => $match[1], 'file' => $file]; } } } return $diff; }