# -*- coding: utf-8 -*- from openerp import models, fields, api from openerp.exceptions import Warning, ValidationError from jinja2 import Environment as JinjaEnv, PackageLoader as JinjaLoader from ..utils.docker_api import get_all_external_ports from ..utils.command import execute from random import randint import unicodedata import stringcase import time import os class OdooInstance(models.Model): _name = 'odoo.instance' CONTAINER_STATUS = [ ('draft', 'Por activar'), ('activated', 'Activado'), ('disapproved', 'No aprobado'), ('suspended', 'Suspendido'), ('destroyed', 'Eliminado') ] DEFAULT_DIRS = [ 'config', 'custom-addons', 'files' ] # 'config: /etc/odoo, custom-addons: /mnt/extra-addons, files: /var/lib/odoo' def _snakeike_name(self, name): try: name = unicodedata.normalize('NFKD', name) name = name.encode('ASCII', 'ignore') except TypeError: pass name = stringcase.trimcase(name) name = stringcase.lowercase(name) name = stringcase.snakecase(name) return name def _check_port_availability(self, port): return port not in get_all_external_ports() def _check_name_availability(self, name): if len(name) == 0: return False root_path = self.env['ir.values'].get_default(self._name, 'odoo_root_path') if len(root_path) == 0: raise Warning('La ruta por defecto para los sistemas no está definido') full_path = os.path.join(root_path, name) return os.path.exists(full_path) def _randomize_port(self): ports = self.env['ir.values'].get_default(self._name, 'odoo_ports_range') ports = filter(lambda x: x != '', ports.split(',')) if len(ports) == 0: raise Warning('El rango de puertos para los sistemas no está definido') port = 0 while not self._check_port_availability(port): port = randint(ports[0], ports[1]) time.sleep(1) return port def _make_default_dirs(self, name): root_path = self.env['ir.values'].get_default(self._name, 'odoo_root_path') for d in self.DEFAULT_DIRS: full_path = os.path.join(root_path, name, d.strip()) os.makedirs(full_path) time.sleep(1) def _make_config_file(self, name): root_path = self.env['ir.values'].get_default(self._name, 'odoo_root_path') config_path = os.path.join(root_path, name, 'config') if not os.path.exists(config_path): raise Warning('No se creó el directorio para el archivo de configuración') env = JinjaEnv(loader=JinjaLoader('assets', 'templates')) tmpl = env.get_template('openerp-server.j2') tmpl_rendered = tmpl.stream({ 'admin_password': 'admin', 'db_host': '', 'db_port': '', 'db_name': '', 'db_user': '', 'db_password': '' }) tmpl_rendered.dump(os.path.join(config_path, 'openerp-config.conf')) def _create_database(self, name): odoo_db = self.env['ir.values'].get_default(self._name, 'odoo_db_link') cmd = 'createdb -U %s %s' % ('odoo', name) return execute(cmd) name = fields.Char(string='Nombre', size=50) normalized_name = fields.Char(string='Nombre normalizado', compute='_normalize_name', size=50) logo = fields.Binary(string='Logo') internal_ip = fields.Char(string='IP interno', size=15) internal_port = fields.Integer(string='Puerto interno') external_ip = fields.Char(string='IP externo', size=15) external_port = fields.Integer(string='Puerto externo') expose_ip = fields.Boolean(string='Exponer IP', default=True) state = fields.Selection(string='Estado', selection=CONTAINER_STATUS, default='draft') demo = fields.Boolean(string='Es un demo?', default=False) domain = fields.Char(string='Dominio', size=100) running = fields.Boolean(string='Está online?', default=False) payment_plan_id = fields.Many2one(string='Plan de pago', comodel_name='payment.plan', required=True) @api.model def create(self, values): snaked_name = self._snakeike_name(values.get('name')) name_is_available = self._check_name_availability(snaked_name) if not name_is_available: raise ValidationError('El nombre ya está siendo usado por otro sistema') return super(self, OdooInstance).create(values) @api.one @api.onchange('name') def _onchange_name(self): if self.name: self.name = self.name.title() @api.one @api.depends('name') def _normalize_name(self): self.normalized_name = self._snakeike_name(self.name) @api.one def action_activate(self): if self.state not in ('draft', 'suspended'): raise Warning('No se puede activar un sistema ya activo') self._check_name_availability(self.normalized_name) # # 1. Check name # name_is_available = odoo_api.check_name_availability(self.normalized_name) # # if not name_is_available: # raise Warning('El nombre ya está siendo usado por otro sistema') # # # 2. Get a port # port_to_use = odoo_api.randomize_port() # # # 3. Create dirs # make_ok = odoo_api.make_default_dirs(self.normalized_name) # # if not make_ok: # raise Warning('No se pudo crear la estructura de directorios') # # # 4. Create configuration file # config_ok = odoo_api.make_config_file(self.normalized_name) # # if not config_ok: # raise Warning('No se pudo crear el archivo de configuración') # # # 5. Create database # db_ok = odoo_api.create_database(self.normalized_name) # # if not db_ok: # raise Warning('No se pudo crear la base de datos') # # # 6. Copy database seed # seed_copied = odoo_api.copy_database_seed() # # if not seed_copied: # raise Warning('No se pudo copiar la estructura inicial de la base de datos') # # # 7. Restore database schema # restored = odoo_api.restore_database(self.normalized_name) # # if not restored: # raise Warning('No se pudo reestablecer la copia de base de datos') # # # 8. Remove database seed # seed_removed = odoo_api.remove_database_seed() # # if not seed_removed: # raise Warning('No se pudo remover la copia de base de datos') # # # 9. Create odoo container # odoo_created = odoo_api.create_container(self.normalized_name, [port_to_use]) # # if not odoo_created: # raise Warning('No se pudo crear el contenedor') # # # 10. Apply permissions # permissions_applied = odoo_api.apply_permissions(self.normalized_name) # # if not permissions_applied: # raise Warning('No se pudo aplicar los permisos correspondientes') # # # 11. Get internal ip # internal_ip = odoo_api.get_internal_ip(self.normalized_name) # # # 12. Send email # # TODO # # print(internal_ip) # # self.state = 'activated' @api.one def action_disapprove(self): if self.state != 'draft': raise Warning('No se puede desaprobar un sistema ya activo') self.state = 'disapproved' @api.one def action_suspend(self): if self.state != 'activated': raise Warning('No se puede suspender un sistema no activo') self.state = 'suspended' self.running = False @api.one def copy(self): raise Warning('Atención', 'No se puede duplicar una instancia. Por favor, cree uno nuevo') @api.one def action_destroy(self): if self.state == 'destroyed': raise Warning('No se puede eliminar un sistema ya eliminado') self.state = 'destroyed' self.running = False @api.one def action_start(self): if self.running: raise Warning('Atención', 'No se puede arrancar una instancia que ya está arrancada') self.running = True @api.one def action_restart(self): if not self.running: raise Warning('Atención', 'No se puede parar y arrancar una instancia que ya está parada') self.running = True @api.model def check_status(self): print('croned')