backup.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. from __future__ import print_function
  4. import os
  5. import pickle
  6. import os.path
  7. import docker
  8. import tarfile
  9. import time
  10. from googleapiclient.discovery import build
  11. from googleapiclient.http import MediaIoBaseUpload, MediaFileUpload
  12. from google_auth_oauthlib.flow import InstalledAppFlow
  13. from google.auth.transport.requests import Request
  14. from docker.errors import NotFound, APIError
  15. from datetime import datetime, timedelta
  16. from io import BytesIO
  17. BACKUP_AGE = 30
  18. DOCKER_SOCK = 'unix://var/run/docker.sock'
  19. POSTGRES_CONTAINER = 'postgres'
  20. POSTGRES_USER = 'postgres'
  21. ODOO_IMAGE = 'odoo/robert:8.0'
  22. # ODOO_PATH = '/opt/odoo'
  23. ODOO_PATH = '/home/robert'
  24. '''
  25. '''
  26. def get_drive_service():
  27. creds = None
  28. if os.path.exists('token.pickle'):
  29. with open('token.pickle', 'rb') as token:
  30. creds = pickle.load(token)
  31. if not creds or not creds.valid:
  32. if creds and creds.expired and creds.refresh_token:
  33. creds.refresh(Request())
  34. else:
  35. flow = InstalledAppFlow.from_client_secrets_file('credentials.json', ['https://www.googleapis.com/auth/drive'])
  36. creds = flow.run_local_server()
  37. with open('token.pickle', 'wb') as token:
  38. pickle.dump(creds, token)
  39. return build('drive', 'v3', credentials=creds)
  40. '''
  41. '''
  42. def delete_drive_old_folders(service=None):
  43. if service == None:
  44. return False
  45. date_old = datetime.utcnow() - timedelta(BACKUP_AGE)
  46. date_old = date_old.strftime("%Y-%m-%dT00:00:00")
  47. query = "mimeType='application/vnd.google-apps.folder' and createdTime < '%s'" % date_old
  48. result = service.files().list(q=query, fields='files(id, name)').execute()
  49. for item in result.get('files', []):
  50. service.files().delete(fileId=item.get('id')).execute()
  51. '''
  52. '''
  53. def create_folder_name():
  54. return datetime.now().strftime('%Y_%m_%d')
  55. '''
  56. '''
  57. def create_drive_folder(folder_name, service=None):
  58. if service == None:
  59. return None
  60. result = service.files().list(q="name='{}'".format(folder_name)).execute()
  61. items = result.get('files', [])
  62. if len(items) > 0:
  63. return items[0].get('id')
  64. folder_metadata = {
  65. 'name': folder_name,
  66. 'mimeType': 'application/vnd.google-apps.folder'
  67. }
  68. result = service.files().create(body=folder_metadata).execute()
  69. return result.get('id')
  70. '''
  71. '''
  72. def get_docker_client():
  73. return docker.DockerClient(base_url=DOCKER_SOCK)
  74. '''
  75. '''
  76. def get_pg_container(docker_client):
  77. try:
  78. pg_container = docker_client.containers.get(POSTGRES_CONTAINER)
  79. return pg_container
  80. except (NotFound, APIError):
  81. return None
  82. '''
  83. '''
  84. def list_postgres_databases(docker_client):
  85. pg_container = get_pg_container(docker_client)
  86. if pg_container is None or pg_container.status == 'exited':
  87. return []
  88. command = "psql -U %s -t -c 'SELECT datname FROM pg_database'" % POSTGRES_USER
  89. result = pg_container.exec_run(command)
  90. if result.exit_code == -1:
  91. return []
  92. output = result.output.split('\n')
  93. output = map(lambda x: x.strip(), output)
  94. output = filter(lambda x: x != '', output)
  95. BLACK_LIST = ['postgres', 'template1', 'template0']
  96. output = filter(lambda x: x not in BLACK_LIST, output)
  97. return output
  98. '''
  99. '''
  100. def filter_databases_by_active_containers(databases, docker_client):
  101. try:
  102. containers = docker_client.containers.list(filters={'status': 'running', 'ancestor': ODOO_IMAGE})
  103. containers_name = map(lambda x: x.name, containers)
  104. return filter(lambda x: x in containers_name, databases)
  105. except APIError:
  106. return []
  107. '''
  108. '''
  109. def create_postgres_backup(database, docker_client):
  110. pg_container = get_pg_container(docker_client)
  111. if pg_container is None or pg_container.status == 'exited':
  112. return (False, None)
  113. tmp_file = '%s_database_%s.tar' % (database, datetime.now().strftime('%Y-%m-%d_%H:%M:%S'))
  114. command = 'pg_dump -U %s -d %s -F tar -C -b -c -f %s' % (POSTGRES_USER, database, tmp_file)
  115. result = pg_container.exec_run(command)
  116. if result.exit_code == -1:
  117. (False, tmp_file)
  118. return (True, tmp_file)
  119. '''
  120. '''
  121. def create_odoo_filestore_backup(folder_name):
  122. root_path = './tmp'
  123. if not os.path.exists(root_path):
  124. os.mkdir(root_path)
  125. tar_name = '%s_filestore_%s.tar' % (folder_name, datetime.now().strftime('%Y-%m-%d_%H:%M:%S'))
  126. tar_path = os.path.join(root_path, tar_name)
  127. filestore_path = os.path.join(ODOO_PATH, folder_name, 'files', 'filestore', folder_name)
  128. with tarfile.open(tar_path, mode='w') as tar:
  129. tar.add(filestore_path, os.path.basename(filestore_path))
  130. tar.close()
  131. return os.path.abspath(tar_path)
  132. '''
  133. '''
  134. def upload_postgres_to_drive(backup_file_name, backup_folder_id, docket_client, service):
  135. if service == None:
  136. return None
  137. pg_container = get_pg_container(docket_client)
  138. if pg_container is None or pg_container.status == 'exited':
  139. return None
  140. (backup_file, _) = pg_container.get_archive('/%s' % backup_file_name)
  141. raw_data = BytesIO()
  142. for chunk in backup_file:
  143. raw_data.write(chunk)
  144. raw_data.seek(0)
  145. backup_metadata = {
  146. 'name': backup_file_name,
  147. 'parents': [backup_folder_id]
  148. }
  149. backup_media = MediaIoBaseUpload(raw_data, mimetype='application/tar', chunksize=2*(1024*1024))
  150. result = service.files().create(body=backup_metadata, media_body=backup_media).execute()
  151. raw_data.close()
  152. return result.get('id')
  153. '''
  154. '''
  155. def upload_filestore_to_drive(backup_path, backup_folder_id, service):
  156. if service == None:
  157. return None
  158. backup_name = os.path.basename(backup_path)
  159. backup_metadata = {
  160. 'name': backup_name,
  161. 'parents': [backup_folder_id]
  162. }
  163. backup_media = MediaFileUpload(backup_path, mimetype='application/tar', chunksize=2*(1024*1024))
  164. result = service.files().create(body=backup_metadata, media_body=backup_media).execute()
  165. return result.get('id')
  166. '''
  167. '''
  168. def delete_postgres_backup(backup_name, docker_client):
  169. pg_container = get_pg_container(docker_client)
  170. if pg_container is None or pg_container.status == 'exited':
  171. return False
  172. command = 'rm %s' % backup_name
  173. result = pg_container.exec_run(command)
  174. if result.exit_code == -1:
  175. return False
  176. return True
  177. '''
  178. '''
  179. def delete_filestore_backup(backup_path):
  180. os.remove(backup_path)
  181. '''
  182. '''
  183. def run_backup():
  184. # 1. get connection
  185. service = get_drive_service()
  186. # 2. delete old folders
  187. delete_drive_old_folders(service)
  188. # 4. create folder name
  189. folder_name = create_folder_name()
  190. # 4. create drive folder
  191. folder_id = create_drive_folder(folder_name, service)
  192. # 5. get docker client
  193. docker_client = get_docker_client()
  194. # 6. list database
  195. databases = list_postgres_databases(docker_client)
  196. # 7. filter databases by active containers
  197. databases = filter_databases_by_active_containers(databases, docker_client)
  198. # 8. backup databases
  199. for db in databases:
  200. (backup_ok, backup_name) = create_postgres_backup(db, docker_client)
  201. if not backup_ok:
  202. if backup_name:
  203. delete_postgres_backup(backup_name, docker_client)
  204. continue
  205. upload_postgres_to_drive(backup_name, folder_id, docker_client, service)
  206. delete_postgres_backup(backup_name, docker_client)
  207. filestore_path = create_odoo_filestore_backup(db)
  208. upload_filestore_to_drive(filestore_path, folder_id, service)
  209. delete_filestore_backup(filestore_path)
  210. time.sleep(1)
  211. docker_client.close()
  212. run_backup()