summaryrefslogtreecommitdiff
path: root/pykolab/confmgmt/model.py
diff options
context:
space:
mode:
Diffstat (limited to 'pykolab/confmgmt/model.py')
-rw-r--r--pykolab/confmgmt/model.py545
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?")