utils.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. from public import app
  2. from libs.tools import AttrDict
  3. from docker import APIClient
  4. from docker.errors import APIError, DockerException
  5. import json
  6. import requests
  7. import os
  8. import subprocess
  9. import datetime
  10. import base64
  11. import uuid
  12. class DockerClient(object):
  13. def __init__(self, base_url):
  14. self.client = APIClient(base_url=base_url, version='auto', timeout=30)
  15. self.auth_config = {
  16. 'username': app.config['DOCKER_REGISTRY_AUTH'].get('username'),
  17. 'password': app.config['DOCKER_REGISTRY_AUTH'].get('password')
  18. }
  19. def __repr__(self):
  20. return '<DockerClient %r>' % self.client.base_url
  21. def __del__(self):
  22. self.client.close()
  23. @property
  24. def api_version(self):
  25. return self.client.api_version
  26. def docker_info(self):
  27. return self.client.info()
  28. def pull_image(self, image, tag, stream=False):
  29. if stream:
  30. return self.client.pull(image, tag, auth_config=self.auth_config, stream=True)
  31. rst = self.client.pull(image, tag, auth_config=self.auth_config)
  32. last_message = json.loads(rst.split('\r\n')[-2])
  33. if last_message.get('error'):
  34. raise APIError(last_message['error'])
  35. def prune_images(self, filters=None):
  36. return self.client.prune_images(filters=filters)
  37. class Container(DockerClient):
  38. def __init__(self, base_url, name):
  39. super().__init__(base_url)
  40. self.name = name
  41. self.host_config = {}
  42. def __repr__(self):
  43. return '<Container %r>' % self.name
  44. @property
  45. def info(self):
  46. cs = self.client.containers(all=True, filters={'name': self.name})
  47. info = AttrDict(cs[0]) if cs and self.api_version >= '1.21' else None
  48. if info is not None:
  49. info.running = info.State == 'running'
  50. if info.running and [x for x in self.client.top(self.name)['Processes'] if 'sleep 777d' in x]:
  51. info.Status = 'v_start exit'
  52. return info
  53. def stop(self, timeout=3):
  54. self.client.stop(self.name, timeout=timeout)
  55. def start(self):
  56. self.client.start(self.name)
  57. def restart(self, timeout=3):
  58. self.client.restart(self.name, timeout=timeout)
  59. def create(self, image, **kwargs):
  60. return self.client.create_container(image, host_config=self.host_config, **kwargs)
  61. def create_host_config(self, **kwargs):
  62. self.host_config = self.client.create_host_config(**kwargs)
  63. def remove(self):
  64. self.client.remove_container(self.name, force=True)
  65. def put_archive(self, path, data):
  66. return self.client.put_archive(self.name, path, data)
  67. def logs(self, stream=False, **kwargs):
  68. output = self.client.logs(self.name, stream=stream, **kwargs)
  69. return output if stream else output.decode()
  70. def exec_command(self, cmd, with_exit_code=False, user='root'):
  71. task = self.client.exec_create(self.name, cmd, user=user)
  72. output = self.client.exec_start(task['Id'], stream=False).decode()
  73. if with_exit_code:
  74. return self.client.exec_inspect(task['Id'])['ExitCode'], output
  75. return output
  76. def exec_command_with_base64(self, cmd, args_str='', timeout=30, token=None, with_exit_code=False, stream=False):
  77. token = token or uuid.uuid4().hex
  78. command = '/entrypoint.sh %d %s %s %s' % (timeout, token, base64.b64encode(cmd.encode()).decode(), args_str)
  79. task = self.client.exec_create(self.name, command)
  80. if with_exit_code:
  81. output = self.client.exec_start(task['Id'], stream=False).decode()
  82. return self.client.exec_inspect(task['Id'])['ExitCode'], output
  83. elif stream:
  84. return self.client.exec_start(task['Id'], stream=True)
  85. else:
  86. return self.client.exec_start(task['Id'], stream=False).decode()
  87. class Registry(object):
  88. def __init__(self, base_url):
  89. self.api = base_url
  90. self.auth = (
  91. app.config['DOCKER_REGISTRY_AUTH'].get('username'),
  92. app.config['DOCKER_REGISTRY_AUTH'].get('password')
  93. )
  94. def list_tags(self, name):
  95. req_url = 'https://%s/v2/%s/tags/list' % (self.api, name)
  96. tags = requests.get(req_url, auth=self.auth).json().get('tags', [])
  97. tags = tags or []
  98. tags.reverse()
  99. return tags
  100. def delete(self, name, digest):
  101. req_url = 'https://%s/v2/%s/manifests/%s' % (self.api, name, digest)
  102. res = requests.delete(req_url, auth=self.auth)
  103. if res.status_code not in [202, 404]:
  104. raise Exception('Delete image error, code: %d content: %s' % (res.status_code, res.content))
  105. def list_images(self):
  106. req_url = 'https://%s/v2/_catalog' % self.api
  107. res = requests.get(req_url, auth=self.auth).json()
  108. return res.get('repositories', [])
  109. def get_tag_digest(self, name, tag):
  110. req_url = 'https://%s/v2/%s/manifests/%s' % (self.api, name, tag)
  111. res = requests.head(req_url, headers={'Accept': 'application/vnd.docker.distribution.manifest.v2+json'}, auth=self.auth)
  112. return res.headers.get('Docker-Content-Digest')
  113. def get_last_modify_date(self, name, tag):
  114. req_url = 'https://%s/v2/%s/manifests/%s' % (self.api, name, tag)
  115. res = requests.get(req_url, auth=self.auth).json()
  116. last_history_date = json.loads(res['history'][0]['v1Compatibility'])['created'].split('.')[0]
  117. created = datetime.datetime.strptime(last_history_date, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=datetime.timezone.utc)
  118. return created.astimezone(datetime.timezone(datetime.timedelta(hours=8))).strftime('%Y-%m-%d %H:%M:%S')
  119. def __repr__(self):
  120. return '<Registry %r>' % self.api
  121. class DockerImage(object):
  122. def __init__(self, base_url):
  123. self.client = APIClient(base_url=base_url, version='auto')
  124. self.full_name = None
  125. def build(self, path, name, tag):
  126. self.full_name = '{0}/{1}:{2}'.format(app.config['DOCKER_REGISTRY_SERVER'], name, tag)
  127. for item in self.client.build(path=path, tag=self.full_name, forcerm=False):
  128. detail = json.loads(item.decode().strip())
  129. if 'errorDetail' in detail:
  130. raise Exception('Build image error: ' + detail['errorDetail'].get('message', '未知错误'))
  131. def push(self, image=None):
  132. repository = image or self.full_name
  133. if repository is None:
  134. raise Exception('Push image error: argument <image> is missing.')
  135. for item in self.client.push(repository, auth_config=app.config['DOCKER_REGISTRY_AUTH'], stream=True):
  136. detail = json.loads(item.decode().strip())
  137. if 'errorDetail' in detail:
  138. raise Exception('Push image error: ' + detail['errorDetail'].get('message', '未知错误'))
  139. if 'aux' in detail:
  140. return detail['aux']['Digest']
  141. raise Exception('Push image error: 未知错误')
  142. def remove(self, image=None):
  143. repository = image or self.full_name
  144. if repository is None:
  145. raise Exception('Remove image error: argument <image> is missing.')
  146. self.client.remove_image(repository)
  147. class Git(object):
  148. def __init__(self, work_tree_dir):
  149. self.work_tree = work_tree_dir
  150. self.git_dir = os.path.join(self.work_tree, '.git')
  151. self.base_command = 'git --git-dir=%s --work-tree=%s ' % (self.git_dir, self.work_tree)
  152. def _exec_command(self, *args):
  153. command = self.base_command + ' '.join(args)
  154. if args[0] == 'clone':
  155. command = 'git ' + ' '.join(args)
  156. status, output = subprocess.getstatusoutput(command)
  157. if status != 0:
  158. raise subprocess.SubprocessError(output)
  159. return output
  160. def clone(self, url):
  161. self._exec_command('clone', url, self.work_tree)
  162. def pull(self):
  163. self._exec_command('pull', '--all')
  164. def fetch_tags(self):
  165. self._exec_command('fetch', '--tags')
  166. def is_valid(self):
  167. return os.path.isdir(self.git_dir)
  168. @property
  169. def tags(self, count=5, refresh=True):
  170. if refresh:
  171. self.fetch_tags()
  172. output = self._exec_command(
  173. 'for-each-ref',
  174. '--sort=-taggerdate',
  175. '--count={0}'.format(count),
  176. '--format=%(tag)',
  177. 'refs/tags'
  178. )
  179. return output.strip().splitlines()
  180. def __repr__(self):
  181. return '<Git %r>' % self.work_tree
  182. def send_ding_msg(token_url='', contacts=[], msg=''):
  183. payload = {
  184. "msgtype": "text",
  185. "text": {
  186. "content": msg,
  187. "isAtAll": False
  188. },
  189. "at": {
  190. "atMobiles": contacts
  191. }
  192. }
  193. req = requests.post(token_url, json=payload)
  194. if req.status_code == 200:
  195. return True
  196. else:
  197. return False