diff options
Diffstat (limited to 'pykolab/confmgmt/model.py')
-rw-r--r-- | pykolab/confmgmt/model.py | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/pykolab/confmgmt/model.py b/pykolab/confmgmt/model.py new file mode 100644 index 0000000..3652d8d --- /dev/null +++ b/pykolab/confmgmt/model.py @@ -0,0 +1,545 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com) +# +# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 or, at your option, any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +import datetime + +import sqlalchemy + +from sqlalchemy import Column +from sqlalchemy import MetaData +from sqlalchemy import Table + +from sqlalchemy import DateTime +from sqlalchemy import ForeignKey +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text + +from sqlalchemy.interfaces import PoolListener + +from sqlalchemy.orm import mapper + +try: + from sqlalchemy.orm import relationship +except: + from sqlalchemy.orm import relation as relationship + +from sqlalchemy.ext.declarative import declarative_base + +from sqlalchemy.schema import Index +from sqlalchemy.schema import UniqueConstraint + +import pykolab +from pykolab.translate import _ + +log = pykolab.getLogger('pykolab.confmgmt') +conf = pykolab.getConf() + +Base = declarative_base() + +# A single file has multiple settings. +file_settings_table = Table( + 'file_settings', Base.metadata, + Column('file_id', Integer, ForeignKey('file.id')), + Column('setting_id', Integer, ForeignKey('setting.id')) + ) + +# A node has multiple roles. +node_roles_table = Table( + 'node_roles', Base.metadata, + Column('node_id', Integer, ForeignKey('node.id')), + Column('role_id', Integer, ForeignKey('role.id')) + ) + +# A package ships multiple files, and each file belongs to one package (at the +# most). +package_files_table = Table( + 'package_files', Base.metadata, + Column('package_id', Integer, ForeignKey('package.id')), + Column('file_id', Integer, ForeignKey('file.id')) + ) + +package_services_table = Table( + 'package_services', Base.metadata, + Column('package_id', Integer, ForeignKey('package.id')), + Column('service_id', Integer, ForeignKey('service.id')) + ) + +# Each role implies running one or more services. +role_services_table = Table( + 'role_services', Base.metadata, + Column('role_id', Integer, ForeignKey('role.id')), + Column('service_id', Integer, ForeignKey('service.id')) + ) + +role_settings_table = Table( + 'role_settings', Base.metadata, + Column('role_id', Integer, ForeignKey('role.id')), + Column('setting_id', Integer, ForeignKey('setting.id')) + ) + +service_files_table = Table( + 'service_files', Base.metadata, + Column('service_id', Integer, ForeignKey('service.id')), + Column('file_id', Integer, ForeignKey('file.id')) + ) + +class Environment(Base): + __tablename__ = 'environment' + + id = Column(Integer, primary_key=True) + # TODO: unique constraint + name = Column(String(16), nullable=False) + description = Column(String(128), nullable=True) + + # One to many + nodes = relationship( + 'Node', + backref='environment' + ) + +class File(Base): + __tablename__ = 'file' + + id = Column(Integer, primary_key=True) + path = Column(Text, nullable=False) + tech = Column(String(7), nullable=False, default='augeas') + + package = relationship( + 'Package', + secondary=package_files_table + ) + + services = relationship( + 'Service', + secondary=service_files_table + ) + + settings = relationship( + 'Setting', + secondary=file_settings_table + ) + +class Node(Base): + __tablename__ = 'node' + + id = Column(Integer, primary_key=True) + fqdn = Column(String(128), nullable=False) + + # One node can only belong to one environment at a time + environment_id = Column(Integer, ForeignKey('environment.id')) + + roles = relationship( + 'Role', + secondary=node_roles_table + ) + +class Package(Base): + __tablename__ = 'package' + + id = Column(Integer, primary_key=True) + name = Column(String(256), nullable=False) + + files = relationship( + 'File', + secondary=package_files_table + ) + + services = relationship( + 'Service', + secondary=package_services_table + ) + +class Role(Base): + __tablename__ = 'role' + + id = Column(Integer, primary_key=True) + name = Column(String(128), nullable=False) + + nodes = relationship( + 'Node', + secondary=node_roles_table + ) + + services = relationship( + 'Service', + secondary=role_services_table + ) + + settings = relationship( + 'Setting', + secondary=role_settings_table + ) + +class Service(Base): + __tablename__ = 'service' + + id = Column(Integer, primary_key=True) + name = Column(String(128), nullable=False) + + files = relationship( + 'File', + secondary=service_files_table + ) + + package = relationship( + 'Package', + secondary=package_services_table + ) + + roles = relationship( + 'Role', + secondary=role_services_table + ) + +class Setting(Base): + __tablename__ = 'setting' + + id = Column(Integer, primary_key=True) + key = Column(String(128), nullable=False) + + environment_id = Column(Integer, ForeignKey('environment.id')) + + value = Column(Text, nullable=True) + function = Column(Text, nullable=True) + + files = relationship( + 'File', + secondary=file_settings_table + ) + + roles = relationship( + 'Role', + secondary=role_settings_table + ) + +def list_nodes_by_role(role): + role = db.query(Role).filter_by(name=role).first() + + nodes = [] + + for node in role.nodes: + if not node.fqdn in nodes: + nodes.append(node.fqdn) + + return nodes + +def list_users_by_role(role, result_attr): + auth = pykolab.auth + auth.connect() + user_dns = auth.search_users('nsrole', 'cn=%s,dc=klab,dc=cc' % (role), base_dn="dc=klab,dc=cc") + + user_login_names = [] + + for user_dn in user_dns: + user_login_names.append(auth.get_user_attribute(None, {'dn': user_dn}, result_attr)) + + return user_login_names + +if __name__ == "__main__": + db = init_db() + + if conf.debuglevel > 8: + environment = db.query(Environment).all() + + if len(environment) == 0: + db.add(Environment(name='development')) + db.commit() + + environment = db.query(Environment).filter_by(name='development').first() + + # Add a series of nodes. + for node_fqdn in PRETEND_TO_HAVE_NODES: + node = db.query(Node).filter_by(fqdn=node_fqdn).first() + + if node == None: + db.add(Node(fqdn=node_fqdn)) + db.commit() + + node = db.query(Node).filter_by(fqdn=node_fqdn).first() + + node.environment = environment + + db.commit() + + roles = { + 'mta-internal': { + 'services': [ + 'postfix' + ] + }, + 'mta-external': { + 'services': [ + 'postfix' + ] + }, + 'mta-backend': { + 'services': [ + 'postfix' + ] + }, + 'mta-content-filter': { + 'services': [ + 'postfix' + ] + }, + 'content-filter': { + 'services': [ + 'postfix' + ] + }, + 'imap-server-backend': { + 'services': [ + 'postfix', + 'cyrus-imapd' + ] + }, + 'imap-server-frontend': { + 'services': [ + 'postfix', + 'cyrus-imapd' + ] + } + } + + for role in roles.keys(): + _role = db.query(Role).filter_by(name=role).first() + if _role == None: + db.add(Role(name=role)) + db.commit() + + _role = db.query(Role).filter_by(name=role).first() + + if isinstance(roles[role], dict): + if roles[role].has_key('services'): + for service in roles[role]['services']: + _service = db.query(Service).filter_by(name=service).first() + + if _service == None: + db.add(Service(name=service)) + db.commit() + + _service = db.query(Service).filter_by(name=service).first() + + _role.services.append(_service) + + db.commit() + + for node_fqdn in PRETEND_TO_HAVE_NODES: + node = db.query(Node).filter_by(fqdn=node_fqdn).first() + if node.fqdn.startswith(role): + node.roles.append(_role) + db.commit() + + db.commit() + + # Add /etc/imapd.conf for cyrus-imapd service + service = db.query(Service).filter_by(name='cyrus-imapd').first() + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + if file == None: + db.add(File(path='/etc/imapd.conf')) + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + service.files.append(file) + db.commit() + + # Add /etc/cyrus.conf for cyrus-imapd service + service = db.query(Service).filter_by(name='cyrus-imapd').first() + file = db.query(File).filter_by(path='/etc/cyrus.conf').first() + if file == None: + db.add(File(path='/etc/cyrus.conf')) + file = db.query(File).filter_by(path='/etc/cyrus.conf').first() + service.files.append(file) + db.commit() + + # Set the service for the roles. + service = db.query(Service).filter_by(name='cyrus-imapd').first() + for role in db.query(Role).filter( + Role.name.in_(['imap-server-backend', 'imap-server-frontend']) + ).all(): + + role.services.append(service) + db.commit() + + # Add setting 'admins' for file '/etc/imapd.conf' (no role) + setting = db.query(Setting).filter_by(key='admins').first() + if setting == None: + db.add( + Setting( + key='admins', + function="list_users_by_role('cyrus-admin', 'uid')" + ) + ) + + db.commit() + setting = db.query(Setting).filter_by(key='admins').first() + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + file.settings.append(setting) + db.commit() + + # Add setting 'proxyservers' for file '/etc/imapd.conf' (role: + # imap-server-backend) + setting = db.query(Setting).filter_by(key='proxyservers').first() + if setting == None: + db.add(Setting(key='proxyservers', function="list_users_by_role('cyrus-proxyserver', 'uid')")) + db.commit() + setting = db.query(Setting).filter_by(key='proxyservers').first() + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + file.settings.append(setting) + db.commit() + + role = db.query(Role).filter_by(name='imap-server-backend').all() + setting.roles = role + db.commit() + + # Add setting 'proxy_authname' for file '/etc/imapd.conf' (role: + # imap-server-frontend) + setting = db.query(Setting).filter_by(key='proxy_authname').first() + if setting == None: + # TODO: Somehow ensure that the value here is returned by the + # function for proxyservers as well! + db.add(Setting(key='proxy_authname', value='cyrus-murder')) + db.commit() + setting = db.query(Setting).filter_by(key='proxy_authname').first() + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + file.settings.append(setting) + db.commit() + + role = db.query(Role).filter_by(name='imap-server-frontend').all() + setting.roles = role + db.commit() + + # Add setting 'proxy_password' for file '/etc/imapd.conf' (role: + # imap-server-frontend) + setting = db.query(Setting).filter_by(key='proxy_password').first() + if setting == None: + db.add(Setting(key='proxy_password', value='V3ryS3cr3t')) + db.commit() + setting = db.query(Setting).filter_by(key='proxy_password').first() + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + file.settings.append(setting) + db.commit() + + role = db.query(Role).filter_by(name='imap-server-frontend').all() + setting.roles = role + db.commit() + + # Add setting 'serverlist' for file '/etc/imapd.conf' (role: + # imap-server-frontend) + setting = db.query(Setting).filter_by(key='serverlist').first() + if setting == None: + db.add(Setting(key='serverlist', function="list_nodes_by_role('imap-server-backend')")) + db.commit() + setting = db.query(Setting).filter_by(key='serverlist').first() + file = db.query(File).filter_by(path='/etc/imapd.conf').first() + file.settings.append(setting) + db.commit() + + role = db.query(Role).filter_by(name='imap-server-frontend').all() + setting.roles = role + db.commit() + + for node_fqdn in PRETEND_TO_HAVE_NODES: + print "Running for NODE FQDN %s" % (node_fqdn) + + mynode = db.query(Node).filter_by(fqdn=node_fqdn).first() + myroles = mynode.roles + + myservices = [] + myfiles = [] + mysettings = [] + + for myrole in myroles: + print "%-2s %s" % ('-', myrole.name) + print "role(%s) settings:" % (myrole.name), ', '.join([x.key for x in myrole.settings]) + + for _myservice in myrole.services: + print "role(%s) service(%s) files:" % (myrole.name, _myservice.name), ', '.join([x.path for x in _myservice.files]) + if not _myservice in myservices: + myservices.append(_myservice) + + for _myfile in _myservice.files: + print "role(%s) service(%s) file(%s) settings:" % (myrole.name,_myservice.name,_myfile.path), ', '.join([x.key for x in _myfile.settings]) + + if not _myfile in myfiles: + myfiles.append(_myfile) + + for _mysetting in _myfile.settings: + if not _mysetting in mysettings: + if not _mysetting.roles == []: + for _role in _mysetting.roles: + if _role in myroles: + mysettings.append(_mysetting) + else: + mysettings.append(_mysetting) + + for _mysetting in myrole.settings: + if not _mysetting in mysettings: + mysettings.append(_mysetting) + + print "\nMy services:" + + for myservice in myservices: + print "%-2s %s" % ('-', myservice.name) + + print "\nMy files:" + + for myfile in myfiles: + print "%-2s %s" % ('-', myfile.path) + if len(myfile.settings) > 0: + print "%-4s %s" % ('', "Related settings:") + + for _mysetting in myfile.settings: + if len(_mysetting.roles) > 0: + is_my_role_too = False + for _role in _mysetting.roles: + if _role in myroles: + is_my_role_too = True + + if not is_my_role_too: + continue + + print "%-4s %s" % ('', _mysetting.key) + + myaugeas = Augeas(flags=Augeas.SAVE_NEWFILE) + + augeas_setting_path = '/files/%s/%s' % (myfile.path,_mysetting.key) + + print "Using augeas setting path: %s" % (augeas_setting_path) + + current_setting = myaugeas.get(augeas_setting_path) + + print "Current setting:", current_setting + + if not _mysetting.function == None: + exec("retval = %s" % (_mysetting.function)) + new_setting = ' '.join(retval) + elif not _mysetting.value == None: + new_setting = _mysetting.value + else: + print "ERROR: No value nor function for setting %s" % (_mysetting.key) + continue + + print "New setting:", new_setting + + myaugeas.set(augeas_setting_path, new_setting) + + myaugeas.save() + + utils.ask_confirmation("Does this look alright?") |