These patches implement the usable part of Server Roles design. There might be some discrepancies between the design and actual implementation, I was head first in hacking and was not very disciplined in keeping the design up to date.

I will fix this ASAP.

http://www.freeipa.org/page/V4/Server_Roles
https://fedorahosted.org/freeipa/ticket/5181
https://fedorahosted.org/freeipa/ticket/5689

--
Martin^3 Babinsky
From 7dd703ba60ec3697a04d48b5bafb50de75399115 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 30 May 2016 18:18:38 +0200
Subject: [PATCH 1/4] Server Roles: public API for server roles

This patch implements the `serverroles` API plugin which introduces the
following commands:

    * server-role-show SERVER ROLE: show status of a single role on a server
    * server-role-find [--server SERVER [--role SUBSTRING]]:
      find role(s) matching the given SUBSTRING and return their status on IPA
      masters. If --server option is given, the query is limited to this
      server

https://fedorahosted.org/freeipa/ticket/5181
http://www.freeipa.org/page/V4/Server_Roles
---
 API.txt                      |  20 ++++++
 VERSION                      |   4 +-
 ipalib/plugins/serverrole.py | 142 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 164 insertions(+), 2 deletions(-)
 create mode 100644 ipalib/plugins/serverrole.py

diff --git a/API.txt b/API.txt
index dbc6f1adc614607fab106ab0de7163961e7ecedc..15cf6d7d72e5ad945b238f0cca45862805a634fe 100644
--- a/API.txt
+++ b/API.txt
@@ -3889,6 +3889,26 @@ output: Output('count', type=[<type 'int'>])
 output: ListOfEntries('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: Output('truncated', type=[<type 'bool'>])
+command: server_role_find
+args: 1,7,4
+arg: Str('criteria?')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('server?', autofill=False, cli_name='server')
+option: Str('servrole?', autofill=False, cli_name='role')
+option: Int('sizelimit?', autofill=False)
+option: Int('timelimit?', autofill=False)
+option: Str('version?')
+output: Output('count', type=[<type 'int'>])
+output: ListOfEntries('result')
+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
+output: Output('truncated', type=[<type 'bool'>])
+command: server_role_show
+args: 2,1,1
+arg: Str('server', cli_name='server')
+arg: Str('servrole', cli_name='role')
+option: Str('version?')
+output: Output('result')
 command: server_show
 args: 1,5,3
 arg: Str('cn', cli_name='name')
diff --git a/VERSION b/VERSION
index eb7957eb1c5ae2487975a2fae4485a43f613cb64..b9bf6316db05cf318f15099d12d7e1a3a751e523 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=169
-# Last change: vault: copy arguments of client commands from server counterparts
+IPA_API_VERSION_MINOR=170
+# Last change: Server Roles: public API for server roles
diff --git a/ipalib/plugins/serverrole.py b/ipalib/plugins/serverrole.py
new file mode 100644
index 0000000000000000000000000000000000000000..514a0e255fe7748ee5f6a45a9ab7ed62295b677b
--- /dev/null
+++ b/ipalib/plugins/serverrole.py
@@ -0,0 +1,142 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+from ipalib.frontend import Object
+from ipalib.parameters import Str, StrEnum
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import (
+    LDAPObject,
+    LDAPQuery,
+    LDAPSearch,
+)
+from ipalib import _, ngettext
+
+
+__doc__ = _("""
+IPA server roles
+""") + _("""
+Get status of roles (DNS server, CA, etc. )provided by IPA masters.
+""") + _("""
+EXAMPLES:
+""") + _("""
+  Show status of 'DNS server' role on a server:
+    ipa server-role-show ipa.example.com "DNS server"
+""") + _("""
+  Show status of all roles containing 'AD' on a server:
+    ipa server-role-find --server ipa.example.com --role='AD'
+""") + _("""
+  Show status of all configured roles on a server:
+    ipa server-role-find ipa.example.com
+""")
+
+
+register = Registry()
+
+
+@register()
+class server_role(LDAPObject):
+    """
+    association between certain role (e.g. DNS server) and its status with
+    an IPA master
+    """
+    backend_name = 'serverroles'
+    object_name = _('server role')
+    object_name_plural = _('server roles')
+    default_attributes = [
+        'role', 'status'
+    ]
+    label = _('IPA Server Roles')
+    label_singular = _('IPA Server Role')
+
+    takes_params = (
+        Str(
+            'server',
+            cli_name='server',
+            label=_('Server name'),
+            doc=_('IPA server hostname'),
+        ),
+        Str(
+            'servrole',
+            cli_name='role',
+            label=_("Role name"),
+            doc=_("IPA server role name"),
+            flags=(u'virtual_attribute',)
+        ),
+        StrEnum(
+            'status?',
+            cli_name='status',
+            label=_('Role status'),
+            doc=_('Status of the role'),
+            values=(u'enabled', u'configured', u'absent'),
+            default=u'enabled',
+            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}
+        )
+    )
+
+
+@register()
+class server_role_show(LDAPQuery):
+    __doc__ = _('Show role status on a server')
+
+    obj_name = 'server_role'
+    attr_name = 'show'
+
+    def get_args(self):
+        for arg in super(server_role_show, self).get_args():
+            yield arg
+
+        for param in self.obj.params():
+            if param.name != u'status':
+                yield param.clone()
+
+    def execute(self, *keys, **options):
+        server = keys[0]
+        servrole = keys[1]
+
+        role_status = self.obj.backend.server_role_retrieve(
+            server, servrole)
+
+        return dict(result=role_status[0])
+
+
+@register()
+class server_role_find(LDAPSearch):
+    __doc__ = _('Find a server role on a server(s)')
+
+    obj_name = 'server_role'
+    attr_name = 'find'
+
+    msg_summary = ngettext('%(count)s server role matched',
+                           '%(count)s server roles matched', 0)
+
+    def execute(self, *keys, **options):
+        role_status = self.obj.backend.server_role_search(
+            server=options.get('server', None),
+            servrole=options.get('servrole', None))
+
+        result = [r for r in role_status if r[u'servrole'] != "master"]
+        return dict(
+            result=result,
+            count=len(result),
+            truncated=False,
+        )
+
+
+@register()
+class servrole(Object):
+    """
+    Server role object
+    """
+    object_name = _('role name')
+    object_name_plural = _('role names')
+    takes_params = (
+        Str(
+            'name',
+            cli_name='name',
+            primary_key=True,
+            label=_("Role name"),
+            doc=_("IPA role name"),
+            flags=(u'virtual_attribute',)
+        )
+    )
-- 
2.5.5

From eade8c3215660f10853a2d478eb8bfae31321dc7 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 30 May 2016 18:27:38 +0200
Subject: [PATCH 2/4] Server Roles: make server-{show,find} utilize role
 information

server-show command will now display list of roles enabled on the master
(unless `--raw` is given).

server-find gained `--(no)-servroles` options which facilitate search by
enabled role(s) on the server or their absence.

http://www.freeipa.org/page/V4/Server_Roles
https://fedorahosted.org/freeipa/ticket/5181
---
 API.txt                  |  4 ++-
 VERSION                  |  4 +--
 ipalib/plugins/server.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 94 insertions(+), 5 deletions(-)

diff --git a/API.txt b/API.txt
index 15cf6d7d72e5ad945b238f0cca45862805a634fe..cc31032789941811050b5388fb0f35487cfea63c 100644
--- a/API.txt
+++ b/API.txt
@@ -3871,16 +3871,18 @@ output: Output('result', type=[<type 'dict'>])
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: ListOfPrimaryKeys('value')
 command: server_find
-args: 1,12,4
+args: 1,14,4
 arg: Str('criteria?')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Str('cn?', autofill=False, cli_name='name')
 option: Int('ipamaxdomainlevel?', autofill=False, cli_name='maxlevel')
 option: Int('ipamindomainlevel?', autofill=False, cli_name='minlevel')
 option: Flag('no_members', autofill=True, default=False)
+option: Str('no_servrole*', cli_name='no_servroles')
 option: Str('no_topologysuffix*', cli_name='no_topologysuffixes')
 option: Flag('pkey_only?', autofill=True, default=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('servrole*', cli_name='servroles')
 option: Int('sizelimit?', autofill=False)
 option: Int('timelimit?', autofill=False)
 option: Str('topologysuffix*', cli_name='topologysuffixes')
diff --git a/VERSION b/VERSION
index b9bf6316db05cf318f15099d12d7e1a3a751e523..449751c5d00d134012d76ef375ab04862d258256 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=170
-# Last change: Server Roles: public API for server roles
+IPA_API_VERSION_MINOR=171
+# Last change: Server Roles: make server-{show,find} utilize role information
diff --git a/ipalib/plugins/server.py b/ipalib/plugins/server.py
index 6faaf8ec5a98501ccdcb3dbc5983ce4a27baacba..306c75b87b19e32a92b789653fd3fd09fc7077de 100644
--- a/ipalib/plugins/server.py
+++ b/ipalib/plugins/server.py
@@ -52,9 +52,11 @@ class server(LDAPObject):
     label_singular = _('IPA Server')
     attribute_members = {
         'iparepltopomanagedsuffix': ['topologysuffix'],
+        'role': ['servrole']
     }
     relationships = {
         'iparepltopomanagedsuffix': ('Managed', '', 'no_'),
+        'role': ('Enabled', '', 'no_'),
     }
     takes_params = (
         Str(
@@ -87,6 +89,12 @@ class server(LDAPObject):
             doc=_('Maximum domain level'),
             flags={'no_create', 'no_update'},
         ),
+        Str(
+            'servrole*',
+            label=_('Enabled server roles'),
+            doc=_('List of enabled roles'),
+            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}
+        ),
     )
 
     def _get_suffixes(self):
@@ -105,6 +113,21 @@ class server(LDAPObject):
                 suffixes.get(m, m) for m in entry['iparepltopomanagedsuffix']
             ]
 
+    def _get_enabled_roles(self, entry_attrs):
+        role_status = self.api.Command.server_role_find(
+            server=entry_attrs['cn'][0])['result']
+
+        enabled_roles = [
+            r['servrole'] for r in role_status if
+            r['status'] == u'enabled']
+
+        try:
+            enabled_roles.remove('master')
+        except ValueError:
+            pass
+
+        entry_attrs['servrole'] = sorted(enabled_roles)
+
 
 @register()
 class server_find(LDAPSearch):
@@ -114,7 +137,7 @@ class server_find(LDAPSearch):
         '%(count)d IPA server matched',
         '%(count)d IPA servers matched', 0
     )
-    member_attributes = ['iparepltopomanagedsuffix']
+    member_attributes = ['iparepltopomanagedsuffix', 'role']
 
     def get_options(self):
         for option in super(server_find, self).get_options():
@@ -128,7 +151,55 @@ class server_find(LDAPSearch):
         options.pop('topologysuffix', None)
         options.pop('no_topologysuffix', None)
 
-        return super(server_find, self).get_member_filter(ldap, **options)
+        options.pop('servrole', None)
+        options.pop('no_servrole', None)
+
+        return super(server_find, self).get_member_filter(
+            ldap, **options)
+
+    def _get_servrole_masters_filter(self, ldap, servroles,
+                                     negative_membership=False):
+        """
+        return a filter matching any master which has all the specified roles
+        enabled.
+
+        if negative_membership is True, return filter that matches none of the
+        masters with any of the specified role enabled
+        """
+        def _get_masters_with_enabled_servrole(role):
+            backend = self.api.Backend.serverroles
+            role_status = backend.server_role_search(
+                server=None, servrole=role)
+
+            return set(
+                r[u'server'] for r in role_status if
+                r[u'status'] == u'enabled'
+            )
+
+        enabled_masters = _get_masters_with_enabled_servrole(
+            servroles[0])
+
+        for role in servroles[1:]:
+            if negative_membership:
+                enabled_masters.update(
+                    _get_masters_with_enabled_servrole(role)
+                )
+            else:
+                enabled_masters.intersection_update(
+                    _get_masters_with_enabled_servrole(role)
+                )
+
+        if not enabled_masters and not negative_membership:
+            return '(!(objectclass=*))'
+
+        matching_rules = (ldap.MATCH_NONE if negative_membership else
+                          ldap.MATCH_ANY)
+
+        return ldap.make_filter_from_attr(
+            'cn',
+            list(enabled_masters),
+            rules=matching_rules
+        )
 
     def pre_callback(self, ldap, filters, attrs_list, base_dn, scope,
                      *args, **options):
@@ -165,6 +236,20 @@ class server_find(LDAPSearch):
                     (filters, filter), ldap.MATCH_ALL
                 )
 
+        if options.get('servrole', []):
+            servrole_filter = self._get_servrole_masters_filter(
+                ldap, options['servrole'])
+            filters = ldap.combine_filters(
+                (filters, servrole_filter), ldap.MATCH_ALL)
+
+        if options.get('no_servrole', []):
+            no_servrole_filter = self._get_servrole_masters_filter(
+                ldap, options['no_servrole'], negative_membership=True)
+            filters = ldap.combine_filters(
+                (filters, no_servrole_filter), ldap.MATCH_ALL)
+
+            self.log.critical('Filters: {}'.format(filters))
+
         return (filters, base_dn, scope)
 
     def post_callback(self, ldap, entries, truncated, *args, **options):
@@ -172,6 +257,7 @@ class server_find(LDAPSearch):
             suffixes = self.obj._get_suffixes()
             for entry in entries:
                 self.obj._apply_suffixes(entry, suffixes)
+                self.obj._get_enabled_roles(entry)
 
         return truncated
 
@@ -184,6 +270,7 @@ class server_show(LDAPRetrieve):
         if not options.get('raw', False):
             suffixes = self.obj._get_suffixes()
             self.obj._apply_suffixes(entry, suffixes)
+            self.obj._get_enabled_roles(entry)
 
         return dn
 
-- 
2.5.5

From 6c79c8b964e829f6625a6c16aafd90331c112e2f Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 30 May 2016 18:42:01 +0200
Subject: [PATCH 3/4] Server Roles: make *config-show consume relevant
 roles/attributes

This patch modifies config objects so that the roles/attributes relevant to
the configuration are shown in the output:

* config-show will show list of all IPA masters, CA servers and CA renewal
  master

* dnsconfig-show will list all DNS server and DNS key master

* trustconfig-show will list all AD trust controllers and agents

* vaultconfig-show will list all Key Recovery Agents
---
 ipalib/plugins/config.py | 33 +++++++++++++++++++++++++++++++++
 ipalib/plugins/dns.py    | 20 ++++++++++++++++++++
 ipalib/plugins/trust.py  | 24 ++++++++++++++++++++++++
 ipalib/plugins/vault.py  | 15 ++++++++++++---
 4 files changed, 89 insertions(+), 3 deletions(-)

diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py
index 46a40ddf7810bac723563cee8d174d8499d21465..d323f037f6057e38cba7b161a59752214f41d178 100644
--- a/ipalib/plugins/config.py
+++ b/ipalib/plugins/config.py
@@ -227,11 +227,41 @@ class config(LDAPObject):
             doc=_('Default types of supported user authentication'),
             values=(u'password', u'radius', u'otp', u'disabled'),
         ),
+        Str(
+            'ipamaster*',
+            label=_('IPA masters'),
+            doc=_('List of all IPA masters'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        ),
+        Str(
+            'ipacaserver*',
+            label=_('IPA CA servers'),
+            doc=_('IPA servers configured as certificate authority'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        ),
+        Str(
+            'ipacarenewalmaster?',
+            cli_name='ca_renewal_master',
+            label=_('IPA CA renewal master'),
+            doc=_('Renewal master for IPA certificate authority'),
+            flags={'no_create', 'no_update'}
+        )
     )
 
     def get_dn(self, *keys, **kwargs):
         return DN(('cn', 'ipaconfig'), ('cn', 'etc'), api.env.basedn)
 
+    def show_servroles_attributes(self, entry_attrs, **options):
+        if options.get('raw', False):
+            return
+
+        backend = self.api.Backend.serverroles
+
+        ca_config = backend.config_retrieve("CA server")
+        master_config = backend.config_retrieve("master")
+
+        entry_attrs.update(ca_config)
+        entry_attrs.update(master_config)
 
 
 @register()
@@ -356,3 +386,6 @@ class config_mod(LDAPUpdate):
 class config_show(LDAPRetrieve):
     __doc__ = _('Show the current configuration.')
 
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj.show_servroles_attributes(entry_attrs, **options)
+        return dn
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index fdca0936f985c6a2c30d2e7a2cac333436543ea4..01e4092d90953f5ff7e979bda0426ee451d935aa 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -4349,6 +4349,18 @@ class dnsconfig(LDAPObject):
             cli_name='zone_refresh',
             label=_('Zone refresh interval'),
         ),
+        Str(
+            'ipadnsserver*',
+            label=_('IPA DNS servers'),
+            doc=_('List of IPA masters configured as DNS servers'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        ),
+        Str(
+            'ipadnsseckeymaster?',
+            label=_('IPA DNSSec key master'),
+            doc=_('IPA server configured as DNSSec key master'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        )
     )
     managed_permissions = {
         'System: Write DNS Configuration': {
@@ -4457,6 +4469,14 @@ class dnsconfig_show(LDAPRetrieve):
         self.obj.postprocess_result(result)
         return result
 
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        backend = self.api.Backend.serverroles
+        entry_attrs.update(
+            backend.config_retrieve("DNS server")
+        )
+
+        return dn
+
 
 @register()
 class dnsforwardzone(DNSZoneBase):
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index c3f616eb5f83f92c3a2c063f2e018afd2f3db6e9..e4babcbf2c5cd42240102e7ee8a3d1e16a4f80fc 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -1165,6 +1165,18 @@ class trustconfig(LDAPObject):
             cli_name='fallback_primary_group',
             label=_('Fallback primary group'),
         ),
+        Str(
+            'ipaadtrustagent*',
+            label=_('IPA AD trust agents'),
+            doc=_('IPA servers configured as AD trust agents'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        ),
+        Str(
+            'ipaadtrustcontroller*',
+            label=_('IPA AD trust controllers'),
+            doc=_('IPA servers configured as AD trust controllers'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        ),
     )
 
     def get_dn(self, *keys, **kwargs):
@@ -1271,6 +1283,18 @@ class trustconfig_show(LDAPRetrieve):
 
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         self.obj._convert_groupdn(entry_attrs, options)
+
+        backend = self.api.Backend.serverroles
+        adtrust_agents = backend.config_retrieve(
+            "AD trust agent"
+        )
+        adtrust_controllers = backend.config_retrieve(
+            "AD trust controller"
+        )
+
+        entry_attrs.update(adtrust_agents)
+        entry_attrs.update(adtrust_controllers)
+
         return dn
 
 
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index 33f7f30daad2e2c24c6058768d827f18268aa469..bd42670f1d08d5cd1301272bbb136b98e22d099e 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -1424,6 +1424,12 @@ class vaultconfig(Object):
             'transport_cert',
             label=_('Transport Certificate'),
         ),
+        Str(
+            'ipakraserver*',
+            label=_('IPA KRA servers'),
+            doc=_('IPA servers configured as key recovery agents'),
+            flags={'virtual_attribute', 'no_create', 'no_update'}
+        )
     )
 
 
@@ -1462,10 +1468,13 @@ class vaultconfig_show(Retrieve):
 
         kra_client = self.api.Backend.kra.get_client()
         transport_cert = kra_client.system_certs.get_transport_cert()
+        config = {'transport_cert': transport_cert.binary}
+        config.update(
+            self.api.Backend.serverroles.config_retrieve("KRA server")
+        )
+
         return {
-            'result': {
-                'transport_cert': transport_cert.binary
-            },
+            'result': config,
             'value': None,
         }
 
-- 
2.5.5

From ae3aee7cc7c53077baf655c06bf243c4486e3f85 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 30 May 2016 18:51:48 +0200
Subject: [PATCH 4/4] Server Roles: provide an API for setting CA renewal
 master

`ipa config-mod` gained '--ca-renewal-master' options which can be used to
set CA renewal master to a different server. Obviously, this server has to
have CA role enabled. A warning will be displayed if the renewal master is
being changed.

https://fedorahosted.org/freeipa/ticket/5689
http://www.freeipa.org/page/V4/Server_Roles
---
 API.txt                  |  3 ++-
 VERSION                  |  4 ++--
 ipalib/messages.py       | 10 ++++++++++
 ipalib/plugins/config.py | 40 ++++++++++++++++++++++++++++++++++++++--
 4 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/API.txt b/API.txt
index cc31032789941811050b5388fb0f35487cfea63c..bb33201a7f806380b786d94c044fa8bb530f51f1 100644
--- a/API.txt
+++ b/API.txt
@@ -762,10 +762,11 @@ args: 0,1,1
 option: Str('version?')
 output: Output('result')
 command: config_mod
-args: 0,25,3
+args: 0,26,3
 option: Str('addattr*', cli_name='addattr')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Str('delattr*', cli_name='delattr')
+option: Str('ipacarenewalmaster?', autofill=False, cli_name='ca_renewal_master')
 option: StrEnum('ipaconfigstring*', autofill=False, cli_name='ipaconfigstring', values=[u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout', u'KDC:Disable Default Preauth for SPNs'])
 option: Str('ipadefaultemaildomain?', autofill=False, cli_name='emaildomain')
 option: Str('ipadefaultloginshell?', autofill=False, cli_name='defaultshell')
diff --git a/VERSION b/VERSION
index 449751c5d00d134012d76ef375ab04862d258256..8ef4aebdb70957a9d379f0a613973a32ed3c33ad 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=171
-# Last change: Server Roles: make server-{show,find} utilize role information
+IPA_API_VERSION_MINOR=172
+# Last change: Server Roles: provide an API for setting CA renewal master
diff --git a/ipalib/messages.py b/ipalib/messages.py
index c760e9d3746a0e46a5b67f801ecddd76f1ce6406..d760f0324f5cf500c94229338c672c992c97385c 100644
--- a/ipalib/messages.py
+++ b/ipalib/messages.py
@@ -378,6 +378,16 @@ class FailedToRemoveHostDNSRecords(PublicMessage):
                "(%(reason)s)")
 
 
+class SingularServerAttributeReset(PublicMessage):
+    """
+    **13022** Singular server attribute is set to a different master
+    """
+
+    errno = 13022
+    type = "warning"
+    format = _("%(attribute)s set to %(curr_server)s from %(prev_server)s")
+
+
 def iter_messages(variables, base):
     """Return a tuple with all subclasses
     """
diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py
index d323f037f6057e38cba7b161a59752214f41d178..65387bed5892d90d19314a81d0b4d918cdc21cfc 100644
--- a/ipalib/plugins/config.py
+++ b/ipalib/plugins/config.py
@@ -20,7 +20,7 @@
 
 from ipalib import api
 from ipalib import Bool, Int, Str, IA5Str, StrEnum, DNParam
-from ipalib import errors
+from ipalib import errors, messages
 from ipalib.plugable import Registry
 from .baseldap import (
     LDAPObject,
@@ -244,7 +244,7 @@ class config(LDAPObject):
             cli_name='ca_renewal_master',
             label=_('IPA CA renewal master'),
             doc=_('Renewal master for IPA certificate authority'),
-            flags={'no_create', 'no_update'}
+            flags={'no_create'}
         )
     )
 
@@ -380,6 +380,42 @@ class config_mod(LDAPUpdate):
 
         return dn
 
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj.show_servroles_attributes(entry_attrs, **options)
+        return dn
+
+    def execute(self, *keys, **options):
+        if 'ipacarenewalmaster' in options:
+            new_master = options['ipacarenewalmaster']
+
+            backend = self.api.Backend.serverroles
+
+            old_master = backend.config_retrieve(
+                "CA server")['ipacarenewalmaster'][0]
+
+            backend.config_update(
+                "CA server",
+                ipacarenewalmaster=[new_master])
+
+            self.add_message(
+                messages.SingularServerAttributeReset(
+                    attribute=self.obj.carenewalmaster_attribute,
+                    curr_server=new_master,
+                    prev_server=old_master
+                )
+            )
+
+        return super(config_mod, self).execute(self, keys, options)
+
+    def exc_callback(self, keys, options, exc, call_func,
+                     *call_args, **call_kwargs):
+        if (isinstance(exc, errors.EmptyModlist) and
+                'ipacarenewalmaster' in keys[-1]):
+            self.log.critical('trve')
+            return
+
+        super(config_mod, self).exc_callback(
+            keys, options, exc, call_func, *call_args, **call_kwargs)
 
 
 @register()
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to