|
|
@ -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 |
|
|
@ -37,6 +39,7 @@ from requests.auth import HTTPBasicAuth |
|
|
|
from werkzeug import urls |
|
|
|
from odoo import api, fields, models, _ |
|
|
|
from odoo.exceptions import UserError, ValidationError |
|
|
|
from odoo.tools import find_pg_tool, exec_pg_environ |
|
|
|
from odoo.http import request |
|
|
|
from odoo.service import db |
|
|
|
|
|
|
@ -616,7 +619,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
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) |
|
|
|
self.dump_data(rec.db_name, f, rec.backup_format) |
|
|
|
f.close() |
|
|
|
# Remove older backups |
|
|
|
if rec.auto_remove: |
|
|
@ -650,7 +653,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, |
|
|
|
self.dump_data(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
ftp_server.storbinary('STOR %s' % backup_filename, |
|
|
|
open(temp.name, "rb")) |
|
|
@ -686,8 +689,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: |
|
|
@ -723,7 +725,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, |
|
|
|
self.dump_data(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
try: |
|
|
|
headers = { |
|
|
@ -784,7 +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, |
|
|
|
self.dump_data(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
try: |
|
|
|
dbx = dropbox.Dropbox( |
|
|
@ -819,8 +821,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'} |
|
|
@ -880,8 +881,7 @@ class DbBackupConfigure(models.Model): |
|
|
|
for item in nc.list(folder_path): |
|
|
|
backup_file_name = item.path.split("/")[-1] |
|
|
|
backup_date_str = \ |
|
|
|
backup_file_name.split("_")[ |
|
|
|
2] |
|
|
|
backup_file_name.split("_")[2] |
|
|
|
backup_date = fields.datetime.strptime( |
|
|
|
backup_date_str, '%Y-%m-%d').date() |
|
|
|
if (fields.date.today() - backup_date).days \ |
|
|
@ -915,7 +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, |
|
|
|
self.dump_data(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
@ -926,7 +926,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, |
|
|
|
self.dump_data(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
@ -986,7 +986,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, |
|
|
|
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}_" \ |
|
|
@ -1004,8 +1004,71 @@ class DbBackupConfigure(models.Model): |
|
|
|
# field to the error message and log the error |
|
|
|
rec.generated_exception = error |
|
|
|
_logger.info('Amazon S3 Exception: %s', error) |
|
|
|
# If notify_user is enabled, send an email to the user |
|
|
|
# If notify_user is enabled, email the user |
|
|
|
# notifying them about the failed backup |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
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 |
|
|
|