Branch 'dev/confmgmt' - 8 commits - pykolab/cli pykolab/imap pykolab/plugins pykolab/setup share/templates ucs/kolab_sieve.py

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Fri Dec 14 13:38:12 CET 2012


 pykolab/cli/sieve/cmd_refresh.py               |    4 ++--
 pykolab/imap/__init__.py                       |    4 ++++
 pykolab/plugins/sievemgmt/__init__.py          |    4 ++--
 pykolab/setup/setup_mta.py                     |    3 +++
 pykolab/setup/setup_roundcube.py               |    1 +
 share/templates/roundcubemail/main.inc.php.tpl |    2 +-
 ucs/kolab_sieve.py                             |    1 +
 7 files changed, 14 insertions(+), 5 deletions(-)

New commits:
commit a6a97459fc5e81c3b0001052bb1b4bce5dcba6e1
Merge: a9b1d3f a7d87ec
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Fri Dec 14 12:38:05 2012 +0000

    Merge remote-tracking branch 'origin/dev/confmgmt' into dev/confmgmt



commit a9b1d3f731e05f0b4d49a681483e73728d5f9aca
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Fri Dec 14 10:57:38 2012 +0000

    Add conf command group

diff --git a/pykolab/cli/conf/__init__.py b/pykolab/cli/conf/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pykolab/cli/conf/cmd_add_role.py b/pykolab/cli/conf/cmd_add_role.py
new file mode 100644
index 0000000..7b0076f
--- /dev/null
+++ b/pykolab/cli/conf/cmd_add_role.py
@@ -0,0 +1,59 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('add_role', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--role',
+                dest    = "role",
+                action  = "store",
+                default = None,
+                metavar = "ROLE",
+                help    = _("Add role ROLE.")
+            )
+
+def description():
+    return """Add a role."""
+
+def execute(*args, **kw):
+    if conf.role == None:
+        try:
+            conf.role = conf.cli_args.pop(0)
+        except IndexError, errmsg:
+            print >> sys.stderr, _("Specify a role to add")
+            sys.exit(1)
+
+    role = confmgmt.add_role(conf.role)
diff --git a/pykolab/cli/conf/cmd_file_add_setting.py b/pykolab/cli/conf/cmd_file_add_setting.py
new file mode 100644
index 0000000..e90e2e8
--- /dev/null
+++ b/pykolab/cli/conf/cmd_file_add_setting.py
@@ -0,0 +1,127 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('file_add_setting', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--file',
+                dest    = "_file",
+                action  = "store",
+                default = None,
+                metavar = "FILE",
+                help    = _("Add file FILE")
+            )
+
+    my_option_group.add_option(
+                '--role',
+                dest    = "roles",
+                action  = "append",
+                default = [],
+                metavar = "ROLE",
+                help    = _("Exclusively associate the setting for file FILE with role ROLE. May be specified multiple times to associate the file setting with multiple roles.")
+            )
+
+    my_option_group.add_option(
+                '--key',
+                dest    = "key",
+                action  = "store",
+                default = None,
+                metavar = "KEY",
+                help    = _("The key or 'name' for the setting.")
+            )
+
+    my_option_group.add_option(
+                '--value',
+                dest    = "value",
+                action  = "store",
+                default = None,
+                metavar = "VALUE",
+                help    = _("Set to value VALUE.")
+            )
+
+    my_option_group.add_option(
+                '--function',
+                dest    = "function",
+                action  = "store",
+                default = None,
+                metavar = "FUNCTION",
+                help    = _("Set to function FUNCTION.")
+            )
+
+def description():
+    return """Add a managed setting with key KEY in file FILE."""
+
+def execute(*args, **kw):
+    if conf._file == None:
+        print >> sys.stderr, _("Must specify a file.")
+        sys.exit(1)
+
+    if conf.key == None:
+        print >> sys.stderr, _("Must specify a key.")
+        sys.exit(1)
+
+    if conf.value == None and conf.function == None:
+        print >> sys.stderr, _("Must specify a value of a function to get the values with.")
+        sys.exit(1)
+
+    if not conf.value == None and not conf.function == None:
+        print >> sys.stderr, _("Can only specify one of value or function.")
+        sys.exit(1)
+
+    _file = confmgmt.get_file(conf._file)
+
+    if _file == None:
+        print >> sys.stderr, _("File %s does not exist.") % (conf._file)
+        sys.exit(1)
+
+    if conf.key in [x.key for x in _file.settings]:
+        print >> sys.stderr, _("Setting with key %s already managed in file %s") % (conf.key, conf._file)
+        sys.exit(1)
+
+    setting = confmgmt.add_setting(conf.key, conf.value, conf.function)
+
+    _file.settings.append(setting)
+
+    for role_name in conf.roles:
+        role = confmgmt.get_role(role_name)
+
+        if role == None:
+            log.warning(_("Role %s does not exist.") % (role_name))
+            continue
+
+        setting.roles.append(role)
+
+    confmgmt.commit()
diff --git a/pykolab/cli/conf/cmd_file_apply_settings.py b/pykolab/cli/conf/cmd_file_apply_settings.py
new file mode 100644
index 0000000..cfeb481
--- /dev/null
+++ b/pykolab/cli/conf/cmd_file_apply_settings.py
@@ -0,0 +1,172 @@
+# -*- 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.
+#
+
+from augeas import Augeas
+from Cheetah.Template import Template
+
+import sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('file_apply_settings', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--file',
+                dest    = "_file",
+                action  = "store",
+                default = None,
+                metavar = "FILE",
+                help    = _("Add file FILE")
+            )
+
+def description():
+    return """Apply managed settings to file FILE."""
+
+def execute(*args, **kw):
+    if conf._file == None:
+        print >> sys.stderr, _("Must specify a file.")
+        sys.exit(1)
+
+    _file = confmgmt.get_file(conf._file)
+
+    if _file == None:
+        print >> sys.stderr, _("File %s is not managed.") % (conf._file)
+        sys.exit(1)
+
+    if len(_file.settings) < 1:
+        print >> sys.stderr, _("No managed settings for file %s.") % (conf._file)
+        sys.exit(1)
+
+    node = confmgmt.get_node(constants.fqdn)
+
+    if node == None:
+        print >> sys.stderr, _("This node (%s) is not managed.") % (constants.fqdn)
+        sys.exit(1)
+
+    myaugeas = Augeas()
+    myaugeas.set('/augeas/load/Postfix_LDAP/lens', '@Postfix_Main')
+    myaugeas.set('/augeas/load/Postfix_LDAP/incl', "/etc/postfix/ldap/*.cf")
+    myaugeas.load()
+
+    for role in node.roles:
+        #print role
+        for service in role.services:
+            #print service
+            for _file in service.files:
+                #print _file
+                if _file.path == conf._file:
+                    _cheetah_searchlist = {}
+                    for setting in _file.settings:
+                        #print "node role names", [haystack.name for haystack in node.roles]
+                        #print "setting role names", [needle.name for needle in setting.roles]
+                        #print [needle.name for needle in setting.roles if needle.name in [haystack.name for haystack in node.roles]]
+                        if len(setting.roles) > 0:
+                            if (len([needle.name for needle in setting.roles if needle.name in [haystack.name for haystack in node.roles]]) > 0):
+                                if not setting.function == None:
+                                    exec("retval = %s" % (setting.function))
+                                    if isinstance(retval, list):
+                                        new_setting = ' '.join(retval)
+                                    else:
+                                        new_setting = retval
+
+                                    if _file.tech == 'augeas':
+                                        current_value = myaugeas.get('/files%s/%s' % (_file.path, setting.key))
+                                        if not new_setting == current_value:
+                                            myaugeas.set('/files%s/%s' % (_file.path, setting.key), new_setting)
+                                    elif _file.tech == 'cheetah':
+                                        _cheetah_searchlist[setting.key] = new_setting
+
+                                else:
+                                    if _file.tech == 'augeas':
+                                        current_value = myaugeas.get('/files%s/%s' % (_file.path, setting.key))
+                                        if not setting.value == current_value:
+                                            myaugeas.set('/files%s/%s' % (_file.path, setting.key), setting.value)
+                                    elif _file.tech == 'cheetah':
+                                        _cheetah_searchlist[setting.key] = setting.value
+                        else:
+                            if not setting.function == None:
+                                exec("retval = %s" % (setting.function))
+                                if isinstance(retval, list):
+                                    new_setting = ' '.join(retval)
+                                else:
+                                    new_setting = retval
+
+                                if _file.tech == 'augeas':
+                                    current_value = myaugeas.get('/files%s/%s' % (_file.path, setting.key))
+                                    if not new_setting == current_value:
+                                        myaugeas.set('/files%s/%s' % (_file.path, setting.key), new_setting)
+                                elif _file.tech == 'cheetah':
+                                    _cheetah_searchlist[setting.key] = new_setting
+
+                            else:
+                                if _file.tech == 'augeas':
+                                    current_value = myaugeas.get('/files%s/%s' % (_file.path, setting.key))
+                                    if not setting.value == current_value:
+                                        myaugeas.set('/files%s/%s' % (_file.path, setting.key), setting.value)
+                                elif _file.tech == 'cheetah':
+                                    _cheetah_searchlist[setting.key] = setting.value
+
+                    if _file.tech == 'cheetah':
+                        _cheetah_searchlist['conf'] = conf
+
+                        _file_basename = os.path.basename(_file.path)
+                        if os.path.isfile('/etc/kolab/templates/roundcubemail/%s.tpl' % (_file_basename)):
+                            template_file = '/etc/kolab/templates/roundcubemail/%s.tpl' % (_file_basename)
+                        elif os.path.isfile('/usr/share/kolab/templates/roundcubemail/%s.tpl' % (_file_basename)):
+                            template_file = '/usr/share/kolab/templates/roundcubemail/%s.tpl' % (_file_basename)
+                        elif os.path.isfile(os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'roundcubemail', '%s.tpl' % (_file_basename)))):
+                            template_file = os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'roundcubemail', '%s.tpl' % (_file_basename)))
+
+                        if not template_file == None:
+                            log.debug(_("Using template file %r") % (template_file), level=8)
+                            fp = open(template_file, 'r')
+                            template_definition = fp.read()
+                            fp.close()
+
+                            t = Template(template_definition, searchList=[_cheetah_searchlist])
+
+                            print t.__dict__
+                            print "Settings:", t._CHEETAH_requiredCheetahMethods
+
+                            log.debug(
+                                    _("Successfully compiled template %r, writing out to %r") % (
+                                            template_file,
+                                            '/etc/roundcubemail/%s' % (_file_basename)
+                                        ),
+                                    level=8
+                                )
+
+                            fp = open('/etc/roundcubemail/%s.new' % (_file_basename), 'w')
+                            fp.write(t.__str__())
+                            fp.close()
+                    else:
+                        myaugeas.save()
diff --git a/pykolab/cli/conf/cmd_file_list_settings.py b/pykolab/cli/conf/cmd_file_list_settings.py
new file mode 100644
index 0000000..9fc9045
--- /dev/null
+++ b/pykolab/cli/conf/cmd_file_list_settings.py
@@ -0,0 +1,80 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('file_list_settings', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--file',
+                dest    = "_file",
+                action  = "store",
+                default = None,
+                metavar = "FILE",
+                help    = _("List settings for file FILE")
+            )
+
+    my_option_group.add_option(
+                '--values',
+                dest    = "values",
+                action  = "store_true",
+                default = False,
+                help    = _("Include values when listing settings.")
+            )
+
+def description():
+    return """List managed settings for file FILE."""
+
+def execute(*args, **kw):
+    if conf._file == None:
+        print >> sys.stderr, _("Unwilling to list all settings for all files. Specify a file path with --file.")
+        sys.exit(1)
+
+    _file = confmgmt.get_file(conf._file)
+
+    if _file == None:
+        print >> sys.stderr, _("No such file %s") %(conf._file)
+        sys.exit(1)
+
+    for setting in _file.settings:
+        if len(setting.roles) > 0:
+            print "- %s (Roles: %s)" % (setting.key, ', '.join([x.name for x in setting.roles]))
+        else:
+            print "- %s" % (setting.key)
+
+        if conf.values:
+            if not setting.value == None:
+                print "%-4s Value: %s" % ('', setting.value)
+            elif not setting.function == None:
+                print "%-4s Function: %s" % ('', setting.function)
diff --git a/pykolab/cli/conf/cmd_file_set_setting.py b/pykolab/cli/conf/cmd_file_set_setting.py
new file mode 100644
index 0000000..9bea1bf
--- /dev/null
+++ b/pykolab/cli/conf/cmd_file_set_setting.py
@@ -0,0 +1,125 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('file_set_setting', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--file',
+                dest    = "_file",
+                action  = "store",
+                default = None,
+                metavar = "FILE",
+                help    = _("Add file FILE")
+            )
+
+    my_option_group.add_option(
+                '--role',
+                dest    = "roles",
+                action  = "append",
+                default = [],
+                metavar = "ROLE",
+                help    = _("Exclusively associate the setting for file FILE with role ROLE. May be specified multiple times to associate the file setting with multiple roles.")
+            )
+
+    my_option_group.add_option(
+                '--key',
+                dest    = "key",
+                action  = "store",
+                default = None,
+                metavar = "KEY",
+                help    = _("The key or 'name' for the setting.")
+            )
+
+    my_option_group.add_option(
+                '--value',
+                dest    = "value",
+                action  = "store",
+                default = None,
+                metavar = "VALUE",
+                help    = _("Set to value VALUE.")
+            )
+
+    my_option_group.add_option(
+                '--function',
+                dest    = "function",
+                action  = "store",
+                default = None,
+                metavar = "FUNCTION",
+                help    = _("Set to function FUNCTION.")
+            )
+
+def description():
+    return """Change the value or function for a managed setting with key KEY in file FILE."""
+
+def execute(*args, **kw):
+    if conf._file == None:
+        print >> sys.stderr, _("Must specify a file.")
+        sys.exit(1)
+
+    if conf.key == None:
+        print >> sys.stderr, _("Must specify a key.")
+        sys.exit(1)
+
+    if conf.value == None and conf.function == None:
+        print >> sys.stderr, _("Must specify a value or a function to get the values with.")
+        sys.exit(1)
+
+    if not conf.value == None and not conf.function == None:
+        print >> sys.stderr, _("Can only specify one of value or function.")
+        sys.exit(1)
+
+    _file = confmgmt.get_file(conf._file)
+
+    if _file == None:
+        print >> sys.stderr, _("File %s does not exist.") % (conf._file)
+        sys.exit(1)
+
+    if not conf.key in [x.key for x in _file.settings]:
+        print >> sys.stderr, _("Setting with key %s is not managed in file %s") % (conf.key, conf._file)
+        sys.exit(1)
+
+    setting = confmgmt.get_setting(conf.key, conf._file)
+
+    if setting == None:
+        print >> sys.stderr, _("Setting with key %s is not managed in file %s") % (conf.key, conf._file)
+        sys.exit(1)
+
+    if not conf.value == None:
+        setting.value = conf.value
+    elif not conf.function == None:
+        setting.function = conf.function
+
+    confmgmt.commit()
diff --git a/pykolab/cli/conf/cmd_init.py b/pykolab/cli/conf/cmd_init.py
new file mode 100644
index 0000000..ec6286d
--- /dev/null
+++ b/pykolab/cli/conf/cmd_init.py
@@ -0,0 +1,44 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import utils
+from pykolab.cli import commands
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('init', execute, group='conf', description=description())
+
+def description():
+    return """Initialize configuration management."""
+
+def execute(*args, **kw):
+    for environment in [ "development", "testing", "production" ]:
+        confmgmt.add_environment(environment)
+
+    for role in [ "imap-server", "imap-server-frontend", "imap-server-mupdate", "imap-server-backend" ]:
+        confmgmt.add_role(role)
\ No newline at end of file
diff --git a/pykolab/cli/conf/cmd_list_environments.py b/pykolab/cli/conf/cmd_list_environments.py
new file mode 100644
index 0000000..92bee0f
--- /dev/null
+++ b/pykolab/cli/conf/cmd_list_environments.py
@@ -0,0 +1,44 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('list_environments', execute, group='conf', description=description())
+
+def description():
+    return """List environments."""
+
+def execute(*args, **kw):
+    environments = confmgmt.list_environments()
+
+    for environment in environments:
+        print "- %s" % (environment.name)
diff --git a/pykolab/cli/conf/cmd_list_files.py b/pykolab/cli/conf/cmd_list_files.py
new file mode 100644
index 0000000..9db5045
--- /dev/null
+++ b/pykolab/cli/conf/cmd_list_files.py
@@ -0,0 +1,65 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('list_files', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--service',
+                dest    = "service",
+                action  = "store",
+                default = None,
+                metavar = "SERVICE",
+                help    = _("List only files associated with service SERVICE.")
+            )
+
+def description():
+    return """List files (for service SERVICE)."""
+
+def execute(*args, **kw):
+    files = confmgmt.list_files()
+
+    if not conf.service == None:
+        service = confmgmt.get_service(conf.service)
+        if service == None:
+            print >> sys.stderr, _("No such service %s") % (conf.service)
+            sys.exit(1)
+
+    for _file in files:
+        if not conf.service == None:
+            if service.name in [x.name for x in _file.services]:
+                print "- %s" % (_file.path)
+        else:
+            print "- %s" % (_file.path)
diff --git a/pykolab/cli/conf/cmd_list_nodes.py b/pykolab/cli/conf/cmd_list_nodes.py
new file mode 100644
index 0000000..853553d
--- /dev/null
+++ b/pykolab/cli/conf/cmd_list_nodes.py
@@ -0,0 +1,64 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('list_nodes', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--environment',
+                dest    = "environment",
+                action  = "store",
+                default = None,
+                metavar = "ENVIRONMENT",
+                help    = _("List only nodes in environment ENVIRONMENT.")
+            )
+
+def description():
+    return """List nodes (in environment ENVIRONMENT)."""
+
+def execute(*args, **kw):
+    nodes = confmgmt.list_nodes()
+
+    if not conf.environment == None:
+        environment = confmgmt.get_environment(conf.environment)
+        if environment == None:
+            print >> sys.stderr, _("No such environment %s") % (conf.environment)
+            sys.exit(1)
+
+    for node in nodes:
+        if not conf.environment == None and node.environment == environment:
+            print "- %s" % (node.fqdn)
+        else:
+            print "- %s" % (node.fqdn)
diff --git a/pykolab/cli/conf/cmd_list_orphans.py b/pykolab/cli/conf/cmd_list_orphans.py
new file mode 100644
index 0000000..e312318
--- /dev/null
+++ b/pykolab/cli/conf/cmd_list_orphans.py
@@ -0,0 +1,100 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('list_orphans', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--purge',
+                dest    = "purge",
+                action  = "store_true",
+                default = False,
+                help    = _("Purge orphaned objects.")
+            )
+
+    my_option_group.add_option(
+                '--files',
+                dest    = "files",
+                action  = "store_true",
+                default = False,
+                help    = _("List orphaned files.")
+            )
+
+    my_option_group.add_option(
+                '--roles',
+                dest    = "roles",
+                action  = "store_true",
+                default = False,
+                help    = _("List orphaned roles.")
+            )
+
+    my_option_group.add_option(
+                '--services',
+                dest    = "services",
+                action  = "store_true",
+                default = False,
+                help    = _("List orphaned services.")
+            )
+
+def description():
+    return """List files, services, settings, roles, etc. that are not associated with anything else."""
+
+def execute(*args, **kw):
+    if conf.files:
+        files = confmgmt.list_files()
+
+        for _file in files:
+            if len(_file.services) < 1:
+                print _("File %s not associated with any services.") % (_file.path)
+
+            if len(_file.settings) < 1:
+                print _("File %s has no settings associated with it.") % (_file.path)
+
+    if conf.roles:
+        roles = confmgmt.list_roles()
+
+        for role in roles:
+            if len(role.nodes) < 1:
+                print _("Role %s not associated with any nodes.") % (role.name)
+
+            if len(role.services) < 1:
+                print _("Role %s has no services associated with it.") % (role.name)
+
+    if conf.services:
+        services = confmgmt.list_services()
+
+        for service in services:
+            if len(service.roles) < 1:
+                print _("Service %s not associated with any roles.") % (service.name)
diff --git a/pykolab/cli/conf/cmd_list_roles.py b/pykolab/cli/conf/cmd_list_roles.py
new file mode 100644
index 0000000..e59ca85
--- /dev/null
+++ b/pykolab/cli/conf/cmd_list_roles.py
@@ -0,0 +1,44 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('list_roles', execute, group='conf', description=description())
+
+def description():
+    return """List roles."""
+
+def execute(*args, **kw):
+    roles = confmgmt.list_roles()
+
+    for role in roles:
+        print "- %s" % (role.name)
diff --git a/pykolab/cli/conf/cmd_node_add_role.py b/pykolab/cli/conf/cmd_node_add_role.py
new file mode 100644
index 0000000..1f73109
--- /dev/null
+++ b/pykolab/cli/conf/cmd_node_add_role.py
@@ -0,0 +1,76 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('node_add_role', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--node',
+                dest    = "node",
+                action  = "store",
+                default = constants.fqdn,
+                metavar = "FQDN",
+                help    = _("Add role ROLE to node FQDN")
+            )
+
+    my_option_group.add_option(
+                '--role',
+                dest    = "role",
+                action  = "store",
+                default = None,
+                metavar = "ROLE",
+                help    = _("Add role ROLE")
+            )
+
+def description():
+    return """Add role to node."""
+
+def execute(*args, **kw):
+    node = confmgmt.get_node(conf.node)
+    role = confmgmt.get_role(conf.role)
+
+    if node == None:
+        print >> sys.stderr, _("No such node %s") % (conf.node)
+        sys.exit(1)
+
+    if role == None:
+        print >> sys.stderr, _("No such role %s") % (conf.role)
+        sys.exit(1)
+
+    if role in node.roles:
+        print >> sys.stderr, _("Node %s already has role %s") % (conf.node, conf.role)
+        sys.exit(1)
+
+    confmgmt.add_node_role(node, role)
diff --git a/pykolab/cli/conf/cmd_node_list_files.py b/pykolab/cli/conf/cmd_node_list_files.py
new file mode 100644
index 0000000..00b4e39
--- /dev/null
+++ b/pykolab/cli/conf/cmd_node_list_files.py
@@ -0,0 +1,64 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('node_list_files', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--node',
+                dest    = "node",
+                action  = "store",
+                default = constants.fqdn,
+                metavar = "FQDN",
+                help    = _("List services (for node FQDN)")
+            )
+
+def description():
+    return """List files with managed settings applicable to node."""
+
+def execute(*args, **kw):
+    node = confmgmt.get_node(conf.node)
+
+    if node == None:
+        print >> sys.stderr, _("No such node %s") %(conf.node)
+        sys.exit(1)
+
+    files = confmgmt.get_node_files(conf.node)
+
+    for _file in files:
+        if _file.tech == "augeas":
+            print "- %s" % (_file.path)
+        else:
+            print "- %s (%s)" % (_file.path, _file.tech)
diff --git a/pykolab/cli/conf/cmd_node_list_roles.py b/pykolab/cli/conf/cmd_node_list_roles.py
new file mode 100644
index 0000000..075e0fd
--- /dev/null
+++ b/pykolab/cli/conf/cmd_node_list_roles.py
@@ -0,0 +1,59 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('node_list_roles', execute, group='conf', description=description(), aliases=['list_node_roles'])
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--node',
+                dest    = "node",
+                action  = "store",
+                default = constants.fqdn,
+                metavar = "FQDN",
+                help    = _("List roles (for node FQDN)")
+            )
+
+def description():
+    return """List roles for node."""
+
+def execute(*args, **kw):
+    node = confmgmt.get_node(conf.node)
+
+    if node == None:
+        print >> sys.stderr, _("No such node %s") %(conf.node)
+        sys.exit(1)
+
+    for role in node.roles:
+        print "- %s" % (role.name)
diff --git a/pykolab/cli/conf/cmd_node_list_services.py b/pykolab/cli/conf/cmd_node_list_services.py
new file mode 100644
index 0000000..e3491c8
--- /dev/null
+++ b/pykolab/cli/conf/cmd_node_list_services.py
@@ -0,0 +1,60 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('node_list_services', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--node',
+                dest    = "node",
+                action  = "store",
+                default = constants.fqdn,
+                metavar = "FQDN",
+                help    = _("List services (for node FQDN)")
+            )
+
+def description():
+    return """List services for node."""
+
+def execute(*args, **kw):
+    node = confmgmt.get_node(conf.node)
+
+    if node == None:
+        print >> sys.stderr, _("No such node %s") %(conf.node)
+        sys.exit(1)
+
+    for role in node.roles:
+        for service in role.services:
+            print "- %s" % (service.name)
diff --git a/pykolab/cli/conf/cmd_node_list_settings.py b/pykolab/cli/conf/cmd_node_list_settings.py
new file mode 100644
index 0000000..ca336d6
--- /dev/null
+++ b/pykolab/cli/conf/cmd_node_list_settings.py
@@ -0,0 +1,82 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('node_list_settings', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--node',
+                dest    = "node",
+                action  = "store",
+                default = constants.fqdn,
+                metavar = "FQDN",
+                help    = _("List settings for node FQDN")
+            )
+
+    my_option_group.add_option(
+                '--file',
+                dest    = "_file",
+                action  = "store",
+                default = None,
+                metavar = "FILE",
+                help    = _("List settings for file FILE")
+            )
+
+def description():
+    return """List managed settings (for file FILE) applicable to node."""
+
+def execute(*args, **kw):
+    node = confmgmt.get_node(conf.node)
+
+    if node == None:
+        print >> sys.stderr, _("No such node %s") %(conf.node)
+        sys.exit(1)
+
+    files = confmgmt.get_node_files(conf.node)
+
+    for _file in files:
+        print "File %s" % (_file.path)
+        if conf._file == None or conf._file == _file.path:
+            for setting in _file.settings:
+                if len(setting.roles) > 0:
+                    is_my_role_too = False
+                    for _role in setting.roles:
+                        if _role in node.roles:
+                            is_my_role_too = True
+
+                    if not is_my_role_too:
+                        continue
+
+                print "%-3s %s" % ('', setting.key)
diff --git a/pykolab/cli/conf/cmd_node_remove_role.py b/pykolab/cli/conf/cmd_node_remove_role.py
new file mode 100644
index 0000000..58aa605
--- /dev/null
+++ b/pykolab/cli/conf/cmd_node_remove_role.py
@@ -0,0 +1,72 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('node_remove_role', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--node',
+                dest    = "node",
+                action  = "store",
+                default = constants.fqdn,
+                metavar = "FQDN",
+                help    = _("Remove role ROLE from node FQDN")
+            )
+
+    my_option_group.add_option(
+                '--role',
+                dest    = "role",
+                action  = "store",
+                default = None,
+                metavar = "ROLE",
+                help    = _("Remove role ROLE")
+            )
+
+def description():
+    return """Remove role from node."""
+
+def execute(*args, **kw):
+    node = confmgmt.get_node(conf.node)
+    role = confmgmt.get_role(conf.role)
+
+    if node == None:
+        print >> sys.stderr, _("No such node %s") %(conf.node)
+        sys.exit(1)
+
+    if role == None:
+        print >> sys.stderr, _("No such role %s") %(conf.role)
+        sys.exit(1)
+
+    confmgmt.remove_node_role(node, role)
diff --git a/pykolab/cli/conf/cmd_remove_environment.py b/pykolab/cli/conf/cmd_remove_environment.py
new file mode 100644
index 0000000..ba54d0d
--- /dev/null
+++ b/pykolab/cli/conf/cmd_remove_environment.py
@@ -0,0 +1,65 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('remove_environment', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--environment',
+                dest    = "environment",
+                action  = "store",
+                default = None,
+                metavar = "ENVIRONMENT",
+                help    = _("Remove environment ENVIRONMENT.")
+            )
+
+def description():
+    return """Remove an environment."""
+
+def execute(*args, **kw):
+    if conf.environment == None:
+        try:
+            conf.environment = conf.cli_args.pop(0)
+        except IndexError, errmsg:
+            print >> sys.stderr, _("Must specify an environment name to delete an environment.")
+            sys.exit(1)
+
+    environment = confmgmt.get_environment(conf.environment)
+
+    if environment == None:
+        print >> sys.stderr, _("No environment with name %s exists.") % (conf.environment)
+        sys.exit(1)
+
+    confmgmt.remove_environment(environment)
\ No newline at end of file
diff --git a/pykolab/cli/conf/cmd_remove_role.py b/pykolab/cli/conf/cmd_remove_role.py
new file mode 100644
index 0000000..2ac9637
--- /dev/null
+++ b/pykolab/cli/conf/cmd_remove_role.py
@@ -0,0 +1,65 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('remove_role', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--role',
+                dest    = "role",
+                action  = "store",
+                default = None,
+                metavar = "ROLE",
+                help    = _("Remove role ROLE.")
+            )
+
+def description():
+    return """Remove a role."""
+
+def execute(*args, **kw):
+    if conf.role == None:
+        try:
+            conf.role = conf.cli_args.pop(0)
+        except IndexError, errmsg:
+            print >> sys.stderr, _("Must specify a role name to delete a role.")
+            sys.exit(1)
+
+    role = confmgmt.get_role(conf.role)
+
+    if role == None:
+        print >> sys.stderr, _("No role with name %s exists.") % (conf.role)
+        sys.exit(1)
+
+    confmgmt.remove_role(role)
diff --git a/pykolab/cli/conf/cmd_role_add_service.py b/pykolab/cli/conf/cmd_role_add_service.py
new file mode 100644
index 0000000..30db5be
--- /dev/null
+++ b/pykolab/cli/conf/cmd_role_add_service.py
@@ -0,0 +1,72 @@
+# -*- 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 sys
+import time
+
+import pykolab
+
+from pykolab import confmgmt
+from pykolab import constants
+from pykolab.cli import commands
+from pykolab.confmgmt.db import get_db
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('role_add_service', execute, group='conf', description=description())
+
+def cli_options():
+    my_option_group = conf.add_cli_parser_option_group(_("CLI Options"))
+    my_option_group.add_option(
+                '--role',
+                dest    = "role",
+                action  = "store",
+                default = None,
+                metavar = "ROLE",
+                help    = _("Use role ROLE")
+            )
+
+    my_option_group.add_option(
+                '--service',
+                dest    = "service",
+                action  = "store",
+                default = None,
+                metavar = "SERVICE",
+                help    = _("Use service SERVICE")
+            )
+
+def description():
+    return """Associate a service with a role."""
+
+def execute(*args, **kw):
+    role = confmgmt.get_role(conf.role)
+    service = confmgmt.get_service(conf.service)
+
+    if role == None:
+        print >> sys.stderr, _("No such role %s") % (conf.role)
+        sys.exit(1)
+
+    if service == None:
+        print >> sys.stderr, _("No such service %s") % (conf.service)
+        sys.exit(1)
+
+    confmgmt.add_role_service(role, service)


commit b5f1f9fc37a6a60e159151ff5e5e81b26bfaf761
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Fri Dec 14 10:57:17 2012 +0000

    Add confmgmt module, model and database

diff --git a/pykolab/confmgmt/__init__.py b/pykolab/confmgmt/__init__.py
new file mode 100644
index 0000000..8958ead
--- /dev/null
+++ b/pykolab/confmgmt/__init__.py
@@ -0,0 +1,333 @@
+# -*- 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 pykolab
+
+log = pykolab.getLogger('pykolab.confmgmt')
+
+from pykolab.confmgmt.db import get_db
+
+from pykolab.confmgmt.model import Environment
+from pykolab.confmgmt.model import File
+from pykolab.confmgmt.model import Node
+from pykolab.confmgmt.model import Package
+from pykolab.confmgmt.model import Role
+from pykolab.confmgmt.model import Service
+from pykolab.confmgmt.model import Setting
+
+def add_environment(name=None):
+    db = get_db()
+
+    if not name == None:
+        environment = db.query(Environment).filter_by(name=name).first()
+        if not environment == None:
+            return environment
+
+    environments = db.query(Environment).all()
+
+    if len(environments) == 0 and name == None:
+        return add_environment('development')
+    else:
+        db.add(Environment(name=name))
+        db.commit()
+
+    environment = db.query(Environment).filter_by(name=name).first()
+
+    return environment
+
+def add_file(file_path, tech=None):
+    log.debug("Adding file %r" % (file_path))
+    db = get_db()
+
+    _file = db.query(File).filter_by(path=file_path).first()
+
+    if _file == None:
+        db.add(File(path=file_path, tech=tech))
+        db.commit()
+
+    _file = db.query(File).filter_by(path=file_path).first()
+
+    return _file
+
+def add_file_setting(_file, setting):
+    db = get_db()
+    _file.settings.append(setting)
+    db.commit()
+
+def add_node(node_fqdn):
+    log.debug("Adding node %r" % (node_fqdn))
+    db = get_db()
+    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 = add_environment()
+
+    db.commit()
+
+    return node
+
+def add_node_role(node, role):
+    log.debug("Adding node %r role %r" % (node.fqdn, role.name))
+    db = get_db()
+    node.roles.append(role)
+    db.commit()
+
+def add_package():
+    pass
+
+def add_package_file():
+    pass
+
+def add_package_service():
+    pass
+
+def add_role(role):
+    log.debug("Adding role %r" % (role))
+    db = get_db()
+
+    _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()
+
+    return _role
+
+def add_role_service(_role,_service):
+    log.debug("Adding role %r service %r" % (_role.name, _service.name))
+    db = get_db()
+
+    _role.services.append(_service)
+
+    db.commit()
+
+def add_role_setting():
+    pass
+
+def add_service(service):
+    db = get_db()
+
+    _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()
+
+    return _service
+
+def add_service_file(service, _file):
+    db = get_db()
+    service.files.append(_file)
+    db.commit()
+
+def add_setting(key, value, function):
+    db = get_db()
+
+    _setting = db.query(Setting).filter_by(key=key, value=value, function=function).first()
+
+    if _setting == None:
+        db.add(Setting(key=key, value=value, function=function))
+        db.commit()
+
+    _setting = db.query(Setting).filter_by(key=key, value=value, function=function).first()
+
+    return _setting
+
+def _add_setting(file_path, key, value=None, function=None, service_name=None, role_name=None, tech=None):
+    if not service_name == None:
+        log.debug(_("Searching for service with name %s") % (service_name), level=8)
+        service = add_service(service_name)
+        if not service == None:
+            log.debug(_("Found service %s") % (service_name), level=8)
+    else:
+        service = None
+
+    if not role_name == None:
+        log.debug(_("Searching for role with name %s") % (role_name), level=8)
+        role = add_role(role_name)
+        if not role == None:
+            log.debug(_("Found role %s") % (role_name), level=8)
+    else:
+        role = None
+
+    log.debug(_("Searching for file with path %s") % (file_path), level=8)
+    _file = add_file(file_path, tech)
+
+    if not service_name == None:
+        log.debug(_("Adding file with path %s to service with name %s") % (file_path, service_name), level=8)
+        add_service_file(service, _file)
+
+    log.debug(_("Adding setting with key %s (%r, %r)") % (key, value, function), level=8)
+    setting = add_setting(key=key, value=value, function=function)
+
+    log.debug(_("Adding setting %s to file with path %s") % (key, file_path), level=8)
+    add_file_setting(_file, setting)
+
+    if not role == None:
+        log.debug(_("Adding role with name %s to setting with key %s") %(role_name, key), level=8)
+        setting.roles.append(role)
+
+def commit():
+    db = get_db()
+    db.commit()
+
+def get_environment(name):
+    """
+        Return the environment record for the environment with name 'name'.
+    """
+    db = get_db()
+    environment = db.query(Environment).filter_by(name=name).first()
+    return environment
+
+def get_file(path):
+    """
+        Return a file record for the file with path 'path'.
+    """
+    db = get_db()
+    _file = db.query(File).filter_by(path=path).first()
+    return _file
+
+def get_node(fqdn):
+    """
+        Return a node record for the node with FQDN 'fqdn'.
+    """
+    db = get_db()
+    node = db.query(Node).filter_by(fqdn=fqdn).first()
+    return node
+
+def get_node_files(fqdn):
+    """
+        Get a list of file records that are relevant for the node with FQDN 'fqdn'.
+    """
+    files = []
+
+    node = get_node(fqdn)
+
+    if node == None:
+        return files
+
+    for role in node.roles:
+        for service in role.services:
+            for _file in service.files:
+                _settings = []
+
+                for setting in _file.settings:
+                    if len(setting.roles) > 0:
+                        if (len([needle.name for needle in setting.roles if needle.name in [haystack.name for haystack in node.roles]]) > 0):
+                            _settings.append(setting.key)
+                    else:
+                        _settings.append(setting.key)
+
+                if len(_settings) > 0:
+                    files.append(_file)
+
+    return files
+
+def get_role(name):
+    """
+        Return a role record for the role with name 'name'.
+    """
+    db = get_db()
+    role = db.query(Role).filter_by(name=name).first()
+    return role
+
+def get_service(name):
+    """
+        Return the service record for the service with name 'name'.
+    """
+    db = get_db()
+    service = db.query(Service).filter_by(name=name).first()
+    return service
+
+def get_setting(key, path, roles=[]):
+    """
+        Return the setting record for setting 'key' in file 'file_path'.
+    """
+    db = get_db()
+    _file = db.query(File).filter_by(path=path).first()
+    setting = db.query(Setting).filter_by(key=key).filter(Setting.files.contains(_file)).first()
+    return setting
+
+def list_environments():
+    """
+        List environments.
+    """
+    db = get_db()
+    environments = db.query(Environment).all()
+    return environments
+
+def list_files():
+    """
+        List files.
+    """
+    db = get_db()
+    files = db.query(File).all()
+    return files
+
+def list_nodes():
+    """
+        List nodes.
+    """
+    db = get_db()
+    nodes = db.query(Node).all()
+    return nodes
+
+def list_roles():
+    """
+        List roles.
+    """
+    db = get_db()
+    roles = db.query(Role).all()
+    return roles
+
+def list_services():
+    """
+        List services.
+    """
+    db = get_db()
+    services = db.query(Service).all()
+    return services
+
+def remove_environment(environment):
+    db = get_db()
+
+    db.delete(environment)
+    db.commit()
+
+def remove_node_role(node, role):
+    log.debug("Removing node %r role %r" % (node.fqdn, role.name))
+    db = get_db()
+    node.roles.pop(node.roles.index(role))
+    db.commit()
+
+def remove_role(role):
+    db = get_db()
+
+    db.delete(role)
+    db.commit()
+
diff --git a/pykolab/confmgmt/db.py b/pykolab/confmgmt/db.py
new file mode 100644
index 0000000..e42bcae
--- /dev/null
+++ b/pykolab/confmgmt/db.py
@@ -0,0 +1,140 @@
+# -*- 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.
+#
+
+from sqlalchemy import create_engine
+
+try:
+    from sqlalchemy.orm import sessionmaker
+except:
+    from sqlalchemy.orm import create_session
+
+from model import Base
+from model import Environment
+from model import File
+from model import Node
+from model import Package
+from model import Role
+from model import Service
+from model import Setting
+#from confmgmt.model import Task
+
+import pykolab
+from pykolab.auth import Auth
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.confmgmt')
+conf = pykolab.getConf()
+
+db = None
+
+def get_db():
+    global db
+
+    if db == None:
+        return init_db()
+
+    return db
+
+def init_db():
+    """
+        Returns a SQLAlchemy Session() instance.
+    """
+    global db
+
+    db_uri = "mysql://root:Welcome2KolabSystems@localhost/kolab"
+
+    if not db_uri == None:
+        echo = conf.debuglevel > 8
+        engine = create_engine(db_uri, echo=echo)
+
+        if conf.debuglevel > 8:
+            log.info(_("Dropping all tables..."))
+            Base.metadata.drop_all(engine)
+
+        log.info(_("Creating the necessary tables..."))
+        Base.metadata.create_all(engine)
+
+        Session = sessionmaker(bind=engine)
+        db = Session()
+
+    if db == None:
+        log.error(_("No database available"))
+
+    return db
+
+def ldap_server_hostname_from_uri():
+    ldap_uri = conf.get('ldap', 'ldap_uri')
+    hostname = None
+    port = None
+
+    from urlparse import urlparse
+
+    result = urlparse(ldap_uri)
+
+    if hasattr(result, 'hostname'):
+        hostname = result.hostname
+    else:
+        scheme = ldap_uri.split(':')[0]
+        (hostname, port) = ldap_uri.split('/')[2].split(':')
+
+    return hostname
+
+def list_domains():
+    domains = []
+    print "Listing domains"
+    auth = Auth()
+    auth.connect()
+    _domains = auth.list_domains()
+    print "Domains:", _domains
+    for domain,domain_aliases in _domains:
+        domains.append(domain)
+        domains.extend(domain_aliases)
+
+    return domains
+
+def list_nodes_by_role(role):
+    print "Executing list_nodes_by_role() for 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):
+    print "Executing list_users_by_role() for role", role
+    auth = Auth()
+    auth.connect()
+    user_dns = auth.search_entry_by_attribute('nsroledn', 'cn=%s,%s' % (role,conf.get('ldap','base_dn')))
+
+    print user_dns
+
+    user_login_names = []
+
+    for user_dn in user_dns:
+        user_login_names.append(auth.get_user_attribute(None, {'dn': user_dn}, result_attr))
+
+    print "User login names:", user_login_names
+
+    return user_login_names
+
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?")


commit 38c2bf2ba44e48d5297c6bdf052c1cdfc50bcb9b
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Fri Dec 14 12:29:59 2012 +0000

    Fix issuing len() on a possible None

diff --git a/pykolab/cli/sieve/cmd_refresh.py b/pykolab/cli/sieve/cmd_refresh.py
index 77eecc5..226fb93 100644
--- a/pykolab/cli/sieve/cmd_refresh.py
+++ b/pykolab/cli/sieve/cmd_refresh.py
@@ -258,7 +258,7 @@ def execute(*args, **kw):
         mgmt_script.require(required_extension)
 
     if vacation_active:
-        if len(vacation_react_domains) > 0:
+        if not vacation_react_domains == None and len(vacation_react_domains) > 0:
             mgmt_script.addfilter(
                     'vacation',
                     [('envelope', ':domain', ":is", "from", vacation_react_domains)],
@@ -275,7 +275,7 @@ def execute(*args, **kw):
                         ]
                 )
 
-        elif len(vacation_noreact_domains) > 0:
+        elif not vacation_noreact_domains == None and len(vacation_noreact_domains) > 0:
             mgmt_script.addfilter(
                     'vacation',
                     [('not', ('envelope', ':domain', ":is", "from", vacation_noreact_domains))],
diff --git a/pykolab/plugins/sievemgmt/__init__.py b/pykolab/plugins/sievemgmt/__init__.py
index f341eae..1e22a47 100644
--- a/pykolab/plugins/sievemgmt/__init__.py
+++ b/pykolab/plugins/sievemgmt/__init__.py
@@ -271,7 +271,7 @@ class KolabSievemgmt(object):
             mgmt_script.require(required_extension)
 
         if vacation_active:
-            if len(vacation_react_domains) > 0:
+            if not vacation_react_domains == None and len(vacation_react_domains) > 0:
                 mgmt_script.addfilter(
                         'vacation',
                         [('envelope', ':domain', ":is", "from", vacation_react_domains)],
@@ -288,7 +288,7 @@ class KolabSievemgmt(object):
                             ]
                     )
 
-            elif len(vacation_noreact_domains) > 0:
+            elif not vacation_noreact_domains == None and len(vacation_noreact_domains) > 0:
                 mgmt_script.addfilter(
                         'vacation',
                         [('not', ('envelope', ':domain', ":is", "from", vacation_noreact_domains))],


commit 1dd9c6570e4ef2b08523e8545d41c97830459624
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Dec 12 13:41:48 2012 +0000

    Make sure result_attr is obtained from our configuration before using the corresponding variable

diff --git a/ucs/kolab_sieve.py b/ucs/kolab_sieve.py
index b6d0349..f2b8df0 100755
--- a/ucs/kolab_sieve.py
+++ b/ucs/kolab_sieve.py
@@ -134,6 +134,7 @@ def handler(*args, **kw):
 
             # See if the mailserver_attribute exists
             mailserver_attribute = conf.get('ldap', 'mailserver_attribute').lower()
+            result_attr = conf.get('cyrus-sasl', 'result_attribute').lower()
 
             if mailserver_attribute == None:
                 log.error("Mail server attribute is not set")


commit 9152aecc7c9a9ce98a0e0854002ef7d086fac26e
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue Dec 11 13:14:19 2012 +0000

    Secure the SMTP server further, by verifying the envelope sender is either not locally hosted, or authenticated

diff --git a/pykolab/setup/setup_mta.py b/pykolab/setup/setup_mta.py
index 5c66f19..c02b024 100644
--- a/pykolab/setup/setup_mta.py
+++ b/pykolab/setup/setup_mta.py
@@ -198,6 +198,9 @@ result_attribute = mail
             "transport_maps": "ldap:/etc/postfix/ldap/transport_maps.cf",
             "virtual_alias_maps": "$alias_maps, ldap:/etc/postfix/ldap/virtual_alias_maps.cf, ldap:/etc/postfix/ldap/mailenabled_distgroups.cf, ldap:/etc/postfix/ldap/mailenabled_dynamic_distgroups.cf",
             "smtpd_tls_auth_only": "yes",
+            "smtpd_sasl_auth_enable": "yes",
+            "smtpd_sender_login_maps": "$relay_recipient_maps",
+            "smtpd_sender_restrictions": "permit_mynetworks, reject_sender_login_mismatch",
             "smtpd_recipient_restrictions": "permit_mynetworks, reject_unauth_pipelining, reject_rbl_client zen.spamhaus.org, reject_non_fqdn_recipient, reject_invalid_helo_hostname, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:private/recipient_policy_incoming, permit",
             "smtpd_sender_restrictions": "permit_mynetworks, check_policy_service unix:private/sender_policy_incoming",
             "submission_recipient_restrictions": "check_policy_service unix:private/submission_policy, permit_sasl_authenticated, reject",


commit 5e895296887b134ca3d809a612ceccdc31795f78
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue Dec 11 13:13:33 2012 +0000

    Lower-case the mailbox name for user mailboxes (#1455)

diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index d0b7d08..1dcae4d 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -543,6 +543,10 @@ class IMAP(object):
         """
             Check if a user mailbox exists.
         """
+        if not mailbox_base_name == mailbox_base_name.lower():
+            log.warning(_("Downcasing mailbox name %r") % (mailbox_base_name))
+            mailbox_base_name = mailbox_base_name.lower()
+
         return self.has_folder('user%s%s' %(self.imap.separator, mailbox_base_name))
 
     def user_mailbox_quota(self, mailbox_quota):


commit 70a3afdb2dd9175fbc7e7f8f155bca25e92fe42c
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Sun Dec 9 10:57:07 2012 +0000

    Set the default username_domain so that users can login with mail and alias localparts

diff --git a/pykolab/setup/setup_roundcube.py b/pykolab/setup/setup_roundcube.py
index 5eed26c..1c1c60c 100644
--- a/pykolab/setup/setup_roundcube.py
+++ b/pykolab/setup/setup_roundcube.py
@@ -81,6 +81,7 @@ def execute(*args, **kw):
             'ldap_service_bind_pw': conf.get('ldap', 'service_bind_pw'),
             'ldap_user_base_dn': conf.get('ldap', 'user_base_dn'),
             'ldap_user_filter': conf.get('ldap', 'user_filter'),
+            'primary_domain': conf.get('kolab','primary_domain'),
             'mysql_uri': 'mysqli://roundcube:%s@localhost/roundcube' % (mysql_roundcube_password),
             'conf': conf
         }
diff --git a/share/templates/roundcubemail/main.inc.php.tpl b/share/templates/roundcubemail/main.inc.php.tpl
index 89068ff..9e4d14f 100644
--- a/share/templates/roundcubemail/main.inc.php.tpl
+++ b/share/templates/roundcubemail/main.inc.php.tpl
@@ -195,7 +195,7 @@
     \$rcmail_config['smtp_server'] = 'tls://localhost';
     \$rcmail_config['session_domain'] = '';
     \$rcmail_config['des_key'] = "$des_key";
-    \$rcmail_config['username_domain'] = '';
+    \$rcmail_config['username_domain'] = '$primary_domain';
 
     \$rcmail_config['mail_domain'] = '';
 





More information about the commits mailing list