diff --git a/.ansible-freeipa.metadata b/.ansible-freeipa.metadata
index 6c6a308..d534268 100644
--- a/.ansible-freeipa.metadata
+++ b/.ansible-freeipa.metadata
@@ -1 +1 @@
-4dcce87f3b09e7c53760980e6687de575a44ee4e SOURCES/ansible-freeipa-0.1.6.tar.gz
+583ac570c030eb68a2026a506054f2f93587beb4 SOURCES/ansible-freeipa-0.1.8.tar.gz
diff --git a/.gitignore b/.gitignore
index c15f2fe..b19cd6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-SOURCES/ansible-freeipa-0.1.6.tar.gz
+SOURCES/ansible-freeipa-0.1.8.tar.gz
diff --git a/SOURCES/ansible-freeipa-0.1.6-ansible_ipa_client_Drop_import_of_configure_nsswitch_database_rhbz#1748905.patch b/SOURCES/ansible-freeipa-0.1.6-ansible_ipa_client_Drop_import_of_configure_nsswitch_database_rhbz#1748905.patch
deleted file mode 100644
index a6ef8e6..0000000
--- a/SOURCES/ansible-freeipa-0.1.6-ansible_ipa_client_Drop_import_of_configure_nsswitch_database_rhbz#1748905.patch
+++ /dev/null
@@ -1,51 +0,0 @@
-From 5bb44245c6c43d752c1e066ebdc6cb3eb0253d98 Mon Sep 17 00:00:00 2001
-From: Thomas Woerner <twoerner@redhat.com>
-Date: Wed, 4 Sep 2019 14:40:46 +0200
-Subject: [PATCH] ansible_ipa_client: Drop import of
- configure_nsswitch_database
-
-configure_nsswitch_database has been removed with the freeipa commit
-
-https://github.com/freeipa/freeipa/commit/41ef8fba31ddbb32e2e5b7cccdc9b582a0809111
-
-The 4.4 compatibility hack leads to a ALREADY installed error in
-ipaclient_test because of the removal. This affects ipaclient and
-ipareplica roles and also the ipaclient deployment part in ipaserver.
-
-configure_nsswitch_database is not used any more in ipaclient role modules
-and therefore simply can be removed from ansible_ipa_client.
----
- roles/ipaclient/module_utils/ansible_ipa_client.py | 6 ++----
- 1 file changed, 2 insertions(+), 4 deletions(-)
-
-diff --git a/roles/ipaclient/module_utils/ansible_ipa_client.py b/roles/ipaclient/module_utils/ansible_ipa_client.py
-index 30b8d42..d45171b 100644
---- a/roles/ipaclient/module_utils/ansible_ipa_client.py
-+++ b/roles/ipaclient/module_utils/ansible_ipa_client.py
-@@ -35,7 +35,7 @@
-            "configure_sssd_conf", "realm_to_suffix", "run", "timeconf",
-            "serialization", "configure_krb5_conf", "get_ca_certs",
-            "SECURE_PATH", "get_server_connection_interface",
--           "configure_nsswitch_database", "disable_ra", "client_dns",
-+           "disable_ra", "client_dns",
-            "configure_certmonger", "update_ssh_keys",
-            "configure_openldap_conf", "hardcode_ldap_server",
-            "get_certs_from_ldap", "save_state", "create_ipa_nssdb",
-@@ -143,7 +143,7 @@ def knobs(self):
-     try:
-         from ipaclient.install.client import configure_krb5_conf, \
-             get_ca_certs, SECURE_PATH, get_server_connection_interface, \
--            configure_nsswitch_database, disable_ra, client_dns, \
-+            disable_ra, client_dns, \
-             configure_certmonger, update_ssh_keys, configure_openldap_conf, \
-             hardcode_ldap_server, get_certs_from_ldap, save_state, \
-             create_ipa_nssdb, configure_ssh_config, configure_sshd_config, \
-@@ -204,8 +204,6 @@ def configure_krb5_conf(
- 
-         get_server_connection_interface = \
-             ipa_client_install.get_server_connection_interface
--        configure_nsswitch_database = \
--            ipa_client_install.configure_nsswitch_database
-         disable_ra = ipa_client_install.disable_ra
-         client_dns = ipa_client_install.client_dns
-         configure_certmonger = ipa_client_install.configure_certmonger
diff --git a/SOURCES/ansible-freeipa-0.1.6-gen_module_docs-drop-key-dirserv_cert_files.patch b/SOURCES/ansible-freeipa-0.1.6-gen_module_docs-drop-key-dirserv_cert_files.patch
deleted file mode 100644
index 1533cb9..0000000
--- a/SOURCES/ansible-freeipa-0.1.6-gen_module_docs-drop-key-dirserv_cert_files.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff -up ansible-freeipa-0.1.6/utils/gen_module_docs.py.remove-key-dirserv_cert_files ansible-freeipa-0.1.6/utils/gen_module_docs.py
---- ansible-freeipa-0.1.6/utils/gen_module_docs.py.gen_module_docs-drop-key-dirserv_cert_files	2019-07-23 10:01:59.000000000 +0200
-+++ ansible-freeipa-0.1.6/utils/gen_module_docs.py	2019-07-23 11:27:12.921162481 +0200
-@@ -47,9 +47,6 @@ param_docs = {
-     "setup_ca": "Configure a dogtag CA",
-     "setup_kra": "Configure a dogtag KRA",
-     "setup_dns": "Configure bind with our zone",
--    "dirserv_cert_files": [
--        "File containing the Directory Server SSL certificate and private key"
--    ],
-     "force_join": "Force client enrollment even if already enrolled",
-     "subject_base": [
-         "The certificate subject base (default O=<realm-name>).",
diff --git a/SOURCES/ansible-freeipa-0.1.6-ipatopologysegment-command-suffix-e4497c18_rhbz#1733547.patch b/SOURCES/ansible-freeipa-0.1.6-ipatopologysegment-command-suffix-e4497c18_rhbz#1733547.patch
deleted file mode 100644
index 053f6c3..0000000
--- a/SOURCES/ansible-freeipa-0.1.6-ipatopologysegment-command-suffix-e4497c18_rhbz#1733547.patch
+++ /dev/null
@@ -1,62 +0,0 @@
-From e4497c18e9fb39b4e8c022eb0898060005cf6af6 Mon Sep 17 00:00:00 2001
-From: Thomas Woerner <twoerner@redhat.com>
-Date: Fri, 26 Jul 2019 18:33:41 +0200
-Subject: [PATCH] ipatopologysegment: Store suffix for commands in command list
-
-With adding the domain and ca suffixes with `suffix: domain+ca` only ca
-has been added as the suffix was only used from the last command. The
-suffix is now stored together with the command and the argument. This
-will fix this error.
-
-Fixes: #106 (Last suffix adding twice in the list of topology segments)
----
- plugins/modules/ipatopologysegment.py | 13 +++++++------
- 1 file changed, 7 insertions(+), 6 deletions(-)
-
-diff --git a/plugins/modules/ipatopologysegment.py b/plugins/modules/ipatopologysegment.py
-index d64c7be..e768f72 100644
---- a/plugins/modules/ipatopologysegment.py
-+++ b/plugins/modules/ipatopologysegment.py
-@@ -256,12 +256,12 @@ def main():
-                             del args[key]
-                     if len(args) > 1:
-                         # cn needs to be in args always
--                        commands.append(["topologysegment_mod", args])
-+                        commands.append(["topologysegment_mod", args, suffix])
-                     # else: Nothing to change
-                 else:
-                     if name is None:
-                         args["cn"] = to_text("%s-to-%s" % (left, right))
--                    commands.append(["topologysegment_add", args])
-+                    commands.append(["topologysegment_add", args, suffix])
- 
-             elif state in ["absent", "disabled"]:
-                 # Make sure topology segment does not exist
-@@ -274,7 +274,7 @@ def main():
-                     args = {
-                         "cn": res_find["cn"][0]
-                     }
--                    commands.append(["topologysegment_del", args])
-+                    commands.append(["topologysegment_del", args, suffix])
- 
-             elif state == "checked":
-                 # Check if topology segment does exists
-@@ -309,14 +309,15 @@ def main():
-                     elif direction == "right-to-left":
-                         args["right"] = True
- 
--                    commands.append(["topologysegment_reinitialize", args])
-+                    commands.append(["topologysegment_reinitialize", args,
-+                                     suffix])
-             else:
-                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
- 
-         # Execute command
- 
--        for command, args in commands:
--            api_command(ansible_module, command, to_text(suffix), args)
-+        for command, args, _suffix in commands:
-+            api_command(ansible_module, command, to_text(_suffix), args)
-             changed = True
- 
-     except Exception as e:
diff --git a/SOURCES/ansible-freeipa-0.1.6-ipatopologysegment-reinitialized-c212b435_rhbz#1733559.patch b/SOURCES/ansible-freeipa-0.1.6-ipatopologysegment-reinitialized-c212b435_rhbz#1733559.patch
deleted file mode 100644
index afcdbfb..0000000
--- a/SOURCES/ansible-freeipa-0.1.6-ipatopologysegment-reinitialized-c212b435_rhbz#1733559.patch
+++ /dev/null
@@ -1,38 +0,0 @@
-From c212b43516cb870bbaa86c607dde33f373768043 Mon Sep 17 00:00:00 2001
-From: Thomas Woerner <twoerner@redhat.com>
-Date: Fri, 26 Jul 2019 19:30:11 +0200
-Subject: [PATCH] ipatopologysegment: Fail for missing entry with reinitialized
-
-Currently it is ignored if the entry for reinitialized can not be found if
-the name or left and right are not correct. Now there is a failure in this
-case.
-
-Fixes: #107 (Reinitialize are failed to find the node in ipatopology ..)
-Signed-off-by: Thomas Woerner <twoerner@redhat.com>
----
- plugins/modules/ipatopologysegment.py | 12 ++++++++++++
- 1 file changed, 12 insertions(+)
-
-diff --git a/plugins/modules/ipatopologysegment.py b/plugins/modules/ipatopologysegment.py
-index e768f72..e506b53 100644
---- a/plugins/modules/ipatopologysegment.py
-+++ b/plugins/modules/ipatopologysegment.py
-@@ -311,6 +311,18 @@ def main():
- 
-                     commands.append(["topologysegment_reinitialize", args,
-                                      suffix])
-+                else:
-+                    params = []
-+                    if name is not None:
-+                        params.append("name=%s" % name)
-+                    if left is not None:
-+                        params.append("left=%s" % left)
-+                    if right is not None:
-+                        params.append("right=%s" % right)
-+                    ansible_module.fail_json(
-+                        msg="No entry '%s' for suffix '%s'" %
-+                        (",".join(params), suffix))
-+
-             else:
-                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
- 
diff --git a/SOURCES/ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch b/SOURCES/ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch
new file mode 100644
index 0000000..f5c5147
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch
@@ -0,0 +1,1129 @@
+From dc0a5585fb036fbeba2200564e26c478465afeec Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Tue, 31 Dec 2019 11:04:49 -0300
+Subject: [PATCH] Add missing attributes to ipasudorule.
+
+This patch adds the following attributes to ipasudorule:
+
+    - order
+    - sudooption
+    - runasuser
+    - runasgroup
+
+It also fixes behavior of sudocmd assigned to the the sudorule, with the
+adittion of the attributes:
+
+    - allow_sudocmds
+    - deny_sudocmds
+    - allow_sudocmdgroups
+    - deny_sudocmdgroups
+
+README-sudorule and tests have been updated to comply with the changes.
+---
+ README-sudorule.md                            |  14 +-
+ ...sure-sudorule-does-not-have-sudooption.yml |  14 +
+ .../ensure-sudorule-has-sudooption.yml        |  13 +
+ .../ensure-sudorule-is-present-with-order.yml |  12 +
+ .../sudorule/ensure-sudorule-is-present.yml   |   2 +
+ .../ensure-sudorule-runasuser-is-absent.yml   |  14 +
+ .../ensure-sudorule-runasuser-is-present.yml  |  13 +
+ .../ensure-sudorule-sudocmd-is-absent.yml     |   7 +-
+ .../ensure-sudorule-sudocmd-is-present.yml    |   7 +-
+ plugins/modules/ipasudorule.py                | 353 +++++++++++++-----
+ tests/sudorule/test_sudorule.yml              | 204 +++++++---
+ 11 files changed, 504 insertions(+), 149 deletions(-)
+ create mode 100644 playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml
+ create mode 100644 playbooks/sudorule/ensure-sudorule-has-sudooption.yml
+ create mode 100644 playbooks/sudorule/ensure-sudorule-is-present-with-order.yml
+ create mode 100644 playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml
+ create mode 100644 playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml
+
+diff --git a/README-sudorule.md b/README-sudorule.md
+index bb3498b..50c73ad 100644
+--- a/README-sudorule.md
++++ b/README-sudorule.md
+@@ -68,7 +68,7 @@ Example playbook to make sure sudocmds are present in Sudo Rule:
+   - ipasudorule:
+       ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
++      allow_sudocmd:
+       - /sbin/ifconfig
+       action: member
+ ```
+@@ -87,7 +87,7 @@ Example playbook to make sure sudocmds are not present in Sudo Rule:
+   - ipasudorule:
+       ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
++      allow_sudocmd:
+       - /sbin/ifconfig
+       action: member
+       state: absent
+@@ -130,8 +130,14 @@ Variable | Description | Required
+ `hostgroup` | List of host group name strings assigned to this sudorule. | no
+ `user` | List of user name strings assigned to this sudorule. | no
+ `group` | List of user group name strings assigned to this sudorule. | no
+-`cmd` | List of sudocmd name strings assigned to this sudorule. | no
+-`cmdgroup` | List of sudocmd group name strings assigned wto this sudorule. | no
++`allow_sudocmd` | List of sudocmd name strings assigned to the allow group of this sudorule. | no
++`deny_sudocmd` | List of sudocmd name strings assigned to the deny group of this sudorule. | no
++`allow_sudocmdgroup` | List of sudocmd groups name strings assigned to the allow group of this sudorule. | no
++`deny_sudocmdgroup` | List of sudocmd groups name strings assigned to the deny group of this sudorule. | no
++`sudooption` \| `option` | List of options to the sudorule | no
++`order` | Integer to order the sudorule | no
++`runasuser` | List of users for Sudo to execute as. | no
++`runasgroup` | List of groups for Sudo to execute as. | no
+ `action` | Work on sudorule or member level. It can be on of `member` or `sudorule` and defaults to `sudorule`. | no
+ `state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | no
+ 
+diff --git a/playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml b/playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml
+new file mode 100644
+index 0000000..1307044
+--- /dev/null
++++ b/playbooks/sudorule/ensure-sudorule-does-not-have-sudooption.yml
+@@ -0,0 +1,14 @@
++---
++- name: Tests
++  hosts: ipaserver
++  become: true
++  gather_facts: false
++
++  tasks:
++  # Ensure sudooption is absent in sudorule
++  - ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      sudooption: "!root"
++      action: member
++      state: absent
+diff --git a/playbooks/sudorule/ensure-sudorule-has-sudooption.yml b/playbooks/sudorule/ensure-sudorule-has-sudooption.yml
+new file mode 100644
+index 0000000..1f32b9a
+--- /dev/null
++++ b/playbooks/sudorule/ensure-sudorule-has-sudooption.yml
+@@ -0,0 +1,13 @@
++---
++- name: Tests
++  hosts: ipaserver
++  become: true
++  gather_facts: false
++
++  tasks:
++  # Ensure sudooption is present in sudorule
++  - ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      sudooption: "!root"
++      action: member
+diff --git a/playbooks/sudorule/ensure-sudorule-is-present-with-order.yml b/playbooks/sudorule/ensure-sudorule-is-present-with-order.yml
+new file mode 100644
+index 0000000..9a3c2b2
+--- /dev/null
++++ b/playbooks/sudorule/ensure-sudorule-is-present-with-order.yml
+@@ -0,0 +1,12 @@
++---
++- name: Tests
++  hosts: ipaserver
++  become: true
++  gather_facts: false
++
++  tasks:
++  # Ensure sudorule is present with the given order.
++  - ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      order: 2
+diff --git a/playbooks/sudorule/ensure-sudorule-is-present.yml b/playbooks/sudorule/ensure-sudorule-is-present.yml
+index 5b8f32b..89041af 100644
+--- a/playbooks/sudorule/ensure-sudorule-is-present.yml
++++ b/playbooks/sudorule/ensure-sudorule-is-present.yml
+@@ -9,4 +9,6 @@
+       ipaadmin_password: MyPassword123
+       name: testrule1
+       description: A test sudo rule.
++      allow_sudocmd: /bin/ls
++      deny_sudocmd: /bin/vim
+       state: present
+diff --git a/playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml b/playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml
+new file mode 100644
+index 0000000..56612f1
+--- /dev/null
++++ b/playbooks/sudorule/ensure-sudorule-runasuser-is-absent.yml
+@@ -0,0 +1,14 @@
++---
++- name: Tests
++  hosts: ipaserver
++  become: true
++  gather_facts: false
++
++  tasks:
++  # Ensure sudorule is present with the given order.
++  - ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      runasuser: admin
++      action: member
++      state: absent
+diff --git a/playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml b/playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml
+new file mode 100644
+index 0000000..8af49b9
+--- /dev/null
++++ b/playbooks/sudorule/ensure-sudorule-runasuser-is-present.yml
+@@ -0,0 +1,13 @@
++---
++- name: Tests
++  hosts: ipaserver
++  become: true
++  gather_facts: false
++
++  tasks:
++  # Ensure sudorule is present with the given order.
++  - ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      runasuser: admin
++      action: member
+diff --git a/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml b/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml
+index 942d0b5..328242a 100644
+--- a/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml
++++ b/playbooks/sudorule/ensure-sudorule-sudocmd-is-absent.yml
+@@ -8,8 +8,13 @@
+   - ipasudorule:
+       ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
++      allow_sudocmd:
+       - /sbin/ifconfig
++      deny_sudocmd:
+       - /usr/bin/vim
++      allow_sudocmdgroup:
++      - devops
++      deny_sudocmdgroup:
++      - users
+       action: member
+       state: absent
+diff --git a/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml b/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml
+index 61fcbb0..55acd61 100644
+--- a/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml
++++ b/playbooks/sudorule/ensure-sudorule-sudocmd-is-present.yml
+@@ -8,7 +8,12 @@
+   - ipasudorule:
+       ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
++      allow_sudocmd:
+       - /sbin/ifconfig
++      deny_sudocmd:
+       - /usr/bin/vim
++      allow_sudocmdgroup:
++      - devops
++      deny_sudocmdgroup:
++      - users
+       action: member
+diff --git a/plugins/modules/ipasudorule.py b/plugins/modules/ipasudorule.py
+index c21f247..285a946 100644
+--- a/plugins/modules/ipasudorule.py
++++ b/plugins/modules/ipasudorule.py
+@@ -79,18 +79,43 @@
+     description: Host category the sudo rule applies to.
+     required: false
+     choices: ["all"]
+-  cmd:
+-    description: List of sudocmds assigned to this sudorule.
++  allow_sudocmd:
++    description: List of allowed sudocmds assigned to this sudorule.
+     required: false
+     type: list
+-  cmdgroup:
+-    description: List of sudocmd groups assigned to this sudorule.
++  allow_sudocmdgroup:
++    description: List of allowed sudocmd groups assigned to this sudorule.
++    required: false
++    type: list
++  deny_sudocmd:
++    description: List of denied sudocmds assigned to this sudorule.
++    required: false
++    type: list
++  deny_sudocmdgroup:
++    description: List of denied sudocmd groups assigned to this sudorule.
+     required: false
+     type: list
+   cmdcategory:
+-    description: Cammand category the sudo rule applies to
++    description: Command category the sudo rule applies to
+     required: false
+     choices: ["all"]
++  order:
++    description: Order to apply this rule.
++    required: false
++    type: int
++  sudooption:
++    description:
++    required: false
++    type: list
++    aliases: ["options"]
++  runasuser:
++    description: List of users for Sudo to execute as.
++    required: false
++    type: list
++  runasgroup:
++    description: List of groups for Sudo to execute as.
++    required: false
++    type: list
+   action:
+     description: Work on sudorule or member level
+     default: sudorule
+@@ -111,13 +136,13 @@
+ 
+ # Ensure sudocmd is present in Sudo Rule
+ - ipasudorule:
+-  ipaadmin_password: pass1234
+-  name: testrule1
+-  cmd:
+-  - /sbin/ifconfig
+-  - /usr/bin/vim
+-  action: member
+-  state: absent
++    ipaadmin_password: pass1234
++    name: testrule1
++    allow_sudocmd:
++      - /sbin/ifconfig
++      - /usr/bin/vim
++    action: member
++    state: absent
+ 
+ # Ensure host server is present in Sudo Rule
+ - ipasudorule:
+@@ -160,7 +185,7 @@
+ 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, compare_args_ipa, \
+-    module_params_get
++    module_params_get, gen_add_del_lists
+ 
+ 
+ def find_sudorule(module, name):
+@@ -180,14 +205,26 @@ def find_sudorule(module, name):
+         return None
+ 
+ 
+-def gen_args(ansible_module):
+-    arglist = ['description', 'usercategory', 'hostcategory', 'cmdcategory',
+-               'runasusercategory', 'runasgroupcategory', 'nomembers']
++def gen_args(description, usercat, hostcat, cmdcat, runasusercat,
++             runasgroupcat, order, nomembers):
+     _args = {}
+-    for arg in arglist:
+-        value = module_params_get(ansible_module, arg)
+-        if value is not None:
+-            _args[arg] = value
++
++    if description is not None:
++        _args['description'] = description
++    if usercat is not None:
++        _args['usercategory'] = usercat
++    if hostcat is not None:
++        _args['hostcategory'] = hostcat
++    if cmdcat is not None:
++        _args['cmdcategory'] = cmdcat
++    if runasusercat is not None:
++        _args['ipasudorunasusercategory'] = runasusercat
++    if runasgroupcat is not None:
++        _args['ipasudorunasgroupcategory'] = runasgroupcat
++    if order is not None:
++        _args['sudoorder'] = order
++    if nomembers is not None:
++        _args['nomembers'] = nomembers
+ 
+     return _args
+ 
+@@ -212,13 +249,21 @@ def main():
+             hostgroup=dict(required=False, type='list', default=None),
+             user=dict(required=False, type='list', default=None),
+             group=dict(required=False, type='list', default=None),
+-            cmd=dict(required=False, type="list", default=None),
++            allow_sudocmd=dict(required=False, type="list", default=None),
++            deny_sudocmd=dict(required=False, type="list", default=None),
++            allow_sudocmdgroup=dict(required=False, type="list", default=None),
++            deny_sudocmdgroup=dict(required=False, type="list", default=None),
+             cmdcategory=dict(required=False, type="str", default=None,
+                              choices=["all"]),
+             runasusercategory=dict(required=False, type="str", default=None,
+                                    choices=["all"]),
+             runasgroupcategory=dict(required=False, type="str", default=None,
+                                     choices=["all"]),
++            runasuser=dict(required=False, type="list", default=None),
++            runasgroup=dict(required=False, type="list", default=None),
++            order=dict(type="int", required=False, aliases=['sudoorder']),
++            sudooption=dict(required=False, type='list', default=None,
++                            aliases=["options"]),
+             action=dict(type="str", default="sudorule",
+                         choices=["member", "sudorule"]),
+             # state
+@@ -256,8 +301,16 @@ def main():
+     hostgroup = module_params_get(ansible_module, "hostgroup")
+     user = module_params_get(ansible_module, "user")
+     group = module_params_get(ansible_module, "group")
+-    cmd = module_params_get(ansible_module, 'cmd')
+-    cmdgroup = module_params_get(ansible_module, 'cmdgroup')
++    allow_sudocmd = module_params_get(ansible_module, 'allow_sudocmd')
++    allow_sudocmdgroup = module_params_get(ansible_module,
++                                           'allow_sudocmdgroup')
++    deny_sudocmd = module_params_get(ansible_module, 'deny_sudocmd')
++    deny_sudocmdgroup = module_params_get(ansible_module,
++                                          'deny_sudocmdgroup')
++    sudooption = module_params_get(ansible_module, "sudooption")
++    order = module_params_get(ansible_module, "order")
++    runasuser = module_params_get(ansible_module, "runasuser")
++    runasgroup = module_params_get(ansible_module, "runasgroup")
+     action = module_params_get(ansible_module, "action")
+ 
+     # state
+@@ -272,28 +325,30 @@ def main():
+         if action == "member":
+             invalid = ["description", "usercategory", "hostcategory",
+                        "cmdcategory", "runasusercategory",
+-                       "runasgroupcategory", "nomembers"]
++                       "runasgroupcategory", "order", "nomembers"]
+ 
+-            for x in invalid:
+-                if x in vars() and vars()[x] is not None:
++            for arg in invalid:
++                if arg in vars() and vars()[arg] is not None:
+                     ansible_module.fail_json(
+                         msg="Argument '%s' can not be used with action "
+-                        "'%s'" % (x, action))
++                        "'%s'" % (arg, action))
+ 
+     elif state == "absent":
+         if len(names) < 1:
+             ansible_module.fail_json(msg="No name given.")
+         invalid = ["description", "usercategory", "hostcategory",
+                    "cmdcategory", "runasusercategory",
+-                   "runasgroupcategory", "nomembers"]
++                   "runasgroupcategory", "nomembers", "order"]
+         if action == "sudorule":
+             invalid.extend(["host", "hostgroup", "user", "group",
+-                            "cmd", "cmdgroup"])
+-        for x in invalid:
+-            if vars()[x] is not None:
++                            "runasuser", "runasgroup", "allow_sudocmd",
++                            "allow_sudocmdgroup", "deny_sudocmd",
++                            "deny_sudocmdgroup", "sudooption"])
++        for arg in invalid:
++            if vars()[arg] is not None:
+                 ansible_module.fail_json(
+                     msg="Argument '%s' can not be used with state '%s'" %
+-                    (x, state))
++                    (arg, state))
+ 
+     elif state in ["enabled", "disabled"]:
+         if len(names) < 1:
+@@ -305,12 +360,14 @@ def main():
+         invalid = ["description", "usercategory", "hostcategory",
+                    "cmdcategory", "runasusercategory", "runasgroupcategory",
+                    "nomembers", "nomembers", "host", "hostgroup",
+-                   "user", "group", "cmd", "cmdgroup"]
+-        for x in invalid:
+-            if vars()[x] is not None:
++                   "user", "group", "allow_sudocmd", "allow_sudocmdgroup",
++                   "deny_sudocmd", "deny_sudocmdgroup", "runasuser",
++                   "runasgroup", "order", "sudooption"]
++        for arg in invalid:
++            if vars()[arg] is not None:
+                 ansible_module.fail_json(
+                     msg="Argument '%s' can not be used with state '%s'" %
+-                    (x, state))
++                    (arg, state))
+     else:
+         ansible_module.fail_json(msg="Invalid state '%s'" % state)
+ 
+@@ -335,7 +392,9 @@ def main():
+             # Create command
+             if state == "present":
+                 # Generate args
+-                args = gen_args(ansible_module)
++                args = gen_args(description, usercategory, hostcategory,
++                                cmdcategory, runasusercategory,
++                                runasgroupcategory, order, nomembers)
+                 if action == "sudorule":
+                     # Found the sudorule
+                     if res_find is not None:
+@@ -351,44 +410,42 @@ def main():
+                         res_find = {}
+ 
+                     # Generate addition and removal lists
+-                    host_add = list(
+-                        set(host or []) -
+-                        set(res_find.get("member_host", [])))
+-                    host_del = list(
+-                        set(res_find.get("member_host", [])) -
+-                        set(host or []))
+-                    hostgroup_add = list(
+-                        set(hostgroup or []) -
+-                        set(res_find.get("member_hostgroup", [])))
+-                    hostgroup_del = list(
+-                        set(res_find.get("member_hostgroup", [])) -
+-                        set(hostgroup or []))
+-
+-                    user_add = list(
+-                        set(user or []) -
+-                        set(res_find.get("member_user", [])))
+-                    user_del = list(
+-                        set(res_find.get("member_user", [])) -
+-                        set(user or []))
+-                    group_add = list(
+-                        set(group or []) -
+-                        set(res_find.get("member_group", [])))
+-                    group_del = list(
+-                        set(res_find.get("member_group", [])) -
+-                        set(group or []))
+-
+-                    cmd_add = list(
+-                        set(cmd or []) -
+-                        set(res_find.get("member_cmd", [])))
+-                    cmd_del = list(
+-                        set(res_find.get("member_cmd", [])) -
+-                        set(cmd or []))
+-                    cmdgroup_add = list(
+-                        set(cmdgroup or []) -
+-                        set(res_find.get("member_cmdgroup", [])))
+-                    cmdgroup_del = list(
+-                        set(res_find.get("member_cmdgroup", [])) -
+-                        set(cmdgroup or []))
++                    host_add, host_del = gen_add_del_lists(
++                        host, res_find.get('member_host', []))
++
++                    hostgroup_add, hostgroup_del = gen_add_del_lists(
++                        hostgroup, res_find.get('member_hostgroup', []))
++
++                    user_add, user_del = gen_add_del_lists(
++                        user, res_find.get('member_user', []))
++
++                    group_add, group_del = gen_add_del_lists(
++                        group, res_find.get('member_group', []))
++
++                    allow_cmd_add, allow_cmd_del = gen_add_del_lists(
++                        allow_sudocmd,
++                        res_find.get('memberallowcmd_sudocmd', []))
++
++                    allow_cmdgroup_add, allow_cmdgroup_del = gen_add_del_lists(
++                        allow_sudocmdgroup,
++                        res_find.get('memberallowcmd_sudocmdgroup', []))
++
++                    deny_cmd_add, deny_cmd_del = gen_add_del_lists(
++                        deny_sudocmd,
++                        res_find.get('memberdenycmd_sudocmd', []))
++
++                    deny_cmdgroup_add, deny_cmdgroup_del = gen_add_del_lists(
++                        deny_sudocmdgroup,
++                        res_find.get('memberdenycmd_sudocmdgroup', []))
++
++                    sudooption_add, sudooption_del = gen_add_del_lists(
++                        sudooption, res_find.get('ipasudoopt', []))
++
++                    runasuser_add, runasuser_del = gen_add_del_lists(
++                        runasuser, res_find.get('ipasudorunas_user', []))
++
++                    runasgroup_add, runasgroup_del = gen_add_del_lists(
++                        runasgroup, res_find.get('ipasudorunas_group', []))
+ 
+                     # Add hosts and hostgroups
+                     if len(host_add) > 0 or len(hostgroup_add) > 0:
+@@ -420,20 +477,59 @@ def main():
+                                              "group": group_del,
+                                          }])
+ 
+-                    # Add commands
+-                    if len(cmd_add) > 0 or len(cmdgroup_add) > 0:
++                    # Add commands allowed
++                    if len(allow_cmd_add) > 0 or len(allow_cmdgroup_add) > 0:
+                         commands.append([name, "sudorule_add_allow_command",
+-                                         {
+-                                             "sudocmd": cmd_add,
+-                                             "sudocmdgroup": cmdgroup_add,
+-                                         }])
+-
+-                    if len(cmd_del) > 0 or len(cmdgroup_del) > 0:
++                                         {"sudocmd": allow_cmd_add,
++                                          "sudocmdgroup": allow_cmdgroup_add,
++                                          }])
++
++                    if len(allow_cmd_del) > 0 or len(allow_cmdgroup_del) > 0:
++                        commands.append([name, "sudorule_remove_allow_command",
++                                         {"sudocmd": allow_cmd_del,
++                                          "sudocmdgroup": allow_cmdgroup_del
++                                          }])
++
++                    # Add commands denied
++                    if len(deny_cmd_add) > 0 or len(deny_cmdgroup_add) > 0:
+                         commands.append([name, "sudorule_add_deny_command",
+-                                         {
+-                                             "sudocmd": cmd_del,
+-                                             "sudocmdgroup": cmdgroup_del
+-                                         }])
++                                         {"sudocmd": deny_cmd_add,
++                                          "sudocmdgroup": deny_cmdgroup_add,
++                                          }])
++
++                    if len(deny_cmd_del) > 0 or len(deny_cmdgroup_del) > 0:
++                        commands.append([name, "sudorule_remove_deny_command",
++                                         {"sudocmd": deny_cmd_del,
++                                          "sudocmdgroup": deny_cmdgroup_del
++                                          }])
++
++                    # Add RunAS Users
++                    if len(runasuser_add) > 0:
++                        commands.append([name, "sudorule_add_runasuser",
++                                         {"user": runasuser_add}])
++                    # Remove RunAS Users
++                    if len(runasuser_del) > 0:
++                        commands.append([name, "sudorule_remove_runasuser",
++                                         {"user": runasuser_del}])
++
++                    # Add RunAS Groups
++                    if len(runasgroup_add) > 0:
++                        commands.append([name, "sudorule_add_runasgroup",
++                                         {"group": runasgroup_add}])
++                    # Remove RunAS Groups
++                    if len(runasgroup_del) > 0:
++                        commands.append([name, "sudorule_remove_runasgroup",
++                                         {"group": runasgroup_del}])
++
++                    # Add sudo options
++                    for sudoopt in sudooption_add:
++                        commands.append([name, "sudorule_add_option",
++                                         {"ipasudoopt": sudoopt}])
++
++                    # Remove sudo options
++                    for sudoopt in sudooption_del:
++                        commands.append([name, "sudorule_remove_option",
++                                         {"ipasudoopt": sudoopt}])
+ 
+                 elif action == "member":
+                     if res_find is None:
+@@ -456,11 +552,38 @@ def main():
+                                          }])
+ 
+                     # Add commands
+-                    if cmd is not None:
++                    if allow_sudocmd is not None \
++                       or allow_sudocmdgroup is not None:
+                         commands.append([name, "sudorule_add_allow_command",
+-                                         {
+-                                             "sudocmd": cmd,
+-                                         }])
++                                         {"sudocmd": allow_sudocmd,
++                                          "sudocmdgroup": allow_sudocmdgroup,
++                                          }])
++
++                    # Add commands
++                    if deny_sudocmd is not None \
++                       or deny_sudocmdgroup is not None:
++                        commands.append([name, "sudorule_add_deny_command",
++                                         {"sudocmd": deny_sudocmd,
++                                          "sudocmdgroup": deny_sudocmdgroup,
++                                          }])
++
++                    # Add RunAS Users
++                    if runasuser is not None:
++                        commands.append([name, "sudorule_add_runasuser",
++                                         {"user": runasuser}])
++
++                    # Add RunAS Groups
++                    if runasgroup is not None:
++                        commands.append([name, "sudorule_add_runasgroup",
++                                         {"group": runasgroup}])
++
++                    # Add options
++                    if sudooption is not None:
++                        existing_opts = res_find.get('ipasudoopt', [])
++                        for sudoopt in sudooption:
++                            if sudoopt not in existing_opts:
++                                commands.append([name, "sudorule_add_option",
++                                                 {"ipasudoopt": sudoopt}])
+ 
+             elif state == "absent":
+                 if action == "sudorule":
+@@ -487,12 +610,40 @@ def main():
+                                              "group": group,
+                                          }])
+ 
+-                    # Remove commands
+-                    if cmd is not None:
+-                        commands.append([name, "sudorule_add_deny_command",
+-                                         {
+-                                             "sudocmd": cmd,
+-                                         }])
++                    # Remove allow commands
++                    if allow_sudocmd is not None \
++                       or allow_sudocmdgroup is not None:
++                        commands.append([name, "sudorule_remove_allow_command",
++                                         {"sudocmd": allow_sudocmd,
++                                          "sudocmdgroup": allow_sudocmdgroup
++                                          }])
++
++                    # Remove deny commands
++                    if deny_sudocmd is not None \
++                       or deny_sudocmdgroup is not None:
++                        commands.append([name, "sudorule_remove_deny_command",
++                                         {"sudocmd": deny_sudocmd,
++                                          "sudocmdgroup": deny_sudocmdgroup
++                                          }])
++
++                    # Remove RunAS Users
++                    if runasuser is not None:
++                        commands.append([name, "sudorule_remove_runasuser",
++                                         {"user": runasuser}])
++
++                    # Remove RunAS Groups
++                    if runasgroup is not None:
++                        commands.append([name, "sudorule_remove_runasgroup",
++                                         {"group": runasgroup}])
++
++                    # Remove options
++                    if sudooption is not None:
++                        existing_opts = res_find.get('ipasudoopt', [])
++                        for sudoopt in sudooption:
++                            if sudoopt in existing_opts:
++                                commands.append([name,
++                                                 "sudorule_remove_option",
++                                                 {"ipasudoopt": sudoopt}])
+ 
+             elif state == "enabled":
+                 if res_find is None:
+@@ -530,9 +681,9 @@ def main():
+                         changed = True
+                 else:
+                     changed = True
+-            except Exception as e:
++            except Exception as ex:
+                 ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
+-                                                             str(e)))
++                                                             str(ex)))
+             # Get all errors
+             # All "already a member" and "not a member" failures in the
+             # result are ignored. All others are reported.
+@@ -549,8 +700,8 @@ def main():
+         if len(errors) > 0:
+             ansible_module.fail_json(msg=", ".join(errors))
+ 
+-    except Exception as e:
+-        ansible_module.fail_json(msg=str(e))
++    except Exception as ex:
++        ansible_module.fail_json(msg=str(ex))
+ 
+     finally:
+         temp_kdestroy(ccache_dir, ccache_name)
+diff --git a/tests/sudorule/test_sudorule.yml b/tests/sudorule/test_sudorule.yml
+index 88ed90a..25090bb 100644
+--- a/tests/sudorule/test_sudorule.yml
++++ b/tests/sudorule/test_sudorule.yml
+@@ -16,15 +16,22 @@
+ 
+   - name: Ensure some sudocmds are available
+     ipasudocmd:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name:
+           - /sbin/ifconfig
+           - /usr/bin/vim
+       state: present
+ 
++  - name: Ensure sudocmdgroup is available
++    ipasudocmdgroup:
++      ipaadmin_password: MyPassword123
++      name: test_sudorule
++      sudocmd: /usr/bin/vim
++      state: present
++
+   - name: Ensure sudorules are absent
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name:
+       - testrule1
+       - allusers
+@@ -34,21 +41,21 @@
+ 
+   - name: Ensure sudorule is present
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+     register: result
+     failed_when: not result.changed
+ 
+   - name: Ensure sudorule is present again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+     register: result
+     failed_when: result.changed
+ 
+   - name: Ensure sudorule is present, runAsUserCategory.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       runAsUserCategory: all
+     register: result
+@@ -56,7 +63,7 @@
+ 
+   - name: Ensure sudorule is present, with usercategory 'all'
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allusers
+       usercategory: all
+     register: result
+@@ -64,7 +71,7 @@
+ 
+   - name: Ensure sudorule is present, with usercategory 'all', again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allusers
+       usercategory: all
+     register: result
+@@ -72,7 +79,7 @@
+ 
+   - name: Ensure sudorule is present, with hostategory 'all'
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allhosts
+       hostcategory: all
+     register: result
+@@ -80,7 +87,7 @@
+ 
+   - name: Ensure sudorule is present, with hostategory 'all', again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allhosts
+       hostcategory: all
+     register: result
+@@ -88,13 +95,13 @@
+ 
+   - name: Ensure sudorule is disabled
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       state: disabled
+ 
+   - name: Ensure sudorule is disabled, again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       state: disabled
+     register: result
+@@ -102,7 +109,7 @@
+ 
+   - name: Ensure sudorule is enabled
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       state: enabled
+     register: result
+@@ -110,37 +117,77 @@
+ 
+   - name: Ensure sudorule is enabled, again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       state: enabled
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure sudorule is present and some sudocmd are a member of it.
++  - name: Ensure sudorule is present and some sudocmd are allowed.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
++      allow_sudocmd:
+       - /sbin/ifconfig
+-      - /usr/bin/vim
+       action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure sudorule is present and some sudocmd are a member of it, again.
++  - name: Ensure sudorule is present and some sudocmd are allowed, again.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
++      allow_sudocmd:
+       - /sbin/ifconfig
++      action: member
++    register: result
++    failed_when: result.changed
++
++  - name: Ensure sudorule is present and some sudocmd are denyed.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      deny_sudocmd:
++      - /usr/bin/vim
++      action: member
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure sudorule is present and some sudocmd are denyed, again.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      deny_sudocmd:
+       - /usr/bin/vim
+       action: member
+     register: result
+     failed_when: result.changed
+ 
++  - name: Ensure sudorule is present and, sudocmds are absent.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      allow_sudocmd: /sbin/ifconfig
++      deny_sudocmd: /usr/bin/vim
++      action: member
++      state: absent
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure sudorule is present and, sudocmds are absent, again.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      allow_sudocmd: /sbin/ifconfig
++      deny_sudocmd: /usr/bin/vim
++      action: member
++      state: absent
++    register: result
++    failed_when: result.changed
++
+   - name: Ensure sudorule is present with cmdcategory 'all'.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allcommands
+       cmdcategory: all
+     register: result
+@@ -148,7 +195,7 @@
+ 
+   - name: Ensure sudorule is present with cmdcategory 'all', again.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allcommands
+       cmdcategory: all
+     register: result
+@@ -156,7 +203,7 @@
+ 
+   - name: Ensure host "{{ groups.ipaserver[0] }}" is present in sudorule.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       host: "{{ groups.ipaserver[0] }}"
+       action: member
+@@ -165,7 +212,7 @@
+ 
+   - name: Ensure host "{{ groups.ipaserver[0] }}" is present in sudorule, again.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       host: "{{ groups.ipaserver[0] }}"
+       action: member
+@@ -190,25 +237,77 @@
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure sudorule sudocmds are absent
++  - name: Ensure sudorule is present, with an allow_sudocmdgroup.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
+-      - /sbin/ifconfig
+-      - /usr/bin/vim
++      allow_sudocmdgroup: test_sudorule
++      state: present
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure sudorule is present, with an allow_sudocmdgroup, again.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      allow_sudocmdgroup: test_sudorule
++      state: present
++    register: result
++    failed_when: result.changed
++
++  - name: Ensure sudorule is present, but allow_sudocmdgroup is absent.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      allow_sudocmdgroup: test_sudorule
+       action: member
+       state: absent
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure sudorule sudocmds are absent, again
++  - name: Ensure sudorule is present, but allow_sudocmdgroup is absent.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+-      cmd:
+-      - /sbin/ifconfig
+-      - /usr/bin/vim
++      allow_sudocmdgroup: test_sudorule
++      action: member
++      state: absent
++    register: result
++    failed_when: result.changed
++
++  - name: Ensure sudorule is present, with an deny_sudocmdgroup.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      deny_sudocmdgroup: test_sudorule
++      state: present
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure sudorule is present, with an deny_sudocmdgroup, again.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      deny_sudocmdgroup: test_sudorule
++      state: present
++    register: result
++    failed_when: result.changed
++
++  - name: Ensure sudorule is present, but deny_sudocmdgroup is absent.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      deny_sudocmdgroup: test_sudorule
++      action: member
++      state: absent
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure sudorule is present, but deny_sudocmdgroup is absent, again.
++    ipasudorule:
++      ipaadmin_password: MyPassword123
++      name: testrule1
++      deny_sudocmdgroup: test_sudorule
+       action: member
+       state: absent
+     register: result
+@@ -216,7 +315,7 @@
+ 
+   - name: Ensure sudorule is absent
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       state: absent
+     register: result
+@@ -224,7 +323,7 @@
+ 
+   - name: Ensure sudorule is absent, again.
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: testrule1
+       state: absent
+     register: result
+@@ -232,7 +331,7 @@
+ 
+   - name: Ensure sudorule allhosts is absent
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allhosts
+       state: absent
+     register: result
+@@ -240,7 +339,7 @@
+ 
+   - name: Ensure sudorule allhosts is absent, again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allhosts
+       state: absent
+     register: result
+@@ -248,7 +347,7 @@
+ 
+   - name: Ensure sudorule allusers is absent
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allusers
+       state: absent
+     register: result
+@@ -256,7 +355,7 @@
+ 
+   - name: Ensure sudorule allusers is absent, again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allusers
+       state: absent
+     register: result
+@@ -264,7 +363,7 @@
+ 
+   - name: Ensure sudorule allcommands is absent
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allcommands
+       state: absent
+     register: result
+@@ -272,8 +371,29 @@
+ 
+   - name: Ensure sudorule allcommands is absent, again
+     ipasudorule:
+-      ipaadmin_password: pass1234
++      ipaadmin_password: MyPassword123
+       name: allcommands
+       state: absent
+     register: result
+     failed_when: result.changed
++
++  # cleanup
++  - name : Ensure sudocmdgroup is absent
++    ipasudocmdgroup:
++      ipaadmin_password: MyPassword123
++      name: test_sudorule
++      state: absent
++
++  - name: Ensure hostgroup is absent.
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name: cluster
++      state: absent
++
++  - name: Ensure sudocmds are absent
++    ipasudocmd:
++      ipaadmin_password: MyPassword123
++      name:
++      - /sbin/ifconfig
++      - /usr/bin/vim
++      state: absent
diff --git a/SOURCES/ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch b/SOURCES/ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch
new file mode 100644
index 0000000..767f487
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch
@@ -0,0 +1,177 @@
+From 3780a9a00e77ae0fd2944b36adad446d094fc90f Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Tue, 11 Feb 2020 10:34:39 +0100
+Subject: [PATCH] ansible_freeipa_module: Fix comparison of bool parameters in
+ compare_args_ipa
+
+Bool types are not iterable. Therefore the comparison using sets was failing
+with a TypeError. This prevented to change the bool parameters for hosts.
+
+A test for the host module has been added to verify that the bool parameters
+can be modified.
+
+New test:
+
+  tests/host/test_host_bool_params.yml
+
+Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1784514
+---
+ .../module_utils/ansible_freeipa_module.py    |  18 ++-
+ tests/host/test_host_bool_params.yml          | 119 ++++++++++++++++++
+ 2 files changed, 133 insertions(+), 4 deletions(-)
+ create mode 100644 tests/host/test_host_bool_params.yml
+
+diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
+index 8154a12..9e97b88 100644
+--- a/plugins/module_utils/ansible_freeipa_module.py
++++ b/plugins/module_utils/ansible_freeipa_module.py
+@@ -222,10 +222,20 @@ def compare_args_ipa(module, args, ipa):
+                     arg = [to_text(_arg) for _arg in arg]
+                 if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
+                     arg = [to_text(_arg) for _arg in arg]
+-            # module.warn("%s <=> %s" % (arg, ipa_arg))
+-            if set(arg) != set(ipa_arg):
+-                # module.warn("DIFFERENT")
+-                return False
++            # module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg)))
++            try:
++                arg_set = set(arg)
++                ipa_arg_set = set(ipa_arg)
++            except TypeError:
++                if arg != ipa_arg:
++                    # module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
++                    return False
++            else:
++                if arg_set != ipa_arg_set:
++                    # module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
++                    return False
++
++        # module.warn("%s == %s" % (repr(arg), repr(ipa_arg)))
+ 
+     return True
+ 
+diff --git a/tests/host/test_host_bool_params.yml b/tests/host/test_host_bool_params.yml
+new file mode 100644
+index 0000000..824ea99
+--- /dev/null
++++ b/tests/host/test_host_bool_params.yml
+@@ -0,0 +1,119 @@
++---
++- name: Test host bool parameters
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  - name: Get Domain from server name
++    set_fact:
++      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
++    when: ipaserver_domain is not defined
++
++  - name: Set host1_fqdn .. host6_fqdn
++    set_fact:
++      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
++
++  - name: Host absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name:
++      - "{{ host1_fqdn }}"
++      update_dns: yes
++      state: absent
++
++  - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      force: yes
++      requires_pre_auth: yes
++      ok_as_delegate: yes
++      ok_to_auth_as_delegate: yes
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      requires_pre_auth: yes
++      ok_as_delegate: yes
++      ok_to_auth_as_delegate: yes
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate set to no
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      requires_pre_auth: no
++      ok_as_delegate: no
++      ok_to_auth_as_delegate: no
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with requires_pre_auth, ok_as_delegate and ok_to_auth_as_delegate set to no again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      requires_pre_auth: no
++      ok_as_delegate: no
++      ok_to_auth_as_delegate: no
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with requires_pre_auth
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      requires_pre_auth: yes
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with requires_pre_auth again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      requires_pre_auth: yes
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with ok_as_delegate
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ok_as_delegate: yes
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with ok_as_delegate again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ok_as_delegate: yes
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with ok_to_auth_as_delegate
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ok_to_auth_as_delegate: yes
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present with ok_to_auth_as_delegate again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ok_to_auth_as_delegate: yes
++    register: result
++    failed_when: result.changed
++
++  - name: Host absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name:
++      - "{{ host1_fqdn }}"
++      update_dns: yes
++      state: absent
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch b/SOURCES/ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch
new file mode 100644
index 0000000..9e04d44
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch
@@ -0,0 +1,838 @@
+From 3865ce657e3ea1b621aa054c792201aedfde2d11 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Fri, 7 Feb 2020 10:11:38 +0100
+Subject: [PATCH] ipahbacrule: Fix handing of members with action hbacrule
+
+Changing members (host, hostgroup, hbacsvc, hbacsvcgroup, user, group) with
+action hbacrule was not working due to the use of the wrong parameter
+prefix. This has been fixed and the old members are removed correctly now.
+
+The test script has been reworked completely to verify the fix.
+
+Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1787996
+---
+ plugins/modules/ipahbacrule.py   |  24 +-
+ tests/hbacrule/test_hbacrule.yml | 549 +++++++++++++++++++++++--------
+ 2 files changed, 432 insertions(+), 141 deletions(-)
+
+diff --git a/plugins/modules/ipahbacrule.py b/plugins/modules/ipahbacrule.py
+index 385876b..82340c2 100644
+--- a/plugins/modules/ipahbacrule.py
++++ b/plugins/modules/ipahbacrule.py
+@@ -344,41 +344,41 @@ def main():
+                     # Generate addition and removal lists
+                     host_add = list(
+                         set(host or []) -
+-                        set(res_find.get("member_host", [])))
++                        set(res_find.get("memberhost_host", [])))
+                     host_del = list(
+-                        set(res_find.get("member_host", [])) -
++                        set(res_find.get("memberhost_host", [])) -
+                         set(host or []))
+                     hostgroup_add = list(
+                         set(hostgroup or []) -
+-                        set(res_find.get("member_hostgroup", [])))
++                        set(res_find.get("memberhost_hostgroup", [])))
+                     hostgroup_del = list(
+-                        set(res_find.get("member_hostgroup", [])) -
++                        set(res_find.get("memberhost_hostgroup", [])) -
+                         set(hostgroup or []))
+ 
+                     hbacsvc_add = list(
+                         set(hbacsvc or []) -
+-                        set(res_find.get("member_hbacsvc", [])))
++                        set(res_find.get("memberservice_hbacsvc", [])))
+                     hbacsvc_del = list(
+-                        set(res_find.get("member_hbacsvc", [])) -
++                        set(res_find.get("memberservice_hbacsvc", [])) -
+                         set(hbacsvc or []))
+                     hbacsvcgroup_add = list(
+                         set(hbacsvcgroup or []) -
+-                        set(res_find.get("member_hbacsvcgroup", [])))
++                        set(res_find.get("memberservice_hbacsvcgroup", [])))
+                     hbacsvcgroup_del = list(
+-                        set(res_find.get("member_hbacsvcgroup", [])) -
++                        set(res_find.get("memberservice_hbacsvcgroup", [])) -
+                         set(hbacsvcgroup or []))
+ 
+                     user_add = list(
+                         set(user or []) -
+-                        set(res_find.get("member_user", [])))
++                        set(res_find.get("memberuser_user", [])))
+                     user_del = list(
+-                        set(res_find.get("member_user", [])) -
++                        set(res_find.get("memberuser_user", [])) -
+                         set(user or []))
+                     group_add = list(
+                         set(group or []) -
+-                        set(res_find.get("member_group", [])))
++                        set(res_find.get("memberuser_group", [])))
+                     group_del = list(
+-                        set(res_find.get("member_group", [])) -
++                        set(res_find.get("memberuser_group", [])) -
+                         set(group or []))
+ 
+                     # Add hosts and hostgroups
+diff --git a/tests/hbacrule/test_hbacrule.yml b/tests/hbacrule/test_hbacrule.yml
+index a5615cc..38858d3 100644
+--- a/tests/hbacrule/test_hbacrule.yml
++++ b/tests/hbacrule/test_hbacrule.yml
+@@ -1,338 +1,629 @@
+ ---
+-- name: Tests
++- name: Playbook to handle hbacrules
+   hosts: ipaserver
+   become: true
+-  gather_facts: false
+ 
+   tasks:
+-  - name: Ensure HBAC Rule allhosts is absent
+-    ipahbacrule:
++  - name: Get Domain from server name
++    set_fact:
++      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
++    when: ipaserver_domain is not defined
++
++  # CLEANUP TEST ITEMS
++
++  - name: Ensure test hosts are absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      - "{{ 'testhost03.' + ipaserver_domain }}"
++      - "{{ 'testhost04.' + ipaserver_domain }}"
++      state: absent
++
++  - name: Ensure test hostgroups are absent
++    ipahostgroup:
+       ipaadmin_password: MyPassword123
+-      name: allhosts,sshd-pinky,loginRule
++      name: testhostgroup01,testhostgroup02,testhostgroup03,testhostgroup04
+       state: absent
+ 
+-  - name: User pinky absent
++  - name: Ensure test users are absent
+     ipauser:
+       ipaadmin_password: MyPassword123
+-      name: pinky
++      name: testuser01,testuser02,testuser03,testuser04
+       state: absent
+ 
+-  - name: User group login absent
++  - name: Ensure test user groups are absent
+     ipagroup:
+       ipaadmin_password: MyPassword123
+-      name: login
++      name: testgroup01,testgroup02,testgroup03,testgroup04
++      state: absent
++
++  - name: Ensure test HBAC Services are absent
++    ipahbacsvc:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvc01,testhbacsvc02,testhbacsvc03,testhbacsvc04
++      state: absent
++
++  - name: Ensure test HBAC Service Groups are absent
++    ipahbacsvcgroup:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvcgroup01,testhbacsvcgroup02,testhbacsvcgroup03,testhbacsvcgroup04
+       state: absent
+ 
+-  - name: User pinky present
++  # CREATE TEST ITEMS
++
++  - name: Ensure hosts "{{ 'host[1..4].' + ipaserver_domain }}" are present
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ 'testhost01.' + ipaserver_domain }}"
++        force: yes
++      - name: "{{ 'testhost02.' + ipaserver_domain }}"
++        force: yes
++      - name: "{{ 'testhost03.' + ipaserver_domain }}"
++        force: yes
++      - name: "{{ 'testhost04.' + ipaserver_domain }}"
++        force: yes
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure host-group testhostgroup01 is present
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name: testhostgroup01
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure host-group testhostgroup02 is present
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name: testhostgroup02
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure host-group testhostgroup03 is present
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name: testhostgroup03
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure host-group testhostgroup04 is present
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name: testhostgroup04
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure testusers are present
+     ipauser:
+       ipaadmin_password: MyPassword123
+-      name: pinky
+-      uid: 10001
+-      gid: 100
+-      phone: "+555123457"
+-      email: pinky@acme.com
+-      principalexpiration: "20220119235959"
+-      #passwordexpiration: "2022-01-19 23:59:59"
+-      first: pinky
+-      last: Acme
++      users:
++      - name: testuser01
++        first: test
++        last: user01
++      - name: testuser02
++        first: test
++        last: user02
++      - name: testuser03
++        first: test
++        last: user03
++      - name: testuser04
++        first: test
++        last: user04
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: User group login present
++  - name: Ensure user group testgroup01 is present
+     ipagroup:
+       ipaadmin_password: MyPassword123
+-      name: login
++      name: testgroup01
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule allhosts is present
+-    ipahbacrule:
++  - name: Ensure user group testgroup02 is present
++    ipagroup:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
+-      usercategory: all
++      name: testgroup02
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule allhosts is present again
+-    ipahbacrule:
++  - name: Ensure user group testgroup03 is present
++    ipagroup:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
+-      usercategory: all
++      name: testgroup03
+     register: result
+-    failed_when: result.changed
++    failed_when: not result.changed
+ 
+-  - name: Ensure host "{{ groups.ipaserver[0] }}" is present in HBAC Rule allhosts
++  - name: Ensure user group testgroup04 is present
++    ipagroup:
++      ipaadmin_password: MyPassword123
++      name: testgroup04
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service testhbacsvc01 is present
++    ipahbacsvc:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvc01
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service testhbacsvc02 is present
++    ipahbacsvc:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvc02
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service testhbacsvc03 is present
++    ipahbacsvc:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvc03
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service testhbacsvc04 is present
++    ipahbacsvc:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvc04
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service Group testhbacsvcgroup01 is present
++    ipahbacsvcgroup:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvcgroup01
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service Group testhbacsvcgroup02 is present
++    ipahbacsvcgroup:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvcgroup02
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service Group testhbacsvcgroup03 is present
++    ipahbacsvcgroup:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvcgroup03
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC Service Group testhbacsvcgroup04 is present
++    ipahbacsvcgroup:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvcgroup04
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure test HBAC rule hbacrule01 is absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
+-      host: "{{ groups.ipaserver[0] }}"
+-      action: member
++      name: hbacrule01
++      state: absent
++
++  # ENSURE HBACRULE
++
++  - name: Ensure HBAC rule hbacrule01 is present
++    ipahbacrule:
++      ipaadmin_password: MyPassword123
++      name: hbacrule01
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure host "{{ groups.ipaserver[0] }}" is present in HBAC Rule allhosts again
++  - name: Ensure HBAC rule hbacrule01 is present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
+-      host: "{{ groups.ipaserver[0] }}"
+-      action: member
++      name: hbacrule01
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is present
++  # CHANGE HBACRULE WITH ALL MEMBERS
++
++  - name: Ensure HBAC rule hbacrule01 is present with hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      hostcategory: all
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      hostgroup: testhostgroup01,testhostgroup02
++      user: testuser01,testuser02
++      group: testgroup01,testgroup02
++      hbacsvc: testhbacsvc01,testhbacsvc02
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is present again
++  - name: Ensure HBAC rule hbacrule01 is present with hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      hostcategory: all
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      hostgroup: testhostgroup01,testhostgroup02
++      user: testuser01,testuser02
++      group: testgroup01,testgroup02
++      hbacsvc: testhbacsvc01,testhbacsvc02
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure user pinky is present in HBAC Rule sshd-pinky
++  # REMOVE MEMBERS ONE BY ONE
++
++  - name: Ensure test HBAC rule hbacrule01 host members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      user: pinky
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      state: absent
+       action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure user pinky is present in HBAC Rule sshd-pinky again
++  - name: Ensure test HBAC rule hbacrule01 host members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      user: pinky
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      state: absent
+       action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC service sshd is present in HBAC Rule sshd-pinky
++  - name: Ensure test HBAC rule hbacrule01 hostgroup members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      hbacsvc: sshd
++      name: hbacrule01
++      hostgroup: testhostgroup01,testhostgroup02
++      state: absent
+       action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC service sshd is present in HBAC Rule sshd-pinky again
++  - name: Ensure test HBAC rule hbacrule01 hostgroup members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      hbacsvc: sshd
++      name: hbacrule01
++      hostgroup: testhostgroup01,testhostgroup02
++      state: absent
+       action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule loginRule is present with HBAC service sshd
++  - name: Ensure test HBAC rule hbacrule01 user members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
+-      group: login
++      name: hbacrule01
++      user: testuser01,testuser02
++      state: absent
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule loginRule is present with HBAC service sshd again
++  - name: Ensure test HBAC rule hbacrule01 user members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
+-      group: login
++      name: hbacrule01
++      user: testuser01,testuser02
++      state: absent
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure user pinky is present in HBAC Rule loginRule
++  - name: Ensure test HBAC rule hbacrule01 user group members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
+-      user: pinky
++      name: hbacrule01
++      group: testgroup01,testgroup02
++      state: absent
+       action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure user pinky is present in HBAC Rule loginRule again
++  - name: Ensure test HBAC rule hbacrule01 user group members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
+-      user: pinky
++      name: hbacrule01
++      group: testgroup01,testgroup02
++      state: absent
+       action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure user pinky is absent in HBAC Rule loginRule
++  - name: Ensure test HBAC rule hbacrule01 hbacsvc members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
+-      user: pinky
+-      action: member
++      name: hbacrule01
++      hbacsvc: testhbacsvc01,testhbacsvc02
+       state: absent
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure user pinky is absent in HBAC Rule loginRule again
++  - name: Ensure test HBAC rule hbacrule01 hbacsvc members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
+-      user: pinky
+-      action: member
++      name: hbacrule01
++      hbacsvc: testhbacsvc01,testhbacsvc02
+       state: absent
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule loginRule is absent
++  - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
++      name: hbacrule01
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+       state: absent
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule loginRule is absent again
++  - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: loginRule
++      name: hbacrule01
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+       state: absent
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC service sshd is absent in HBAC Rule sshd-pinky
++  # ADD MEMBERS BACK
++
++  - name: Ensure test HBAC rule hbacrule01 host members are present
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      hbacsvc: sshd
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
+       action: member
+-      state: absent
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC service sshd is absent in HBAC Rule sshd-pinky again
++  - name: Ensure test HBAC rule hbacrule01 host members are present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      hbacsvc: sshd
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
+       action: member
+-      state: absent
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure user pinky is absent in HBAC Rule sshd-pinky
++  - name: Ensure test HBAC rule hbacrule01 hostgroup members are present
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      user: pinky
++      name: hbacrule01
++      hostgroup: testhostgroup01,testhostgroup02
+       action: member
+-      state: absent
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure user pinky is absent in HBAC Rule sshd-pinky again
++  - name: Ensure test HBAC rule hbacrule01 hostgroup members are present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      user: pinky
++      name: hbacrule01
++      hostgroup: testhostgroup01,testhostgroup02
+       action: member
+-      state: absent
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is disabled
++  - name: Ensure test HBAC rule hbacrule01 user members are present
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      state: disabled
++      name: hbacrule01
++      user: testuser01,testuser02
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is disabled again
++  - name: Ensure test HBAC rule hbacrule01 user members are present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      state: disabled
++      name: hbacrule01
++      user: testuser01,testuser02
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is enabled
++  - name: Ensure test HBAC rule hbacrule01 user group members are present
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      state: enabled
++      name: hbacrule01
++      group: testgroup01,testgroup02
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is enabled again
++  - name: Ensure test HBAC rule hbacrule01 user group members are present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      state: enabled
++      name: hbacrule01
++      group: testgroup01,testgroup02
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is absent
++  - name: Ensure test HBAC rule hbacrule01 hbacsvc members are present
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      state: absent
++      name: hbacrule01
++      hbacsvc: testhbacsvc01,testhbacsvc02
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule sshd-pinky is absent again
++  - name: Ensure test HBAC rule hbacrule01 hbacsvc members are present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: sshd-pinky
+-      state: absent
++      name: hbacrule01
++      hbacsvc: testhbacsvc01,testhbacsvc02
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure host "{{ groups.ipaserver[0] }}" is absent in HBAC Rule allhosts
++  - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are present
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
+-      host: "{{ groups.ipaserver[0] }}"
++      name: hbacrule01
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+       action: member
+-      state: absent
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure host "{{ groups.ipaserver[0] }}" is absent in HBAC Rule allhosts again
++  - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are present again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
+-      host: "{{ groups.ipaserver[0] }}"
++      name: hbacrule01
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+       action: member
++    register: result
++    failed_when: result.changed
++
++  # CHANGE TO DIFFERENT MEMBERS
++
++  - name: Ensure HBAC rule hbacrule01 is present with different hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups
++    ipahbacrule:
++      ipaadmin_password: MyPassword123
++      name: hbacrule01
++      host:
++      - "{{ 'testhost03.' + ipaserver_domain }}"
++      - "{{ 'testhost04.' + ipaserver_domain }}"
++      hostgroup: testhostgroup03,testhostgroup04
++      user: testuser03,testuser04
++      group: testgroup03,testgroup04
++      hbacsvc: testhbacsvc03,testhbacsvc04
++      hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure HBAC rule hbacrule01 is present with different hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups again
++    ipahbacrule:
++      ipaadmin_password: MyPassword123
++      name: hbacrule01
++      host:
++      - "{{ 'testhost03.' + ipaserver_domain }}"
++      - "{{ 'testhost04.' + ipaserver_domain }}"
++      hostgroup: testhostgroup03,testhostgroup04
++      user: testuser03,testuser04
++      group: testgroup03,testgroup04
++      hbacsvc: testhbacsvc03,testhbacsvc04
++      hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04
++    register: result
++    failed_when: result.changed
++
++  # ENSURE OLD TEST MEMBERS ARE ABSENT
++
++  - name: Ensure HBAC rule hbacrule01 members (same) are present
++    ipahbacrule:
++      ipaadmin_password: MyPassword123
++      name: hbacrule01
++      host:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      hostgroup: testhostgroup01,testhostgroup02
++      user: testuser01,testuser02
++      group: testgroup01,testgroup02
++      hbacsvc: testhbacsvc01,testhbacsvc02
++      hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
+       state: absent
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: Ensure HBAC Rule allhosts is absent
++  # ENSURE NEW TEST MEMBERS ARE ABSENT
++
++  - name: Ensure HBAC rule hbacrule01 members are absent
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
++      name: hbacrule01
++      host:
++      - "{{ 'testhost03.' + ipaserver_domain }}"
++      - "{{ 'testhost04.' + ipaserver_domain }}"
++      hostgroup: testhostgroup03,testhostgroup04
++      user: testuser03,testuser04
++      group: testgroup03,testgroup04
++      hbacsvc: testhbacsvc03,testhbacsvc04
++      hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04
+       state: absent
++      action: member
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: Ensure HBAC Rule allhosts is absent again
++  - name: Ensure HBAC rule hbacrule01 members are absent again
+     ipahbacrule:
+       ipaadmin_password: MyPassword123
+-      name: allhosts
++      name: hbacrule01
++      host:
++      - "{{ 'testhost03.' + ipaserver_domain }}"
++      - "{{ 'testhost04.' + ipaserver_domain }}"
++      hostgroup: testhostgroup03,testhostgroup04
++      user: testuser03,testuser04
++      group: testgroup03,testgroup04
++      hbacsvc: testhbacsvc03,testhbacsvc04
++      hbacsvcgroup: testhbacsvcgroup03,testhbacsvcgroup04
+       state: absent
++      action: member
+     register: result
+     failed_when: result.changed
+ 
+-  - name: User pinky absent
++  # CLEANUP TEST ITEMS
++
++  - name: Ensure test HBAC rule hbacrule01 is absent
++    ipahbacrule:
++      ipaadmin_password: MyPassword123
++      name: hbacrule01
++      state: absent
++
++  - name: Ensure test hosts are absent
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name:
++      - "{{ 'testhost01.' + ipaserver_domain }}"
++      - "{{ 'testhost02.' + ipaserver_domain }}"
++      - "{{ 'testhost03.' + ipaserver_domain }}"
++      - "{{ 'testhost04.' + ipaserver_domain }}"
++      state: absent
++
++  - name: Ensure test hostgroups are absent
++    ipahostgroup:
++      ipaadmin_password: MyPassword123
++      name: testhostgroup01,testhostgroup02,testhostgroup03,testhostgroup04
++      state: absent
++
++  - name: Ensure test users are absent
+     ipauser:
+       ipaadmin_password: MyPassword123
+-      name: pinky
++      name: testuser01,testuser02,testuser03,testuser04
+       state: absent
+ 
+-  - name: User group login absent
++  - name: Ensure test user groups are absent
+     ipagroup:
+       ipaadmin_password: MyPassword123
+-      name: login
++      name: testgroup01,testgroup02,testgroup03,testgroup04
++      state: absent
++
++  - name: Ensure test HBAC Services are absent
++    ipahbacsvc:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvc01,testhbacsvc02,testhbacsvc03,testhbacsvc04
++      state: absent
++
++  - name: Ensure test HBAC Service Groups are absent
++    ipahbacsvcgroup:
++      ipaadmin_password: MyPassword123
++      name: testhbacsvcgroup01,testhbacsvcgroup02,testhbacsvcgroup03,testhbacsvcgroup04
+       state: absent
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch
new file mode 100644
index 0000000..512a213
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch
@@ -0,0 +1,106 @@
+From 22d8784da29dcfede0744ef6b691b4506eae5deb Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Thu, 20 Feb 2020 12:58:11 +0100
+Subject: [PATCH] ipahost: Do not fail on missing DNS or zone when no IP
+ address given
+
+If no IP address is given and either DNS is not configured or if the zone is
+not found then ipahost may not fail in dnsrecord_find.
+
+The error happened for example by ensuring the absence of a host that is not
+part of the domain or for a host that has been added with force and is using
+a domain that is not served by the DNS server in the domain. It also
+happened if there was no DNS server in the domain at all.
+
+A new test case has been added to test_host_ipaddresses.yml
+
+The fix requires ipalib_errors provided by ansible_freeipa_module.
+
+Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1804838
+---
+ plugins/modules/ipahost.py           | 17 +++++++++++++++--
+ tests/host/test_host_ipaddresses.yml |  9 +++++++++
+ 2 files changed, 24 insertions(+), 2 deletions(-)
+
+diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
+index 558560e..062f768 100644
+--- a/plugins/modules/ipahost.py
++++ b/plugins/modules/ipahost.py
+@@ -409,7 +409,7 @@
+ from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
+     temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
+     module_params_get, gen_add_del_lists, encode_certificate, api_get_realm, \
+-    is_ipv4_addr, is_ipv6_addr
++    is_ipv4_addr, is_ipv6_addr, ipalib_errors
+ import six
+ 
+ 
+@@ -871,7 +871,20 @@ def main():
+ 
+             # Make sure host exists
+             res_find = find_host(ansible_module, name)
+-            res_find_dnsrecord = find_dnsrecord(ansible_module, name)
++            try:
++                res_find_dnsrecord = find_dnsrecord(ansible_module, name)
++            except ipalib_errors.NotFound as e:
++                msg = str(e)
++                if ip_address is None and \
++                   ("DNS is not configured" in msg or \
++                    "DNS zone not found" in msg):
++                    # IP address(es) not given and no DNS support in IPA
++                    # -> Ignore failure
++                    # IP address(es) not given and DNS zone is not found
++                    # -> Ignore failure
++                    res_find_dnsrecord = None
++                else:
++                    ansible_module.fail_json(msg="%s: %s" % (host, msg))
+ 
+             # Create command
+             if state == "present":
+diff --git a/tests/host/test_host_ipaddresses.yml b/tests/host/test_host_ipaddresses.yml
+index 0a97dd5..136a610 100644
+--- a/tests/host/test_host_ipaddresses.yml
++++ b/tests/host/test_host_ipaddresses.yml
+@@ -301,6 +301,15 @@
+     register: result
+     failed_when: result.changed
+ 
++  - name: Absent host01.ihavenodns.info test
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: host01.ihavenodns.info
++      state: absent
++    register: result
++    failed_when: result.changed
++
+   - name: Host absent
+     ipahost:
+       ipaadmin_password: MyPassword123
+From 4d94cb09a9fb09dd2576223b9be7f77d515202fb Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Thu, 20 Feb 2020 12:54:32 +0100
+Subject: [PATCH] ansible_freeipa_module: Import ipalib.errors as ipalib_errors
+
+For beeing able to catch ipalib.errors.NotFound errors in ipahost it is
+needed to import ipalib.errors. ipalib.errors is now imported as
+ipalib_errors to not have name conflicts with the errors list used in some
+of the modules.
+
+Related: https://bugzilla.redhat.com/show_bug.cgi?id=1804838
+---
+ plugins/module_utils/ansible_freeipa_module.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
+index 6acdbef..5066de3 100644
+--- a/plugins/module_utils/ansible_freeipa_module.py
++++ b/plugins/module_utils/ansible_freeipa_module.py
+@@ -28,6 +28,7 @@
+ import gssapi
+ from datetime import datetime
+ from ipalib import api
++from ipalib import errors as ipalib_errors
+ from ipalib.config import Env
+ from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
+ try:
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch
new file mode 100644
index 0000000..aea3a62
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch
@@ -0,0 +1,51 @@
+From 24515e40ad289552d45bddd33c7a0dda93117a7f Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Wed, 18 Dec 2019 12:28:03 +0100
+Subject: [PATCH] ipahost: Enhanced failure msg for member params used without
+ member action
+
+The failure message if member parameters like certificate, managedby_host,
+principal, allow_create_keytab_* and allow_retrieve_keytab_* are used
+without member action for state absent has been enhanced to propose the
+member action.
+---
+ plugins/modules/ipahost.py | 20 +++++++++++++-------
+ 1 file changed, 13 insertions(+), 7 deletions(-)
+
+diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
+index ec5e196..8ee9532 100644
+--- a/plugins/modules/ipahost.py
++++ b/plugins/modules/ipahost.py
+@@ -511,19 +511,25 @@ def check_parameters(
+                    "userclass", "auth_ind", "requires_pre_auth",
+                    "ok_as_delegate", "ok_to_auth_as_delegate", "force",
+                    "reverse", "ip_address", "update_password"]
++        for x in invalid:
++            if vars()[x] is not None:
++                module.fail_json(
++                    msg="Argument '%s' can not be used with state '%s'" %
++                    (x, state))
+         if action == "host":
+-            invalid.extend([
++            invalid = [
+                 "certificate", "managedby_host", "principal",
+                 "allow_create_keytab_user", "allow_create_keytab_group",
+                 "allow_create_keytab_host", "allow_create_keytab_hostgroup",
+                 "allow_retrieve_keytab_user", "allow_retrieve_keytab_group",
+                 "allow_retrieve_keytab_host",
+-                "allow_retrieve_keytab_hostgroup"])
+-        for x in invalid:
+-            if vars()[x] is not None:
+-                module.fail_json(
+-                    msg="Argument '%s' can not be used with state '%s'" %
+-                    (x, state))
++                "allow_retrieve_keytab_hostgroup"
++            ]
++            for x in invalid:
++                if vars()[x] is not None:
++                    module.fail_json(
++                        msg="Argument '%s' can only be used with action "
++                        "'member' for state '%s'" % (x, state))
+ 
+ 
+ def main():
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch
new file mode 100644
index 0000000..fa2dadd
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch
@@ -0,0 +1,57 @@
+From 0816b0773b1535780c7c3e5f05bda39434ab6bac Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Fri, 14 Feb 2020 13:21:54 +0100
+Subject: [PATCH] ipahost: Fail on action member for new hosts, fix
+ dnsrecord_add reverse flag
+
+The check to make sure that member can not be used on non existing hosts
+has bee missing. Also the reverse flag for the dnsrecord_add call was None
+if the varaible was not set.
+
+Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1803026
+---
+ plugins/modules/ipahost.py | 23 +++++++++++++++--------
+ 1 file changed, 15 insertions(+), 8 deletions(-)
+
+diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
+index a5fd482..558560e 100644
+--- a/plugins/modules/ipahost.py
++++ b/plugins/modules/ipahost.py
+@@ -1005,6 +1005,11 @@ def main():
+                                 dnsrecord_args.get("aaaarecord"),
+                                 _dnsrec.get("aaaarecord"))
+ 
++                else:
++                    if res_find is None:
++                        ansible_module.fail_json(
++                            msg="No host '%s'" % name)
++
+                 if action != "host" or (action == "host" and res_find is None):
+                     certificate_add = certificate or []
+                     certificate_del = []
+@@ -1178,15 +1183,17 @@ def main():
+                     domain_name = name[name.find(".")+1:]
+                     host_name = name[:name.find(".")]
+ 
++                    _args = {"idnsname": host_name}
++                    if reverse is not None:
++                        _args["a_extra_create_reverse"] = reverse
++                        _args["aaaa_extra_create_reverse"] = reverse
++                    if len(dnsrecord_a_add) > 0:
++                        _args["arecord"] = dnsrecord_a_add
++                    if len(dnsrecord_aaaa_add) > 0:
++                        _args["aaaarecord"] = dnsrecord_aaaa_add
++
+                     commands.append([domain_name,
+-                                     "dnsrecord_add",
+-                                     {
+-                                         "idnsname": host_name,
+-                                         "arecord": dnsrecord_a_add,
+-                                         "a_extra_create_reverse": reverse,
+-                                         "aaaarecord": dnsrecord_aaaa_add,
+-                                         "aaaa_extra_create_reverse": reverse
+-                                     }])
++                                     "dnsrecord_add", _args])
+ 
+                 if len(dnsrecord_a_del) > 0 or len(dnsrecord_aaaa_del) > 0:
+                     domain_name = name[name.find(".")+1:]
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch
new file mode 100644
index 0000000..592da6e
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch
@@ -0,0 +1,78 @@
+From b6100f0c19e2caf73ab70bbc572d3e47e6066b48 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Tue, 17 Dec 2019 14:04:43 +0100
+Subject: [PATCH] ipahost: Fix choices of auth_ind parameter, allow to reset
+ parameter
+
+The choices for the auth_ind parameter have been wrong. The choices are now
+['radius', 'otp', 'pkinit', 'hardened', '']. The empty string has been added
+to be able to rest auth_ind for the host entry.
+---
+ README-host.md             |  2 +-
+ plugins/modules/ipahost.py | 15 ++++++++++++---
+ 2 files changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/README-host.md b/README-host.md
+index edec8d9..be5ad79 100644
+--- a/README-host.md
++++ b/README-host.md
+@@ -280,7 +280,7 @@ Variable | Description | Required
+ `mac_address` \| `macaddress` | List of hardware MAC addresses. | no
+ `sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no
+ `userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no
+-`auth_ind` \| `krbprincipalauthind` | Defines a whitelist for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened"] | no
++`auth_ind` \| `krbprincipalauthind` | Defines a whitelist for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened", ""] | no
+ `requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no
+ `ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no
+ `ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no
+diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
+index ec5e196..b130395 100644
+--- a/plugins/modules/ipahost.py
++++ b/plugins/modules/ipahost.py
+@@ -147,9 +147,10 @@
+           Defines a whitelist for Authentication Indicators. Use 'otp' to allow
+           OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA
+           authentications. Other values may be used for custom configurations.
++          Use empty string to reset auth_ind to the initial value.
+         type: list
+         aliases: ["krbprincipalauthind"]
+-        choices: ["radius", "otp", "pkinit", "hardened"]
++        choices: ["radius", "otp", "pkinit", "hardened", ""]
+         required: false
+       requires_pre_auth:
+         description: Pre-authentication is required for the service
+@@ -277,9 +278,10 @@
+       Defines a whitelist for Authentication Indicators. Use 'otp' to allow
+       OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA
+       authentications. Other values may be used for custom configurations.
++      Use empty string to reset auth_ind to the initial value.
+     type: list
+     aliases: ["krbprincipalauthind"]
+-    choices: ["radius", "otp", "pkinit", "hardened"]
++    choices: ["radius", "otp", "pkinit", "hardened", ""]
+     required: false
+   requires_pre_auth:
+     description: Pre-authentication is required for the service
+@@ -590,7 +592,7 @@ def main():
+                        default=None),
+         auth_ind=dict(type='list', aliases=["krbprincipalauthind"],
+                       default=None,
+-                      choices=['password', 'radius', 'otp']),
++                      choices=['radius', 'otp', 'pkinit', 'hardened', '']),
+         requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"],
+                                default=None),
+         ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"],
+@@ -835,6 +837,13 @@ def main():
+                             if x in args:
+                                 del args[x]
+ 
++                        # Ignore auth_ind if it is empty (for resetting)
++                        # and not set in for the host
++                        if "krbprincipalauthind" not in res_find and \
++                           "krbprincipalauthind" in args and \
++                           args["krbprincipalauthind"] == ['']:
++                            del args["krbprincipalauthind"]
++
+                         # For all settings is args, check if there are
+                         # different settings in the find result.
+                         # If yes: modify
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch b/SOURCES/ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch
new file mode 100644
index 0000000..bde4624
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch
@@ -0,0 +1,179 @@
+From 4dd1d25eacd1481be0a881a017144ff4d3396ccd Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Thu, 6 Feb 2020 15:38:00 +0100
+Subject: [PATCH] ipapwpolicy: Use global_policy if name is not set
+
+If the name is not set, the policy global_policy is now used. It was needed
+before to explicitly name the global_policy. Also a check has been added
+to fail early if global_policy is used with state absent.
+
+The README for pwpolicy has been extended with an example for global_policy
+and also the description of the name variable.
+
+The test has also been extended to check a change of maxlife for
+global_policy and that global_policy can not be used with state: absent
+
+Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1797532
+---
+ README-pwpolicy.md               | 19 +++++++++++--
+ plugins/modules/ipapwpolicy.py   |  9 ++++--
+ tests/pwpolicy/test_pwpolicy.yml | 49 ++++++++++++++++++++++++++++++++
+ 3 files changed, 73 insertions(+), 4 deletions(-)
+
+diff --git a/README-pwpolicy.md b/README-pwpolicy.md
+index 16306b7..847b32d 100644
+--- a/README-pwpolicy.md
++++ b/README-pwpolicy.md
+@@ -56,7 +56,7 @@ Example playbook to ensure presence of pwpolicies for exisiting group ops:
+       maxfail: 3
+ ```
+ 
+-Example playbook to ensure absence of pwpolicies for group ops
++Example playbook to ensure absence of pwpolicies for group ops:
+ 
+ ```yaml
+ ---
+@@ -72,6 +72,21 @@ Example playbook to ensure absence of pwpolicies for group ops
+       state: absent
+ ```
+ 
++Example playbook to ensure maxlife is set to 49 in global policy:
++
++```yaml
++---
++- name: Playbook to handle pwpolicies
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  # Ensure absence of pwpolicies for group ops
++  - ipapwpolicy:
++      ipaadmin_password: MyPassword123
++      maxlife: 49
++```
++
+ 
+ Variables
+ =========
+@@ -83,7 +98,7 @@ 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
+-`name` \| `cn` | The list of pwpolicy name strings. | no
++`name` \| `cn` | The list of pwpolicy name strings. If name is not given, `global_policy` will be used automatically. | no
+ `maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int) | no
+ `minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int) | no
+ `history` \| `krbpwdhistorylength` | Password history size. (int) | no
+diff --git a/plugins/modules/ipapwpolicy.py b/plugins/modules/ipapwpolicy.py
+index 9437b59..f168703 100644
+--- a/plugins/modules/ipapwpolicy.py
++++ b/plugins/modules/ipapwpolicy.py
+@@ -167,7 +167,7 @@ def main():
+             ipaadmin_password=dict(type="str", required=False, no_log=True),
+ 
+             name=dict(type="list", aliases=["cn"], default=None,
+-                      required=True),
++                      required=False),
+             # present
+ 
+             maxlife=dict(type="int", aliases=["krbmaxpwdlife"], default=None),
+@@ -218,6 +218,9 @@ def main():
+ 
+     # Check parameters
+ 
++    if names is None:
++        names = ["global_policy"]
++
+     if state == "present":
+         if len(names) != 1:
+             ansible_module.fail_json(
+@@ -225,8 +228,10 @@ def main():
+ 
+     if state == "absent":
+         if len(names) < 1:
++            ansible_module.fail_json(msg="No name given.")
++        if "global_policy" in names:
+             ansible_module.fail_json(
+-                msg="No name given.")
++                msg="'global_policy' can not be made absent.")
+         invalid = ["maxlife", "minlife", "history", "minclasses",
+                    "minlength", "priority", "maxfail", "failinterval",
+                    "lockouttime"]
+diff --git a/tests/pwpolicy/test_pwpolicy.yml b/tests/pwpolicy/test_pwpolicy.yml
+index 5c69345..f93f275 100644
+--- a/tests/pwpolicy/test_pwpolicy.yml
++++ b/tests/pwpolicy/test_pwpolicy.yml
+@@ -5,10 +5,30 @@
+   gather_facts: false
+ 
+   tasks:
++  - name: Ensure maxlife of 90 for global_policy
++    ipapwpolicy:
++      ipaadmin_password: SomeADMINpassword
++      maxlife: 90
++
++  - name: Ensure absence of group ops
++    ipagroup:
++      ipaadmin_password: SomeADMINpassword
++      name: ops
++      state: absent
++
++  - name: Ensure absence of pwpolicies for group ops
++    ipapwpolicy:
++      ipaadmin_password: SomeADMINpassword
++      name: ops
++      state: absent
++
+   - name: Ensure presence of group ops
+     ipagroup:
+       ipaadmin_password: SomeADMINpassword
+       name: ops
++      state: present
++    register: result
++    failed_when: not result.changed
+ 
+   - name: Ensure presence of pwpolicies for group ops
+     ipapwpolicy:
+@@ -42,6 +62,28 @@
+     register: result
+     failed_when: result.changed
+ 
++  - name: Ensure maxlife of 49 for global_policy
++    ipapwpolicy:
++      ipaadmin_password: SomeADMINpassword
++      maxlife: 49
++    register: result
++    failed_when: not result.changed
++
++  - name: Ensure maxlife of 49 for global_policy again
++    ipapwpolicy:
++      ipaadmin_password: SomeADMINpassword
++      maxlife: 49
++    register: result
++    failed_when: result.changed
++
++  - name: Ensure absence of pwpoliciy global_policy will fail
++    ipapwpolicy:
++      ipaadmin_password: SomeADMINpassword
++      state: absent
++    register: result
++    ignore_errors: True
++    failed_when: result is defined and result
++
+   - name: Ensure absence of pwpolicies for group ops
+     ipapwpolicy:
+       ipaadmin_password: SomeADMINpassword
+@@ -50,6 +92,13 @@
+     register: result
+     failed_when: not result.changed
+ 
++  - name: Ensure maxlife of 90 for global_policy
++    ipapwpolicy:
++      ipaadmin_password: MyPassword123
++      maxlife: 90
++    register: result
++    failed_when: not result.changed
++
+   - name: Ensure absence of pwpolicies for group ops
+     ipapwpolicy:
+       ipaadmin_password: SomeADMINpassword
diff --git a/SOURCES/ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch b/SOURCES/ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch
new file mode 100644
index 0000000..cb0a7ef
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch
@@ -0,0 +1,116 @@
+From 36c1c837086c42049f09cf689a1ebd61627abae0 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Tue, 17 Dec 2019 15:30:45 +0100
+Subject: [PATCH] ipauser: Allow reset of userauthtype, do not depend on
+ first,last for mod
+
+It was not possible to reset the userauthtype. The empty string has been
+added to userauthtype for this.
+
+Also ipauser will only depend on given first and last name if the user
+does not exist yet. For the update operation these parameters are not
+needed anymore.
+---
+ README-user.md             |  2 +-
+ plugins/modules/ipauser.py | 38 ++++++++++++++++++++++++++------------
+ 2 files changed, 27 insertions(+), 13 deletions(-)
+
+diff --git a/README-user.md b/README-user.md
+index 56772a7..991121c 100644
+--- a/README-user.md
++++ b/README-user.md
+@@ -408,7 +408,7 @@ Variable | Description | Required
+ `manager` | List of manager user names. | no
+ `carlicense` | List of car licenses. | no
+ `sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no
+-`userauthtype` | List of supported user authentication types. Choices: `password`, `radius` and `otp` | no
++`userauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp` and ``. Use empty string to reset userauthtype to the initial value. | no
+ `userclass` | User category. (semantics placed on this attribute are for local interpretation). | no
+ `radius` | RADIUS proxy configuration  | no
+ `radiususer` | RADIUS proxy username | no
+diff --git a/plugins/modules/ipauser.py b/plugins/modules/ipauser.py
+index ac45295..36e8bae 100644
+--- a/plugins/modules/ipauser.py
++++ b/plugins/modules/ipauser.py
+@@ -153,9 +153,12 @@
+         required: false
+         aliases: ["ipasshpubkey"]
+       userauthtype:
+-        description: List of supported user authentication types
+-        choices=['password', 'radius', 'otp']
++        description:
++          List of supported user authentication types
++          Use empty string to reset userauthtype to the initial value.
++        choices=['password', 'radius', 'otp', '']
+         required: false
++        aliases: ["ipauserauthtype"]
+       userclass:
+         description:
+         - User category
+@@ -310,9 +313,12 @@
+     required: false
+     aliases: ["ipasshpubkey"]
+   userauthtype:
+-    description: List of supported user authentication types
+-    choices=['password', 'radius', 'otp']
++    description:
++      List of supported user authentication types
++      Use empty string to reset userauthtype to the initial value.
++    choices=['password', 'radius', 'otp', '']
+     required: false
++    aliases: ["ipauserauthtype"]
+   userclass:
+     description:
+     - User category
+@@ -701,7 +707,7 @@ def main():
+                        default=None),
+         userauthtype=dict(type='list', aliases=["ipauserauthtype"],
+                           default=None,
+-                          choices=['password', 'radius', 'otp']),
++                          choices=['password', 'radius', 'otp', '']),
+         userclass=dict(type="list", aliases=["class"],
+                        default=None),
+         radius=dict(type="str", aliases=["ipatokenradiusconfiglink"],
+@@ -845,13 +851,6 @@ def main():
+         if names is not None and len(names) != 1:
+             ansible_module.fail_json(
+                 msg="Only one user can be added at a time using name.")
+-        if action != "member":
+-            # Only check first and last here if names is set
+-            if names is not None:
+-                if first is None:
+-                    ansible_module.fail_json(msg="First name is needed")
+-                if last is None:
+-                    ansible_module.fail_json(msg="Last name is needed")
+ 
+     check_parameters(
+         ansible_module, state, action,
+@@ -1011,6 +1010,13 @@ def main():
+                         if "noprivate" in args:
+                             del args["noprivate"]
+ 
++                        # Ignore userauthtype if it is empty (for resetting)
++                        # and not set in for the user
++                        if "ipauserauthtype" not in res_find and \
++                           "ipauserauthtype" in args and \
++                           args["ipauserauthtype"] == ['']:
++                            del args["ipauserauthtype"]
++
+                         # For all settings is args, check if there are
+                         # different settings in the find result.
+                         # If yes: modify
+@@ -1019,6 +1025,14 @@ def main():
+                             commands.append([name, "user_mod", args])
+ 
+                     else:
++                        # Make sure we have a first and last name
++                        if first is None:
++                            ansible_module.fail_json(
++                                msg="First name is needed")
++                        if last is None:
++                            ansible_module.fail_json(
++                                msg="Last name is needed")
++
+                         commands.append([name, "user_add", args])
+ 
+                     # Handle members: principal, manager, certificate and
diff --git a/SOURCES/ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch b/SOURCES/ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch
new file mode 100644
index 0000000..4f2093d
--- /dev/null
+++ b/SOURCES/ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch
@@ -0,0 +1,915 @@
+From 167c76311da72c2bfabf4b2bce9e128c11d519d0 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Wed, 12 Feb 2020 16:54:13 +0100
+Subject: [PATCH] ipahost: Add support for several IP addresses and also to
+ change them
+
+ipahost was so far ignoring IP addresses when the host already existed.
+This happened because host_mod is not providing functionality to do this.
+Now ipaddress is a list and it is possible to ensure a host with several
+IP addresses (these can be IPv4 and IPv6). Also it is possible to ensure
+presence and absence of IP addresses for an exising host using action
+member.
+
+There are no IP address conclict checks as this would lead into issues with
+updating an existing host that already is using a duplicate IP address for
+example for round-robin (RR). Also this might lead into issues with ensuring
+a new host with several IP addresses in this case. Also to ensure a list of
+hosts with changing the IP address of one host to another in the list would
+result in issues here.
+
+New example playbooks have been added:
+
+    playbooks/host/host-present-with-several-ip-addresses.yml
+    playbooks/host/host-member-ipaddresses-absent.yml
+    playbooks/host/host-member-ipaddresses-present.yml
+
+A new test has been added for verification:
+
+    tests/host/test_host_ipaddresses.yml
+
+Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1783976
+       https://bugzilla.redhat.com/show_bug.cgi?id=1783979
+---
+ README-host.md                                |  79 ++++-
+ .../host/host-member-ipaddresses-absent.yml   |  17 +
+ .../host/host-member-ipaddresses-present.yml  |  16 +
+ ...host-present-with-several-ip-addresses.yml |  24 ++
+ .../module_utils/ansible_freeipa_module.py    |  23 ++
+ plugins/modules/ipahost.py                    | 179 +++++++---
+ tests/host/test_host_ipaddresses.yml          | 312 ++++++++++++++++++
+ 7 files changed, 600 insertions(+), 50 deletions(-)
+ create mode 100644 playbooks/host/host-member-ipaddresses-absent.yml
+ create mode 100644 playbooks/host/host-member-ipaddresses-present.yml
+ create mode 100644 playbooks/host/host-present-with-several-ip-addresses.yml
+ create mode 100644 tests/host/test_host_ipaddresses.yml
+
+diff --git a/README-host.md b/README-host.md
+index be5ad79..ecc59a9 100644
+--- a/README-host.md
++++ b/README-host.md
+@@ -65,6 +65,79 @@ Example playbook to ensure host presence:
+       - "52:54:00:BD:97:1E"
+       state: present
+ ```
++Compared to `ipa host-add` command no IP address conflict check is done as the ipahost module supports to have several IPv4 and IPv6 addresses for a host.
++
++
++Example playbook to ensure host presence with several IP addresses:
++
++```yaml
++---
++- name: Playbook to handle hosts
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  # Ensure host is present
++  - ipahost:
++      ipaadmin_password: MyPassword123
++      name: host01.example.com
++      description: Example host
++      ip_address:
++      - 192.168.0.123
++      - 192.168.0.124
++      - fe80::20c:29ff:fe02:a1b3
++      - fe80::20c:29ff:fe02:a1b4
++      locality: Lab
++      ns_host_location: Lab
++      ns_os_version: CentOS 7
++      ns_hardware_platform: Lenovo T61
++      mac_address:
++      - "08:00:27:E3:B1:2D"
++      - "52:54:00:BD:97:1E"
++      state: present
++```
++
++
++Example playbook to ensure IP addresses are present for a host:
++
++```yaml
++---
++- name: Playbook to handle hosts
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  # Ensure host is present
++  - ipahost:
++      ipaadmin_password: MyPassword123
++      name: host01.example.com
++      ip_address:
++      - 192.168.0.124
++      - fe80::20c:29ff:fe02:a1b4
++      action: member
++      state: present
++```
++
++
++Example playbook to ensure IP addresses are absent for a host:
++
++```yaml
++---
++- name: Playbook to handle hosts
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  # Ensure host is present
++  - ipahost:
++      ipaadmin_password: MyPassword123
++      name: host01.example.com
++      ip_address:
++      - 192.168.0.124
++      - fe80::20c:29ff:fe02:a1b4
++      action: member
++      state: absent
++```
+ 
+ 
+ Example playbook to ensure host presence without DNS:
+@@ -215,7 +288,7 @@ Example playbook to disable a host:
+       update_dns: yes
+       state: disabled
+ ```
+-`update_dns` controls if the DNS entries will be updated.
++`update_dns` controls if the DNS entries will be updated in this case. For `state` present it is controlling the update of the DNS SSHFP records, but not the the other DNS records.
+ 
+ 
+ Example playbook to ensure a host is absent:
+@@ -286,8 +359,8 @@ Variable | Description | Required
+ `ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no
+ `force` | Force host name even if not in DNS. | no
+ `reverse` | Reverse DNS detection. | no
+-`ip_address` \| `ipaddress` | The host IP address. | no
+-`update_dns` | Update DNS entries. | no
++`ip_address` \| `ipaddress` | The host IP address list. It can contain IPv4 and IPv6 addresses. No conflict check for IP addresses is done. | no
++`update_dns` | For existing hosts: DNS SSHFP records are updated with `state` present and all DNS entries for a host removed with `state` absent. | no
+ 
+ 
+ Return Values
+diff --git a/playbooks/host/host-member-ipaddresses-absent.yml b/playbooks/host/host-member-ipaddresses-absent.yml
+new file mode 100644
+index 0000000..2466dbd
+--- /dev/null
++++ b/playbooks/host/host-member-ipaddresses-absent.yml
+@@ -0,0 +1,17 @@
++---
++- name: Host member IP addresses absent
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  - name: Ensure host01.example.com IP addresses absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: host01.example.com
++      ip_address:
++      - 192.168.0.123
++      - fe80::20c:29ff:fe02:a1b3
++      - 192.168.0.124
++      - fe80::20c:29ff:fe02:a1b4
++      action: member
++      state: absent
+diff --git a/playbooks/host/host-member-ipaddresses-present.yml b/playbooks/host/host-member-ipaddresses-present.yml
+new file mode 100644
+index 0000000..f473993
+--- /dev/null
++++ b/playbooks/host/host-member-ipaddresses-present.yml
+@@ -0,0 +1,16 @@
++---
++- name: Host member IP addresses present
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  - name: Ensure host01.example.com IP addresses present
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: host01.example.com
++      ip_address:
++      - 192.168.0.123
++      - fe80::20c:29ff:fe02:a1b3
++      - 192.168.0.124
++      - fe80::20c:29ff:fe02:a1b4
++      action: member
+diff --git a/playbooks/host/host-present-with-several-ip-addresses.yml b/playbooks/host/host-present-with-several-ip-addresses.yml
+new file mode 100644
+index 0000000..4956562
+--- /dev/null
++++ b/playbooks/host/host-present-with-several-ip-addresses.yml
+@@ -0,0 +1,24 @@
++---
++- name: Host present with several IP addresses
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  - name: Ensure host is present
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: host01.example.com
++      description: Example host
++      ip_address:
++      - 192.168.0.123
++      - fe80::20c:29ff:fe02:a1b3
++      - 192.168.0.124
++      - fe80::20c:29ff:fe02:a1b4
++      locality: Lab
++      ns_host_location: Lab
++      ns_os_version: CentOS 7
++      ns_hardware_platform: Lenovo T61
++      mac_address:
++      - "08:00:27:E3:B1:2D"
++      - "52:54:00:BD:97:1E"
++      state: present
+diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
+index 9e97b88..6acdbef 100644
+--- a/plugins/module_utils/ansible_freeipa_module.py
++++ b/plugins/module_utils/ansible_freeipa_module.py
+@@ -42,6 +42,7 @@
+     from ipalib.x509 import Encoding
+ except ImportError:
+     from cryptography.hazmat.primitives.serialization import Encoding
++import socket
+ import base64
+ import six
+ 
+@@ -285,3 +286,25 @@ def encode_certificate(cert):
+     if not six.PY2:
+         encoded = encoded.decode('ascii')
+     return encoded
++
++
++def is_ipv4_addr(ipaddr):
++    """
++    Test if figen IP address is a valid IPv4 address
++    """
++    try:
++        socket.inet_pton(socket.AF_INET, ipaddr)
++    except socket.error:
++        return False
++    return True
++
++
++def is_ipv6_addr(ipaddr):
++    """
++    Test if figen IP address is a valid IPv6 address
++    """
++    try:
++        socket.inet_pton(socket.AF_INET6, ipaddr)
++    except socket.error:
++        return False
++    return True
+diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
+index dba4181..a5fd482 100644
+--- a/plugins/modules/ipahost.py
++++ b/plugins/modules/ipahost.py
+@@ -176,11 +176,16 @@
+         default: true
+         required: false
+       ip_address:
+-        description: The host IP address
++        description:
++          The host IP address list (IPv4 and IPv6). No IP address conflict
++          check will be done.
+         aliases: ["ipaddress"]
+         required: false
+       update_dns:
+-        description: Update DNS entries
++        description:
++          Controls the update of the DNS SSHFP records for existing hosts and
++          the removal of all DNS entries if a host gets removed with state
++          absent.
+         required: false
+   description:
+     description: The host description
+@@ -306,11 +311,16 @@
+     default: true
+     required: false
+   ip_address:
+-    description: The host IP address
++    description:
++      The host IP address list (IPv4 and IPv6). No IP address conflict
++      check will be done.
+     aliases: ["ipaddress"]
+     required: false
+   update_dns:
+-    description: Update DNS entries
++    description:
++      Controls the update of the DNS SSHFP records for existing hosts and
++      the removal of all DNS entries if a host gets removed with state
++      absent.
+     required: false
+   update_password:
+     description:
+@@ -398,7 +408,8 @@
+ from ansible.module_utils._text import to_text
+ from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
+     temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
+-    module_params_get, gen_add_del_lists, encode_certificate, api_get_realm
++    module_params_get, gen_add_del_lists, encode_certificate, api_get_realm, \
++    is_ipv4_addr, is_ipv6_addr
+ import six
+ 
+ 
+@@ -428,6 +439,32 @@ def find_host(module, name):
+         return None
+ 
+ 
++def find_dnsrecord(module, name):
++    domain_name = name[name.find(".")+1:]
++    host_name = name[:name.find(".")]
++
++    _args = {
++        "all": True,
++        "idnsname": to_text(host_name),
++    }
++
++    _result = api_command(module, "dnsrecord_find", to_text(domain_name),
++                          _args)
++
++    if len(_result["result"]) > 1:
++        module.fail_json(
++            msg="There is more than one host '%s'" % (name))
++    elif len(_result["result"]) == 1:
++        _res = _result["result"][0]
++        certs = _res.get("usercertificate")
++        if certs is not None:
++            _res["usercertificate"] = [encode_certificate(cert) for
++                                       cert in certs]
++        return _res
++    else:
++        return None
++
++
+ def show_host(module, name):
+     _result = api_command(module, "host_show", to_text(name), {})
+     return _result["result"]
+@@ -470,16 +507,34 @@ def gen_args(description, locality, location, platform, os, password, random,
+         _args["ipakrboktoauthasdelegate"] = ok_to_auth_as_delegate
+     if force is not None:
+         _args["force"] = force
+-    if reverse is not None:
+-        _args["no_reverse"] = not reverse
+     if ip_address is not None:
+-        _args["ip_address"] = ip_address
++        # IP addresses are handed extra, therefore it is needed to set
++        # the force option here to make sure that host-add is able to
++        # add a host without IP address.
++        _args["force"] = True
+     if update_dns is not None:
+         _args["updatedns"] = update_dns
+ 
+     return _args
+ 
+ 
++def gen_dnsrecord_args(module, ip_address, reverse):
++    _args = {}
++    if reverse is not None:
++        _args["a_extra_create_reverse"] = reverse
++        _args["aaaa_extra_create_reverse"] = reverse
++    if ip_address is not None:
++        for ip in ip_address:
++            if is_ipv4_addr(ip):
++                _args.setdefault("arecord", []).append(ip)
++            elif is_ipv6_addr(ip):
++                _args.setdefault("aaaarecord", []).append(ip)
++            else:
++                module.fail_json(msg="'%s' is not a valid IP address." % ip)
++
++    return _args
++
++
+ def check_parameters(
+         module, state, action,
+         description, locality, location, platform, os, password, random,
+@@ -499,8 +554,7 @@ def check_parameters(
+                        "os", "password", "random", "mac_address", "sshpubkey",
+                        "userclass", "auth_ind", "requires_pre_auth",
+                        "ok_as_delegate", "ok_to_auth_as_delegate", "force",
+-                       "reverse", "ip_address", "update_dns",
+-                       "update_password"]
++                       "reverse", "update_dns", "update_password"]
+             for x in invalid:
+                 if vars()[x] is not None:
+                     module.fail_json(
+@@ -512,7 +566,7 @@ def check_parameters(
+                    "password", "random", "mac_address", "sshpubkey",
+                    "userclass", "auth_ind", "requires_pre_auth",
+                    "ok_as_delegate", "ok_to_auth_as_delegate", "force",
+-                   "reverse", "ip_address", "update_password"]
++                   "reverse", "update_password"]
+         for x in invalid:
+             if vars()[x] is not None:
+                 module.fail_json(
+@@ -549,9 +603,6 @@ def main():
+                       default=None, no_log=True),
+         random=dict(type="bool", aliases=["random_password"],
+                     default=None),
+-
+-
+-
+         certificate=dict(type="list", aliases=["usercertificate"],
+                          default=None),
+         managedby_host=dict(type="list",
+@@ -608,7 +659,7 @@ def main():
+                                     default=None),
+         force=dict(type='bool', default=None),
+         reverse=dict(type='bool', default=None),
+-        ip_address=dict(type="str", aliases=["ipaddress"],
++        ip_address=dict(type="list", aliases=["ipaddress"],
+                         default=None),
+         update_dns=dict(type="bool", aliases=["updatedns"],
+                         default=None),
+@@ -820,6 +871,7 @@ def main():
+ 
+             # Make sure host exists
+             res_find = find_host(ansible_module, name)
++            res_find_dnsrecord = find_dnsrecord(ansible_module, name)
+ 
+             # Create command
+             if state == "present":
+@@ -829,6 +881,8 @@ def main():
+                     random, mac_address, sshpubkey, userclass, auth_ind,
+                     requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
+                     force, reverse, ip_address, update_dns)
++                dnsrecord_args = gen_dnsrecord_args(
++                    ansible_module, ip_address, reverse)
+ 
+                 if action == "host":
+                     # Found the host
+@@ -938,39 +992,20 @@ def main():
+                                 res_find.get(
+                                     "ipaallowedtoperform_read_keys_hostgroup"))
+ 
+-                    else:
+-                        certificate_add = certificate or []
+-                        certificate_del = []
+-                        managedby_host_add = managedby_host or []
+-                        managedby_host_del = []
+-                        principal_add = principal or []
+-                        principal_del = []
+-                        allow_create_keytab_user_add = \
+-                            allow_create_keytab_user or []
+-                        allow_create_keytab_user_del = []
+-                        allow_create_keytab_group_add = \
+-                            allow_create_keytab_group or []
+-                        allow_create_keytab_group_del = []
+-                        allow_create_keytab_host_add = \
+-                            allow_create_keytab_host or []
+-                        allow_create_keytab_host_del = []
+-                        allow_create_keytab_hostgroup_add = \
+-                            allow_create_keytab_hostgroup or []
+-                        allow_create_keytab_hostgroup_del = []
+-                        allow_retrieve_keytab_user_add = \
+-                            allow_retrieve_keytab_user or []
+-                        allow_retrieve_keytab_user_del = []
+-                        allow_retrieve_keytab_group_add = \
+-                            allow_retrieve_keytab_group or []
+-                        allow_retrieve_keytab_group_del = []
+-                        allow_retrieve_keytab_host_add = \
+-                            allow_retrieve_keytab_host or []
+-                        allow_retrieve_keytab_host_del = []
+-                        allow_retrieve_keytab_hostgroup_add = \
+-                            allow_retrieve_keytab_hostgroup or []
+-                        allow_retrieve_keytab_hostgroup_del = []
++                        # IP addresses are not really a member of hosts, but
++                        # we will simply treat it as this to enable the
++                        # addition and removal of IPv4 and IPv6 addresses in
++                        # a simple way.
++                        _dnsrec = res_find_dnsrecord or {}
++                        dnsrecord_a_add, dnsrecord_a_del = gen_add_del_lists(
++                            dnsrecord_args.get("arecord"),
++                            _dnsrec.get("arecord"))
++                        dnsrecord_aaaa_add, dnsrecord_aaaa_del = \
++                            gen_add_del_lists(
++                                dnsrecord_args.get("aaaarecord"),
++                                _dnsrec.get("aaaarecord"))
+ 
+-                else:
++                if action != "host" or (action == "host" and res_find is None):
+                     certificate_add = certificate or []
+                     certificate_del = []
+                     managedby_host_add = managedby_host or []
+@@ -1001,6 +1036,10 @@ def main():
+                     allow_retrieve_keytab_hostgroup_add = \
+                         allow_retrieve_keytab_hostgroup or []
+                     allow_retrieve_keytab_hostgroup_del = []
++                    dnsrecord_a_add = dnsrecord_args.get("arecord") or []
++                    dnsrecord_a_del = []
++                    dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or []
++                    dnsrecord_aaaa_del = []
+ 
+                 # Remove canonical principal from principal_del
+                 canonical_principal = "host/" + name + "@" + server_realm
+@@ -1135,6 +1174,36 @@ def main():
+                              "hostgroup": allow_retrieve_keytab_hostgroup_del,
+                          }])
+ 
++                if len(dnsrecord_a_add) > 0 or len(dnsrecord_aaaa_add) > 0:
++                    domain_name = name[name.find(".")+1:]
++                    host_name = name[:name.find(".")]
++
++                    commands.append([domain_name,
++                                     "dnsrecord_add",
++                                     {
++                                         "idnsname": host_name,
++                                         "arecord": dnsrecord_a_add,
++                                         "a_extra_create_reverse": reverse,
++                                         "aaaarecord": dnsrecord_aaaa_add,
++                                         "aaaa_extra_create_reverse": reverse
++                                     }])
++
++                if len(dnsrecord_a_del) > 0 or len(dnsrecord_aaaa_del) > 0:
++                    domain_name = name[name.find(".")+1:]
++                    host_name = name[:name.find(".")]
++
++                    # There seems to be an issue with dnsrecord_del (not
++                    # for dnsrecord_add) if aaaarecord is an empty list.
++                    # Therefore this is done differently here:
++                    _args = {"idnsname": host_name}
++                    if len(dnsrecord_a_del) > 0:
++                        _args["arecord"] = dnsrecord_a_del
++                    if len(dnsrecord_aaaa_del) > 0:
++                        _args["aaaarecord"] = dnsrecord_aaaa_del
++
++                    commands.append([domain_name,
++                                     "dnsrecord_del", _args])
++
+             elif state == "absent":
+                 if action == "host":
+ 
+@@ -1215,6 +1284,17 @@ def main():
+                                  "hostgroup": allow_retrieve_keytab_hostgroup,
+                              }])
+ 
++                    dnsrecord_args = gen_dnsrecord_args(ansible_module,
++                                                        ip_address, reverse)
++                    if "arecord" in dnsrecord_args or \
++                       "aaaarecord" in dnsrecord_args:
++                        domain_name = name[name.find(".")+1:]
++                        host_name = name[:name.find(".")]
++                        dnsrecord_args["idnsname"] = host_name
++
++                        commands.append([domain_name, "dnsrecord_del",
++                                         dnsrecord_args])
++
+             elif state == "disabled":
+                 if res_find is not None:
+                     commands.append([name, "host_disable", {}])
+@@ -1259,6 +1339,11 @@ def main():
+                 # Host is already disabled, ignore error
+                 if "This entry is already disabled" in msg:
+                     continue
++
++                # Ignore no modification error.
++                if "no modifications to be performed" in msg:
++                    continue
++
+                 ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
+                                                              msg))
+ 
+diff --git a/tests/host/test_host_ipaddresses.yml b/tests/host/test_host_ipaddresses.yml
+new file mode 100644
+index 0000000..0a97dd5
+--- /dev/null
++++ b/tests/host/test_host_ipaddresses.yml
+@@ -0,0 +1,312 @@
++---
++- name: Test host IP addresses
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  - name: Get Domain from server name
++    set_fact:
++      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
++    when: ipaserver_domain is not defined
++
++  - name: Set host1_fqdn .. host6_fqdn
++    set_fact:
++      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
++      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
++      host3_fqdn: "{{ 'host3.' + ipaserver_domain }}"
++
++  - name: Get IPv4 address prefix from server node
++    set_fact:
++      ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] |
++                       join('.') }}"
++
++  - name: Host absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name:
++      - "{{ host1_fqdn }}"
++      - "{{ host2_fqdn }}"
++      - "{{ host3_fqdn }}"
++      update_dns: yes
++      state: absent
++
++  - name: Host "{{ host1_fqdn }}" present
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address:
++      - "{{ ipv4_prefix + '.201' }}"
++      - fe80::20c:29ff:fe02:a1b2
++      update_dns: yes
++      reverse: no
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address:
++      - "{{ ipv4_prefix + '.201' }}"
++      - fe80::20c:29ff:fe02:a1b2
++      update_dns: yes
++      reverse: no
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" present again with new IP address
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address:
++      - "{{ ipv4_prefix + '.211' }}"
++      - fe80::20c:29ff:fe02:a1b3
++      - "{{ ipv4_prefix + '.221' }}"
++      - fe80::20c:29ff:fe02:a1b4
++      update_dns: yes
++      reverse: no
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" present again with new IP address again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address:
++      - "{{ ipv4_prefix + '.211' }}"
++      - fe80::20c:29ff:fe02:a1b3
++      - "{{ ipv4_prefix + '.221' }}"
++      - fe80::20c:29ff:fe02:a1b4
++      update_dns: yes
++      reverse: no
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv4 address present
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: "{{ ipv4_prefix + '.201' }}"
++      action: member
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv4 address present again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: "{{ ipv4_prefix + '.201' }}"
++      action: member
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv4 address absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: "{{ ipv4_prefix + '.201' }}"
++      action: member
++      state: absent
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv4 address absent again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: "{{ ipv4_prefix + '.201' }}"
++      action: member
++      state: absent
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv6 address present
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: fe80::20c:29ff:fe02:a1b2
++      action: member
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv6 address present again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: fe80::20c:29ff:fe02:a1b2
++      action: member
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv6 address absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: fe80::20c:29ff:fe02:a1b2
++      action: member
++      state: absent
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" member IPv6 address absent again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address: fe80::20c:29ff:fe02:a1b2
++      action: member
++      state: absent
++    register: result
++
++  - name: Host "{{ host1_fqdn }}" member all ip-addresses absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address:
++      - "{{ ipv4_prefix + '.211' }}"
++      - fe80::20c:29ff:fe02:a1b3
++      - "{{ ipv4_prefix + '.221' }}"
++      - fe80::20c:29ff:fe02:a1b4
++      action: member
++      state: absent
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host1_fqdn }}" all member ip-addresses absent again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name: "{{ host1_fqdn }}"
++      ip_address:
++      - "{{ ipv4_prefix + '.211' }}"
++      - fe80::20c:29ff:fe02:a1b3
++      - "{{ ipv4_prefix + '.221' }}"
++      - fe80::20c:29ff:fe02:a1b4
++      action: member
++      state: absent
++    register: result
++    failed_when: result.changed
++
++  - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with same IP addresses
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host1_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++      - name: "{{ host2_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++    register: result
++    failed_when: not result.changed
++
++  - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with same IP addresses again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host1_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++      - name: "{{ host2_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++    register: result
++    failed_when: result.changed
++
++  - name: Hosts "{{ host3_fqdn }}" present with same IP addresses
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host3_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++    register: result
++    failed_when: not result.changed
++
++  - name: Hosts "{{ host3_fqdn }}" present with same IP addresses again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host3_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host3_fqdn }}" present with differnt IP addresses
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host3_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.111' }}"
++        - fe80::20c:29ff:fe02:a1b1
++        - "{{ ipv4_prefix + '.121' }}"
++        - fe80::20c:29ff:fe02:a1b2
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host3_fqdn }}" present with different IP addresses again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host3_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.111' }}"
++        - fe80::20c:29ff:fe02:a1b1
++        - "{{ ipv4_prefix + '.121' }}"
++        - fe80::20c:29ff:fe02:a1b2
++    register: result
++    failed_when: result.changed
++
++  - name: Host "{{ host3_fqdn }}" present with old IP addresses
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host3_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++    register: result
++    failed_when: not result.changed
++
++  - name: Host "{{ host3_fqdn }}" present with old IP addresses again
++    ipahost:
++      ipaadmin_password: MyPassword123
++      hosts:
++      - name: "{{ host3_fqdn }}"
++        ip_address:
++        - "{{ ipv4_prefix + '.211' }}"
++        - fe80::20c:29ff:fe02:a1b3
++        - "{{ ipv4_prefix + '.221' }}"
++        - fe80::20c:29ff:fe02:a1b4
++    register: result
++    failed_when: result.changed
++
++  - name: Host absent
++    ipahost:
++      ipaadmin_password: MyPassword123
++      name:
++      - "{{ host1_fqdn }}"
++      - "{{ host2_fqdn }}"
++      - "{{ host3_fqdn }}"
++      update_dns: yes
++      state: absent
+From 8f32cb04c1e161e1e3217f10413685a2cc9bf492 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Thu, 13 Feb 2020 14:10:38 +0100
+Subject: [PATCH] tests/host/test_host: Fix use of wrong host in the host5 test
+
+host1 was used instead of host5 in the repeated host5 test. This lead to an
+error with the new IP address handling in ipahost. It was correctly
+reporting a change for host1 which resulted in a failed test.
+---
+ tests/host/test_host.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tests/host/test_host.yml b/tests/host/test_host.yml
+index 1a555a1..f3ec11d 100644
+--- a/tests/host/test_host.yml
++++ b/tests/host/test_host.yml
+@@ -129,7 +129,7 @@
+   - name: Host "{{ host5_fqdn }}" present again
+     ipahost:
+       ipaadmin_password: MyPassword123
+-      name: "{{ host1_fqdn }}"
++      name: "{{ host5_fqdn }}"
+       ip_address: "{{ ipv4_prefix + '.205' }}"
+       update_dns: yes
+       reverse: no
diff --git a/SPECS/ansible-freeipa.spec b/SPECS/ansible-freeipa.spec
index 5f91374..0b748d4 100644
--- a/SPECS/ansible-freeipa.spec
+++ b/SPECS/ansible-freeipa.spec
@@ -5,22 +5,28 @@
 
 Summary: Roles and playbooks to deploy FreeIPA servers, replicas and clients
 Name: ansible-freeipa
-Version: 0.1.6
-Release: 4%{?dist}
+Version: 0.1.8
+Release: 3%{?dist}
 URL: https://github.com/freeipa/ansible-freeipa
 License: GPLv3+
 Source: https://github.com/freeipa/ansible-freeipa/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
-Patch1: ansible-freeipa-0.1.6-gen_module_docs-drop-key-dirserv_cert_files.patch
-Patch2: ansible-freeipa-0.1.6-ipatopologysegment-command-suffix-e4497c18_rhbz#1733547.patch
-Patch3: ansible-freeipa-0.1.6-ipatopologysegment-reinitialized-c212b435_rhbz#1733559.patch
-Patch4: ansible-freeipa-0.1.6-ansible_ipa_client_Drop_import_of_configure_nsswitch_database_rhbz#1748905.patch
+Patch1: ansible-freeipa-0.1.8-ipahost-Fix-choices-of-auth_ind-parameter-allow-to-reset-parameter_rhbz#1783992.patch
+Patch2: ansible-freeipa-0.1.8-ipauser-Allow-reset-of-userauthtype-do-not-depend-on-first-last-for-mod_rhbz#1784474.patch
+Patch3: ansible-freeipa-0.1.8-ipahost-Enhanced-failure-msg-for-member-params-used-without-member-action_rhbz#1783948.patch
+Patch4: ansible-freeipa-0.1.8-Add-missing-attributes-to-ipasudorule_rhbz#1788168,1788035,1788024.patch
+Patch5: ansible-freeipa-0.1.8-ipapwpolicy-Use-global_policy-if-name-is-not-set_rhbz#1797532.patch
+Patch6: ansible-freeipa-0.1.8-ipahbacrule-Fix-handing-of-members-with-action-hbacrule_rhbz#1787996.patch
+Patch7: ansible-freeipa-0.1.8-ansible_freeipa_module-Fix-comparison-of-bool-parameters-in-compare_args_ipa_rhbz#1784514.patch
+Patch8: ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch
+Patch9: ansible-freeipa-0.1.8-ipahost-Fail-on-action-member-for-new-hosts-fix-dnsrecord_add-reverse-flag_rhbz#1803026.patch
+Patch10: ansible-freeipa-0.1.8-ipahost-Do-not-fail-on-missing-DNS-or-zone-when-no-IP-address-given_rhbz#1804838.patch
 BuildArch: noarch
 
 #Requires: ansible
 
 %description
 ansible-freeipa provides Ansible roles and playbooks to install and uninstall
-FreeIPA servers, replicas and clients.
+FreeIPA servers, replicas and clients also modules for management.
 
 Note: The ansible playbooks and roles require a configured ansible environment
 where the ansible nodes are reachable and are properly set up to have an IP
@@ -32,6 +38,18 @@ Features
 - Cluster deployments: Server, replicas and clients in one playbook
 - One-time-password (OTP) support for client installation
 - Repair mode for clients
+- Modules for group management
+- Modules for hbacrule management
+- Modules for hbacsvc management
+- Modules for hbacsvcgroup management
+- Modules for host management
+- Modules for hostgroup management
+- Modules for pwpolicy management
+- Modules for sudocmd management
+- Modules for sudocmdgroup management
+- Modules for sudorule management
+- Modules for topology management
+- Modules for user management
 
 Supported FreeIPA Versions
 
@@ -46,13 +64,16 @@ Supported Distributions
 - RHEL/CentOS 7.4+
 - Fedora 26+
 - Ubuntu
+- Debian 10+ (ipaclient only, no server or replica!)
 
 Requirements
 
   Controller
-  - Ansible version: 2.5+
+  - Ansible version: 2.8+ (ansible-freeipa is an Ansible Collection)
+  - /usr/bin/kinit is required on the controller if a one time password (OTP)
+    is used
   - python3-gssapi is required on the controller if a one time password (OTP)
-    is used to install the client.
+    is used with keytab to install the client.
 
   Node
   - Supported FreeIPA version (see above)
@@ -73,6 +94,12 @@ or clients in one playbook.
 %patch2 -p1
 %patch3 -p1
 %patch4 -p1
+%patch5 -p1
+%patch6 -p1
+%patch7 -p1
+%patch8 -p1
+%patch9 -p1
+%patch10 -p1
 # Fix python modules and module utils:
 # - Remove shebang
 # - Remove execute flag
@@ -104,13 +131,103 @@ cp -rp plugins/* %{buildroot}%{_datadir}/ansible/plugins/
 %{_datadir}/ansible/plugins/module_utils
 %{_datadir}/ansible/plugins/modules
 %doc README.md
-%doc README-topology.md
-%doc README-server.md
-%doc README-replica.md
-%doc README-client.md
+%doc README-*.md
 %doc playbooks
 
 %changelog
+* Thu Feb 20 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.8-3
+- ipahost: Do not fail on missing DNS or zone when no IP address given
+  Resolves: RHBZ#1804838
+
+* Fri Feb 14 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.8-2
+- Updated RPM description for ansible-freeipa 0.1.8
+  Related: RHBZ#1748986
+- ipahost: Fix choices of auth_ind parameter, allow to reset parameter
+  Resolves: RHBZ#1783992
+- ipauser: Allow reset of userauthtype, do not depend on first,last for mod
+  Resolves: RHBZ#1784474
+- ipahost: Enhanced failure msg for member params used without member action
+  Resolves: RHBZ#1783948
+- Add missing attributes to ipasudorule
+  Resolves: RHBZ#1788168
+  Resolves: RHBZ#1788035
+  Resolves: RHBZ#1788024
+- ipapwpolicy: Use global_policy if name is not set
+  Resolves: RHBZ#1797532
+- ipahbacrule: Fix handing of members with action hbacrule
+  Resolves: RHBZ#1787996
+- ansible_freeipa_module: Fix comparison of bool parameters in compare_args_isa
+  Resolves: RHBZ#1784514
+- ipahost: Add support for several IP addresses and also to change them
+  Resolves: RHBZ#1783979
+  Resolves: RHBZ#1783976
+- ipahost: Fail on action member for new hosts, fix dnsrecord_add reverse flag
+  Resolves: RHBZ#1803026
+
+* Sat Dec 14 2019 Thomas Woerner <twoerner@redhat.com> - 0.1.8-1
+- Update to version 0.1.8 (bug fix release)
+  - roles/ipaclient/README.md: Add information about ipaclient_otp
+  - Install and enable firewalld if it is configured for ipaserver and
+    ipareplica roles
+  - ipaserver_test: Do not use zone_overlap_check for domain name validation
+  - Allow execution of API commands that do not require a name
+  - Update README-host: Drop options from allow_*keytab parameters docs
+  - ipauser: Extend email addresses with default email domain if no domain is
+    given
+    Resolves: RHBZ#1747413
+  Related: RHBZ#1748986
+
+* Mon Dec  2 2019 Thomas Woerner <twoerner@redhat.com> - 0.1.7-1
+- Update to version 0.1.7
+  - Add debian support for ipaclient
+  - Added support for predefining client OTP using ipaclient_otp
+  - ipatopologysegment: Store suffix for commands in command list
+  - ipatopologysegment: Fail for missing entry with reinitialized
+  - Utils scripts: ansible-ipa-[server,replica,client]-install
+  - ipaserver_test,ipareplica_prepare: Do not return _pkcs12_file settings
+  - ansible_freeipa_module: Add support for GSSAPI
+  - ansible_ipa_client: Drop import of configure_nsswitch_database
+  - New host management module
+  - New hostgroup management module
+  - ipagroup: Remove unused member_[present,absent] states
+  - external-ca tests: Fix typo in inventory files
+  - tests/external-signed-ca tests: Fix external-ca.sh to use proper serials
+  - ipagroup: Rework to use same mechanisms as ipahostgroup module
+  - ansible_freeipa_module: api_command should not have extra try clause
+  - ansible_freeipa_module: compare_args_ipa needs to compare lists orderless
+  - ansible_freeipa_module: New function api_check_param
+  - ansible_freeipa_module: New functions module_params_get and _afm_convert
+  - ansible_freeipa_module: Add missing to_text import for _afm_convert
+  - ansible_freeipa_module: Convert tuple to list in compare_args_ipa
+  - ansible_freeipa_module: New function api_get_realm
+  - ipauser: User module extension
+  - New sudocmd management module
+  - New sudocmdgroup management module
+  - ansible_freeipa_module: Convert int to string in compare_args_ipa
+  - New pwpolicy management module
+  - New hbacsvc (HBAC Service) management module
+  - New hbacsvcgroup (HBAC Service Group) management module
+  - ipagroup: Properly support IPA versions 4.6 and RHEL-7
+  - ipagroup: Fix changed flag, new test cases
+  - ipauser: Add info about version limitation of passwordexpiration
+  - New hbacrule (HBAC Rule) management module
+  - ipahostgroup: Fix changed flag, support IPA 4.6 on RHEL-7, new test cases
+  - New sudorule (Sudo Rule) management module
+  - ipauser: Support 'sn' alias of 'last' for surname
+  - Update galaxy.yml: Update description, drop empty dependencies
+  - Update ipauser.py: Fix typo in users.name description
+  - ipaclient: Fix misspelled sssd options
+  - ipauser: Return generated random password
+  - ipahost: Return generated random password
+  - Added context configuration to api_connect
+  - ansible_freeipa_module: Better support for KRB5CCNAME environment variable
+  - ipa[server,replica,client]: Add support for CentOS-8
+  - ipahost: Extension to be able handle several hosts and all settings
+  - Flake8 fixes
+  - Documentation updates
+  - Cleanup
+  Resolves: RHBZ#1748986
+
 * Fri Sep  6 2019 Thomas Woerner <twoerner@redhat.com> - 0.1.6-4
 - ansible_ipa_client: Drop import of configure_nsswitch_database
   (RHBZ#1748905)