|
@ -27,7 +27,8 @@ import logging |
|
|
import os |
|
|
import os |
|
|
import tempfile |
|
|
import tempfile |
|
|
from datetime import timedelta |
|
|
from datetime import timedelta |
|
|
|
|
|
import shutil |
|
|
|
|
|
import subprocess |
|
|
import boto3 |
|
|
import boto3 |
|
|
import dropbox |
|
|
import dropbox |
|
|
import nextcloud_client |
|
|
import nextcloud_client |
|
@ -40,6 +41,7 @@ from werkzeug import urls |
|
|
import odoo |
|
|
import odoo |
|
|
from odoo import api, fields, models, _ |
|
|
from odoo import api, fields, models, _ |
|
|
from odoo.exceptions import UserError, ValidationError |
|
|
from odoo.exceptions import UserError, ValidationError |
|
|
|
|
|
from odoo.tools.misc import find_pg_tool, exec_pg_environ |
|
|
from odoo.http import request |
|
|
from odoo.http import request |
|
|
from odoo.service import db |
|
|
from odoo.service import db |
|
|
|
|
|
|
|
@ -675,7 +677,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
os.makedirs(rec.backup_path) |
|
|
os.makedirs(rec.backup_path) |
|
|
backup_file = os.path.join(rec.backup_path, backup_filename) |
|
|
backup_file = os.path.join(rec.backup_path, backup_filename) |
|
|
f = open(backup_file, "wb") |
|
|
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() |
|
|
f.close() |
|
|
# remove older backups |
|
|
# remove older backups |
|
|
if rec.auto_remove: |
|
|
if rec.auto_remove: |
|
@ -708,7 +710,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
ftp_server.mkd(rec.ftp_path) |
|
|
ftp_server.mkd(rec.ftp_path) |
|
|
ftp_server.cwd(rec.ftp_path) |
|
|
ftp_server.cwd(rec.ftp_path) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
ftp_server.storbinary('STOR %s' % backup_filename, |
|
|
ftp_server.storbinary('STOR %s' % backup_filename, |
|
|
open(temp.name, "rb")) |
|
|
open(temp.name, "rb")) |
|
@ -743,7 +745,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
try: |
|
|
try: |
|
|
sftp.chdir(rec.sftp_path) |
|
|
sftp.chdir(rec.sftp_path) |
|
@ -779,7 +781,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
try: |
|
|
try: |
|
|
headers = { |
|
|
headers = { |
|
@ -839,7 +841,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
try: |
|
|
try: |
|
|
dbx = dropbox.Dropbox( |
|
|
dbx = dropbox.Dropbox( |
|
@ -877,7 +879,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
headers = { |
|
|
headers = { |
|
|
'Authorization': 'Bearer %s' % rec.onedrive_access_token, |
|
|
'Authorization': 'Bearer %s' % rec.onedrive_access_token, |
|
@ -989,7 +991,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
backup_file_path = temp.name |
|
|
backup_file_path = temp.name |
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
@ -1000,7 +1002,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
backup_file_path = temp.name |
|
|
backup_file_path = temp.name |
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
@ -1060,7 +1062,7 @@ class AutoDatabaseBackup(models.Model): |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
suffix='.%s' % rec.backup_format) |
|
|
suffix='.%s' % rec.backup_format) |
|
|
with open(temp.name, "wb+") as tmp: |
|
|
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) |
|
|
rec.backup_format) |
|
|
backup_file_path = temp.name |
|
|
backup_file_path = temp.name |
|
|
remote_file_path = f"{rec.aws_folder_name}/{rec.db_name}_" \ |
|
|
remote_file_path = f"{rec.aws_folder_name}/{rec.db_name}_" \ |
|
@ -1083,3 +1085,67 @@ class AutoDatabaseBackup(models.Model): |
|
|
if rec.notify_user: |
|
|
if rec.notify_user: |
|
|
mail_template_failed.send_mail(rec.id, |
|
|
mail_template_failed.send_mail(rec.id, |
|
|
force_send=True) |
|
|
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.insert(-1,'--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.insert(-1,'--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 |
|
|