123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- import time
- from datetime import datetime
- import os
- import re
- from flask import current_app
- from flask_socketio import emit
- from walle.model.project import ProjectModel
- from walle.model.record import RecordModel
- from walle.model.task import TaskModel
- from walle.service.extensions import socketio
- from walle.service.utils import color_clean
- from walle.service.waller import Waller
- from walle.service.code import Code
- from walle.service.error import WalleError
- class Deployer:
- '''
- 序列号
- '''
- stage = '0'
- sequence = 0
- stage_prev_deploy = 'prev_deploy'
- stage_deploy = 'deploy'
- stage_post_deploy = 'post_deploy'
- stage_prev_release = 'prev_release'
- stage_release = 'release'
- stage_post_release = 'post_release'
- task_id = '0'
- user_id = '0'
- taskMdl = None
- TaskRecord = None
- console = False
- version = datetime.now().strftime('%Y%m%d%H%M%s')
- local_codebase, dir_codebase_project, project_name = None, None, None
- dir_release, dir_webroot = None, None
- connections, success, errors = {}, {}, {}
- release_version_tar, release_version = None, None
- local = None
- def __init__(self, task_id=None, project_id=None, console=False):
- self.local_codebase = current_app.config.get('CODE_BASE')
- self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
- user=current_app.config.get('LOCAL_SERVER_USER'),
- port=current_app.config.get('LOCAL_SERVER_PORT'))
- self.TaskRecord = RecordModel()
- if task_id:
- self.task_id = task_id
-
- current_app.logger.info(self.task_id)
- self.taskMdl = TaskModel().item(self.task_id)
- self.user_id = self.taskMdl.get('user_id')
- self.servers = self.taskMdl.get('servers_info')
- self.task = self.taskMdl.get('target_user')
- self.project_info = self.taskMdl.get('project_info')
- if project_id:
- self.project_id = project_id
- self.project_info = ProjectModel(id=project_id).item()
- self.project_name = self.project_info['id']
- self.dir_codebase_project = self.local_codebase + str(self.project_name)
-
- self.console = console
- def config(self):
- return {'task_id': self.task_id, 'user_id': self.user_id, 'stage': self.stage, 'sequence': self.sequence,
- 'console': self.console}
- def start(self):
- TaskModel().get_by_id(self.task_id).update({'status': TaskModel.status_doing})
- self.taskMdl = TaskModel().item(self.task_id)
-
-
- def prev_deploy(self):
- '''
- # TODO
- socketio.sleep(0.001)
- 1.代码检出前要做的基础工作
- - 检查 当前用户
- - 检查 python 版本
- - 检查 git 版本
- - 检查 目录是否存在
- - 用户自定义命令
- :return:
- '''
- self.stage = self.stage_prev_deploy
- self.sequence = 1
-
- command = 'whoami'
- current_app.logger.info(command)
- result = self.local.run(command, wenv=self.config())
-
- command = 'python --version'
- result = self.local.run(command, wenv=self.config())
-
- command = 'git --version'
- result = self.local.run(command, wenv=self.config())
-
- self.init_repo()
-
- command = 'mkdir -p %s' % (self.dir_codebase_project)
- result = self.local.run(command, wenv=self.config())
-
- command = self.project_info['prev_deploy']
- current_app.logger.info(command)
- with self.local.cd(self.dir_codebase_project):
- result = self.local.run(command, wenv=self.config())
- def deploy(self):
- '''
- 2.检出代码
- :param project_name:
- :return:
- '''
-
- socketio.sleep(0.001)
- self.stage = self.stage_deploy
- self.sequence = 2
- current_app.logger.info('git dir: %s', self.dir_codebase_project + '/.git')
-
-
- if os.path.exists(self.dir_codebase_project + '/.git'):
- with self.local.cd(self.dir_codebase_project):
- command = 'pwd && git pull'
- result = self.local.run(command, wenv=self.config())
- else:
-
- with self.local.cd(self.dir_codebase_project):
- command = 'pwd && git clone %s .' % (self.project_info['repo_url'])
- current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
- result = self.local.run(command, wenv=self.config())
-
- self.release_version = '%s_%s_%s' % (
- self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))
- with self.local.cd(self.dir_codebase):
- command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
- current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
- result = self.local.run(command, wenv=self.config())
-
- with self.local.cd(self.local_codebase + self.release_version):
- command = 'git reset -q --hard %s' % (self.taskMdl.get('commit_id'))
- result = self.local.run(command, wenv=self.config())
- pass
- def post_deploy(self):
- '''
- 3.检出代码后要做的任务
- - 用户自定义操作命令
- - 代码编译
- - 清除日志文件及无用文件
- -
- - 压缩打包
- - 传送到版本库 release
- :return:
- '''
-
- socketio.sleep(0.001)
- self.stage = self.stage_post_deploy
- self.sequence = 3
-
- command = self.project_info['post_deploy']
- with self.local.cd(self.local_codebase + self.release_version):
- result = self.local.run(command, wenv=self.config())
-
- self.release_version_tar = '%s.tgz' % (self.release_version)
- with self.local.cd(self.dir_codebase):
- command = 'tar zcvf %s %s' % (self.release_version_tar, self.release_version)
- result = self.local.run(command, wenv=self.config())
- def prev_release(self, waller):
- '''
- 4.部署代码到目标机器前做的任务
- - 检查 webroot 父目录是否存在
- :return:
- '''
-
- socketio.sleep(0.001)
- self.stage = self.stage_prev_release
- self.sequence = 4
-
- command = 'mkdir -p %s' % (self.project_info['target_releases'])
- result = waller.run(command, wenv=self.config())
-
-
-
-
-
- command = self.project_info['prev_release']
- current_app.logger.info(command)
- with waller.cd(self.project_info['target_releases']):
- result = waller.run(command, wenv=self.config())
-
-
- current_app.logger.info('/tmp/walle/codebase/' + self.release_version_tar)
- result = waller.put('/tmp/walle/codebase/' + self.release_version_tar,
- remote=self.project_info['target_releases'], wenv=self.config())
- current_app.logger.info('command: %s', dir(result))
-
- self.release_untar(waller)
- def release(self, waller):
- '''
- 5.部署代码到目标机器做的任务
- - 打包代码 local
- - scp local => remote
- - 解压 remote
- :return:
- '''
-
- socketio.sleep(0.001)
- self.stage = self.stage_release
- self.sequence = 5
- with waller.cd(self.project_info['target_releases']):
-
- current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
- command = 'ln -sfn %s/%s %s' % (
- self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
- result = waller.run(command, wenv=self.config())
-
-
- current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
- command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
- result = waller.run(command, wenv=self.config())
- def release_untar(self, waller):
- '''
- 解压版本包
- :return:
- '''
-
- socketio.sleep(0.001)
- with waller.cd(self.project_info['target_releases']):
- command = 'tar zxf %s' % (self.release_version_tar)
- result = waller.run(command, wenv=self.config())
- def post_release(self, waller):
- '''
- 6.部署代码到目标机器后要做的任务
- - 切换软链
- - 重启 nginx
- :return:
- '''
- self.stage = self.stage_post_release
- self.sequence = 6
-
- command = self.project_info['post_release']
- current_app.logger.info(command)
- with waller.cd(self.project_info['target_root']):
- result = waller.run(command, wenv=self.config())
- self.post_release_service(waller)
- def post_release_service(self, waller):
- '''
- 代码部署完成后,服务启动工作,如: nginx重启
- :param connection:
- :return:
- '''
- with waller.cd(self.project_info['target_root']):
- command = 'sudo service nginx restart'
- result = waller.run(command, wenv=self.config())
- def list_tag(self):
- self.init_repo()
- with self.local.cd(self.dir_codebase_project):
- command = 'git tag -l'
- result = self.local.run(command, wenv=self.config())
- return result
- return None
- def list_branch(self):
- self.init_repo()
- with self.local.cd(self.dir_codebase_project):
- command = 'git pull'
- result = self.local.run(command, wenv=self.config())
- if result.exited != Code.Ok:
- raise WalleError(Code.shell_git_pull_fail)
- current_app.logger.info(self.dir_codebase_project)
- command = 'git branch -r'
- result = self.local.run(command, wenv=self.config())
- if result.exited != Code.Ok:
- raise WalleError(Code.shell_run_fail)
-
- branches = color_clean(result.stdout.strip())
- branches = branches.split('\n')
-
-
- branches = [branch.strip().lstrip('origin/') for branch in branches if
- not branch.strip().startswith('origin/HEAD')]
- return branches
- return None
- def list_commit(self, branch):
- self.init_repo()
- with self.local.cd(self.dir_codebase_project):
- command = 'git checkout %s && git pull' % (branch)
- result = self.local.run(command, wenv=self.config())
-
- command = 'git log -50 --pretty="%h #_# %an #_# %s"'
- result = self.local.run(command, wenv=self.config())
- commit_log = color_clean(result.stdout.strip())
- commit_list = commit_log.split('\n')
- commits = []
- for commit in commit_list:
- if not re.search('^.+ #_# .+ #_# .*$', commit):
- continue
- commit_dict = commit.split(' #_# ')
- current_app.logger.info(commit_dict)
- commits.append({
- 'id': commit_dict[0],
- 'name': commit_dict[1],
- 'message': commit_dict[2],
- })
- return commits
-
- return None
- def init_repo(self):
- if not os.path.exists(self.dir_codebase_project):
-
- command = 'mkdir -p %s' % (self.dir_codebase_project)
-
- current_app.logger.info(command)
- self.local.run(command, wenv=self.config())
- is_git_dir = self.local.run('git status', wenv=self.config())
- if is_git_dir.exited != Code.Ok:
-
- with self.local.cd(self.dir_codebase_project):
- command = 'pwd && rm -rf .* .git && git clone %s .' % (self.project_info['repo_url'])
- current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
- result = self.local.run(command, wenv=self.config())
- if result.exited != Code.Ok:
- raise WalleError(Code.shell_git_init_fail)
- def end(self, success=True):
- status = TaskModel.status_success if success else TaskModel.status_fail
- TaskModel().get_by_id(self.task_id).update({'status': status})
- def walle_deploy(self):
- self.start()
- self.prev_deploy()
- self.deploy()
- self.post_deploy()
- all_servers_success = True
- for server_info in self.servers:
- server = server_info['host']
- try:
- self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
- self.prev_release(self.connections[server])
- self.release(self.connections[server])
- self.post_release(self.connections[server])
- except Exception as e:
- current_app.logger.error(e)
- all_servers_success = False
- self.errors[server] = e.message
- self.end(all_servers_success)
- return {'success': self.success, 'errors': self.errors}
|