Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade auto_backup from v17 to v18 #280

Open
wants to merge 1 commit into
base: 18.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
## 1. Prerequisites
This module needs the Python library pysftp, otherwise it cannot be installed and used. Install pysftp through the command <code>sudo pip install pysftp</code>
This module needs the Python library paramiko, otherwise it cannot be installed and used. Install paramiko through the command <code>sudo pip install paramiko</code>

## 2. Which version to choose?
Version 16.0 is the latest stable version for this module and is compatible with the latest Odoo version (Odoo 16).
Version 18.0 is the latest stable version for this module and is compatible with the latest Odoo version (Odoo 18).
The versions 8.0, 9.0, 10.0, 11.0, 12.0 and 13.0 of this module are tested and verified to work for their specific Odoo versions. The master version is the development version and will be for the next Odoo version.
The master version is still in testing and contains the newest features, which might still have problems/error.<br/>
<b>Tip:</b> At this point the master version is being rewritten to drop the pysftp library need, please don't use this version at this point.<br/>
Expand Down
2 changes: 1 addition & 1 deletion auto_backup/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'author': "Yenthe Van Ginneken",
'website': "http://www.odoo.yenthevg.com",
'category': 'Administration',
'version': '17.0.0.1',
'version': '18.0.0.1',
'installable': True,
'license': 'LGPL-3',
'module_type': 'official',
Expand Down
2 changes: 0 additions & 2 deletions auto_backup/data/backup_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
<field name="priority">5</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="active">False</field>
<field name="doall">False</field>
</record>
</data>
</odoo>
Expand Down
69 changes: 50 additions & 19 deletions auto_backup/models/db_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import tempfile
import subprocess

from odoo import models, fields, api, tools, _
from odoo.exceptions import UserError, AccessDenied
import odoo
Expand All @@ -26,14 +27,15 @@ class DbBackup(models.Model):
_description = 'Backup configuration record'

def _get_db_name(self):
dbName = self._cr.dbname
return dbName
db_name = self._cr.dbname
return db_name

# Columns for local server configuration
host = fields.Char('Host', required=True, default='localhost')
port = fields.Char('Port', required=True, default=8069)
name = fields.Char('Database', required=True, help='Database you want to schedule backups for',
default=_get_db_name)
db_port = fields.Char(string='Database Port', required=True, default=5432)
folder = fields.Char('Backup Directory', help='Absolute path for storing the backups', required=True,
default='/odoo/backups')
backup_type = fields.Selection([('zip', 'Zip'), ('dump', 'Dump')], 'Backup Type', required=True, default='zip')
Expand Down Expand Up @@ -116,20 +118,22 @@ def test_sftp_connection(self, context=None):
@api.model
def schedule_backup(self):
conf_ids = self.search([])
for rec in conf_ids:

for rec in conf_ids:
try:
if not os.path.isdir(rec.folder):
os.makedirs(rec.folder)
except:
raise

# Create name for dumpfile.
bkp_file = '%s_%s.%s' % (time.strftime('%Y_%m_%d_%H_%M_%S'), rec.name, rec.backup_type)
file_path = os.path.join(rec.folder, bkp_file)

try:
# try to backup database and write it away
fp = open(file_path, 'wb')
self._take_dump(rec.name, fp, 'db.backup', rec.backup_type)
self._take_dump(rec.name, rec.db_port, fp, 'db.backup', rec.backup_type)
fp.close()
except Exception as error:
_logger.debug(
Expand Down Expand Up @@ -163,8 +167,10 @@ def schedule_backup(self):
except IOError:
# Create directory and subdirs if they do not exist.
current_directory = ''

for dirElement in path_to_write_to.split('/'):
current_directory += dirElement + '/'

try:
sftp.chdir(current_directory)
except:
Expand All @@ -174,11 +180,14 @@ def schedule_backup(self):
sftp.mkdir(current_directory, 777)
sftp.chdir(current_directory)
pass

sftp.chdir(path_to_write_to)

# Loop over all files in the directory.
for f in os.listdir(dir):
if rec.name in f:
fullpath = os.path.join(dir, f)

if os.path.isfile(fullpath):
try:
sftp.stat(os.path.join(path_to_write_to, f))
Expand Down Expand Up @@ -215,6 +224,7 @@ def schedule_backup(self):
if ".dump" in file or '.zip' in file:
_logger.info("Delete too old file from SFTP servers: %s", file)
sftp.unlink(file)

# Close the SFTP session.
sftp.close()
s.close()
Expand All @@ -226,6 +236,7 @@ def schedule_backup(self):
pass
_logger.error('Exception! We couldn\'t back up to the FTP server. Here is what we got back '
'instead: %s', str(e))

# At this point the SFTP backup failed. We will now check if the user wants
# an e-mail notification about this.
if rec.send_mail_sftp_fail:
Expand All @@ -249,9 +260,11 @@ def schedule_backup(self):
# Remove all old files (on local server) in case this is configured..
if rec.autoremove:
directory = rec.folder

# Loop over all files in the directory.
for f in os.listdir(directory):
fullpath = os.path.join(directory, f)

# Only delete the ones wich are from the current database
# (Makes it possible to save different databases in the same folder)
if rec.name in fullpath:
Expand All @@ -272,45 +285,63 @@ def schedule_backup(self):
# call. Since this function is called from the cron and since we have these security checks on model and on user_id
# its pretty impossible to hack any way to take a backup. This allows us to disable the Odoo database manager
# which is a MUCH safer way
def _take_dump(self, db_name, stream, model, backup_format='zip'):
def _take_dump(self, db_name, db_port, stream, model, backup_format='zip'):
"""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_backup.backup_scheduler').user_id.id

if self._name != 'db.backup' or cron_user_id != self.env.user.id:
_logger.error('Unauthorized database operation. Backups should only be available from the cron job.')
raise AccessDenied()

_logger.info('DUMP DB: %s format %s', db_name, backup_format)

cmd = [tools.find_pg_tool('pg_dump'), '--no-owner', db_name]
env = tools.exec_pg_environ()
cmd = ['pg_dump', '--no-owner', db_name, '-p', db_port]

if backup_format == 'zip':
with tempfile.TemporaryDirectory() as dump_dir:
filestore = odoo.tools.config.filestore(db_name)

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)
cmd.insert(-1, '--file=' + os.path.join(dump_dir, 'dump.sql'))
subprocess.run(cmd, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=True)

dump_file = os.path.join(dump_dir, 'dump.sql')
cmd.append(f'--file={dump_file}')

try:
subprocess.run(cmd, check=True)
except Exception as e:
_logger.error('Database dump failed: %s', e)
raise

if stream:
odoo.tools.osutil.zip_dir(dump_dir, stream, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql')
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 = 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')
stdout = subprocess.Popen(cmd, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE).stdout
if stream:
shutil.copyfileobj(stdout, stream)
else:
return stdout
cmd.append('--format=c')
try:
process = subprocess.run(cmd, capture_output=True, check=True, text=True)

if stream:
stream.write(process.stdout)
else:
t = tempfile.TemporaryFile()
t.write(process.stdout.encode())
t.seek(0)
return t
except subprocess.CalledProcessError as e:
_logger.error('Database dump failed: %s', e)
raise

def _dump_db_manifest(self, cr):
pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100)
Expand Down
40 changes: 21 additions & 19 deletions auto_backup/views/backup_view.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
<odoo>
<record id="view_backup_config_list" model="ir.ui.view">
<field name="name">db.backup.list</field>
<field name="model">db.backup</field>
<field name="type">list</field>
<field name="arch" type="xml">
<list>
<field name='host'/>
<field name='port'/>
<field name='name'/>
<field name='db_port'/>
<field name='folder'/>
<field name="autoremove"/>
<field name="sftp_host"/>
</list>
</field>
</record>

<record id="view_backup_config_form" model="ir.ui.view">
<field name="name">db.backup.form</field>
<field name="model">db.backup</field>
Expand All @@ -11,8 +28,9 @@
</group>
<group name="configuration">
<field name="host" colspan="2"/>
<field name="name"/>
<field name="port"/>
<field name="name"/>
<field name="db_port"/>
<field name="backup_type"/>
<field name="folder"/>
<field name="autoremove"/>
Expand Down Expand Up @@ -66,27 +84,11 @@
</field>
</record>

<record id="view_backup_config_tree" model="ir.ui.view">
<field name="name">db.backup.tree</field>
<field name="model">db.backup</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree>
<field name='host'/>
<field name='port'/>
<field name='name'/>
<field name='folder'/>
<field name="autoremove"/>
<field name="sftp_host"/>
</tree>
</field>
</record>

<record id="action_backup" model="ir.actions.act_window">
<field name="name">Configure back-ups</field>
<field name="res_model">db.backup</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_backup_config_tree"/>
<field name="view_mode">list,form</field>
<field name="view_id" ref="view_backup_config_list"/>
</record>

<menuitem id="auto_backup_menu" name="Back-ups" parent="base.menu_custom"/>
Expand Down