Exemple #1
0
 def generate_default(self):
     res = ''
     if self.name == 'db_password':
         res = model.generate_random_password(20)
     if self.name == 'secret':
         res = model.generate_random_password(50)
     if self.name == 'ssh_privatekey':
         res = self.env['clouder.server']._default_private_key()
     if self.name == 'ssh_publickey':
         res = self.env['clouder.server']._default_public_key()
     return res
Exemple #2
0
class ClouderBase(models.Model):
    """
    Define the base object, which represent all websites hosted in this clouder
    with a specific url and a specific database.
    """

    _name = 'clouder.base'
    _inherit = ['clouder.model']

    name = fields.Char('Name', required=True)
    domain_id = fields.Many2one('clouder.domain', 'Domain name', required=True)
    environment_id = fields.Many2one('clouder.environment',
                                     'Environment',
                                     required=True)
    title = fields.Char('Title', required=True)
    application_id = fields.Many2one('clouder.application',
                                     'Application',
                                     required=True)
    container_id = fields.Many2one('clouder.container',
                                   'Container',
                                   required=True)
    admin_name = fields.Char('Admin name', required=True)
    admin_password = fields.Char('Admin password',
                                 required=True,
                                 default=model.generate_random_password(20))
    admin_email = fields.Char('Admin email', required=True)
    poweruser_name = fields.Char('PowerUser name')
    poweruser_password = fields.Char(
        'PowerUser password', default=model.generate_random_password(12))
    poweruser_email = fields.Char('PowerUser email')
    build = fields.Selection([('none', 'No action'), ('build', 'Build'),
                              ('restore', 'Restore')],
                             'Build?',
                             default='build')
    ssl_only = fields.Boolean('SSL Only?', default=True)
    test = fields.Boolean('Test?')
    lang = fields.Selection([('en_US', 'en_US'), ('fr_FR', 'fr_FR')],
                            'Language',
                            required=True,
                            default='en_US')
    state = fields.Selection([('installing', 'Installing'),
                              ('enabled', 'Enabled'), ('blocked', 'Blocked'),
                              ('removing', 'Removing')],
                             'State',
                             readonly=True)
    option_ids = fields.One2many('clouder.base.option', 'base_id', 'Options')
    link_ids = fields.One2many('clouder.base.link', 'base_id', 'Links')
    parent_id = fields.Many2one('clouder.base.child', 'Parent')
    child_ids = fields.One2many('clouder.base.child', 'base_id', 'Childs')
    metadata_ids = fields.One2many('clouder.base.metadata', 'base_id',
                                   'Metadata')
    time_between_save = fields.Integer('Minutes between each save')
    save_expiration = fields.Integer('Days before save expiration')
    date_next_save = fields.Datetime('Next save planned')
    save_comment = fields.Text('Save Comment')
    autosave = fields.Boolean('Save?', default=True)
    reset_each_day = fields.Boolean('Reset each day?')
    cert_key = fields.Text('Cert Key')
    cert_cert = fields.Text('Cert')
    cert_renewal_date = fields.Date('Cert renewal date')
    reset_id = fields.Many2one('clouder.base', 'Reset with this base')
    backup_ids = fields.Many2many('clouder.container',
                                  'clouder_base_backup_rel',
                                  'base_id',
                                  'backup_id',
                                  'Backup containers',
                                  required=True)
    public = fields.Boolean('Public?')

    @property
    def is_root(self):
        """
        Property returning is this base is the root of the domain or not.
        """
        if self.name == 'www':
            return True
        return False

    @property
    def fullname(self):
        """
        Property returning the full name of the base.
        """
        return self.application_id.fullcode + '-' + self.fulldomain.replace(
            '.', '-')

    @property
    def fullname_(self):
        """
        Property returning the full name of the base with all - replace by
        underscore (databases compatible names).
        """
        return self.fullname.replace('-', '_')

    @property
    def fulldomain(self):
        """
        Property returning the full url of the base.
        """
        if self.is_root:
            return self.domain_id.name
        return self.name + '.' + self.domain_id.name

    @property
    def databases(self):
        """
        Property returning all databases names used for this base, in a dict.
        """
        databases = {'single': self.fullname_}
        if self.application_id.type_id.multiple_databases:
            databases = {}
            for database in \
                    self.application_id.type_id.multiple_databases.split(','):
                databases[database] = self.fullname_ + '_' + database
        return databases

    @property
    def databases_comma(self):
        """
        Property returning all databases names used for this base,
        separated by a comma.
        """
        return ','.join([d for k, d in self.databases.iteritems()])

    @property
    def http_port(self):
        return self.container_id.childs['exec'] and \
            self.container_id.childs['exec'].ports['http']['hostport']

    @property
    def options(self):
        """
        Property returning a dictionary containing the value of all options
        for this base, even is they are not defined here.
        """
        options = {}
        for option in \
                self.application_id.type_id.option_ids:
            if option.type == 'base':
                options[option.name] = {
                    'id': option.id,
                    'name': option.id,
                    'value': option.default
                }
        for option in self.option_ids:
            options[option.name.name] = {
                'id': option.id,
                'name': option.name.id,
                'value': option.value
            }
        return options

    @property
    def links(self):
        """
        Property returning a dictionary containing the value of all links
        for this base.
        """
        links = {}
        for link in self.link_ids:
            links[link.name.name.code] = link
        return links

    _sql_constraints = [('name_uniq', 'unique (name,domain_id)',
                         'Name must be unique per domain !')]

    @api.one
    @api.constrains('name', 'admin_name', 'admin_email', 'poweruser_email')
    def _check_forbidden_chars_credentials(self):
        """
        Check that the base name and some other fields does not contain any
        forbidden characters.
        """
        if not re.match("^[\w\d-]*$", self.name):
            raise except_orm(_('Data error!'),
                             _("Name can only contains letters, digits and -"))
        if not re.match("^[\w\d_.@-]*$", self.admin_name):
            raise except_orm(
                _('Data error!'),
                _("Admin name can only contains letters, digits and underscore"
                  ))
        if self.admin_email\
                and not re.match("^[\w\d_.@-]*$", self.admin_email):
            raise except_orm(
                _('Data error!'),
                _("Admin email can only contains letters, "
                  "digits, underscore, - and @"))
        if self.poweruser_email \
                and not re.match("^[\w\d_.@-]*$", self.poweruser_email):
            raise except_orm(
                _('Data error!'),
                _("Poweruser email can only contains letters, "
                  "digits, underscore, - and @"))

    @api.one
    @api.constrains('container_id', 'application_id')
    def _check_application(self):
        """
        Check that the application of the base is the same than application
        of services.
        """
        if self.application_id.id != \
                self.container_id.application_id.id:
            raise except_orm(
                _('Data error!'),
                _("The application of base must be the same "
                  "than the application of the container."))

    @api.multi
    def onchange_application_id_vals(self, vals):
        """
        Update the options, links and some other fields when we change
        the application_id field.
        """
        if 'application_id' in vals and vals['application_id']:
            application = self.env['clouder.application'].browse(
                vals['application_id'])

            if 'admin_name' not in vals or not vals['admin_name']:
                vals['admin_name'] = application.admin_name \
                     and application.admin_name \
                     or self.email_sysadmin
            if 'admin_email' not in vals or not vals['admin_email']:
                vals['admin_email'] = application.admin_email \
                    and application.admin_email \
                    or self.email_sysadmin

            options = []
            # Getting sources for new options
            option_sources = {x.id: x for x in application.type_id.option_ids}
            sources_to_add = option_sources.keys()
            # Checking old options
            if 'option_ids' in vals:
                for option in vals['option_ids']:
                    # Standardizing for possible odoo x2m input
                    if isinstance(option, (list, tuple)):
                        option = {
                            'name': option[2].get('name', False),
                            'value': option[2].get('value', False)
                        }
                        # This case means we do not have an odoo recordset and need to load the link manually
                        if isinstance(option['name'], int):
                            option['name'] = self.env[
                                'clouder.application.type.option'].browse(
                                    option['name'])
                    else:
                        option = {
                            'name': getattr(option, 'name', False),
                            'value': getattr(option, 'value', False)
                        }
                    # Keeping the option if there is a match with the sources
                    if option['name'] and option['name'].id in option_sources:
                        option['source'] = option_sources[option['name'].id]

                        if option['source'].type == 'base' and option[
                                'source'].auto:
                            # Updating the default value if there is no current one set
                            options.append((0, 0, {
                                'name':
                                option['source'].id,
                                'value':
                                option['value'] or option['source'].get_default
                            }))

                            # Removing the source id from those to add later
                            sources_to_add.remove(option['name'].id)

            # Adding missing option from sources
            for def_opt_key in sources_to_add:
                if option_sources[
                        def_opt_key].type == 'base' and option_sources[
                            def_opt_key].auto:
                    options.append((0, 0, {
                        'name':
                        option_sources[def_opt_key].id,
                        'value':
                        option_sources[def_opt_key].get_default
                    }))

            # Replacing old options
            vals['option_ids'] = options

            link_sources = {
                x.id: x
                for code, x in application.links.iteritems()
            }
            sources_to_add = link_sources.keys()
            links_to_process = []
            # Checking old links
            if 'link_ids' in vals:
                for link in vals['link_ids']:
                    # Standardizing for possible odoo x2m input
                    if isinstance(link, (list, tuple)):
                        link = {
                            'name': link[2].get('name', False),
                            'required': link[2].get('required', False),
                            'auto': link[2].get('auto', False),
                            'next': link[2].get('next', False)
                        }
                        # This case means we do not have an odoo recordset and need to load the link manually
                        if isinstance(link['name'], int):
                            link['name'] = self.env[
                                'clouder.application.link'].browse(
                                    link['name'])
                    else:
                        link = {
                            'name': getattr(link, 'name', False),
                            'required': getattr(link, 'required', False),
                            'auto': getattr(link, 'auto', False),
                            'next': getattr(link, 'next', False)
                        }
                    # Keeping the link if there is a match with the sources
                    if link['name'] and link['name'].id in link_sources:
                        link['source'] = link_sources[link['name'].id]
                        links_to_process.append(link)

                        # Remove used link from sources
                        sources_to_add.remove(link['name'].id)

            # Adding links from source
            for def_key_link in sources_to_add:
                link = {
                    'name':
                    getattr(link_sources[def_key_link], 'name', False),
                    'required':
                    getattr(link_sources[def_key_link], 'required', False),
                    'auto':
                    getattr(link_sources[def_key_link], 'auto', False),
                    'next':
                    getattr(link_sources[def_key_link], 'next', False),
                    'source':
                    link_sources[def_key_link]
                }
                links_to_process.append(link)

            # Running algorithm to determine new links
            links = []
            for link in links_to_process:
                if link['source'].base and link['source'].auto:
                    next_id = link['next']
                    if 'parent_id' in vals and vals['parent_id']:
                        parent = self.env['clouder.base.child'].browse(
                            vals['parent_id'])
                        for parent_link in parent.base_id.link_ids:
                            if link['source'].name.code == parent_link.name.name.code \
                                    and parent_link.target:
                                next_id = parent_link.target.id
                    context = self.env.context
                    if not next_id and 'base_links' in context:
                        fullcode = link['source'].name.fullcode
                        if fullcode in context['base_links']:
                            next_id = context['base_links'][fullcode]
                    if not next_id:
                        next_id = link['source'].next.id
                    if not next_id:
                        target_ids = self.env['clouder.container'].search([
                            ('application_id.code', '=',
                             link['source'].name.code),
                            ('parent_id', '=', False)
                        ])
                        if target_ids:
                            next_id = target_ids[0].id
                    links.append((0, 0, {
                        'name': link['source'].name.id,
                        'required': link['required'],
                        'auto': link['auto'],
                        'target': next_id
                    }))
            # Replacing old links
            vals['link_ids'] = links

            childs = []
            # Getting source for childs
            child_sources = {x.id: x for x in application.child_ids}
            sources_to_add = child_sources.keys()
            childs_to_process = []

            # Checking for old childs
            if 'child_ids' in vals:
                for child in vals['child_ids']:
                    # Standardizing for possible odoo x2m input
                    if isinstance(child, (list, tuple)):
                        child = {
                            'name': child[2].get('name', False),
                            'sequence': child[2].get('sequence', False)
                        }
                        # This case means we do not have an odoo recordset and need to load the link manually
                        if isinstance(child['name'], int):
                            child['name'] = self.env[
                                'clouder.application'].browse(child['name'])
                    else:
                        child = {
                            'name': getattr(child, 'name', False),
                            'sequence': getattr(child, 'sequence', False)
                        }
                    if child['name'] and child['name'].id in child_sources:
                        child['source'] = child_sources[child['name'].id]
                        childs_to_process.append(child)

                        # Removing from sources
                        sources_to_add.remove(child['name'].id)

            # Adding remaining childs from source
            for def_child_key in sources_to_add:
                child = {
                    'name':
                    getattr(child_sources[def_child_key], 'name', False),
                    'sequence':
                    getattr(child_sources[def_child_key], 'sequence', False),
                    'source':
                    child_sources[def_child_key]
                }
                childs_to_process.append(child)

            # Processing new childs
            for child in childs_to_process:
                if child['source'].required and child['source'].base:
                    childs.append((0, 0, {
                        'name': child['source'].id,
                        'sequence': child['sequence']
                    }))

            # Replacing old childs
            vals['child_ids'] = childs

            # Processing Metadata
            metadata_vals = []
            metadata_sources = {
                x.id: x
                for x in application.metadata_ids if x.clouder_type == 'base'
            }
            sources_to_add = metadata_sources.keys()
            metadata_to_process = []
            if 'metadata_ids' in vals:
                for metadata in vals['metadata_ids']:
                    # Standardizing for possible odoo x2m input
                    if isinstance(metadata, (list, tuple)):
                        metadata = {
                            'name': metadata[2].get('name', False),
                            'value_data': metadata[2].get('value_data', False)
                        }
                        # This case means we do not have an odoo recordset and need to load the link manually
                        if isinstance(metadata['name'], int):
                            metadata['name'] = self.env[
                                'clouder.application'].browse(metadata['name'])
                    else:
                        metadata = {
                            'name': getattr(metadata, 'name', False),
                            'value_data': getattr(metadata, 'value_data',
                                                  False)
                        }
                    # Processing metadata and adding to list
                    if metadata['name'] and metadata[
                            'name'].id in metadata_sources:
                        metadata['source'] = metadata_sources[
                            metadata['name'].id]
                        metadata['value_data'] = metadata[
                            'value_data'] or metadata['source'].default_value
                        metadata_to_process.append(metadata)

                        # Removing from sources
                        sources_to_add.remove(metadata['name'].id)

            # Adding remaining metadata from source
            for metadata_key in sources_to_add:
                metadata = {
                    'name': getattr(metadata_sources[metadata_key], 'name',
                                    False),
                    'value_data': metadata_sources[metadata_key].default_value,
                    'source': metadata_sources[metadata_key]
                }
                metadata_to_process.append(metadata)

            # Processing new metadata
            for metadata in metadata_to_process:
                if metadata['source'].clouder_type == 'base':
                    metadata_vals.append((0, 0, {
                        'name': metadata['source'].id,
                        'value_data': metadata['value_data']
                    }))

            # Replacing old metadata
            vals['metadata_ids'] = metadata_vals

            if 'backup_ids' not in vals or not vals['backup_ids']:
                if application.base_backup_ids:
                    vals['backup_ids'] = [
                        (6, 0, [b.id for b in application.base_backup_ids])
                    ]
                else:
                    backups = self.env['clouder.container'].search([
                        ('application_id.type_id.name', '=', 'backup')
                    ])
                    if backups:
                        vals['backup_ids'] = [(6, 0, [backups[0].id])]

            vals['autosave'] = application.autosave

            vals['time_between_save'] = \
                application.base_time_between_save
            vals['save_expiration'] = \
                application.base_save_expiration

        return vals

    @api.multi
    @api.onchange('application_id')
    def onchange_application_id(self):
        vals = {
            'application_id':
            self.application_id.id,
            'container_id':
            self.application_id.next_container_id
            and self.application_id.next_container_id.id or False,
            'admin_name':
            self.admin_name,
            'admin_email':
            self.admin_email,
            'option_ids':
            self.option_ids,
            'link_ids':
            self.link_ids,
            'child_ids':
            self.child_ids,
            'metadata_ids':
            self.metadata_ids,
            'parent_id':
            self.parent_id and self.parent_id.id or False
        }
        vals = self.onchange_application_id_vals(vals)
        self.env['clouder.container.option'].search([('container_id', '=',
                                                      self.id)]).unlink()
        self.env['clouder.container.link'].search([('container_id', '=',
                                                    self.id)]).unlink()
        self.env['clouder.container.child'].search([('container_id', '=',
                                                     self.id)]).unlink()
        for key, value in vals.iteritems():
            setattr(self, key, value)

    @api.multi
    def control_priority(self):
        return self.container_id.check_priority_childs(self)

    @api.model
    def create(self, vals):
        """
        Override create method to create a container and a service if none
        are specified.

        :param vals: The values needed to create the record.
        """
        if ('container_id' not in vals) or (not vals['container_id']):
            application_obj = self.env['clouder.application']
            domain_obj = self.env['clouder.domain']
            container_obj = self.env['clouder.container']
            if 'application_id' not in vals or not vals['application_id']:
                raise except_orm(
                    _('Error!'),
                    _("You need to specify the application of the base."))
            application = application_obj.browse(vals['application_id'])
            if not application.next_server_id:
                raise except_orm(
                    _('Error!'),
                    _("You need to specify the next server in "
                      "application for the container autocreate."))
            if not application.default_image_id.version_ids:
                raise except_orm(
                    _('Error!'),
                    _("No version for the image linked to the application, "
                      "abandoning container autocreate..."))
            if 'domain_id' not in vals or not vals['domain_id']:
                raise except_orm(
                    _('Error!'),
                    _("You need to specify the domain of the base."))
            if 'environment_id' not in vals or not vals['environment_id']:
                raise except_orm(
                    _('Error!'),
                    _("You need to specify the environment of the base."))
            domain = domain_obj.browse(vals['domain_id'])
            container_vals = {
                'name': vals['name'] + '-' + domain.name.replace('.', '-'),
                'server_id': application.next_server_id.id,
                'application_id': application.id,
                'image_id': application.default_image_id.id,
                'image_version_id':
                application.default_image_id.version_ids[0].id,
                'environment_id': vals['environment_id'],
                'suffix': vals['name']
            }
            vals['container_id'] = container_obj.create(container_vals).id

        vals = self.onchange_application_id_vals(vals)

        return super(ClouderBase, self).create(vals)

    @api.multi
    def write(self, vals):
        """
        Override write method to move base if we change the service.

        :param vals: The values to update.
        """

        save = False
        if 'service_id' in vals:
            self = self.with_context(self.create_log('service change'))
            self = self.with_context(save_comment='Before service change')
            save = self.save_exec(no_enqueue=True, forcesave=True)
            self.purge()

        res = super(ClouderBase, self).write(vals)
        if save:
            save.service_id = vals['service_id']
            self = self.with_context(base_restoration=True)
            self.deploy()
            save.restore()
            self.end_log()
        if 'autosave' in vals and self.autosave != vals[
                'autosave'] or 'ssl_only' in vals and self.ssl_only != vals[
                    'ssl_only']:
            self.deploy_links()

        return res

    @api.one
    def unlink(self):
        """
        Override unlink method to make a save before we delete a base.
        """
        self = self.with_context(save_comment='Before unlink')
        save = self.save_exec(no_enqueue=True)
        if self.parent_id:
            self.parent_id.save_id = save
        return super(ClouderBase, self).unlink()

    @api.multi
    def save(self):
        self.do('save', 'save_exec')

    @api.multi
    def save_exec(self, no_enqueue=False, forcesave=False):
        """
        Make a new save.
        """
        save = False
        now = datetime.now()

        if forcesave:
            self = self.with_context(forcesave=True)

        if no_enqueue:
            self = self.with_context(no_enqueue=True)

        if 'nosave' in self.env.context \
                or (not self.autosave and 'forcesave' not in self.env.context):
            self.log('This base shall not be saved or the backup '
                     'isnt configured in conf, skipping save base')
            return

        if no_enqueue:
            self = self.with_context(no_enqueue=True)

        for backup_server in self.backup_ids:
            save_vals = {
                'name':
                self.now_bup + '_' + self.fullname,
                'backup_id':
                backup_server.id,
                # 'repo_id': self.save_repository_id.id,
                'date_expiration':
                (now +
                 timedelta(days=self.save_expiration or self.application_id.
                           base_save_expiration)).strftime("%Y-%m-%d"),
                'comment':
                'save_comment' in self.env.context
                and self.env.context['save_comment'] or self.save_comment
                or 'Manual',
                'now_bup':
                self.now_bup,
                'container_id':
                self.container_id.id,
                'base_id':
                self.id,
            }
            save = self.env['clouder.save'].create(save_vals)
        date_next_save = (
            datetime.now() +
            timedelta(minutes=self.time_between_save or self.application_id.
                      base_time_between_save)).strftime("%Y-%m-%d %H:%M:%S")
        self.write({'save_comment': False, 'date_next_save': date_next_save})
        return save

    @api.multi
    def post_reset(self):
        """
        Hook which can be called by submodules to execute commands after we
        reset a base.
        """
        self.deploy_links()
        return

    @api.multi
    def reset_base(self):
        self = self.with_context(no_enqueue=True)
        self.do('reset_base', 'reset_base_exec')

    @api.multi
    def reset_base_exec(self):
        """
        Reset the base with the parent base.

        :param base_name: Specify another base name
        if the reset need to be done in a new base.

        :param service_id: Specify the service_id is the reset
        need to be done in another service.
        """
        base_name = False
        if 'reset_base_name' in self.env.context:
            base_name = self.env.context['reset_base_name']
        container = False
        if 'reset_container' in self.env.context:
            container = self.env.context['reset_container']
        base_reset_id = self.reset_id and self.reset_id or self
        if 'save_comment' not in self.env.context:
            self = self.with_context(save_comment='Reset base')
        save = base_reset_id.save_exec(no_enqueue=True, forcesave=True)
        self.with_context(nosave=True)
        vals = {
            'base_id': self.id,
            'base_restore_to_name': self.name,
            'base_restore_to_domain_id': self.domain_id.id,
            'container_id': self.container_id.id,
            'base_nosave': True
        }
        if base_name and container:
            vals = {
                'base_id': False,
                'base_restore_to_name': base_name,
                'base_restore_to_domain_id': self.domain_id.id,
                'container_id': container.id,
                'base_nosave': True
            }
        save.write(vals)
        base = save.restore()
        base.write({'reset_id': base_reset_id.id})
        base = base.with_context(base_reset_fullname_=base_reset_id.fullname_)
        base = base.with_context(
            container_reset_name=base_reset_id.container_id.name)
        base.deploy_salt()
        base.update_exec()
        base.post_reset()
        base.deploy_post()

    @api.multi
    def deploy_database(self):
        """
        Hook which can be called by submodules to execute commands when we
        want to create the database. If return False, the database will be
        created by default method.
        """
        return False

    @api.multi
    def deploy_build(self):
        """
        Hook which can be called by submodules to execute commands when we
        want to build the database.
        """
        return

    @api.multi
    def deploy_post_restore(self):
        """
        Hook which can be called by submodules to execute commands after we
        restore a database.
        """
        return

    @api.multi
    def deploy_create_poweruser(self):
        """
        Hook which can be called by submodules to execute commands when we
        want to create a poweruser.
        """
        return

    @api.multi
    def deploy_test(self):
        """
        Hook which can be called by submodules to execute commands when we
        want to deploy test datas.
        """
        return

    @api.multi
    def deploy_post(self):
        """
        Hook which can be called by submodules to execute commands after we
        deploy a base.
        """
        return

    @api.multi
    def deploy(self):
        """
        Deploy the base.
        """
        super(ClouderBase, self).deploy()

        if 'base_restoration' in self.env.context:
            return

        if self.child_ids:
            for child in self.child_ids:
                child.create_child_exec()
            return

        self.deploy_salt()

        self.deploy_database()
        self.log('Database created')

        if self.build == 'build':
            self.deploy_build()

        elif self.build == 'restore':
            # TODO restore from a selected save
            self.deploy_post_restore()

        if self.build != 'none':
            if self.poweruser_name and self.poweruser_email \
                    and self.admin_name != self.poweruser_name:
                self.deploy_create_poweruser()
            if self.test:
                self.deploy_test()

        self.deploy_post()

        # For shinken
        self = self.with_context(save_comment='First save')
        self.save_exec(no_enqueue=True)

        if self.application_id.update_bases:
            self.container_id.deploy_salt()
        for key, child in self.container_id.childs.iteritems():
            if child.application_id.update_bases:
                child.deploy_salt()

    @api.multi
    def purge_post(self):
        """
        Hook which can be called by submodules to execute commands after we
        purge a base.
        """
        return

    @api.multi
    def purge_database(self):
        """
        Purge the database.
        """
        return

    @api.multi
    def purge(self):
        """
        Purge the base.
        """
        self.purge_database()
        self.purge_post()
        self.purge_salt()

        if self.application_id.update_bases:
            self.container_id.deploy_salt()
        for key, child in self.container_id.childs.iteritems():
            if child.application_id.update_bases:
                child.deploy_salt()

        super(ClouderBase, self).purge()

    @api.multi
    def update(self):
        self = self.with_context(no_enqueue=True)
        self.do('update', 'update_exec')

    @api.multi
    def update_exec(self):
        """
        Hook which can be called by submodules to execute commands when we
        want to update a base.
        """
        self = self.with_context(save_comment='Before update')
        self.save_exec(no_enqueue=True)
        return

    @api.multi
    def generate_cert(self):
        self = self.with_context(no_enqueue=True)
        self.do('generate_cert', 'generate_cert_exec')

    @api.multi
    def generate_cert_exec(self):
        """
        Generate a new certificate
        """
        return True

    @api.multi
    def renew_cert(self):
        self = self.with_context(no_enqueue=True)
        self.do('renew_cert', 'renew_cert_exec')

    @api.multi
    def renew_cert_exec(self):
        """
        Renew a certificate
        """
        return True