Browse Source

[11.0][MIG] auto_backup

pull/1384/head
Andrea 7 years ago
committed by Aitor Bouzas
parent
commit
7a32b0eddb
  1. 13
      auto_backup/README.rst
  2. 6
      auto_backup/__init__.py
  3. 12
      auto_backup/__manifest__.py
  4. 12
      auto_backup/data/ir_cron.xml
  5. 3
      auto_backup/data/mail_message_subtype.xml
  6. 5
      auto_backup/models/__init__.py
  7. 53
      auto_backup/models/db_backup.py
  8. 6
      auto_backup/tests/__init__.py
  9. 28
      auto_backup/tests/test_db_backup.py
  10. 53
      auto_backup/view/db_backup_view.xml

13
auto_backup/README.rst

@ -1,5 +1,5 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3 :alt: License: AGPL-3
==================== ====================
@ -13,12 +13,12 @@ Installation
Before installing this module, you need to execute:: Before installing this module, you need to execute::
pip install pysftp
pip3 install pysftp
Configuration Configuration
============= =============
Go to *Settings -> Configuration -> Configure Backup* to
Go to *Settings -> Database Structure -> Automated Backup* to
create your configurations for each database that you needed create your configurations for each database that you needed
to backups. to backups.
@ -70,7 +70,7 @@ manually execute the selected processes.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0
:target: https://runbot.odoo-community.org/runbot/149/11.0
Known issues / Roadmap Known issues / Roadmap
====================== ======================
@ -87,7 +87,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please <https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first, check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
help us smash it by providing detailed and welcomed feedback.
Credits Credits
======= =======
@ -99,6 +99,7 @@ Contributors
* Alessio Gerace <alessio.gerace@agilebg.com> * Alessio Gerace <alessio.gerace@agilebg.com>
* Jairo Llopis <yajo.sk8@gmail.com> * Jairo Llopis <yajo.sk8@gmail.com>
* Dave Lasley <dave@laslabs.com> * Dave Lasley <dave@laslabs.com>
* Andrea Stirpe <a.stirpe@onestein.nl>
Maintainer Maintainer
---------- ----------

6
auto_backup/__init__.py

@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
# © 2004-2009 Tiny SPRL (<http://tiny.be>).
# © 2015 Agile Business Group <http://www.agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models from . import models

12
auto_backup/__manifest__.py

@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# © 2004-2009 Tiny SPRL (<http://tiny.be>). # © 2004-2009 Tiny SPRL (<http://tiny.be>).
# © 2015 Agile Business Group <http://www.agilebg.com> # © 2015 Agile Business Group <http://www.agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis # © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{ {
"name": "Database Auto-Backup", "name": "Database Auto-Backup",
"summary": "Backups database", "summary": "Backups database",
"version": "10.0.1.0.2",
"version": "11.0.1.0.0",
"author": ( "author": (
"Yenthe Van Ginneken, " "Yenthe Van Ginneken, "
"Agile Business Group, " "Agile Business Group, "
@ -15,11 +14,11 @@
"LasLabs, " "LasLabs, "
"Odoo Community Association (OCA)" "Odoo Community Association (OCA)"
), ),
'license': "AGPL-3",
"website": "http://www.vanroey.be/applications/bedrijfsbeheer/odoo",
"license": "AGPL-3",
"website": "https://github.com/OCA/server-tools/",
"category": "Tools", "category": "Tools",
"depends": [ "depends": [
'mail',
"mail",
], ],
"data": [ "data": [
"data/ir_cron.xml", "data/ir_cron.xml",
@ -27,7 +26,6 @@
"security/ir.model.access.csv", "security/ir.model.access.csv",
"view/db_backup_view.xml", "view/db_backup_view.xml",
], ],
"application": True,
"installable": True, "installable": True,
"external_dependencies": { "external_dependencies": {
"python": ["pysftp"], "python": ["pysftp"],

12
auto_backup/data/ir_cron.xml

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1"> <odoo noupdate="1">
<record id="ir_cron_backup_scheduler_0" model="ir.cron"> <record id="ir_cron_backup_scheduler_0" model="ir.cron">
@ -8,11 +7,10 @@
<field name="interval_number">1</field> <field name="interval_number">1</field>
<field name="interval_type">days</field> <field name="interval_type">days</field>
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
<field name="nextcall"
eval="(datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d 02:00:00')"
/>
<field name="model">db.backup</field>
<field name="function">action_backup_all</field>
<field name="nextcall" eval="(datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d 03:00:00')"/>
<field name="model_id" ref="model_db_backup"/>
<field name="state">code</field>
<field name="code">model.action_backup_all()</field>
</record> </record>
</odoo> </odoo>

3
auto_backup/data/mail_message_subtype.xml

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1"> <odoo noupdate="1">
<record id="mail_message_subtype_success" model="mail.message.subtype"> <record id="mail_message_subtype_success" model="mail.message.subtype">

5
auto_backup/models/__init__.py

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
# © 2004-2009 Tiny SPRL (<http://tiny.be>).
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/gpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import db_backup from . import db_backup

53
auto_backup/models/db_backup.py

@ -1,18 +1,19 @@
# -*- coding: utf-8 -*-
# © 2004-2009 Tiny SPRL (<http://tiny.be>). # © 2004-2009 Tiny SPRL (<http://tiny.be>).
# © 2015 Agile Business Group <http://www.agilebg.com> # © 2015 Agile Business Group <http://www.agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis # © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/gpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
import os import os
import shutil import shutil
import traceback import traceback
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timedelta from datetime import datetime, timedelta
from glob import iglob from glob import iglob
from odoo import exceptions, models, fields, api, _, tools
from odoo import _, api, exceptions, fields, models, tools
from odoo.service import db from odoo.service import db
import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
try: try:
import pysftp import pysftp
@ -21,6 +22,7 @@ except ImportError: # pragma: no cover
class DbBackup(models.Model): class DbBackup(models.Model):
_description = 'Database Backup'
_name = 'db.backup' _name = 'db.backup'
_inherit = "mail.thread" _inherit = "mail.thread"
@ -31,59 +33,52 @@ class DbBackup(models.Model):
] ]
name = fields.Char( name = fields.Char(
string="Name",
compute="_compute_name", compute="_compute_name",
store=True, store=True,
help="Summary of this backup process", help="Summary of this backup process",
) )
folder = fields.Char( folder = fields.Char(
default=lambda self: self._default_folder(), default=lambda self: self._default_folder(),
oldname="bkp_dir",
help='Absolute path for storing the backups', help='Absolute path for storing the backups',
required=True required=True
) )
days_to_keep = fields.Integer( days_to_keep = fields.Integer(
oldname="daystokeep",
required=True, required=True,
default=0, default=0,
help="Backups older than this will be deleted automatically. " help="Backups older than this will be deleted automatically. "
"Set 0 to disable autodeletion.", "Set 0 to disable autodeletion.",
) )
method = fields.Selection( method = fields.Selection(
selection=[("local", "Local disk"), ("sftp", "Remote SFTP server")],
[("local", "Local disk"), ("sftp", "Remote SFTP server")],
default="local", default="local",
help="Choose the storage method for this backup.", help="Choose the storage method for this backup.",
) )
sftp_host = fields.Char( sftp_host = fields.Char(
string='SFTP Server',
oldname="sftpip",
'SFTP Server',
help=( help=(
"The host name or IP address from your remote" "The host name or IP address from your remote"
" server. For example 192.168.0.1" " server. For example 192.168.0.1"
) )
) )
sftp_port = fields.Integer( sftp_port = fields.Integer(
string="SFTP Port",
"SFTP Port",
default=22, default=22,
oldname="sftpport",
help="The port on the FTP server that accepts SSH/SFTP calls." help="The port on the FTP server that accepts SSH/SFTP calls."
) )
sftp_user = fields.Char( sftp_user = fields.Char(
string='Username in the SFTP Server',
oldname="sftpusername",
'Username in the SFTP Server',
help=( help=(
"The username where the SFTP connection " "The username where the SFTP connection "
"should be made with. This is the user on the external server." "should be made with. This is the user on the external server."
) )
) )
sftp_password = fields.Char( sftp_password = fields.Char(
string="SFTP Password",
oldname="sftppassword",
"SFTP Password",
help="The password for the SFTP connection. If you specify a private " help="The password for the SFTP connection. If you specify a private "
"key file, then this is the password to decrypt it.", "key file, then this is the password to decrypt it.",
) )
sftp_private_key = fields.Char( sftp_private_key = fields.Char(
string="Private key location",
"Private key location",
help="Path to the private key file. Only the Odoo user should have " help="Path to the private key file. Only the Odoo user should have "
"read permissions for that file.", "read permissions for that file.",
) )
@ -111,9 +106,9 @@ class DbBackup(models.Model):
@api.constrains("folder", "method") @api.constrains("folder", "method")
def _check_folder(self): def _check_folder(self):
"""Do not use the filestore or you will backup your backups.""" """Do not use the filestore or you will backup your backups."""
for s in self:
if (s.method == "local" and
s.folder.startswith(
for record in self:
if (record.method == "local" and
record.folder.startswith(
tools.config.filestore(self.env.cr.dbname))): tools.config.filestore(self.env.cr.dbname))):
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("Do not save backups on your filestore, or you will " _("Do not save backups on your filestore, or you will "
@ -200,10 +195,10 @@ class DbBackup(models.Model):
try: try:
_logger.info("Starting database backup: %s", self.name) _logger.info("Starting database backup: %s", self.name)
yield yield
except:
except Exception:
_logger.exception("Database backup failed: %s", self.name) _logger.exception("Database backup failed: %s", self.name)
escaped_tb = tools.html_escape(traceback.format_exc()) escaped_tb = tools.html_escape(traceback.format_exc())
self.message_post(
self.message_post( # pylint: disable=translation-required
"<p>%s</p><pre>%s</pre>" % ( "<p>%s</p><pre>%s</pre>" % (
_("Database backup failed."), _("Database backup failed."),
escaped_tb), escaped_tb),
@ -242,23 +237,25 @@ class DbBackup(models.Model):
"""Log a possible cleanup failure.""" """Log a possible cleanup failure."""
self.ensure_one() self.ensure_one()
try: try:
_logger.info("Starting cleanup process after database backup: %s",
_logger.info(
"Starting cleanup process after database backup: %s",
self.name) self.name)
yield yield
except:
except Exception:
_logger.exception("Cleanup of old database backups failed: %s") _logger.exception("Cleanup of old database backups failed: %s")
escaped_tb = tools.html_escape(traceback.format_exc()) escaped_tb = tools.html_escape(traceback.format_exc())
self.message_post(
self.message_post( # pylint: disable=translation-required
"<p>%s</p><pre>%s</pre>" % ( "<p>%s</p><pre>%s</pre>" % (
_("Cleanup of old database backups failed."), _("Cleanup of old database backups failed."),
escaped_tb), escaped_tb),
subtype=self.env.ref("auto_backup.failure")) subtype=self.env.ref("auto_backup.failure"))
else: else:
_logger.info("Cleanup of old database backups succeeded: %s",
_logger.info(
"Cleanup of old database backups succeeded: %s",
self.name) self.name)
@api.model
def filename(self, when):
@staticmethod
def filename(when):
"""Generate a file name for a backup. """Generate a file name for a backup.
:param datetime.datetime when: :param datetime.datetime when:

6
auto_backup/tests/__init__.py

@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
# © 2015 Agile Business Group <http://www.agilebg.com>
# © 2015 Alessio Gerace <alesiso.gerace@agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_db_backup from . import test_db_backup

28
auto_backup/tests/test_db_backup.py

@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
# © 2015 Agile Business Group <http://www.agilebg.com> # © 2015 Agile Business Group <http://www.agilebg.com>
# © 2015 Alessio Gerace <alesiso.gerace@agilebg.com> # © 2015 Alessio Gerace <alesiso.gerace@agilebg.com>
# © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis # © 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# Copyright 2016 LasLabs Inc. # Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import os import os
import mock
from datetime import datetime
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timedelta
import mock
from odoo.tests import common
from odoo import exceptions, tools from odoo import exceptions, tools
from odoo.tests import common
try: try:
import pysftp import pysftp
@ -127,6 +126,23 @@ class TestDbBackup(common.TransactionCase):
if f >= filename] if f >= filename]
self.assertEqual(1, len(generated_backup)) self.assertEqual(1, len(generated_backup))
def test_action_backup_local_cleanup(self):
""" Backup local database and cleanup old databases """
rec_id = self.new_record('local')
rec_id.days_to_keep = 1
old_date = datetime.now() - timedelta(days=3)
filename = rec_id.filename(old_date)
rec_id.action_backup()
generated_backup = [f for f in os.listdir(rec_id.folder)
if f >= filename]
self.assertEqual(2, len(generated_backup))
filename = rec_id.filename(datetime.now())
rec_id.action_backup()
generated_backup = [f for f in os.listdir(rec_id.folder)
if f >= filename]
self.assertEqual(1, len(generated_backup))
def test_action_backup_sftp_mkdirs(self): def test_action_backup_sftp_mkdirs(self):
""" It should create remote dirs """ """ It should create remote dirs """
rec_id = self.new_record() rec_id = self.new_record()

53
auto_backup/view/db_backup_view.xml

@ -1,14 +1,16 @@
<?xml version="1.0"?>
<?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<record model="ir.ui.view" id="view_backup_conf_form">
<field name="name">Automated Backups</field>
<record id="view_backup_conf_form" model="ir.ui.view">
<field name="model">db.backup</field> <field name="model">db.backup</field>
<field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header>
<button name="action_backup" type="object" string="Execute backup" class="oe_highlight"/>
</header>
<div class="oe_title">
<h1><field name="name"/></h1> <h1><field name="name"/></h1>
</div>
<group string="Basic backup configuration"> <group string="Basic backup configuration">
<field name="folder"/> <field name="folder"/>
<field name="days_to_keep"/> <field name="days_to_keep"/>
@ -47,27 +49,23 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_backup_conf_tree">
<field name="name">Automated Backups</field>
<record id="view_backup_conf_tree" model="ir.ui.view">
<field name="model">db.backup</field> <field name="model">db.backup</field>
<field name="type">tree</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Backups">
<field name='name'/>
<field name='folder'/>
<field name="sftp_host"/>
<tree>
<field name="name"/>
<field name="folder"/>
<field name="days_to_keep"/>
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_backup_conf_search">
<field name="name">Automated Backups</field>
<record id="view_backup_conf_search" model="ir.ui.view">
<field name="model">db.backup</field> <field name="model">db.backup</field>
<field name="type">search</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Search options">
<field name='name'/>
<field name='folder'/>
<search>
<field name="name"/>
<field name="folder"/>
<field name="sftp_host"/> <field name="sftp_host"/>
</search> </search>
</field> </field>
@ -86,22 +84,11 @@
<!-- Execute backup from "More" menu --> <!-- Execute backup from "More" menu -->
<record id="action_server_backup" model="ir.actions.server"> <record id="action_server_backup" model="ir.actions.server">
<field name="name">Execute backup(s)</field> <field name="name">Execute backup(s)</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_db_backup" /> <field name="model_id" ref="model_db_backup" />
<field name="code">
object.action_backup()
</field>
</record>
<record model="ir.values" id="action_backup">
<field name="name">Execute backup(s)</field>
<field name="action_id" ref="action_server_backup" />
<field
name="value"
eval="'ir.actions.server,%d' % ref('action_server_backup')" />
<field name="key">action</field>
<field name="model_id" ref="model_db_backup" />
<field name="model">db.backup</field>
<field name="key2">client_action_multi</field>
<field name="binding_model_id" ref="model_db_backup" />
<field name="state">code</field>
<field name="code">records.action_backup()</field>
</record> </record>
</odoo> </odoo>
Loading…
Cancel
Save