diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index 51c02c739a9650198811fb44f3b1994053242013..16b4fa810e00bb78ef37428010adc63725a6eff6 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -31,7 +31,7 @@ __all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
            "paths", "tasks", "get_credentials_if_valid", "Encoding",
            "DNSName", "getargspec", "certificate_loader",
            "write_certificate_list", "boolean", "template_str",
-           "urlparse"]
+           "urlparse", "normalize_sshpubkey"]
 
 import os
 # ansible-freeipa requires locale to be C, IPA requires utf-8.
@@ -157,6 +157,8 @@ try:
     except ImportError:
         from ansible.module_utils.six.moves.urllib.parse import urlparse
 
+    from ipalib.util import normalize_sshpubkey
+
 except ImportError as _err:
     ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR = str(_err)
 
diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
index 8daf1c42ca9d648ac5eeabcabea6daf9fdebc19b..a1d2142365acbc514cfd1bbac634193334bd9dd1 100644
--- a/plugins/modules/ipahost.py
+++ b/plugins/modules/ipahost.py
@@ -509,7 +509,8 @@ host:
 
 from ansible.module_utils.ansible_freeipa_module import \
     IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
-    encode_certificate, is_ipv4_addr, is_ipv6_addr, ipalib_errors
+    encode_certificate, is_ipv4_addr, is_ipv6_addr, ipalib_errors, \
+    gen_add_list, gen_intersection_list, normalize_sshpubkey
 from ansible.module_utils import six
 if six.PY3:
     unicode = str
@@ -533,6 +534,11 @@ def find_host(module, name):
     if certs is not None:
         _res["usercertificate"] = [encode_certificate(cert) for
                                    cert in certs]
+    # krbprincipalname is returned as ipapython.kerberos.Principal, convert
+    # to string
+    principals = _res.get("krbprincipalname")
+    if principals is not None:
+        _res["krbprincipalname"] = [str(princ) for princ in principals]
     return _res
 
 
@@ -676,8 +682,15 @@ def check_authind(module, auth_ind):
             "by your IPA version" % "','".join(_invalid))
 
 
+def convert_certificate(certificate):
+    if certificate is None:
+        return None
+
+    return [cert.strip() for cert in certificate]
+
+
 # pylint: disable=unused-argument
-def result_handler(module, result, command, name, args, errors, exit_args,
+def result_handler(module, result, command, name, args, exit_args,
                    single_host):
     if "random" in args and command in ["host_add", "host_mod"] \
        and "randompassword" in result["result"]:
@@ -688,41 +701,6 @@ def result_handler(module, result, command, name, args, errors, exit_args,
             exit_args.setdefault(name, {})["randompassword"] = \
                 result["result"]["randompassword"]
 
-    # All "already a member" and "not a member" failures in the
-    # result are ignored. All others are reported.
-    if "failed" in result and len(result["failed"]) > 0:
-        for item in result["failed"]:
-            failed_item = result["failed"][item]
-            for member_type in failed_item:
-                for member, failure in failed_item[member_type]:
-                    if "already a member" in failure \
-                       or "not a member" in failure:
-                        continue
-                    errors.append("%s: %s %s: %s" % (
-                        command, member_type, member, failure))
-
-
-# pylint: disable=unused-argument
-def exception_handler(module, ex, errors, exit_args, single_host):
-    msg = str(ex)
-    if "already contains" in msg \
-       or "does not contain" in msg:
-        return True
-
-    #  The canonical principal name may not be removed
-    if "equal to the canonical principal name must" in msg:
-        return True
-
-    # Host is already disabled, ignore error
-    if "This entry is already disabled" in msg:
-        return True
-
-    # Ignore no modification error.
-    if "no modifications to be performed" in msg:
-        return True
-
-    return False
-
 
 def main():
     host_spec = dict(
@@ -916,6 +894,11 @@ def main():
         auth_ind, requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
         force, reverse, ip_address, update_dns, update_password)
 
+    certificate = convert_certificate(certificate)
+
+    if sshpubkey is not None:
+        sshpubkey = [str(normalize_sshpubkey(key)) for key in sshpubkey]
+
     # Use hosts if names is None
     if hosts is not None:
         names = hosts
@@ -999,6 +982,12 @@ def main():
                     ok_to_auth_as_delegate, force, reverse, ip_address,
                     update_dns, update_password)
 
+                certificate = convert_certificate(certificate)
+
+                if sshpubkey is not None:
+                    sshpubkey = [str(normalize_sshpubkey(key)) for
+                                 key in sshpubkey]
+
             elif isinstance(host, (str, unicode)):
                 name = host
             else:
@@ -1074,6 +1063,17 @@ def main():
                            args["krbprincipalauthind"] == ['']:
                             del args["krbprincipalauthind"]
 
+                        # Ignore sshpubkey if it is empty (for resetting)
+                        # and not set for the host
+                        if "ipasshpubkey" not in res_find and \
+                           "ipasshpubkey" in args and \
+                           args["ipasshpubkey"] == []:
+                            del args["ipasshpubkey"]
+
+                        # Ignore updatedns if it is the only arg
+                        if "updatedns" in args and len(args) == 1:
+                            del args["updatedns"]
+
                         # For all settings is args, check if there are
                         # different settings in the find result.
                         # If yes: modify
@@ -1106,7 +1106,7 @@ def main():
                             gen_add_del_lists(managedby_host,
                                               res_find.get("managedby_host"))
                         principal_add, principal_del = gen_add_del_lists(
-                            principal, res_find.get("principal"))
+                            principal, res_find.get("krbprincipalname"))
                         # Principals are not returned as utf8 for IPA using
                         # python2 using host_show, therefore we need to
                         # convert the principals that we should remove.
@@ -1174,50 +1174,115 @@ def main():
                             gen_add_del_lists(
                                 dnsrecord_args.get("aaaarecord"),
                                 _dnsrec.get("aaaarecord"))
+                    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 = []
+                        _dnsrec = res_find_dnsrecord or {}
+                        dnsrecord_a_add = gen_add_list(
+                            dnsrecord_args.get("arecord"),
+                            _dnsrec.get("arecord"))
+                        dnsrecord_a_del = []
+                        dnsrecord_aaaa_add = gen_add_list(
+                            dnsrecord_args.get("aaaarecord"),
+                            _dnsrec.get("aaaarecord"))
+                        dnsrecord_aaaa_del = []
 
                 else:
+                    # action member
                     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_add = gen_add_list(
+                        certificate, res_find.get("usercertificate"))
                     certificate_del = []
-                    managedby_host_add = managedby_host or []
+                    managedby_host_add = gen_add_list(
+                        managedby_host, res_find.get("managedby_host"))
                     managedby_host_del = []
-                    principal_add = principal or []
+                    principal_add = gen_add_list(
+                        principal, res_find.get("krbprincipalname"))
                     principal_del = []
-                    allow_create_keytab_user_add = \
-                        allow_create_keytab_user or []
+                    allow_create_keytab_user_add = gen_add_list(
+                        allow_create_keytab_user,
+                        res_find.get(
+                            "ipaallowedtoperform_write_keys_user"))
                     allow_create_keytab_user_del = []
-                    allow_create_keytab_group_add = \
-                        allow_create_keytab_group or []
+                    allow_create_keytab_group_add = gen_add_list(
+                        allow_create_keytab_group,
+                        res_find.get(
+                            "ipaallowedtoperform_write_keys_group"))
                     allow_create_keytab_group_del = []
-                    allow_create_keytab_host_add = \
-                        allow_create_keytab_host or []
+                    allow_create_keytab_host_add = gen_add_list(
+                        allow_create_keytab_host,
+                        res_find.get(
+                            "ipaallowedtoperform_write_keys_host"))
                     allow_create_keytab_host_del = []
-                    allow_create_keytab_hostgroup_add = \
-                        allow_create_keytab_hostgroup or []
+                    allow_create_keytab_hostgroup_add = gen_add_list(
+                        allow_create_keytab_hostgroup,
+                        res_find.get(
+                            "ipaallowedtoperform_write_keys_hostgroup"))
                     allow_create_keytab_hostgroup_del = []
-                    allow_retrieve_keytab_user_add = \
-                        allow_retrieve_keytab_user or []
+                    allow_retrieve_keytab_user_add = gen_add_list(
+                        allow_retrieve_keytab_user,
+                        res_find.get(
+                            "ipaallowedtoperform_read_keys_user"))
                     allow_retrieve_keytab_user_del = []
-                    allow_retrieve_keytab_group_add = \
-                        allow_retrieve_keytab_group or []
+                    allow_retrieve_keytab_group_add = gen_add_list(
+                        allow_retrieve_keytab_group,
+                        res_find.get(
+                            "ipaallowedtoperform_read_keys_group"))
                     allow_retrieve_keytab_group_del = []
-                    allow_retrieve_keytab_host_add = \
-                        allow_retrieve_keytab_host or []
+                    allow_retrieve_keytab_host_add = gen_add_list(
+                        allow_retrieve_keytab_host,
+                        res_find.get(
+                            "ipaallowedtoperform_read_keys_host"))
                     allow_retrieve_keytab_host_del = []
-                    allow_retrieve_keytab_hostgroup_add = \
-                        allow_retrieve_keytab_hostgroup or []
+                    allow_retrieve_keytab_hostgroup_add = gen_add_list(
+                        allow_retrieve_keytab_hostgroup,
+                        res_find.get(
+                            "ipaallowedtoperform_read_keys_hostgroup"))
                     allow_retrieve_keytab_hostgroup_del = []
-                    dnsrecord_a_add = dnsrecord_args.get("arecord") or []
+                    _dnsrec = res_find_dnsrecord or {}
+                    dnsrecord_a_add = gen_add_list(
+                        dnsrecord_args.get("arecord"),
+                        _dnsrec.get("arecord"))
                     dnsrecord_a_del = []
-                    dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or []
+                    dnsrecord_aaaa_add = gen_add_list(
+                        dnsrecord_args.get("aaaarecord"),
+                        _dnsrec.get("aaaarecord"))
                     dnsrecord_aaaa_del = []
 
                 # Remove canonical principal from principal_del
                 canonical_principal = "host/" + name + "@" + server_realm
+                # canonical_principal is also in find_res["krbcanonicalname"]
                 if canonical_principal in principal_del and \
                    action == "host" and (principal is not None or
                                          canonical_principal not in principal):
@@ -1398,8 +1463,10 @@ def main():
                     # the removal of non-existing entries.
 
                     # Remove certificates
-                    if certificate is not None:
-                        for _certificate in certificate:
+                    certificate_del = gen_intersection_list(
+                        certificate, res_find.get("usercertificate"))
+                    if certificate_del is not None:
+                        for _certificate in certificate_del:
                             commands.append([name, "host_remove_cert",
                                              {
                                                  "usercertificate":
@@ -1412,8 +1479,10 @@ def main():
                     # the removal of non-existing entries.
 
                     # Remove managedby_hosts
-                    if managedby_host is not None:
-                        for _managedby_host in managedby_host:
+                    managedby_host_del = gen_intersection_list(
+                        managedby_host, res_find.get("managedby_host"))
+                    if managedby_host_del is not None:
+                        for _managedby_host in managedby_host_del:
                             commands.append([name, "host_remove_managedby",
                                              {
                                                  "host":
@@ -1426,8 +1495,10 @@ def main():
                     # the removal of non-existing entries.
 
                     # Remove principals
-                    if principal is not None:
-                        for _principal in principal:
+                    principal_del = gen_intersection_list(
+                        principal, res_find.get("krbprincipalname"))
+                    if principal_del is not None:
+                        for _principal in principal_del:
                             commands.append([name, "host_remove_principal",
                                              {
                                                  "krbprincipalname":
@@ -1435,60 +1506,86 @@ def main():
                                              }])
 
                     # Disallow create keytab
-                    if allow_create_keytab_user is not None or \
-                       allow_create_keytab_group is not None or \
-                       allow_create_keytab_host is not None or \
-                       allow_create_keytab_hostgroup is not None:
+                    allow_create_keytab_user_del = gen_intersection_list(
+                        allow_create_keytab_user,
+                        res_find.get("ipaallowedtoperform_write_keys_user"))
+                    allow_create_keytab_group_del = gen_intersection_list(
+                        allow_create_keytab_group,
+                        res_find.get("ipaallowedtoperform_write_keys_group"))
+                    allow_create_keytab_host_del = gen_intersection_list(
+                        allow_create_keytab_host,
+                        res_find.get("ipaallowedtoperform_write_keys_host"))
+                    allow_create_keytab_hostgroup_del = gen_intersection_list(
+                        allow_create_keytab_hostgroup,
+                        res_find.get(
+                            "ipaallowedtoperform_write_keys_hostgroup"))
+                    if len(allow_create_keytab_user_del) > 0 or \
+                       len(allow_create_keytab_group_del) > 0 or \
+                       len(allow_create_keytab_host_del) > 0 or \
+                       len(allow_create_keytab_hostgroup_del) > 0:
                         commands.append(
                             [name, "host_disallow_create_keytab",
                              {
-                                 "user": allow_create_keytab_user,
-                                 "group": allow_create_keytab_group,
-                                 "host": allow_create_keytab_host,
-                                 "hostgroup": allow_create_keytab_hostgroup,
+                                 "user": allow_create_keytab_user_del,
+                                 "group": allow_create_keytab_group_del,
+                                 "host": allow_create_keytab_host_del,
+                                 "hostgroup":
+                                 allow_create_keytab_hostgroup_del,
                              }])
 
                     # Disallow retrieve keytab
-                    if allow_retrieve_keytab_user is not None or \
-                       allow_retrieve_keytab_group is not None or \
-                       allow_retrieve_keytab_host is not None or \
-                       allow_retrieve_keytab_hostgroup is not None:
+                    allow_retrieve_keytab_user_del = gen_intersection_list(
+                        allow_retrieve_keytab_user,
+                        res_find.get("ipaallowedtoperform_read_keys_user"))
+                    allow_retrieve_keytab_group_del = gen_intersection_list(
+                        allow_retrieve_keytab_group,
+                        res_find.get("ipaallowedtoperform_read_keys_group"))
+                    allow_retrieve_keytab_host_del = gen_intersection_list(
+                        allow_retrieve_keytab_host,
+                        res_find.get("ipaallowedtoperform_read_keys_host"))
+                    allow_retrieve_keytab_hostgroup_del = \
+                        gen_intersection_list(
+                            allow_retrieve_keytab_hostgroup,
+                            res_find.get(
+                                "ipaallowedtoperform_read_keys_hostgroup"))
+                    if len(allow_retrieve_keytab_user_del) > 0 or \
+                       len(allow_retrieve_keytab_group_del) > 0 or \
+                       len(allow_retrieve_keytab_host_del) > 0 or \
+                       len(allow_retrieve_keytab_hostgroup_del) > 0:
                         commands.append(
                             [name, "host_disallow_retrieve_keytab",
                              {
-                                 "user": allow_retrieve_keytab_user,
-                                 "group": allow_retrieve_keytab_group,
-                                 "host": allow_retrieve_keytab_host,
-                                 "hostgroup": allow_retrieve_keytab_hostgroup,
+                                 "user": allow_retrieve_keytab_user_del,
+                                 "group": allow_retrieve_keytab_group_del,
+                                 "host": allow_retrieve_keytab_host_del,
+                                 "hostgroup":
+                                 allow_retrieve_keytab_hostgroup_del,
                              }])
 
-                    dnsrecord_args = gen_dnsrecord_args(ansible_module,
-                                                        ip_address, reverse)
-
-                    # Remove arecord and aaaarecord from dnsrecord_args
-                    # if the record does not exits in res_find_dnsrecord
-                    # to prevent "DNS resource record not found" error
-                    if "arecord" in dnsrecord_args \
-                       and dnsrecord_args["arecord"] is not None \
-                       and len(dnsrecord_args["arecord"]) > 0 \
-                       and (res_find_dnsrecord is None
-                            or "arecord" not in res_find_dnsrecord):
-                        del dnsrecord_args["arecord"]
-                    if "aaaarecord" in dnsrecord_args \
-                       and dnsrecord_args["aaaarecord"] is not None \
-                       and len(dnsrecord_args["aaaarecord"]) > 0 \
-                       and (res_find_dnsrecord is None
-                            or "aaaarecord" not in res_find_dnsrecord):
-                        del dnsrecord_args["aaaarecord"]
-
-                    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])
+                    if res_find_dnsrecord is not None:
+                        dnsrecord_args = gen_dnsrecord_args(
+                            ansible_module, ip_address, reverse)
+
+                        # Only keep a and aaaa recrords that are part
+                        # of res_find_dnsrecord.
+                        for _type in ["arecord", "aaaarecord"]:
+                            if _type in dnsrecord_args:
+                                recs = gen_intersection_list(
+                                    dnsrecord_args[_type],
+                                    res_find_dnsrecord.get(_type))
+                                if len(recs) > 0:
+                                    dnsrecord_args[_type] = recs
+                                else:
+                                    del dnsrecord_args[_type]
+
+                        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:
@@ -1504,7 +1601,7 @@ def main():
         # Execute commands
 
         changed = ansible_module.execute_ipa_commands(
-            commands, result_handler, exception_handler,
+            commands, result_handler,
             exit_args=exit_args, single_host=hosts is None)
 
     # Done