deployer.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Author: wushuiyong
  4. # @Created Time : 日 1/ 1 23:43:12 2017
  5. # @Description:
  6. import time
  7. from datetime import datetime
  8. import os
  9. # from fabric import context_managers, colors
  10. from flask import current_app
  11. from walle.model import deploy as TaskModel
  12. from walle.service.waller import Waller
  13. from walle.model.deploy import ProjectModel
  14. from flask_socketio import emit
  15. from walle.service.extensions import socketio
  16. class Deployer:
  17. '''
  18. 序列号
  19. '''
  20. stage = '0'
  21. sequence = 0
  22. stage_prev_deploy = 'prev_deploy'
  23. stage_deploy = 'deploy'
  24. stage_post_deploy = 'post_deploy'
  25. stage_prev_release = 'prev_release'
  26. stage_release = 'release'
  27. stage_post_release = 'post_release'
  28. task_id = '0'
  29. user_id = '0'
  30. taskMdl = None
  31. TaskRecord = None
  32. version = datetime.now().strftime('%Y%m%d%H%M%s')
  33. project_name = 'walden'
  34. dir_codebase = '/tmp/walle/codebase/'
  35. dir_codebase_project = dir_codebase + project_name
  36. # 定义远程机器
  37. # env.hosts = ['172.16.0.231', '172.16.0.177']
  38. dir_release = None
  39. dir_webroot = None
  40. connections, success, errors = {}, {}, {}
  41. release_version_tar, release_version = None, None
  42. local = None
  43. def __init__(self, task_id=None, project_id=None):
  44. self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
  45. user=current_app.config.get('LOCAL_SERVER_USER'),
  46. port=current_app.config.get('LOCAL_SERVER_PORT'))
  47. self.TaskRecord = TaskModel.TaskRecordModel()
  48. if task_id:
  49. self.task_id = task_id
  50. self.taskMdl = TaskModel.TaskModel().item(self.task_id)
  51. self.user_id = self.taskMdl.get('user_id')
  52. self.servers = self.taskMdl.get('servers_info')
  53. self.task = self.taskMdl.get('target_user')
  54. self.project_info = self.taskMdl.get('project_info')
  55. if project_id:
  56. self.project_id = project_id
  57. self.project_info = ProjectModel(id=project_id).item()
  58. def config(self):
  59. return {'task_id': self.task_id, 'user_id': self.user_id, 'stage': self.stage, 'sequence': self.sequence}
  60. # ===================== fabric ================
  61. # SocketHandler
  62. def prev_deploy(self):
  63. '''
  64. # TODO
  65. socketio.sleep(0.001)
  66. 1.代码检出前要做的基础工作
  67. - 检查 当前用户
  68. - 检查 python 版本
  69. - 检查 git 版本
  70. - 检查 目录是否存在
  71. - 用户自定义命令
  72. :return:
  73. '''
  74. self.stage = self.stage_prev_deploy
  75. self.sequence = 1
  76. # TODO remove
  77. # result = self.local.run('sleep 30', wenv=self.config())
  78. # 检查 当前用户
  79. command = 'whoami'
  80. current_app.logger.info(command)
  81. socketio.emit('console', {'event': 'task:console', 'data': {'cmd':command}}, room=self.task_id, ignore_queue=True)
  82. result = self.local.run(command, wenv=self.config())
  83. # 检查 python 版本
  84. command = 'python --version'
  85. result = self.local.run(command, wenv=self.config())
  86. current_app.logger.info(command)
  87. # 检查 git 版本
  88. command = 'git --version'
  89. result = self.local.run(command, wenv=self.config())
  90. current_app.logger.info(command)
  91. time.sleep(3)
  92. # 检查 目录是否存在
  93. command = 'mkdir -p %s' % (self.dir_codebase_project)
  94. # TODO remove
  95. current_app.logger.info(command)
  96. result = self.local.run(command, wenv=self.config())
  97. # 用户自定义命令
  98. command = self.project_info['prev_deploy']
  99. current_app.logger.info(command)
  100. with self.local.cd(self.dir_codebase_project):
  101. result = self.local.run(command, wenv=self.config())
  102. # SocketHandler.send_to_all({
  103. # 'type': 'user',
  104. # 'id': 33,
  105. # 'host': env.host_string,
  106. # 'command': command,
  107. # 'message': result.stdout,
  108. # })
  109. def deploy(self):
  110. '''
  111. 2.检出代码
  112. :param project_name:
  113. :return:
  114. '''
  115. # TODO
  116. socketio.sleep(0.001)
  117. self.stage = self.stage_deploy
  118. self.sequence = 2
  119. current_app.logger.info('git dir: %s', self.dir_codebase_project + '/.git')
  120. # 如果项目底下有 .git 目录则认为项目完整,可以直接检出代码
  121. # TODO 不标准
  122. if os.path.exists(self.dir_codebase_project + '/.git'):
  123. with self.local.cd(self.dir_codebase_project):
  124. command = 'pwd && git pull'
  125. result = self.local.run(command, wenv=self.config())
  126. else:
  127. # 否则当作新项目检出完整代码
  128. with self.local.cd(self.dir_codebase_project):
  129. command = 'pwd && git clone %s .' % (self.project_info['repo_url'])
  130. current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
  131. result = self.local.run(command, wenv=self.config())
  132. # copy to a local version
  133. self.release_version = '%s_%s_%s' % (
  134. self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))
  135. with self.local.cd(self.dir_codebase):
  136. command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
  137. current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
  138. result = self.local.run(command, wenv=self.config())
  139. # 更新到指定 commit_id
  140. with self.local.cd(self.dir_codebase + self.release_version):
  141. command = 'git reset -q --hard %s' % (self.taskMdl.get('commit_id'))
  142. result = self.local.run(command, wenv=self.config())
  143. # SocketHandler.send_to_all({
  144. # 'type': 'user',
  145. # 'id': 33,
  146. # 'host': env.host_string,
  147. # 'command': command,
  148. # 'message': result.stdout,
  149. # })
  150. # 用户自定义命令
  151. # command = self.project_info['deploy']
  152. # current_app.logger.info(command)
  153. # with self.local.cd(self.dir_codebase):
  154. # result = self.local.run(command)
  155. pass
  156. def post_deploy(self):
  157. '''
  158. 3.检出代码后要做的任务
  159. - 用户自定义操作命令
  160. - 代码编译
  161. - 清除日志文件及无用文件
  162. -
  163. - 压缩打包
  164. - 传送到版本库 release
  165. :return:
  166. '''
  167. # TODO
  168. socketio.sleep(0.001)
  169. self.stage = self.stage_post_deploy
  170. self.sequence = 3
  171. # 用户自定义命令
  172. command = self.project_info['post_deploy']
  173. current_app.logger.info(command)
  174. current_app.logger.info(self.dir_codebase)
  175. current_app.logger.info(self.release_version)
  176. with self.local.cd(self.dir_codebase + self.release_version):
  177. result = self.local.run(command, wenv=self.config())
  178. # 压缩打包
  179. self.release_version_tar = '%s.tgz' % (self.release_version)
  180. with self.local.cd(self.dir_codebase):
  181. command = 'tar zcvf %s %s' % (self.release_version_tar, self.release_version)
  182. result = self.local.run(command, wenv=self.config())
  183. def prev_release(self, waller):
  184. '''
  185. 4.部署代码到目标机器前做的任务
  186. - 检查 webroot 父目录是否存在
  187. :return:
  188. '''
  189. # TODO
  190. socketio.sleep(0.001)
  191. self.stage = self.stage_prev_release
  192. self.sequence = 4
  193. # 检查 target_releases 父目录是否存在
  194. command = 'mkdir -p %s' % (self.project_info['target_releases'])
  195. result = waller.run(command, wenv=self.config())
  196. # TODO 检查 webroot 父目录是否存在,是否为软链
  197. # command = 'mkdir -p %s' % (self.project_info['target_root'])
  198. # result = waller.run(command)
  199. # current_app.logger.info('command: %s', dir(result))
  200. # TODO md5
  201. # 传送到版本库 release
  202. current_app.logger.info('/tmp/walle/codebase/' + self.release_version_tar)
  203. result = waller.put('/tmp/walle/codebase/' + self.release_version_tar,
  204. remote=self.project_info['target_releases'], wenv=self.config())
  205. current_app.logger.info('command: %s', dir(result))
  206. # 解压
  207. self.release_untar(waller)
  208. def release(self, waller):
  209. '''
  210. 5.部署代码到目标机器做的任务
  211. - 打包代码 local
  212. - scp local => remote
  213. - 解压 remote
  214. :return:
  215. '''
  216. # TODO
  217. socketio.sleep(0.001)
  218. self.stage = self.stage_release
  219. self.sequence = 5
  220. with waller.cd(self.project_info['target_releases']):
  221. # 1. create a tmp link dir
  222. current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
  223. command = 'ln -sfn %s/%s %s' % (
  224. self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
  225. result = waller.run(command, wenv=self.config())
  226. # 2. make a soft link from release to tmp link
  227. # 3. move tmp link to webroot
  228. current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
  229. command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
  230. result = waller.run(command, wenv=self.config())
  231. def release_untar(self, waller):
  232. '''
  233. 解压版本包
  234. :return:
  235. '''
  236. # TODO
  237. socketio.sleep(0.001)
  238. with waller.cd(self.project_info['target_releases']):
  239. command = 'tar zxf %s' % (self.release_version_tar)
  240. result = waller.run(command, wenv=self.config())
  241. def post_release(self, waller):
  242. '''
  243. 6.部署代码到目标机器后要做的任务
  244. - 切换软链
  245. - 重启 nginx
  246. :return:
  247. '''
  248. self.stage = self.stage_post_release
  249. self.sequence = 6
  250. self.post_release_service(waller)
  251. def post_release_service(self, waller):
  252. '''
  253. 代码部署完成后,服务启动工作,如: nginx重启
  254. :param connection:
  255. :return:
  256. '''
  257. with waller.cd(self.project_info['target_root']):
  258. command = 'sudo service nginx restart'
  259. result = waller.run(command, wenv=self.config())
  260. def list_tag(self):
  261. with self.local.cd(self.dir_codebase_project):
  262. command = 'git tag -l'
  263. current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
  264. result = self.local.run(command, wenv=self.config())
  265. current_app.logger.info(dir(result))
  266. return result
  267. return None
  268. def list_branch(self):
  269. with self.local.cd(self.dir_codebase_project):
  270. command = 'git pull'
  271. # result = self.local.run(command, wenv=self.config())
  272. current_app.logger.info(self.dir_codebase_project)
  273. command = 'git branch -r'
  274. result = self.local.run(command, wenv=self.config())
  275. # TODO 三种可能: false, error, success
  276. branches = result.stdout.strip().split('\n')
  277. # 去除 origin/HEAD -> 当前指向
  278. # 去除远端前缀
  279. branches = [branch.strip().lstrip('origin/') for branch in branches if not branch.startswith('origin/HEAD')]
  280. return branches
  281. return None
  282. def list_commit(self, branch):
  283. with self.local.cd(self.dir_codebase_project):
  284. command = 'git checkout %s && git pull' % (branch)
  285. result = self.local.run(command, wenv=self.config())
  286. # TODO 10是需要前端传的
  287. command = 'git log -10 --pretty="%h #_# %an #_# %s"'
  288. result = self.local.run(command, wenv=self.config())
  289. commit_list = result.stdout.strip().split('\n')
  290. commits = []
  291. for commit in commit_list:
  292. commit_dict = commit.split(' #_# ')
  293. commits.append({
  294. 'id': commit_dict[0],
  295. 'name': commit_dict[1],
  296. 'message': commit_dict[2],
  297. })
  298. return commits
  299. return None
  300. def walle_deploy(self):
  301. self.prev_deploy()
  302. self.deploy()
  303. self.post_deploy()
  304. server = '172.16.0.231'
  305. try:
  306. self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
  307. self.prev_release(self.connections[server])
  308. self.release(self.connections[server])
  309. self.post_release(self.connections[server])
  310. except Exception, e:
  311. current_app.logger.exception(e)
  312. self.errors[server] = e.message
  313. # for server_info in self.servers:
  314. # server = server_info.host
  315. # try:
  316. # self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
  317. # self.prev_release(self.connections[server])
  318. # self.release(self.connections[server])
  319. # self.post_release(self.connections[server])
  320. # except Exception, e:
  321. # self.errors[server] = e.message
  322. return {'success': self.success, 'errors': self.errors}