Skip to content
conf.py 6.8 KiB
Newer Older
Antoine Millet's avatar
Antoine Millet committed
#!/usr/bin/env python
#coding=utf8

'''
This module provide an abstraction to the clients configuration directory.

The client configuration directory contains a list of ``.json`` files, each
file contains the configuration for one client. The username of the client is
the filename (excluding the extension).

The schema of the json file is described below::

    {
     'password': '<client password>',
     'role': '<node|client>',
     'tags': ['tag1', 'tag2'],
     'perms': Null
    }

Usage example:

>>> conf = CCConf('/etc/cloudcontrol/clients')
>>> conf.create_account(login='rms', password='secret', role='client')
>>> conf.create_account(login='server42', password='secret', role='node')
>>> print conf.authentify('server42', 'pouet')
None
>>> print conf.authentify('server42', 'secret')
u'node'
>>> conf.add_tag('rms', 'admin')
>>> conf.show('rms')
{'password': 'secret'
 'role': 'client',
 'tags': ['admin'],
 'perms': None}
>>> conf.remove_account('rms')
>>> 
'''

import threading
import logging
import json
import os

from functools import wraps

class CCConf(object):
    '''
    Create a new configuration interface.

    :param path_directory: the directory to store the configuration files
    '''

    CONF_TEMPLATE = {'password': None,
                     'type': None,
                     'tags': [],
                     'perms': None}

    def __init__(self, path_directory):
        self._path = path_directory
        self._lock = threading.Lock()

    def _writer(func):
        '''
        Decorator used to threadsafize methods that made write operations on
        client configuration tree.
        '''

        @wraps(func)
        def f(self, *args, **kwargs):
            with self._lock:
                return func(self, *args, **kwargs)

        return f

    def _get_conf(self, login):
        '''
        Return the configuration of a client by its login.

        :param login: login of the client
        :return: the configuration of the client
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        filename = os.path.join(self._path, '%s.json' % login)
        if os.path.isfile(filename):
            conf = json.load(open(filename, 'r'))
            logging.debug('Getting configuration %s: %s' % (filename, conf))
            return conf
        else:
            raise CCConf.UnknownAccount('%s is not a file' % filename)

    def _set_conf(self, login, conf, create=False):
        '''
        Update the configuration of a client by its login.

        :param login: login of the client
        :param conf: configuration to set for the client
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        filename = os.path.join(self._path, '%s.json' % login)
Antoine Millet's avatar
Antoine Millet committed
        logging.debug('Writing configuration %s: %s' % (filename, conf))
Antoine Millet's avatar
Antoine Millet committed
        if os.path.isfile(filename) ^ create:
            json.dump(conf, open(filename, 'w'))
        else:
            raise CCConf.UnknownAccount('%s is not a file' % filename)

    def show(self, login):
        '''
        Show the configuration for specified account.

        :param login: the login of the client
        :return: configuration of user
        '''

        return self._get_conf(login)

    def authentify(self, login, password):
        '''
        Authentify the client providing its login and password. The function
        return the role of the client on success, or ``None``.

        :param login: the login of the client
        :param password: the password of the client
        :return: the client's role or None on failed authentication
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        conf = self._get_conf(login)
        if conf['password'] == password:
            return conf['role']
        else:
            return None

    @_writer
    def set_password(self, login, password):
        '''
        Update the client's password in the configuration.

        :param login: login of the user
        :param password: new password
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        conf = self._get_conf(login)
        conf['password'] = password
        self._set_conf(login, conf)

    @_writer
    def add_tag(self, login, tag):
        '''
        Add the tag to the user.

        :param login: login of the user
        :param tag: tag to add to the user
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        logging.debug('Added tag %s for %s account' % (login, tag))
        conf = self._get_conf(login)
        tags = set(conf['tags'])
        tags.add(tag)
        conf['tags'] = list(tags)
        self._set_conf(login, conf)

    @_writer
    def remove_tag(self, login, tag):
        '''
        Remove the tag to the user.

        :param login: login of the user
        :param tag: tag to remove to the user
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        logging.debug('Removed tag %s for %s account' % (login, tag))
        conf = self._get_conf(login)
        tags = set(conf['tags'])
        tags.remove(tag)
        conf['tags'] = list(tags)
        self._set_conf(login, conf)

    @_writer
    def remove_account(self, login):
        '''
        Remove the configuration of the account.

        :param login: login of the account to remove
        :raise CCConf.UnknownAccount: if user login is unknown
        '''

        logging.debug('Removed %s account' % login)
        filename = os.path.join(self._path, '%s.json' % login)
        if os.path.exists(filename):
            os.remove(filename)
        else:
            raise CCConf.UnknownAccount('%s is not a file' % filename)

    @_writer
    def create_account(self, login, password, role):
        '''
        Create a new account.

        :param login: login of the new user
        :param password: password of the new user
        :raise CCConf.AlreadyExistingAccount: if the login is already
        '''

        logging.debug('Creating %s account with role %s' % (login, role))
        filename = os.path.join(self._path, '%s.json' % login)
        if os.path.exists(filename):
            raise CCConf.AlreadyExistingAccount('%s found' % filename)
        else:
            conf = CCConf.CONF_TEMPLATE.copy()
            conf['password'] = password
            conf['role'] = role

            self._set_conf(login, conf, create=True)

    def list_accounts(self):
        '''
        List all registered accounts.

        :return: :class:`tuple` of :class:`str`, each item being an
            account login
        '''

        logins = []
        for filename in os.listdir(self._path):
            login, ext = os.path.splitext(filename)
            if ext == '.json':
                logins.append(login)

        return tuple(logins)

Antoine Millet's avatar
Antoine Millet committed

    class UnknownAccount(Exception):
        pass


    class AlreadyExistingAccount(Exception):
        pass