helper.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. # Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
  2. # Copyright: (c) <spug.dev@gmail.com>
  3. # Released under the AGPL-3.0 License.
  4. from django_redis import get_redis_connection
  5. from django.conf import settings
  6. from libs.utils import human_datetime
  7. from libs.spug import Notification
  8. from apps.host.models import Host
  9. import subprocess
  10. import json
  11. import os
  12. class SpugError(Exception):
  13. pass
  14. class Helper:
  15. def __init__(self, rds, key):
  16. self.rds = rds
  17. self.key = key
  18. self.rds.delete(self.key)
  19. @classmethod
  20. def _make_dd_notify(cls, url, action, req, version, host_str):
  21. texts = [
  22. f'**申请标题:** {req.name}',
  23. f'**应用名称:** {req.deploy.app.name}',
  24. f'**应用版本:** {version}',
  25. f'**发布环境:** {req.deploy.env.name}',
  26. f'**发布主机:** {host_str}',
  27. ]
  28. if action == 'approve_req':
  29. texts.insert(0, '## %s ## ' % '发布审核申请')
  30. texts.extend([
  31. f'**申请人员:** {req.created_by.nickname}',
  32. f'**申请时间:** {human_datetime()}',
  33. '> 来自 Spug运维平台'
  34. ])
  35. elif action == 'approve_rst':
  36. color, text = ('#008000', '通过') if req.status == '1' else ('#f90202', '驳回')
  37. texts.insert(0, '## %s ## ' % '发布审核结果')
  38. texts.extend([
  39. f'**审核人员:** {req.approve_by.nickname}',
  40. f'**审核结果:** <font color="{color}">{text}</font>',
  41. f'**审核意见:** {req.reason or ""}',
  42. f'**审核时间:** {human_datetime()}',
  43. '> 来自 Spug运维平台'
  44. ])
  45. else:
  46. color, text = ('#008000', '成功') if req.status == '3' else ('#f90202', '失败')
  47. texts.insert(0, '## %s ## ' % '发布结果通知')
  48. if req.approve_at:
  49. texts.append(f'**审核人员:** {req.approve_by.nickname}')
  50. do_user = req.do_by.nickname if req.type != '3' else 'Webhook'
  51. texts.extend([
  52. f'**执行人员:** {do_user}',
  53. f'**发布结果:** <font color="{color}">{text}</font>',
  54. f'**发布时间:** {human_datetime()}',
  55. '> 来自 Spug运维平台'
  56. ])
  57. data = {
  58. 'msgtype': 'markdown',
  59. 'markdown': {
  60. 'title': 'Spug 发布消息通知',
  61. 'text': '\n\n'.join(texts)
  62. }
  63. }
  64. Notification.handle_request(url, data, 'dd')
  65. @classmethod
  66. def _make_wx_notify(cls, url, action, req, version, host_str):
  67. texts = [
  68. f'申请标题: {req.name}',
  69. f'应用名称: {req.deploy.app.name}',
  70. f'应用版本: {version}',
  71. f'发布环境: {req.deploy.env.name}',
  72. f'发布主机: {host_str}',
  73. ]
  74. if action == 'approve_req':
  75. texts.insert(0, '## %s' % '发布审核申请')
  76. texts.extend([
  77. f'申请人员: {req.created_by.nickname}',
  78. f'申请时间: {human_datetime()}',
  79. '> 来自 Spug运维平台'
  80. ])
  81. elif action == 'approve_rst':
  82. color, text = ('info', '通过') if req.status == '1' else ('warning', '驳回')
  83. texts.insert(0, '## %s' % '发布审核结果')
  84. texts.extend([
  85. f'审核人员: {req.approve_by.nickname}',
  86. f'审核结果: <font color="{color}">{text}</font>',
  87. f'审核意见: {req.reason or ""}',
  88. f'审核时间: {human_datetime()}',
  89. '> 来自 Spug运维平台'
  90. ])
  91. else:
  92. color, text = ('info', '成功') if req.status == '3' else ('warning', '失败')
  93. texts.insert(0, '## %s' % '发布结果通知')
  94. if req.approve_at:
  95. texts.append(f'审核人员: {req.approve_by.nickname}')
  96. do_user = req.do_by.nickname if req.type != '3' else 'Webhook'
  97. texts.extend([
  98. f'执行人员: {do_user}',
  99. f'发布结果: <font color="{color}">{text}</font>',
  100. f'发布时间: {human_datetime()}',
  101. '> 来自 Spug运维平台'
  102. ])
  103. data = {
  104. 'msgtype': 'markdown',
  105. 'markdown': {
  106. 'content': '\n'.join(texts)
  107. }
  108. }
  109. Notification.handle_request(url, data, 'wx')
  110. @classmethod
  111. def _make_fs_notify(cls, url, action, req, version, host_str):
  112. texts = [
  113. f'申请标题: {req.name}',
  114. f'应用名称: {req.deploy.app.name}',
  115. f'应用版本: {version}',
  116. f'发布环境: {req.deploy.env.name}',
  117. f'发布主机: {host_str}',
  118. ]
  119. if action == 'approve_req':
  120. title = '发布审核申请'
  121. texts.extend([
  122. f'申请人员: {req.created_by.nickname}',
  123. f'申请时间: {human_datetime()}',
  124. ])
  125. elif action == 'approve_rst':
  126. title = '发布审核结果'
  127. text = '通过' if req.status == '1' else '驳回'
  128. texts.extend([
  129. f'审核人员: {req.approve_by.nickname}',
  130. f'审核结果: {text}',
  131. f'审核意见: {req.reason or ""}',
  132. f'审核时间: {human_datetime()}',
  133. ])
  134. else:
  135. title = '发布结果通知'
  136. text = '成功 ✅' if req.status == '3' else '失败 ❗'
  137. if req.approve_at:
  138. texts.append(f'审核人员: {req.approve_by.nickname}')
  139. do_user = req.do_by.nickname if req.type != '3' else 'Webhook'
  140. texts.extend([
  141. f'执行人员: {do_user}',
  142. f'发布结果: {text}',
  143. f'发布时间: {human_datetime()}',
  144. ])
  145. data = {
  146. 'msg_type': 'post',
  147. 'content': {
  148. 'post': {
  149. 'zh_cn': {
  150. 'title': title,
  151. 'content': [[{'tag': 'text', 'text': x}] for x in texts] + [[{'tag': 'at', 'user_id': 'all'}]]
  152. }
  153. }
  154. }
  155. }
  156. Notification.handle_request(url, data, 'fs')
  157. @classmethod
  158. def send_deploy_notify(cls, req, action=None):
  159. rst_notify = json.loads(req.deploy.rst_notify)
  160. host_ids = json.loads(req.host_ids)
  161. if rst_notify['mode'] != '0' and rst_notify.get('value'):
  162. url = rst_notify['value']
  163. version = req.version
  164. hosts = [{'id': x.id, 'name': x.name} for x in Host.objects.filter(id__in=host_ids)]
  165. host_str = ', '.join(x['name'] for x in hosts[:2])
  166. if len(hosts) > 2:
  167. host_str += f'等{len(hosts)}台主机'
  168. if rst_notify['mode'] == '1':
  169. cls._make_dd_notify(url, action, req, version, host_str)
  170. elif rst_notify['mode'] == '2':
  171. data = {
  172. 'action': action,
  173. 'req_id': req.id,
  174. 'req_name': req.name,
  175. 'app_id': req.deploy.app_id,
  176. 'app_name': req.deploy.app.name,
  177. 'env_id': req.deploy.env_id,
  178. 'env_name': req.deploy.env.name,
  179. 'status': req.status,
  180. 'reason': req.reason,
  181. 'version': version,
  182. 'targets': hosts,
  183. 'is_success': req.status == '3',
  184. 'created_at': human_datetime()
  185. }
  186. Notification.handle_request(url, data)
  187. elif rst_notify['mode'] == '3':
  188. cls._make_wx_notify(url, action, req, version, host_str)
  189. elif rst_notify['mode'] == '4':
  190. cls._make_fs_notify(url, action, req, version, host_str)
  191. else:
  192. raise NotImplementedError
  193. def parse_filter_rule(self, data: str, sep='\n'):
  194. data, files = data.strip(), []
  195. if data:
  196. for line in data.split(sep):
  197. line = line.strip()
  198. if line and not line.startswith('#'):
  199. files.append(line)
  200. return files
  201. def _send(self, message):
  202. self.rds.rpush(self.key, json.dumps(message))
  203. def send_info(self, key, message):
  204. if message:
  205. self._send({'key': key, 'data': message})
  206. def send_error(self, key, message, with_break=True):
  207. message = f'\r\n\033[31m{message}\033[0m'
  208. self._send({'key': key, 'status': 'error', 'data': message})
  209. if with_break:
  210. raise SpugError
  211. def send_step(self, key, step, data):
  212. self._send({'key': key, 'step': step, 'data': data})
  213. def clear(self):
  214. # save logs for two weeks
  215. self.rds.expire(self.key, 14 * 24 * 60 * 60)
  216. self.rds.close()
  217. def local(self, command, env=None):
  218. if env:
  219. env = dict(env.items())
  220. env.update(os.environ)
  221. task = subprocess.Popen(command, env=env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  222. while True:
  223. message = task.stdout.readline()
  224. if not message:
  225. break
  226. message = message.decode().rstrip('\r\n')
  227. self.send_info('local', message + '\r\n')
  228. if task.wait() != 0:
  229. self.send_error('local', f'exit code: {task.returncode}')
  230. def remote(self, key, ssh, command, env=None):
  231. code = -1
  232. for code, out in ssh.exec_command_with_stream(command, environment=env):
  233. self.send_info(key, out)
  234. if code != 0:
  235. self.send_error(key, f'exit code: {code}')
  236. def remote_raw(self, key, ssh, command):
  237. code, out = ssh.exec_command_raw(command)
  238. if code != 0:
  239. self.send_error(key, f'exit code: {code}, {out}')