diff --git a/README-config.md b/README-config.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee96981673d43917c726938f6e221baad6dfcfb9
--- /dev/null
+++ b/README-config.md
@@ -0,0 +1,149 @@
+Config module
+===========
+
+Description
+-----------
+
+The config module allows the setting of global config parameters within IPA. If no parameters are specified it returns the list of all current parameters.
+
+The config module is as compatible as possible to the Ansible upstream `ipa_config` module, but adds many additional parameters
+
+
+Features
+--------
+* IPA server configuration management
+
+
+Supported FreeIPA Versions
+--------------------------
+
+FreeIPA versions 4.4.0 and up are supported by the ipaconfig module.
+
+
+Requirements
+------------
+
+**Controller**
+* Ansible version: 2.8+
+
+**Node**
+* Supported FreeIPA version (see above)
+
+
+Usage
+=====
+
+Example inventory file
+
+```ini
+[ipaserver]
+ipaserver.test.local
+```
+
+
+Example playbook to read config options:
+
+```yaml
+---
+- name: Playbook to handle global config options
+  hosts: ipaserver
+  become: true
+  tasks:
+    - name: return current values of the global configuration options
+      ipaconfig:
+        ipaadmin_password: password
+      register: result
+    - name: display default login shell
+      debug:
+        msg: '{{result.config.defaultlogin }}'
+
+    - name: ensure defaultloginshell and maxusernamelength are set as required
+      ipaconfig:
+        ipaadmin_password: password
+        defaultlogin: /bin/bash
+        maxusername: 64
+```
+
+```yaml
+---
+- name: Playbook to ensure some config options are set
+  hosts: ipaserver
+  become: true
+  tasks:
+    - name: set defaultlogin and maxusername
+      ipaconfig:
+        ipaadmin_password: password
+        defaultlogin: /bin/bash
+        maxusername: 64
+```
+
+
+Variables
+=========
+
+ipauser
+-------
+
+**General Variables:**
+
+Variable | Description | Required
+-------- | ----------- | --------
+`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
+`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`maxusername` \| `ipamaxusernamelength` |  Set the maximum username length (1 to 255) | no
+`maxhostname` \| `ipamaxhostnamelength` |  Set the maximum hostname length between 64-255 | no
+`homedirectory` \| `ipahomesrootdir` |  Set the default location of home directories | no
+`defaultshell` \| `ipadefaultloginshell` |  Set the default shell for new users | no
+`defaultgroup` \| `ipadefaultprimarygroup` |  Set the default group for new users | no
+`emaildomain`\| `ipadefaultemaildomain`  |  Set the default e-mail domain | false
+`searchtimelimit` \| `ipasearchtimelimit` |  Set maximum amount of time (seconds) for a search -1 to 2147483647 (-1 or 0 is unlimited) | no
+`searchrecordslimit` \| `ipasearchrecordslimit` |  Set maximum number of records to search -1 to 2147483647 (-1 or 0 is unlimited) | no
+`usersearch` \| `ipausersearchfields` |  Set list of fields to search when searching for users | no
+`groupsearch` \| `ipagroupsearchfields` |  Set list of fields to search in when searching for groups | no
+`enable_migration` \| `ipamigrationenabled` |  Enable migration mode (choices: True, False ) | no
+`groupobjectclasses` \| `ipagroupobjectclasses` |  Set default group objectclasses (list) | no
+`userobjectclasses` \| `ipauserobjectclasses` |  Set default user objectclasses (list) | no
+`pwdexpnotify` \| `ipapwdexpadvnotify` |  Set number of days's notice of impending password expiration (0 to 2147483647) | no
+`configstring` \| `ipaconfigstring` |  Set extra hashes to generate in password plug-in (choices:`AllowNThash`, `KDC:Disable Last Success`, `KDC:Disable Lockout`, `KDC:Disable Default Preauth for SPNs`). Use `""` to clear this variable. | no
+`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | no
+`selinuxusermapdefault`\| `ipaselinuxusermapdefault` |  Set default SELinux user when no match is found in SELinux map rule | no
+`pac_type` \| `ipakrbauthzdata` |  set default types of PAC supported for services (choices: `MS-PAC`, `PAD`, `nfs:NONE`). Use `""` to clear this variable. | no
+`user_auth_type` \| `ipauserauthtype` |  set default types of supported user authentication (choices: `password`, `radius`, `otp`, `disabled`). Use `""` to clear this variable. | no
+`domain_resolution_order` \| `ipadomainresolutionorder` | Set list of domains used for short name qualification | no
+`ca_renewal_master_server` \| `ipacarenewalmasterserver`| Renewal master for IPA certificate authority. | no
+
+
+Return Values
+=============
+
+Variable | Description | Returned When
+-------- | ----------- | -------------
+`config` | config dict <br />Fields: | No values to configure are specified
+&nbsp; | `maxusername` | &nbsp;
+&nbsp; | `maxhostname` | &nbsp;
+&nbsp; | `homedirectory` | &nbsp;
+&nbsp; | `defaultshell` | &nbsp;
+&nbsp; | `defaultgroup` | &nbsp;
+&nbsp; | `emaildomain` | &nbsp;
+&nbsp; | `searchtimelimit` | &nbsp;
+&nbsp; | `searchrecordslimit` | &nbsp;
+&nbsp; | `usersearch` | &nbsp;
+&nbsp; | `groupsearch` | &nbsp;
+&nbsp; | `enable_migration` | &nbsp;
+&nbsp; | `groupobjectclasses` | &nbsp;
+&nbsp; | `userobjectclasses` | &nbsp;
+&nbsp; | `pwdexpnotify` | &nbsp;
+&nbsp; | `configstring` | &nbsp;
+&nbsp; | `selinuxusermapdefault` | &nbsp;
+&nbsp; | `selinuxusermaporder` | &nbsp;
+&nbsp; | `pac_type` | &nbsp;
+&nbsp; | `user_auth_type` | &nbsp;
+&nbsp; | `domain_resolution_order` | &nbsp;
+&nbsp; | `ca_renewal_master_server` | &nbsp;
+
+All returned fields take the same form as their namesake input parameters
+
+Authors
+=======
+
+Chris Procter
diff --git a/playbooks/config/retrieve-config.yml b/playbooks/config/retrieve-config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7f05e802e2730a7d6cf83e02dbd1c4f91ed766fc
--- /dev/null
+++ b/playbooks/config/retrieve-config.yml
@@ -0,0 +1,14 @@
+---
+- name: Playbook to handle global DNS configuration
+  hosts: ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Query IPA global configuration
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+    register: serverconfig
+
+  - debug:
+      msg: "{{ serverconfig }}"
diff --git a/playbooks/config/set-ca-renewal-master-server.yml b/playbooks/config/set-ca-renewal-master-server.yml
new file mode 100644
index 0000000000000000000000000000000000000000..128ac8d96a9c2f74fbecf6518e33a69dbe50d206
--- /dev/null
+++ b/playbooks/config/set-ca-renewal-master-server.yml
@@ -0,0 +1,11 @@
+---
+- name: Playbook to handle global DNS configuration
+  hosts: ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: set ca_renewal_master_server
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      ca_renewal_master_server: carenewal.example.com
diff --git a/plugins/modules/ipaconfig.py b/plugins/modules/ipaconfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..41a6d0a877a177bd30ecc312534c51dbe66dbe1f
--- /dev/null
+++ b/plugins/modules/ipaconfig.py
@@ -0,0 +1,479 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Chris Procter <cprocter@redhat.com>
+#
+# Copyright (C) 2020 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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, either version 3 of the License, 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ANSIBLE_METADATA = {
+    "metadata_version": "1.0",
+    "supported_by": "community",
+    "status": ["preview"],
+}
+
+
+DOCUMENTATION = '''
+---
+module: ipa_config
+author: chris procter
+short_description: Modify IPA global config options
+description:
+- Modify IPA global config options
+options:
+    ipaadmin_principal:
+        description: The admin principal
+        default: admin
+    ipaadmin_password:
+        description: The admin password
+        required: false
+    maxusername:
+        description: Set the maximum username length between 1-255
+        required: false
+        aliases: ['ipamaxusernamelength']
+    maxhostname:
+        description: Set the maximum hostname length between 64-255
+        required: false
+        aliases: ['ipamaxhostnamelength']
+    homedirectory:
+        description: Set the default location of home directories
+        required: false
+        aliases: ['ipahomesrootdir']
+    defaultshell:
+        description: Set the default shell for new users
+        required: false
+        aliases: ['ipadefaultloginshell', 'loginshell']
+    defaultgroup:
+        description: Set the default group for new users
+        required: false
+        aliases: ['ipadefaultprimarygroup']
+    emaildomain:
+        description: Set the default e-mail domain
+        required: false
+        aliases: ['ipadefaultemaildomain']
+    searchtimelimit:
+        description:
+        - Set maximum amount of time (seconds) for a search
+        - values -1 to 2147483647 (-1 or 0 is unlimited)
+        required: false
+        aliases: ['ipasearchtimelimit']
+    searchrecordslimit:
+        description:
+        - Set maximum number of records to search
+        - values -1 to 2147483647 (-1 or 0 is unlimited)
+        required: false
+        aliases: ['ipasearchrecordslimit']
+    usersearch:
+        description:
+        - Set comma-separated list of fields to search for user search
+        required: false
+        aliases: ['ipausersearchfields']
+    groupsearch:
+        description:
+        - Set comma-separated list of fields to search for group search
+        required: false
+        aliases: ['ipagroupsearchfields']
+    enable_migration:
+        description: Enable migration mode
+        type: bool
+        required: false
+        aliases: ['ipamigrationenabled']
+    groupobjectclasses:
+        description: Set default group objectclasses (comma-separated list)
+        required: false
+        type: list
+        aliases: ['ipagroupobjectclasses']
+    userobjectclasses:
+        description: Set default user objectclasses (comma-separated list)
+        required: false
+        type: list
+        aliases: ['ipauserobjectclasses']
+    pwdexpnotify:
+        description:
+        - Set number of days's notice of impending password expiration
+        - values 0 to 2147483647
+        required: false
+        aliases: ['ipapwdexpadvnotify']
+    configstring:
+        description: Set extra hashes to generate in password plug-in
+        required: false
+        type: list
+        choices:
+        - "AllowNThash"
+        - "KDC:Disable Last Success"
+        - "KDC:Disable Lockout"
+        - "KDC:Disable Default Preauth for SPNs"
+        - ""
+        aliases: ['ipaconfigstring']
+    selinuxusermaporder:
+        description: Set order in increasing priority of SELinux users
+        required: false
+        type: list
+        aliases: ['ipaselinuxusermaporder']
+    selinuxusermapdefault:
+        description: Set default SELinux user when no match found in map rule
+        required: false
+        aliases: ['ipaselinuxusermapdefault']
+    pac_type:
+        description: set default types of PAC supported for services
+        required: false
+        type: list
+        choices: ["MS-PAC", "PAD", "nfs:NONE", ""]
+        aliases: ["ipakrbauthzdata"]
+    user_auth_type:
+        description: set default types of supported user authentication
+        required: false
+        type: list
+        choices: ["password", "radius", "otp", "disabled", ""]
+        aliases: ["ipauserauthtype"]
+    ca_renewal_master_server:
+        description: Renewal master for IPA certificate authority.
+        required: false
+        type: string
+    domain_resolution_order:
+        description: set list of domains used for short name qualification
+        required: false
+        type: list
+        aliases: ["ipadomainresolutionorder"]
+'''
+
+EXAMPLES = '''
+---
+- name: Playbook to handle global configuration options
+  hosts: ipaserver
+  become: true
+  tasks:
+    - name: return current values of the global configuration options
+      ipaconfig:
+        ipaadmin_password: password
+      register: result
+    - name: display default login shell
+      debug:
+        msg: '{{result.config.defaultshell[0] }}'
+
+    - name: set defaultshell and maxusername
+      ipaconfig:
+        ipaadmin_password: password
+        defaultshell: /bin/bash
+        maxusername: 64
+'''
+
+RETURN = '''
+config:
+  description: Dict of all global config options
+  returned: When no options are set
+  type: dict
+  options:
+    maxusername:
+        description: maximum username length
+        returned: always
+    maxhostname:
+        description: maximum hostname length
+        returned: always
+    homedirectory:
+        description: default location of home directories
+        returned: always
+    defaultshell:
+        description: default shell for new users
+        returned: always
+    defaultgroup:
+        description: default group for new users
+        returned: always
+    emaildomain:
+        description: default e-mail domain
+        returned: always
+    searchtimelimit:
+        description: maximum amount of time (seconds) for a search
+        returned: always
+    searchrecordslimit:
+        description: maximum number of records to search
+        returned: always
+    usersearch:
+        description: comma-separated list of fields to search in user search
+        type: list
+        returned: always
+    groupsearch:
+        description: comma-separated list of fields to search in group search
+        type: list
+        returned: always
+    enable_migration:
+        description: Enable migration mode
+        type: bool
+        returned: always
+    groupobjectclasses:
+        description: default group objectclasses (comma-separated list)
+        type: list
+        returned: always
+    userobjectclasses:
+        description: default user objectclasses (comma-separated list)
+        type: list
+        returned: always
+    pwdexpnotify:
+        description: number of days's notice of impending password expiration
+        returned: always
+    configstring:
+        description: extra hashes to generate in password plug-in
+        type: list
+        returned: always
+    selinuxusermaporder:
+        description: order in increasing priority of SELinux users
+        returned: always
+    selinuxusermapdefault:
+        description: default SELinux user when no match is found in map rule
+        returned: always
+    pac_type:
+        description: default types of PAC supported for services
+        type: list
+        returned: always
+    user_auth_type:
+        description: default types of supported user authentication
+        returned: always
+    ca_renewal_master_server:
+        description: master for IPA certificate authority.
+        returned: always
+    domain_resolution_order:
+        description: list of domains used for short name qualification
+        returned: always
+'''
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
+    temp_kdestroy, valid_creds, api_connect, api_command_no_name, \
+    compare_args_ipa, module_params_get
+import ipalib.errors
+
+
+def config_show(module):
+    _result = api_command_no_name(module, "config_show", {})
+
+    return _result["result"]
+
+
+def gen_args(params):
+    _args = {}
+    for k, v in params.items():
+        if v is not None:
+            _args[k] = v
+
+    return _args
+
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec=dict(
+            # general
+            ipaadmin_principal=dict(type="str", default="admin"),
+            ipaadmin_password=dict(type="str", required=False, no_log=True),
+            maxusername=dict(type="int", required=False,
+                             aliases=['ipamaxusernamelength']),
+            maxhostname=dict(type="int", required=False,
+                             aliases=['ipamaxhostnamelength']),
+            homedirectory=dict(type="str", required=False,
+                               aliases=['ipahomesrootdir']),
+            defaultshell=dict(type="str", required=False,
+                              aliases=['ipadefaultloginshell',
+                                       'loginshell']),
+            defaultgroup=dict(type="str", required=False,
+                              aliases=['ipadefaultprimarygroup']),
+            emaildomain=dict(type="str", required=False,
+                             aliases=['ipadefaultemaildomain']),
+            searchtimelimit=dict(type="int", required=False,
+                                 aliases=['ipasearchtimelimit']),
+            searchrecordslimit=dict(type="int", required=False,
+                                    aliases=['ipasearchrecordslimit']),
+            usersearch=dict(type="list", required=False,
+                            aliases=['ipausersearchfields']),
+            groupsearch=dict(type="list", required=False,
+                             aliases=['ipagroupsearchfields']),
+            enable_migration=dict(type="bool", required=False,
+                                  aliases=['ipamigrationenabled']),
+            groupobjectclasses=dict(type="list", required=False,
+                                    aliases=['ipagroupobjectclasses']),
+            userobjectclasses=dict(type="list", required=False,
+                                   aliases=['ipauserobjectclasses']),
+            pwdexpnotify=dict(type="int", required=False,
+                              aliases=['ipapwdexpadvnotify']),
+            configstring=dict(type="list", required=False,
+                              aliases=['ipaconfigstring'],
+                              choices=["AllowNThash",
+                                       "KDC:Disable Last Success",
+                                       "KDC:Disable Lockout",
+                                       "KDC:Disable Default Preauth for SPNs",
+                                       ""]), # noqa E128
+            selinuxusermaporder=dict(type="list", required=False,
+                                     aliases=['ipaselinuxusermaporder']),
+            selinuxusermapdefault=dict(type="str", required=False,
+                                       aliases=['ipaselinuxusermapdefault']),
+            pac_type=dict(type="list", required=False,
+                          aliases=["ipakrbauthzdata"],
+                          choices=["MS-PAC", "PAD", "nfs:NONE", ""]),
+            user_auth_type=dict(type="list", required=False,
+                                choices=["password", "radius", "otp",
+                                         "disabled", ""],
+                                aliases=["ipauserauthtype"]),
+            ca_renewal_master_server=dict(type="str", required=False),
+            domain_resolution_order=dict(type="list", required=False,
+                                         aliases=["ipadomainresolutionorder"])
+        ),
+        supports_check_mode=True,
+    )
+
+    ansible_module._ansible_debug = True
+
+    # Get parameters
+
+    # general
+    ipaadmin_principal = module_params_get(ansible_module,
+                                           "ipaadmin_principal")
+    ipaadmin_password = module_params_get(ansible_module,
+                                          "ipaadmin_password")
+
+    field_map = {
+        "maxusername": "ipamaxusernamelength",
+        "maxhostname": "ipamaxhostnamelength",
+        "homedirectory": "ipahomesrootdir",
+        "defaultshell": "ipadefaultloginshell",
+        "defaultgroup": "ipadefaultprimarygroup",
+        "emaildomain": "ipadefaultemaildomain",
+        "searchtimelimit": "ipasearchtimelimit",
+        "searchrecordslimit": "ipasearchrecordslimit",
+        "usersearch": "ipausersearchfields",
+        "groupsearch": "ipagroupsearchfields",
+        "enable_migration": "ipamigrationenabled",
+        "groupobjectclasses": "ipagroupobjectclasses",
+        "userobjectclasses": "ipauserobjectclasses",
+        "pwdexpnotify": "ipapwdexpadvnotify",
+        "configstring": "ipaconfigstring",
+        "selinuxusermaporder": "ipaselinuxusermaporder",
+        "selinuxusermapdefault": "ipaselinuxusermapdefault",
+        "pac_type": "ipakrbauthzdata",
+        "user_auth_type": "ipauserauthtype",
+        "ca_renewal_master_server": "ca_renewal_master_server",
+        "domain_resolution_order": "ipadomainresolutionorder"
+    }
+    reverse_field_map = {v: k for k, v in field_map.items()}
+
+    params = {}
+    for x in field_map.keys():
+        val = module_params_get(ansible_module, x)
+
+        if val is not None:
+            params[field_map.get(x, x)] = val
+
+    if params.get("ipamigrationenabled") is not None:
+        params["ipamigrationenabled"] = \
+            str(params["ipamigrationenabled"]).upper()
+
+    if params.get("ipaselinuxusermaporder", None):
+        params["ipaselinuxusermaporder"] = \
+            "$".join(params["ipaselinuxusermaporder"])
+
+    if params.get("ipadomainresolutionorder", None):
+        params["ipadomainresolutionorder"] = \
+             ":".join(params["ipadomainresolutionorder"])
+
+    if params.get("ipausersearchfields", None):
+        params["ipausersearchfields"] = \
+             ",".join(params["ipausersearchfields"])
+
+    if params.get("ipagroupsearchfields", None):
+        params["ipagroupsearchfields"] = \
+             ",".join(params["ipagroupsearchfields"])
+
+    # verify limits on INT values.
+    args_with_limits = [
+        ("ipamaxusernamelength", 1, 255),
+        ("ipamaxhostnamelength", 64, 255),
+        ("ipasearchtimelimit", -1, 2147483647),
+        ("ipasearchrecordslimit", -1, 2147483647),
+        ("ipapwdexpadvnotify", 0, 2147483647),
+    ]
+    for arg, min, max in args_with_limits:
+        if arg in params and (params[arg] > max or params[arg] < min):
+            ansible_module.fail_json(
+                msg="Argument '%s' must be between %d and %d."
+                    % (arg, min, max))
+
+    changed = False
+    exit_args = {}
+    ccache_dir = None
+    ccache_name = None
+    res_show = None
+    try:
+        if not valid_creds(ansible_module, ipaadmin_principal):
+            ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
+                                                 ipaadmin_password)
+        api_connect()
+        if params:
+            res_show = config_show(ansible_module)
+            params = {
+                k: v for k, v in params.items()
+                if k not in res_show or res_show[k] != v
+            }
+            if params \
+               and not compare_args_ipa(ansible_module, params, res_show):
+                changed = True
+                api_command_no_name(ansible_module, "config_mod", params)
+
+        else:
+            rawresult = api_command_no_name(ansible_module, "config_show", {})
+            result = rawresult['result']
+            del result['dn']
+            for key, v in result.items():
+                k = reverse_field_map.get(key, key)
+                if ansible_module.argument_spec.get(k):
+                    if k == 'ipaselinuxusermaporder':
+                        exit_args['ipaselinuxusermaporder'] = \
+                            result.get(key)[0].split('$')
+                    elif k == 'domain_resolution_order':
+                        exit_args['domain_resolution_order'] = \
+                           result.get(key)[0].split('$')
+                    elif k == 'usersearch':
+                        exit_args['usersearch'] = \
+                            result.get(key)[0].split(',')
+                    elif k == 'groupsearch':
+                        exit_args['groupsearch'] = \
+                            result.get(key)[0].split(',')
+                    elif isinstance(v, str) and \
+                            ansible_module.argument_spec[k]['type'] == "list":
+                        exit_args[k] = [v]
+                    elif isinstance(v, list) and \
+                            ansible_module.argument_spec[k]['type'] == "str":
+                        exit_args[k] = ",".join(v)
+                    elif isinstance(v, list) and \
+                            ansible_module.argument_spec[k]['type'] == "int":
+                        exit_args[k] = ",".join(v)
+                    elif isinstance(v, list) and \
+                            ansible_module.argument_spec[k]['type'] == "bool":
+                        exit_args[k] = (v[0] == "TRUE")
+                    else:
+                        exit_args[k] = v
+    except ipalib.errors.EmptyModlist:
+        changed = False
+    except Exception as e:
+        ansible_module.fail_json(msg="%s %s" % (params, str(e)))
+
+    finally:
+        temp_kdestroy(ccache_dir, ccache_name)
+
+    # Done
+    ansible_module.exit_json(changed=changed, config=exit_args)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/config/test_config.yml b/tests/config/test_config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c288e45197c749756dd1c46395edac3ca40299c2
--- /dev/null
+++ b/tests/config/test_config.yml
@@ -0,0 +1,388 @@
+---
+- name: Playbook to handle server configuration
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Retrieve current configuration.
+  - name: return current values of the global configuration options
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+    register: previousconfig
+
+  - debug:
+      msg: "{{previousconfig}}"
+
+  # setup environment.
+  - name: create test group
+    ipagroup:
+      ipaadmin_password: 'SomeADMINpassword'
+      name: somedefaultgroup
+
+  - name: Ensure the default e-mail domain is ipa.test.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      emaildomain: ipa.test
+
+  - name: set default shell to '/bin/sh'
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      defaultshell: /bin/sh
+
+  - name: set default group
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      defaultgroup: ipausers
+
+  - name: set default home directory
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      homedirectory: /home
+
+  - name: clear pac-type
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      pac_type: ""
+
+  - name: set maxusername to 255
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      maxusername: 255
+
+  - name: set maxhostname to 255
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      maxhostname: 255
+
+  - name: set pwdexpnotify to 0
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      pwdexpnotify: 0
+
+  - name: set searchrecordslimit to 10
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      searchrecordslimit: 10
+
+  - name: set searchtimelimit to 1
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      searchtimelimit: 1
+
+  - name: clear configstring
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      configstring: ""
+
+  - name: set configstring to AllowNThash
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      configstring: 'KDC:Disable Lockout'
+
+  - name: set selinuxusermapdefault
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      selinuxusermapdefault: "staff_u:s0-s0:c0.c1023"
+
+  - name: set selinuxusermaporder
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023'
+
+  - name: set usersearch to `uid`
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      usersearch: uid
+
+  - name: set groupsearch to `cn`
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      groupsearch: cn
+
+  # tests
+  - name: Ensure the default e-mail domain is somedomain.test.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      emaildomain: somedomain.test
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure the default e-mail domain is somedomain.test, again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      emaildomain: somedomain.test
+    register: result
+    failed_when: result.changed
+
+  - name: set default shell to '/bin/someshell'
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      defaultshell: /bin/someshell
+    register: result
+    failed_when: not result.changed
+
+  - name: set default shell to '/bin/someshell', again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      defaultshell: /bin/someshell
+    register: result
+    failed_when: result.changed
+
+  - name: set default group
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      defaultgroup: somedefaultgroup
+    register: result
+    failed_when: not result.changed
+
+  - name: set default group
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      defaultgroup: somedefaultgroup
+    register: result
+    failed_when: result.changed
+
+  - name: set default home directory
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      homedirectory: /Users
+    register: result
+    failed_when: not result.changed
+
+  - name: set default home directory
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      homedirectory: /Users
+    register: result
+    failed_when: result.changed
+
+  - name: set pac-type
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      pac_type: "nfs:NONE"
+    register: result
+    failed_when: not result.changed
+
+  - name: set pac-type, again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      pac_type: "nfs:NONE"
+    register: result
+    failed_when: result.changed
+
+  - name: set maxusername to 33
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      maxusername: 33
+    register: result
+    failed_when: not result.changed
+
+  - name: set maxusername to 33, again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      maxusername: 33
+    register: result
+    failed_when: result.changed
+
+  - name: set maxhostname to 77
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      maxhostname: 77
+    register: result
+    failed_when: not result.changed
+
+  - name: set maxhostname to 77, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      maxhostname: 77
+    register: result
+    failed_when: result.changed
+
+  - name: set pwdexpnotify to 17
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      pwdexpnotify: 17
+    register: result
+    failed_when: not result.changed
+
+  - name: set pwdexpnotify to 17, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      pwdexpnotify: 17
+    register: result
+    failed_when: result.changed
+
+  - name: set searchrecordslimit to -1
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      searchrecordslimit: -1
+    register: result
+    failed_when: not result.changed
+
+  - name: set searchrecordslimit to -1, again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      searchrecordslimit: -1
+    register: result
+    failed_when: result.changed
+
+  - name: set searchtimelimit to 12345
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      searchtimelimit: 12345
+    register: result
+    failed_when: not result.changed
+
+  - name: set searchtimelimit to 12345, again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      searchtimelimit: 12345
+    register: result
+    failed_when: result.changed
+
+  - name: change enable_migration
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      enable_migration: '{{ not previousconfig.config.enable_migration }}'
+    register: result
+    failed_when: not result.changed
+
+  - name: change enable_migration, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      enable_migration: '{{ not previousconfig.config.enable_migration }}'
+    register: result
+    failed_when: result.changed
+
+  - name: set configstring to AllowNThash
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      configstring: AllowNThash
+    register: result
+    failed_when: not result.changed
+
+  - name: set configstring to AllowNThash, again.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      configstring: AllowNThash
+    register: result
+    failed_when: result.changed
+
+  - name: set selinuxusermaporder
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
+    register: result
+    failed_when: not result.changed
+
+  - name: set selinuxusermaporder, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
+    register: result
+    failed_when: result.changed
+
+  - name: set selinuxusermapdefault
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      selinuxusermapdefault: 'user_u:s0'
+    register: result
+    failed_when: not result.changed
+
+  - name: set selinuxusermapdefault, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      selinuxusermapdefault: 'user_u:s0'
+    register: result
+    failed_when: result.changed
+
+  - name: set groupsearch to `description`
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      groupsearch: description
+    register: result
+    failed_when: not result.changed
+
+  - name: set groupsearch to `gidNumber`, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      groupsearch: description
+    register: result
+    failed_when: result.changed
+
+  - name: set usersearch to `uidNumber`
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      usersearch: uidNumber
+    register: result
+    failed_when: not result.changed
+
+  - name: set usersearch to `uidNumber`, again
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      usersearch: uidNumber
+    register: result
+    failed_when: result.changed
+
+  - name: reset changed fields
+    ipaconfig:
+      ipaadmin_password: 'SomeADMINpassword'
+      maxusername: '{{previousconfig.config.maxusername | default(omit)}}'
+      maxhostname: '{{previousconfig.config.maxhostname | default(omit)}}'
+      homedirectory: '{{previousconfig.config.homedirectory | default(omit)}}'
+      defaultshell: '{{previousconfig.config.defaultshell | default(omit)}}'
+      defaultgroup: '{{previousconfig.config.defaultgroup | default(omit)}}'
+      emaildomain: '{{previousconfig.config.emaildomain | default(omit)}}'
+      searchtimelimit: '{{previousconfig.config.searchtimelimit | default(omit)}}'
+      searchrecordslimit: '{{previousconfig.config.searchrecordslimit | default(omit)}}'
+      usersearch: '{{previousconfig.config.usersearch | default(omit)}}'
+      groupsearch: '{{previousconfig.config.groupsearch | default(omit)}}'
+      enable_migration: '{{previousconfig.config.enable_migration | default(omit)}}'
+      groupobjectclasses: '{{previousconfig.config.groupobjectclasses | default(omit)}}'
+      userobjectclasses: '{{previousconfig.config.userobjectclasses | default(omit)}}'
+      pwdexpnotify: '{{previousconfig.config.pwdexpnotify | default(omit)}}'
+      configstring: '{{previousconfig.config.configstring | default(omit)}}'
+      selinuxusermapdefault: '{{previousconfig.config.selinuxusermapdefault | default(omit)}}'
+      selinuxusermaporder: '{{previousconfig.config.selinuxusermaporder | default(omit)}}'
+      pac_type: '{{previousconfig.config.pac_type | default(omit)}}'
+      user_auth_type: '{{previousconfig.config.user_auth_type | default(omit)}}'
+      domain_resolution_order: '{{previousconfig.config.domain_resolution_order | default(omit)}}'
+      ca_renewal_master_server: '{{previousconfig.config.ca_renewal_master_server | default(omit)}}'
+    register: result
+    failed_when: not result.changed
+
+  - name: reset changed fields, again
+    ipaconfig:
+      ipaadmin_password: 'SomeADMINpassword'
+      maxusername: '{{previousconfig.config.maxusername | default(omit)}}'
+      maxhostname: '{{previousconfig.config.maxhostname | default(omit)}}'
+      homedirectory: '{{previousconfig.config.homedirectory | default(omit)}}'
+      defaultshell: '{{previousconfig.config.defaultshell | default(omit)}}'
+      defaultgroup: '{{previousconfig.config.defaultgroup | default(omit)}}'
+      emaildomain: '{{previousconfig.config.emaildomain | default(omit)}}'
+      searchtimelimit: '{{previousconfig.config.searchtimelimit | default(omit)}}'
+      searchrecordslimit: '{{previousconfig.config.searchrecordslimit | default(omit)}}'
+      usersearch: '{{previousconfig.config.usersearch | default(omit)}}'
+      groupsearch: '{{previousconfig.config.groupsearch | default(omit)}}'
+      enable_migration: '{{previousconfig.config.enable_migration | default(omit)}}'
+      groupobjectclasses: '{{previousconfig.config.groupobjectclasses | default(omit)}}'
+      userobjectclasses: '{{previousconfig.config.userobjectclasses | default(omit)}}'
+      pwdexpnotify: '{{previousconfig.config.pwdexpnotify | default(omit)}}'
+      configstring: '{{previousconfig.config.configstring | default(omit)}}'
+      selinuxusermapdefault: '{{previousconfig.config.selinuxusermapdefault | default(omit)}}'
+      selinuxusermaporder: '{{previousconfig.config.selinuxusermaporder | default(omit)}}'
+      pac_type: '{{previousconfig.config.pac_type | default(omit)}}'
+      user_auth_type: '{{previousconfig.config.user_auth_type | default(omit)}}'
+      domain_resolution_order: '{{previousconfig.config.domain_resolution_order | default(omit)}}'
+      ca_renewal_master_server: '{{previousconfig.config.ca_renewal_master_server | default(omit)}}'
+    register: result
+    failed_when: result.changed
+
+  # cleanup
+
+  - name: cleanup test group
+    ipagroup:
+      ipaadmin_password: 'SomeADMINpassword'
+      name: somedefaultgroup
+      state: absent