123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- # -*- coding: utf-8 -*-
- from openerp import models, fields, api
- from openerp.tools import email_send
- from openerp.exceptions import Warning, ValidationError
- from jinja2 import Environment as JinjaEnv, PackageLoader as JinjaLoader
- from ..utils.docker_api import (
- get_all_external_ports,
- run_command,
- copy_in,
- run_container,
- get_internal_ip,
- get_status,
- stop_container,
- start_container,
- restart_container
- )
- from ..utils.command import execute, list_files_and_folders
- from random import randint
- from git.util import join_path
- from git.repo.base import Repo
- from git.exc import GitCommandError
- 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['odoo.management.config'].get_default_settings([]).get('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 not os.path.exists(full_path)
- def _randomize_port(self):
- ports = self.env['odoo.management.config'].get_default_settings([]).get('odoo_ports_range')
- ports = filter(lambda x: x != '', ports.split(','))
- if len(ports) == 0:
- ports = [10000, 20000]
- # 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(0.05)
- 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)
- def _delete_dirs(self, name):
- root_path = self.env['ir.values'].get_default(self._name, 'odoo_root_path')
- full_path = os.path.join(root_path, name)
- execute(['rm', '-Rf', full_path])
- 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)
- output = run_command(odoo_db, cmd)
- return output
- def _drop_database(self, name):
- odoo_db = self.env['ir.values'].get_default(self._name, 'odoo_db_link')
- terminate_cmd = "psql -U %s select pg_terminate_backend(pid) from pg_stats where datname = '%s'" % ('odoo', name)
- run_command(odoo_db, terminate_cmd)
- drop_cmd = 'drop database %s' % name
- run_command(odoo_db, drop_cmd)
- def _copy_database_seed(self):
- odoo_db = self.env['ir.values'].get_default(self._name, 'odoo_db_link')
- backup_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'files', 'odoo.tar')
- output = copy_in(odoo_db, '/tmp', backup_path)
- return output
- def _restore_database(self, name):
- odoo_db = self.env['ir.values'].get_default(self._name, 'odoo_db_link')
- cmd = 'psql -U %s -d %s -f %s' % (odoo_db, name, '/tmp/odoo.sql')
- output = run_command(odoo_db, cmd)
- return output
- def _remove_database_seed(self):
- odoo_db = self.env['ir.values'].get_default(self._name, 'odoo_db_link')
- cmd = 'rm -f %s' % '/tmp/odoo.sql'
- output = run_command(odoo_db, cmd)
- return output
- def _create_container(self, name, ports=[]):
- ir_values = self.env['ir.values']
- root_path = ir_values.get_default(self._name, 'odoo_root_path')
- odoo_docker_image = ir_values.get_default(self._name, 'odoo_docker_image')
- odoo_network = ir_values.get_default(self._name, 'odoo_network')
- ports = dict(map(lambda p: ('%d/tcp' % 8069, p), ports))
- volumes = dict()
- run_container(
- odoo_docker_image,
- name,
- ports,
- volumes,
- odoo_network,
- '150m',
- '150m'
- )
- time.sleep(3)
- return True
- def _apply_permissions(self, name):
- root_path = self.env['ir.values'].get_default(self._name, 'odoo_root_path')
- full_path = os.path.join(root_path, name)
- execute(['chmod', '-Rf', '777'], full_path)
- return True
- def _get_internal_ip(self, name):
- return get_internal_ip(name)
- def _clone_repositories(self, names=[], path=None, branch='master'):
- if len(names) == 0 or not path:
- return False
- git_path = self.env['ir.values'].get_default(self._name, 'modules_git_path')
- paths = list_files_and_folders(git_path)
- if len(git_path) == 0:
- raise Warning('La ruta por defecto del repositorio no está definido')
- for path in paths:
- for name in names:
- repo_path = '%s.git' % join_path(git_path, path, name)
- if not os.path.exists(repo_path):
- continue
- try:
- repo = Repo(repo_path)
- repo.clone(join_path(path, name), branch=branch)
- repo.close()
- except GitCommandError:
- pass
- return True
- def _clone_repository(self, name, path):
- result = self._clone_repositories([name], path)
- return result
- def _copy_modules(self, name, modules=[]):
- root_path = self.env['ir.values'].get_default(self._name, 'odoo_root_path')
- if not os.path.exists(root_path):
- raise Warning('El directorio del sistema no existe')
- container_is_running = get_status(name)
- if container_is_running:
- stopped = stop_container(name)
- if not stopped:
- raise Warning('No se pudo parar el contenedor')
- for module in modules:
- module_path = os.path.join(root_path, 'custom-addons', module)
- if os.path.exists(module_path):
- execute(['rm', '-Rf', module_path])
- addons_path = os.path.join(root_path, 'custom-addons')
- self._clone_repository(module, addons_path)
- git_data_path = os.path.join(module, '.git')
- if os.path.exists(git_data_path):
- execute(['rm', '-Rf', git_data_path])
- try:
- execute(['chmod', '-Rf', '777', module_path])
- except Exception:
- pass
- if container_is_running:
- started = start_container(name)
- if not started:
- raise Warning('No se pudo arrancar el contenedor')
- def _copy_module(self, name, module):
- self._copy_modules(name, [module])
- # def _handle_container_event(self, e):
- # event_type = e.get('Type')
- # event_action = e.get('Action')
- # container_id = e.get('Actor').get('ID')
- #
- # print(event_type)
- # print(event_action)
- # print(container_id)
- #
- # def _register_hook(self, cr):
- # run_watchdog(self._handle_container_event)
- # super(OdooInstance, self)._register_hook(cr)
- 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)
- def _send_email(self, ):
- email_send()
- @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(OdooInstance, self).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')
- # 1. Check name
- name_is_available = self._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 = self._randomize_port()
- # 3. Create dirs
- make_ok = self.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 = self.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 = self.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 = self.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 = self.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 = self.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 = self.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 = self.apply_permissions(self.normalized_name)
- if not permissions_applied:
- raise Warning('No se pudo aplicar los permisos correspondientes')
- # 11. Get internal ip
- internal_ip = self.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')
- # 1. Get container status and stop it
- container_is_running = get_status(self.normalized_name)
- if container_is_running:
- stopped = stop_container(self.normalized_name)
- if not stopped:
- raise Warning('No se pudo parar el contenedor %s' % self.normalized_name)
- self.state = 'suspended'
- self.running = False
- @api.one
- def copy(self):
- raise Warning('Atención', 'Está prohibido 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')
- # 1. Get container status and stop it if necessary
- container_is_running = get_status(self.normalized_name)
- if container_is_running:
- stopped = stop_container(self.normalized_name)
- if not stopped:
- raise Warning('Atención', 'No se pudo parar el contenedor %s' % self.normalized_name)
- # 2. Delete dirs
- self._delete_dirs(self.normalized_name)
- # 3. Drop database
- self._drop_database(self.normalized_name)
- 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')
- # 1. Get container status
- container_is_running = get_status(self.normalized_name)
- if container_is_running:
- raise Warning('Atención', 'No se puede arrancar una instancia que ya está arrancada')
- # 2. Start container
- started = start_container(self.normalized_name)
- if not started:
- raise Warning('Atención', 'No se pudo arrancar el contenedor')
- 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')
- # 1. Get container status
- container_is_running = get_status(self.normalized_name)
- if not container_is_running:
- raise Warning('Atención', 'No se puede parar y arrancar una instancia que ya está parada')
- # 2. Restart container
- restarted = restart_container(self.normalized_name)
- if not restarted:
- raise Warning('Atención', 'No se pudo reiniciar el contenedor')
- self.running = True
- @api.model
- def check_status(self):
- print('croned')
|