|
|
@ -29,6 +29,8 @@ import nextcloud_client |
|
|
|
import os |
|
|
|
import paramiko |
|
|
|
import requests |
|
|
|
import shutil |
|
|
|
import subprocess |
|
|
|
import tempfile |
|
|
|
import odoo |
|
|
|
from datetime import timedelta |
|
|
@ -39,6 +41,7 @@ from odoo import api, fields, models, _ |
|
|
|
from odoo.exceptions import UserError, ValidationError |
|
|
|
from odoo.http import request |
|
|
|
from odoo.service import db |
|
|
|
from odoo.tools import find_pg_tool, exec_pg_environ |
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__) |
|
|
|
ONEDRIVE_SCOPE = ['offline_access openid Files.ReadWrite.All'] |
|
|
@ -119,7 +122,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
help='Folder id of the drive') |
|
|
|
notify_user = fields.Boolean(string='Notify User', |
|
|
|
help='Send an email notification to user when' |
|
|
|
'the backup operation is successful' |
|
|
|
'the backup operation is successful ' |
|
|
|
'or failed') |
|
|
|
user_id = fields.Many2one('res.users', string='User', |
|
|
|
help='Name of the user') |
|
|
@ -619,8 +622,8 @@ class DbBackupConfigure(models.Model): |
|
|
|
os.makedirs(rec.backup_path) |
|
|
|
backup_file = os.path.join(rec.backup_path, |
|
|
|
backup_filename) |
|
|
|
f = open(backup_file, "wb") |
|
|
|
odoo.service.db.dump_db(rec.db_name, f, rec.backup_format) |
|
|
|
f = open(backup_file, 'wb') |
|
|
|
self.dump_data(rec.db_name, f, rec.backup_format) |
|
|
|
f.close() |
|
|
|
# Remove older backups |
|
|
|
if rec.auto_remove: |
|
|
@ -632,8 +635,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
if backup_duration.days >= rec.days_to_remove: |
|
|
|
os.remove(file) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_success.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
mail_template_success.send_mail(rec.id, force_send=True) |
|
|
|
except Exception as e: |
|
|
|
rec.generated_exception = e |
|
|
|
_logger.info('FTP Exception: %s', e) |
|
|
@ -654,8 +656,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
ftp_server.mkd(rec.ftp_path) |
|
|
|
ftp_server.cwd(rec.ftp_path) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
ftp_server.storbinary('STOR %s' % backup_filename, |
|
|
|
open(temp.name, "rb")) |
|
|
|
if rec.auto_remove: |
|
|
@ -690,8 +691,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
try: |
|
|
|
sftp.chdir(rec.sftp_path) |
|
|
|
except IOError as e: |
|
|
@ -727,8 +727,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
try: |
|
|
|
headers = { |
|
|
|
"Authorization": "Bearer %s" % rec.gdrive_access_token} |
|
|
@ -787,8 +786,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
try: |
|
|
|
dbx = dropbox.Dropbox( |
|
|
|
app_key=rec.dropbox_client_key, |
|
|
@ -822,8 +820,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
headers = { |
|
|
|
'Authorization': 'Bearer %s' % rec.onedrive_access_token, |
|
|
|
'Content-Type': 'application/json'} |
|
|
@ -918,8 +915,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
|
f"{backup_time}.{rec.backup_format}" |
|
|
@ -929,8 +925,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
|
f"{backup_time}.{rec.backup_format}" |
|
|
@ -989,8 +984,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
self.dump_data(rec.db_name, tmp, rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"{rec.aws_folder_name}/{rec.db_name}_" \ |
|
|
|
f"{backup_time}.{rec.backup_format}" |
|
|
@ -1012,3 +1006,70 @@ class DbBackupConfigure(models.Model): |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
|
|
|
|
def dump_data(self, db_name, stream, backup_format): |
|
|
|
"""Dump database `db` into file-like object `stream` if stream is None |
|
|
|
return a file object with the dump. """ |
|
|
|
|
|
|
|
cron_user_id = self.env.ref( |
|
|
|
'auto_database_backup.ir_cron_auto_db_backup').user_id.id |
|
|
|
if cron_user_id != self.env.user.id: |
|
|
|
_logger.error( |
|
|
|
'Unauthorized database operation. Backups should only be available from the cron job.') |
|
|
|
raise ValidationError( |
|
|
|
"Unauthorized database operation. Backups should only be available from the cron job.") |
|
|
|
|
|
|
|
_logger.info('DUMP DB: %s format %s', db_name, backup_format) |
|
|
|
cmd = [find_pg_tool('pg_dump'), '--no-owner', db_name] |
|
|
|
env = exec_pg_environ() |
|
|
|
if backup_format == 'zip': |
|
|
|
with tempfile.TemporaryDirectory() as dump_dir: |
|
|
|
filestore = odoo.tools.config.filestore(db_name) |
|
|
|
cmd.append('--file=' + os.path.join(dump_dir, 'dump.sql')) |
|
|
|
subprocess.run(cmd, env=env, stdout=subprocess.DEVNULL, |
|
|
|
stderr=subprocess.STDOUT, check=True) |
|
|
|
if os.path.exists(filestore): |
|
|
|
shutil.copytree(filestore, |
|
|
|
os.path.join(dump_dir, 'filestore')) |
|
|
|
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh: |
|
|
|
db = odoo.sql_db.db_connect(db_name) |
|
|
|
with db.cursor() as cr: |
|
|
|
json.dump(self._dump_db_manifest(cr), fh, indent=4) |
|
|
|
if stream: |
|
|
|
odoo.tools.osutil.zip_dir(dump_dir, stream, |
|
|
|
include_dir=False, |
|
|
|
fnct_sort=lambda |
|
|
|
file_name: file_name != 'dump.sql') |
|
|
|
else: |
|
|
|
t = tempfile.TemporaryFile() |
|
|
|
odoo.tools.osutil.zip_dir(dump_dir, t, include_dir=False, |
|
|
|
fnct_sort=lambda |
|
|
|
file_name: file_name != 'dump.sql') |
|
|
|
t.seek(0) |
|
|
|
return t |
|
|
|
else: |
|
|
|
cmd.append('--format=c') |
|
|
|
process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE) |
|
|
|
stdout, _ = process.communicate() |
|
|
|
if stream: |
|
|
|
stream.write(stdout) |
|
|
|
else: |
|
|
|
return stdout |
|
|
|
|
|
|
|
def _dump_db_manifest(self, cr): |
|
|
|
""" This function generates a manifest dictionary for database dump.""" |
|
|
|
pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, |
|
|
|
100) |
|
|
|
cr.execute( |
|
|
|
"SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'") |
|
|
|
modules = dict(cr.fetchall()) |
|
|
|
manifest = { |
|
|
|
'odoo_dump': '1', |
|
|
|
'db_name': cr.dbname, |
|
|
|
'version': odoo.release.version, |
|
|
|
'version_info': odoo.release.version_info, |
|
|
|
'major_version': odoo.release.major_version, |
|
|
|
'pg_version': pg_version, |
|
|
|
'modules': modules, |
|
|
|
} |
|
|
|
return manifest |
|
|
|