first commit

This commit is contained in:
Ruslan Grak
2025-01-07 10:00:02 +03:00
commit 626d8d3c56
349 changed files with 44175 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from . import saas_app
from . import models

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
import os
from odoo import models, fields, api, _
from odoo.exceptions import UserError, MissingError
import requests
import xmlrpc
import logging
import base64
_logger = logging.getLogger(__name__)
class SaaSAppBackup(models.Model):
_name = 'kk_odoo_saas.app.backup'
_description = 'SaaS App Backup'
name = fields.Char()
app = fields.Many2one('kk_odoo_saas.app', 'SaaS App')
file_name = fields.Char(string="File Name")
file_path = fields.Char(string="File Path")
url = fields.Char(string="Url")
backup_date_time = fields.Datetime(string="Backup Time (UTC)")
status = fields.Selection(string="Status", selection=[('failed', 'Failed'), ('success', 'Success')])
message = fields.Char(string="Message")
file_size = fields.Char(string="File Size")
def download_db_file(self):
"""
to download the database backup, it stores the file in attachment
:return: Action
"""
file_path = self.file_path
_logger.info("------------ %r ----------------" % file_path)
if self.url:
return {
'type': 'ir.actions.act_url',
'url': self.url,
'target': 'new',
}
try:
with open(file_path, 'rb') as reader:
result = base64.b64encode(reader.read())
except IOError as e:
raise MissingError('Unable to find File on the path')
attachment_obj = self.env['ir.attachment'].sudo()
name = self.file_name
attachment_id = attachment_obj.create({
'name': name,
'datas': result,
'public': False
})
download_url = '/web/content/' + str(attachment_id.id) + '?download=true'
_logger.info("--- %r ----" % download_url)
self.url = download_url
return {
'type': 'ir.actions.act_url',
'url': download_url,
'target': 'new',
}
def action_restore_backup_to_instance(self, restore_to_id=False):
"""
it will restore the backup to a new instance,
the instance should be created manually,
and there should be no database at new_instance.com/web/database/selector
:param: restore_to_id is kk_saa_app object on which we have to restore backup
:return: False
"""
if self.app and restore_to_id:
restore_url = restore_to_id.get_url()
if self.file_path and os.path.exists(self.file_path) and requests.get(restore_url).status_code < 400:
db_list = []
try:
db_list = xmlrpc.client.ServerProxy(restore_url + '/xmlrpc/db').list()
except xmlrpc.client.ProtocolError as e:
_logger.info("There is no database on Db selector")
_logger.info("All Databases on Postgres Server -> {} <-".format(db_list))
_logger.info("New Db name: {}".format(restore_to_id.app_name))
if restore_to_id.app_name not in db_list:
self.restore_backup_to_client(self.file_path, restore_url, restore_to_id.app_name,
restore_to_id.backup_master_pass)
else:
raise UserError("Cant restore Backup, Database already existed, please delete it.")
else:
raise UserError("Cant restore Backup! the url is not accessible or backup file not exists.")
else:
_logger.error("Cant restore Backup, Backup Id, or Restore App Missing")
raise UserError("Cant restore Backup, Backup Id, or Restore App Missing")
def restore_backup_to_client(self, file_path, restore_url, db_name, master_pwd):
if file_path and restore_url and db_name and master_pwd:
restore_url = restore_url + '/web/database/restore'
data = {
'master_pwd': master_pwd,
'name': db_name,
'copy': 'true',
'backup_file': '@' + file_path
}
backup = open(file_path, "rb")
try:
response = requests.post(restore_url, data=data, files={"backup_file": backup})
if response.status_code == 200:
_logger.info("Restore Done, this is the response Code: {}".format(response.status_code))
else:
_logger.info("Restore Done, this is the response Code: {}".format(response.status_code))
return {
'success': True,
}
except Exception as e:
return {
'success': False,
}
else:
_logger.error("Cant restore Db One of the parameter is Missing")
@api.model
def create(self, vals):
vals['name'] = self.env['ir.sequence'].next_by_code('saas_app.backup')
res = super(SaaSAppBackup, self).create(vals)
return res
def calc_backup_size(self):
if not os.path.exists(self.file_path):
return
# calculate file size in KB, MB, GB
def convert_bytes(size):
""" Convert bytes to KB, or MB or GB"""
for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
if size < 1024.0:
return "%3.1f %s" % (size, x)
size /= 1024.0
self.file_size = convert_bytes(os.path.getsize(self.file_path))

View File

@@ -0,0 +1,124 @@
from datetime import timedelta
import requests
from odoo import models, fields, api, _
import logging
import os
_logger = logging.getLogger(__name__)
class SaaSApp(models.Model):
_inherit = 'kk_odoo_saas.app'
backup_db_name = fields.Char(string="Database Name", default=lambda a: a.client_db_name)
backup_master_pass = fields.Char(string="Master Password")
backups_enabled = fields.Boolean()
backups = fields.Many2many(comodel_name='kk_odoo_saas.app.backup', string='Backups')
def action_create_backup(self):
"""
It is being called from 2 locations
:return:
"""
for app in self:
response = self.backup_db()
backup = self.env['kk_odoo_saas.app.backup'].create({'backup_date_time': fields.Datetime.now(),
'app': app.id,
'file_name': response.get('filename'),
'file_path': response.get('filepath'),
'message': response.get('message')
})
if response.get('success'):
backup.write({'status': 'success'})
else:
backup.write({'status': 'failed'})
app.write({'backups': [(4, backup.id)]})
def action_delete_old_backup(self):
for app in self:
for backup in app.backups:
if backup.backup_date_time < fields.Datetime.now() - timedelta(days=7.0):
if os.path.exists(backup.file_path):
try:
os.remove(backup.file_path)
if backup.url:
# deleting the attachments related to this backup
att_id = backup.url.replace('?download=true', '').replace('/web/content/', '')
if att_id:
try:
attch_id = int(att_id)
if attch_id:
self.env['ir.attachment'].browse([attch_id]).unlink()
except ValueError as e:
_logger.error(e)
backup.unlink()
except OSError as e:
_logger.error("Error while deleting file: %s - %s." % (e.filename, e.strerror))
else:
_logger.error("The file does not exist")
def backup_db(self):
"""
Actual Backup function
:return:
"""
# get the creds for db manager
data = {
'master_pwd': self.backup_master_pass,
'name': self.backup_db_name,
'backup_format': 'zip'
}
client_url = 'https://{0}{1}'.format(self.sub_domain_name, self.domain_name)
msg = ''
# where we want to store backups, in the linux user, with which the odoo-service is running
backup_dir = os.path.join(os.path.expanduser('~'), 'client_backups')
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
backup_dir = os.path.join(backup_dir, self.sub_domain_name)
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
client_url += '/web/database/backup'
# Without Streaming method
# response = requests.post(client_url, data=data)
# Streaming zip, so that everything is not stored in RAM.
try:
filename = self.backup_db_name + '-' + fields.Datetime.now().strftime("%m-%d-%Y-%H-%M") + '.zip'
backed_up_file_path = os.path.join(backup_dir, filename)
with requests.post(client_url, data=data, stream=True) as response:
response.raise_for_status()
with open(os.path.join(backup_dir, filename), 'wb') as file:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
file.write(chunk)
msg = 'Database backup Successful at ' + fields.Datetime.now().strftime("%m-%d-%Y-%H:%M:%S")
return {
'success': True,
'msg': msg,
'filename': filename,
'filepath': backed_up_file_path
}
except Exception as e:
msg = 'Failed at ' + fields.Datetime.now().strftime("%m-%d-%Y-%H:%M:%S") + ' ' + str(e)
return {
'success': False,
'msg': msg
}
@api.model
def ignite_backup_server_cron(self):
"""
A Scheduled Action which will take new backups and del old
:return: False
"""
# search for saas instance in lanched and modified states and backups enabled
apps = self.env['kk_odoo_saas.app'].sudo().search(
[('status', 'in', ['l', 'm']), ('backups_enabled', '=', True)])
for app in apps:
app.action_create_backup()
app.action_delete_old_backup()