diff --git a/.ansible-freeipa.metadata b/.ansible-freeipa.metadata
index aaec13d..c58e727 100644
--- a/.ansible-freeipa.metadata
+++ b/.ansible-freeipa.metadata
@@ -1 +1 @@
-a139427bb9c6fd44bd59ab258d1b17827a3dbe9a SOURCES/ansible-freeipa-0.1.10.tar.gz
+5d09d3b590e8568d04edb288c9c515e308f3168f SOURCES/ansible-freeipa-0.1.12.tar.gz
diff --git a/.gitignore b/.gitignore
index 154e3ed..7591236 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-SOURCES/ansible-freeipa-0.1.10.tar.gz
+SOURCES/ansible-freeipa-0.1.12.tar.gz
diff --git a/SOURCES/ansible-freeipa-0.1.12-Add-suppport-for-changing-password-of-symmetric-vaults_rhbz#1839197.patch b/SOURCES/ansible-freeipa-0.1.12-Add-suppport-for-changing-password-of-symmetric-vaults_rhbz#1839197.patch
new file mode 100644
index 0000000..8ff43ea
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-Add-suppport-for-changing-password-of-symmetric-vaults_rhbz#1839197.patch
@@ -0,0 +1,435 @@
+From 78b635ae78346fdfb298dd0d0c82ae1ff34b754a Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Tue, 23 Jun 2020 17:53:47 -0300
+Subject: [PATCH] Add suppport for changing password of symmetric vaults.
+
+Allows changing passwords of symmetric waults, using a new variable
+`new_password` (or the file-base version, `new_password_file`). The
+old password must be passed using the `password` or `password_file`
+variables that also received new aliases `old_password` and
+`old_password_file`, respectively.
+
+Tests were modyfied to reflect the changes.
+---
+ README-vault.md                               |  23 +++-
+ .../vault/change-password-symmetric-vault.yml |   2 +-
+ plugins/modules/ipavault.py                   | 129 +++++++++++++++---
+ tests/vault/test_vault_symmetric.yml          |  64 +++++++++
+ 4 files changed, 194 insertions(+), 24 deletions(-)
+
+diff --git a/README-vault.md b/README-vault.md
+index c7ae6916..fa1d3e11 100644
+--- a/README-vault.md
++++ b/README-vault.md
+@@ -165,6 +165,22 @@ Example playbook to make sure vault data is absent in a symmetric vault:
+       state: absent
+ ```
+ 
++Example playbook to change the password of a symmetric:
++
++```yaml
++---
++- name: Playbook to handle vaults
++  hosts: ipaserver
++  become: true
++
++  tasks:
++  - ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: symvault
++      old_password: SomeVAULTpassword
++      new_password: SomeNEWpassword
++```
++
+ Example playbook to make sure vault is absent:
+ 
+ ```yaml
+@@ -197,8 +213,11 @@ Variable | Description | Required
+ `name` \| `cn` | The list of vault name strings. | yes
+ `description` | The vault description string. | no
+ `nomembers` | Suppress processing of membership attributes. (bool) | no
+-`password ` \| `vault_password` \| `ipavaultpassword` | Vault password. | no
+-`public_key ` \| `vault_public_key` \| `ipavaultpublickey` | Base64 encoded vault public key. | no
++`password` \| `vault_password` \| `ipavaultpassword` \| `old_password`| Vault password. | no
++`password_file` \| `vault_password_file` \| `old_password_file`| File containing Base64 encoded Vault password. | no
++`new_password` | Vault new password. | no
++`new_password_file` | File containing Base64 encoded new Vault password. | no
++`public_key ` \| `vault_public_key` \| `old_password_file` | Base64 encoded vault public key. | no
+ `public_key_file` \| `vault_public_key_file` | Path to file with public key. | no
+ `private_key `\| `vault_private_key` | Base64 encoded vault private key. Used only to retrieve data. | no
+ `private_key_file` \| `vault_private_key_file` | Path to file with private key. Used only to retrieve data. | no
+diff --git a/playbooks/vault/change-password-symmetric-vault.yml b/playbooks/vault/change-password-symmetric-vault.yml
+index 3871f45d..396a79f6 100644
+--- a/playbooks/vault/change-password-symmetric-vault.yml
++++ b/playbooks/vault/change-password-symmetric-vault.yml
+@@ -10,7 +10,7 @@
+       ipaadmin_password: SomeADMINpassword
+       name: symvault
+       password: SomeVAULTpassword
+-  - name: Change vault passord.
++  - name: Change vault password.
+     ipavault:
+       ipaadmin_password: SomeADMINpassword
+       name: symvault
+diff --git a/plugins/modules/ipavault.py b/plugins/modules/ipavault.py
+index ad5dd413..46c6fcdb 100644
+--- a/plugins/modules/ipavault.py
++++ b/plugins/modules/ipavault.py
+@@ -69,12 +69,20 @@
+     description: password to be used on symmetric vault.
+     required: false
+     type: string
+-    aliases: ["ipavaultpassword", "vault_password"]
++    aliases: ["ipavaultpassword", "vault_password", "old_password"]
+   password_file:
+     description: file with password to be used on symmetric vault.
+     required: false
+     type: string
+-    aliases: ["vault_password_file"]
++    aliases: ["vault_password_file", "old_password_file"]
++  new_password:
++    description: new password to be used on symmetric vault.
++    required: false
++    type: string
++  new_password_file:
++    description: file with new password to be used on symmetric vault.
++    required: false
++    type: string
+   salt:
+     description: Vault salt.
+     required: false
+@@ -235,7 +243,15 @@
+     state: retrieved
+   register: result
+ - debug:
+-    msg: "{{ result.data | b64decode }}"
++    msg: "{{ result.data }}"
++
++# Change password of a symmetric vault
++- ipavault:
++    ipaadmin_password: SomeADMINpassword
++    name: symvault
++    username: admin
++    old_password: SomeVAULTpassword
++    new_password: SomeNEWpassword
+ 
+ # Ensure vault symvault is absent
+ - ipavault:
+@@ -416,18 +432,29 @@ def check_parameters(module, state, action, description, username, service,
+                      shared, users, groups, services, owners, ownergroups,
+                      ownerservices, vault_type, salt, password, password_file,
+                      public_key, public_key_file, private_key,
+-                     private_key_file, vault_data, datafile_in, datafile_out):
++                     private_key_file, vault_data, datafile_in, datafile_out,
++                     new_password, new_password_file):
+     invalid = []
+     if state == "present":
+         invalid = ['private_key', 'private_key_file', 'datafile_out']
+ 
++        if all([password, password_file]) \
++           or all([new_password, new_password_file]):
++            module.fail_json(msg="Password specified multiple times.")
++
++        if any([new_password, new_password_file]) \
++           and not any([password, password_file]):
++            module.fail_json(
++                msg="Either `password` or `password_file` must be provided to "
++                    "change symmetric vault password.")
++
+         if action == "member":
+             invalid.extend(['description'])
+ 
+     elif state == "absent":
+         invalid = ['description', 'salt', 'vault_type', 'private_key',
+                    'private_key_file', 'datafile_in', 'datafile_out',
+-                   'vault_data']
++                   'vault_data', 'new_password', 'new_password_file']
+ 
+         if action == "vault":
+             invalid.extend(['users', 'groups', 'services', 'owners',
+@@ -437,7 +464,7 @@ def check_parameters(module, state, action, description, username, service,
+     elif state == "retrieved":
+         invalid = ['description', 'salt', 'datafile_in', 'users', 'groups',
+                    'owners', 'ownergroups', 'public_key', 'public_key_file',
+-                   'vault_data']
++                   'vault_data', 'new_password', 'new_password_file']
+         if action == 'member':
+             module.fail_json(
+                 msg="State `retrieved` do not support action `member`.")
+@@ -458,11 +485,17 @@ def check_parameters(module, state, action, description, username, service,
+ def check_encryption_params(module, state, action, vault_type, salt,
+                             password, password_file, public_key,
+                             public_key_file, private_key, private_key_file,
+-                            vault_data, datafile_in, datafile_out, res_find):
++                            vault_data, datafile_in, datafile_out,
++                            new_password, new_password_file, res_find):
+     vault_type_invalid = []
++
++    if res_find is not None:
++        vault_type = res_find['ipavaulttype']
++
+     if vault_type == "standard":
+         vault_type_invalid = ['public_key', 'public_key_file', 'password',
+-                              'password_file', 'salt']
++                              'password_file', 'salt', 'new_password',
++                              'new_password_file']
+ 
+     if vault_type is None or vault_type == "symmetric":
+         vault_type_invalid = ['public_key', 'public_key_file',
+@@ -473,8 +506,14 @@ def check_encryption_params(module, state, action, vault_type, salt,
+                 msg="Symmetric vault requires password or password_file "
+                     "to store data or change `salt`.")
+ 
++        if any([new_password, new_password_file]) and res_find is None:
++            module.fail_json(
++                msg="Cannot modify password of inexistent vault.")
++
+     if vault_type == "asymmetric":
+-        vault_type_invalid = ['password', 'password_file']
++        vault_type_invalid = [
++            'password', 'password_file', 'new_password', 'new_password_file'
++        ]
+         if not any([public_key, public_key_file]) and res_find is None:
+             module.fail_json(
+                 msg="Assymmetric vault requires public_key "
+@@ -487,6 +526,43 @@ def check_encryption_params(module, state, action, vault_type, salt,
+                 (param, vault_type or 'symmetric'))
+ 
+ 
++def change_password(module, res_find, password, password_file, new_password,
++                    new_password_file):
++    """
++    Change the password of a symmetric vault.
++
++    To change the password of a vault, it is needed to retrieve the stored
++    data with the current password, and store the data again, with the new
++    password, forcing it to override the old one.
++    """
++    # verify parameters.
++    if not any([new_password, new_password_file]):
++        return []
++    if res_find["ipavaulttype"][0] != "symmetric":
++        module.fail_json(msg="Cannot change password of `%s` vault."
++                             % res_find["ipavaulttype"])
++
++    # prepare arguments to retrieve data.
++    name = res_find["cn"][0]
++    args = {}
++    if password:
++        args["password"] = password
++    if password_file:
++        args["password"] = password_file
++    # retrieve current stored data
++    result = api_command(module, 'vault_retrieve', name, args)
++    args['data'] = result['result']['data']
++
++    # modify arguments to store data with new password.
++    if password:
++        args["password"] = new_password
++    if password_file:
++        args["password"] = new_password_file
++    args["override_password"] = True
++    # return the command to store data with the new password.
++    return [(name, "vault_archive", args)]
++
++
+ def main():
+     ansible_module = AnsibleModule(
+         argument_spec=dict(
+@@ -533,10 +609,18 @@ def main():
+             datafile_out=dict(type="str", required=False, default=None,
+                               aliases=['out']),
+             vault_password=dict(type="str", required=False, default=None,
+-                                aliases=['ipavaultpassword', 'password'],
+-                                no_log=True),
++                                no_log=True,
++                                aliases=['ipavaultpassword', 'password',
++                                         "old_password"]),
+             vault_password_file=dict(type="str", required=False, default=None,
+-                                     no_log=False, aliases=['password_file']),
++                                     no_log=False,
++                                     aliases=[
++                                        'password_file', "old_password_file"
++                                     ]),
++            new_password=dict(type="str", required=False, default=None,
++                              no_log=True),
++            new_password_file=dict(type="str", required=False, default=None,
++                                   no_log=False),
+             # state
+             action=dict(type="str", default="vault",
+                         choices=["vault", "data", "member"]),
+@@ -546,6 +630,7 @@ def main():
+         supports_check_mode=True,
+         mutually_exclusive=[['username', 'service', 'shared'],
+                             ['datafile_in', 'vault_data'],
++                            ['new_password', 'new_password_file'],
+                             ['vault_password', 'vault_password_file'],
+                             ['vault_public_key', 'vault_public_key_file']],
+     )
+@@ -576,6 +661,8 @@ def main():
+     salt = module_params_get(ansible_module, "vault_salt")
+     password = module_params_get(ansible_module, "vault_password")
+     password_file = module_params_get(ansible_module, "vault_password_file")
++    new_password = module_params_get(ansible_module, "new_password")
++    new_password_file = module_params_get(ansible_module, "new_password_file")
+     public_key = module_params_get(ansible_module, "vault_public_key")
+     public_key_file = module_params_get(ansible_module,
+                                         "vault_public_key_file")
+@@ -614,7 +701,8 @@ def main():
+                      service, shared, users, groups, services, owners,
+                      ownergroups, ownerservices, vault_type, salt, password,
+                      password_file, public_key, public_key_file, private_key,
+-                     private_key_file, vault_data, datafile_in, datafile_out)
++                     private_key_file, vault_data, datafile_in, datafile_out,
++                     new_password, new_password_file)
+     # Init
+ 
+     changed = False
+@@ -660,7 +748,7 @@ def main():
+                     ansible_module, state, action, vault_type, salt, password,
+                     password_file, public_key, public_key_file, private_key,
+                     private_key_file, vault_data, datafile_in, datafile_out,
+-                    res_find)
++                    new_password, new_password_file, res_find)
+ 
+                 # Found the vault
+                 if action == "vault":
+@@ -721,7 +809,6 @@ def main():
+                     owner_add_args = gen_member_args(
+                         args, owner_add, ownergroups_add, ownerservice_add)
+                     if owner_add_args is not None:
+-                        # ansible_module.warn("OWNER ADD: %s" % owner_add_args)
+                         commands.append(
+                             [name, 'vault_add_owner', owner_add_args])
+ 
+@@ -729,7 +816,6 @@ def main():
+                     owner_del_args = gen_member_args(
+                         args, owner_del, ownergroups_del, ownerservice_del)
+                     if owner_del_args is not None:
+-                        # ansible_module.warn("OWNER DEL: %s" % owner_del_args)
+                         commands.append(
+                             [name, 'vault_remove_owner', owner_del_args])
+ 
+@@ -758,19 +844,22 @@ def main():
+                 if any([vault_data, datafile_in]):
+                     commands.append([name, "vault_archive", pwdargs])
+ 
++                cmds = change_password(
++                    ansible_module, res_find, password, password_file,
++                    new_password, new_password_file)
++                commands.extend(cmds)
++
+             elif state == "retrieved":
+                 if res_find is None:
+                     ansible_module.fail_json(
+                         msg="Vault `%s` not found to retrieve data." % name)
+ 
+-                vault_type = res_find['cn']
+-
+                 # verify data encription args
+                 check_encryption_params(
+                     ansible_module, state, action, vault_type, salt, password,
+                     password_file, public_key, public_key_file, private_key,
+                     private_key_file, vault_data, datafile_in, datafile_out,
+-                    res_find)
++                    new_password, new_password_file, res_find)
+ 
+                 pwdargs = data_storage_args(
+                     args, vault_data, password, password_file, private_key,
+@@ -813,7 +902,6 @@ def main():
+         errors = []
+         for name, command, args in commands:
+             try:
+-                # ansible_module.warn("RUN: %s %s %s" % (command, name, args))
+                 result = api_command(ansible_module, command, name, args)
+ 
+                 if command == 'vault_archive':
+@@ -829,7 +917,6 @@ def main():
+                         raise Exception("No data retrieved.")
+                     changed = False
+                 else:
+-                    # ansible_module.warn("RESULT: %s" % (result))
+                     if "completed" in result:
+                         if result["completed"] > 0:
+                             changed = True
+diff --git a/tests/vault/test_vault_symmetric.yml b/tests/vault/test_vault_symmetric.yml
+index c9429f4f..a6072d88 100644
+--- a/tests/vault/test_vault_symmetric.yml
++++ b/tests/vault/test_vault_symmetric.yml
+@@ -178,6 +178,61 @@
+     register: result
+     failed_when: result.data != 'Hello World.' or result.changed
+ 
++  - name: Change vault password.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: symvault
++      password: SomeVAULTpassword
++      new_password: SomeNEWpassword
++    register: result
++    failed_when: not result.changed
++
++  - name: Retrieve data from symmetric vault, with wrong password.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: symvault
++      password: SomeVAULTpassword
++      state: retrieved
++    register: result
++    failed_when: not result.failed or "Invalid credentials" not in result.msg
++
++  - name: Change vault password, with wrong `old_password`.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: symvault
++      password: SomeVAULTpassword
++      new_password: SomeNEWpassword
++    register: result
++    failed_when: not result.failed or "Invalid credentials" not in result.msg
++
++  - name: Retrieve data from symmetric vault, with new password.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: symvault
++      password: SomeNEWpassword
++      state: retrieved
++    register: result
++    failed_when: result.data != 'Hello World.' or result.changed
++
++  - name: Try to add vault with multiple passwords.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: inexistentvault
++      password: SomeVAULTpassword
++      password_file: "{{ ansible_env.HOME }}/password.txt"
++    register: result
++    failed_when: not result.failed or "parameters are mutually exclusive" not in result.msg
++
++  - name: Try to add vault with multiple new passwords.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: inexistentvault
++      password: SomeVAULTpassword
++      new_password: SomeVAULTpassword
++      new_password_file: "{{ ansible_env.HOME }}/password.txt"
++    register: result
++    failed_when: not result.failed or "parameters are mutually exclusive" not in result.msg
++
+   - name: Ensure symmetric vault is absent
+     ipavault:
+       ipaadmin_password: SomeADMINpassword
+@@ -194,5 +249,14 @@
+     register: result
+     failed_when: result.changed
+ 
++  - name: Try to change password of inexistent vault.
++    ipavault:
++      ipaadmin_password: SomeADMINpassword
++      name: inexistentvault
++      password: SomeVAULTpassword
++      new_password: SomeNEWpassword
++    register: result
++    failed_when: not result.failed or "Cannot modify password of inexistent vault" not in result.msg
++
+   - name: Cleanup testing environment.
+     import_tasks: env_cleanup.yml
diff --git a/SOURCES/ansible-freeipa-0.1.12-Fix-forwardzone-issues_rhbz#1843826,1843828,1843829,1843830,1843831.patch b/SOURCES/ansible-freeipa-0.1.12-Fix-forwardzone-issues_rhbz#1843826,1843828,1843829,1843830,1843831.patch
new file mode 100644
index 0000000..5470d06
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-Fix-forwardzone-issues_rhbz#1843826,1843828,1843829,1843830,1843831.patch
@@ -0,0 +1,1447 @@
+From f0f933b4630bce810475a519e295828013d301d6 Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Wed, 10 Jun 2020 20:40:45 -0300
+Subject: [PATCH] Changed admin password on tests to match other modules.
+
+Use of the same password on all module tests ease test automation,
+and this change ensure that dnsforwardzone use the same password as
+other modules.
+---
+ tests/dnsforwardzone/test_dnsforwardzone.yml | 42 ++++++++++----------
+ 1 file changed, 21 insertions(+), 21 deletions(-)
+
+diff --git a/tests/dnsforwardzone/test_dnsforwardzone.yml b/tests/dnsforwardzone/test_dnsforwardzone.yml
+index 1a45e826..ac08a48f 100644
+--- a/tests/dnsforwardzone/test_dnsforwardzone.yml
++++ b/tests/dnsforwardzone/test_dnsforwardzone.yml
+@@ -7,13 +7,13 @@
+   tasks:
+   - name: ensure forwardzone example.com is absent - prep
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: absent
+ 
+   - name: ensure forwardzone example.com is created
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -25,7 +25,7 @@
+ 
+   - name: ensure forwardzone example.com is present again
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -37,7 +37,7 @@
+ 
+   - name: ensure forwardzone example.com has two forwarders
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -50,7 +50,7 @@
+ 
+   - name: ensure forwardzone example.com has one forwarder again
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       forwarders:
+         - 8.8.8.8
+@@ -62,7 +62,7 @@
+ 
+   - name: skip_overlap_check can only be set on creation so change nothing
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       forwarders:
+         - 8.8.8.8
+@@ -74,7 +74,7 @@
+ 
+   - name: change all the things at once
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -87,13 +87,13 @@
+ 
+   - name: ensure forwardzone example.com is absent for next testset
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: absent
+ 
+   - name: ensure forwardzone example.com is created with minimal args
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       skip_overlap_check: true
+@@ -104,7 +104,7 @@
+ 
+   - name: add a forwarder to any existing ones
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -115,7 +115,7 @@
+ 
+   - name: check the list of forwarders is what we expect
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -127,7 +127,7 @@
+ 
+   - name: remove a single forwarder
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: absent
+       name: example.com
+       forwarders:
+@@ -138,7 +138,7 @@
+ 
+   - name: check the list of forwarders is what we expect now
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -149,13 +149,13 @@
+ 
+   - name: ensure forwardzone example.com is absent again
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: absent
+ 
+   - name: try to create a new forwarder with action=member
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -167,13 +167,13 @@
+ 
+   - name: ensure forwardzone example.com is absent - tidy up
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: absent
+ 
+   - name: try to create a new forwarder is disabled state
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: disabled
+       name: example.com
+       forwarders:
+@@ -184,7 +184,7 @@
+ 
+   - name: enable the forwarder
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: enabled
+     register: result
+@@ -192,7 +192,7 @@
+ 
+   - name: disable the forwarder again
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: disabled
+       action: member
+@@ -201,7 +201,7 @@
+ 
+   - name: ensure it stays disabled
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: disabled
+     register: result
+@@ -209,6 +209,6 @@
+ 
+   - name: ensure forwardzone example.com is absent - tidy up
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: absent
+From f8ebca760dbaaf38c7b74b0c855b05d26e9cb812 Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Wed, 10 Jun 2020 22:14:27 -0300
+Subject: [PATCH] Allow processing of multiple names for deleting
+ dnsforwardzones.
+
+---
+ plugins/modules/ipadnsforwardzone.py | 189 ++++++++++++++-------------
+ 1 file changed, 98 insertions(+), 91 deletions(-)
+
+diff --git a/plugins/modules/ipadnsforwardzone.py b/plugins/modules/ipadnsforwardzone.py
+index 90bd3876..b28f28db 100644
+--- a/plugins/modules/ipadnsforwardzone.py
++++ b/plugins/modules/ipadnsforwardzone.py
+@@ -134,7 +134,7 @@ def main():
+             # general
+             ipaadmin_principal=dict(type="str", default="admin"),
+             ipaadmin_password=dict(type="str", required=False, no_log=True),
+-            name=dict(type="str", aliases=["cn"], default=None,
++            name=dict(type="list", aliases=["cn"], default=None,
+                       required=True),
+             forwarders=dict(type='list', aliases=["idnsforwarders"],
+                             required=False),
+@@ -158,7 +158,7 @@ def main():
+                                            "ipaadmin_principal")
+     ipaadmin_password = module_params_get(ansible_module,
+                                           "ipaadmin_password")
+-    name = module_params_get(ansible_module, "name")
++    names = module_params_get(ansible_module, "name")
+     action = module_params_get(ansible_module, "action")
+     forwarders = module_params_get(ansible_module, "forwarders")
+     forwardpolicy = module_params_get(ansible_module, "forwardpolicy")
+@@ -166,6 +166,12 @@ def main():
+                                            "skip_overlap_check")
+     state = module_params_get(ansible_module, "state")
+ 
++    if state == 'present' and len(names) != 1:
++        ansible_module.fail_json(
++            msg="Only one dnsforwardzone can be added at a time.")
++    if state == 'absent' and len(names) < 1:
++        ansible_module.fail_json(msg="No name given.")
++
+     # absent stae means delete if the action is NOT member but update if it is
+     # if action is member then update an exisiting resource
+     # and if action is not member then create a resource
+@@ -207,101 +213,102 @@ def main():
+                                                  ipaadmin_password)
+         api_connect()
+ 
+-        # Make sure forwardzone exists
+-        existing_resource = find_dnsforwardzone(ansible_module, name)
+-
+-        if existing_resource is None and operation == "update":
+-            # does not exist and is updating
+-            # trying to update something that doesn't exist, so error
+-            ansible_module.fail_json(msg="""dnsforwardzone '%s' is not
+-                                                     valid""" % (name))
+-        elif existing_resource is None and operation == "del":
+-            # does not exists and should be absent
+-            # set command
+-            command = None
+-            # enabled or disabled?
+-            is_enabled = "IGNORE"
+-        elif existing_resource is not None and operation == "del":
+-            # exists but should be absent
+-            # set command
+-            command = "dnsforwardzone_del"
+-            # enabled or disabled?
+-            is_enabled = "IGNORE"
+-        elif forwarders is None:
+-            # forwarders are not defined its not a delete, update state?
+-            # set command
+-            command = None
+-            # enabled or disabled?
+-            if existing_resource is not None:
+-                is_enabled = existing_resource["idnszoneactive"][0]
+-            else:
+-                is_enabled = "IGNORE"
+-        elif existing_resource is not None and operation == "update":
+-            # exists and is updating
+-            # calculate the new forwarders and mod
+-            # determine args
+-            if state != "absent":
+-                forwarders = list(set(existing_resource["idnsforwarders"]
+-                                      + forwarders))
+-            else:
+-                forwarders = list(set(existing_resource["idnsforwarders"])
+-                                  - set(forwarders))
+-            args = gen_args(forwarders, forwardpolicy,
+-                            skip_overlap_check)
+-            if skip_overlap_check is not None:
+-                del args['skip_overlap_check']
+-
+-            # command
+-            if not compare_args_ipa(ansible_module, args, existing_resource):
+-                command = "dnsforwardzone_mod"
+-            else:
++        for name in names:
++            # Make sure forwardzone exists
++            existing_resource = find_dnsforwardzone(ansible_module, name)
++
++            if existing_resource is None and operation == "update":
++                # does not exist and is updating
++                # trying to update something that doesn't exist, so error
++                ansible_module.fail_json(msg="""dnsforwardzone '%s' is not
++                                                         valid""" % (name))
++            elif existing_resource is None and operation == "del":
++                # does not exists and should be absent
++                # set command
+                 command = None
+-
+-            # enabled or disabled?
+-            is_enabled = existing_resource["idnszoneactive"][0]
+-
+-        elif existing_resource is None and operation == "add":
+-            # does not exist but should be present
+-            # determine args
+-            args = gen_args(forwarders, forwardpolicy,
+-                            skip_overlap_check)
+-            # set command
+-            command = "dnsforwardzone_add"
+-            # enabled or disabled?
+-            is_enabled = "TRUE"
+-
+-        elif existing_resource is not None and operation == "add":
+-            # exists and should be present, has it changed?
+-            # determine args
+-            args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
+-            if skip_overlap_check is not None:
+-                del args['skip_overlap_check']
+-
+-            # set command
+-            if not compare_args_ipa(ansible_module, args, existing_resource):
+-                command = "dnsforwardzone_mod"
+-            else:
++                # enabled or disabled?
++                is_enabled = "IGNORE"
++            elif existing_resource is not None and operation == "del":
++                # exists but should be absent
++                # set command
++                command = "dnsforwardzone_del"
++                # enabled or disabled?
++                is_enabled = "IGNORE"
++            elif forwarders is None:
++                # forwarders are not defined its not a delete, update state?
++                # set command
+                 command = None
++                # enabled or disabled?
++                if existing_resource is not None:
++                    is_enabled = existing_resource["idnszoneactive"][0]
++                else:
++                    is_enabled = "IGNORE"
++            elif existing_resource is not None and operation == "update":
++                # exists and is updating
++                # calculate the new forwarders and mod
++                # determine args
++                if state != "absent":
++                    forwarders = list(set(existing_resource["idnsforwarders"]
++                                          + forwarders))
++                else:
++                    forwarders = list(set(existing_resource["idnsforwarders"])
++                                      - set(forwarders))
++                args = gen_args(forwarders, forwardpolicy,
++                                skip_overlap_check)
++                if skip_overlap_check is not None:
++                    del args['skip_overlap_check']
++
++                # command
++                if not compare_args_ipa(ansible_module, args, existing_resource):
++                    command = "dnsforwardzone_mod"
++                else:
++                    command = None
++
++                # enabled or disabled?
++                is_enabled = existing_resource["idnszoneactive"][0]
+ 
+-            # enabled or disabled?
+-            is_enabled = existing_resource["idnszoneactive"][0]
+-
+-        # if command is set then run it with the args
+-        if command is not None:
+-            api_command(ansible_module, command, name, args)
+-            changed = True
++            elif existing_resource is None and operation == "add":
++                # does not exist but should be present
++                # determine args
++                args = gen_args(forwarders, forwardpolicy,
++                                skip_overlap_check)
++                # set command
++                command = "dnsforwardzone_add"
++                # enabled or disabled?
++                is_enabled = "TRUE"
++
++            elif existing_resource is not None and operation == "add":
++                # exists and should be present, has it changed?
++                # determine args
++                args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
++                if skip_overlap_check is not None:
++                    del args['skip_overlap_check']
++
++                # set command
++                if not compare_args_ipa(ansible_module, args, existing_resource):
++                    command = "dnsforwardzone_mod"
++                else:
++                    command = None
++
++                # enabled or disabled?
++                is_enabled = existing_resource["idnszoneactive"][0]
+ 
+-        # does the enabled state match what we want (if we care)
+-        if is_enabled != "IGNORE":
+-            if wants_enable and is_enabled != "TRUE":
+-                api_command(ansible_module, "dnsforwardzone_enable",
+-                            name, {})
+-                changed = True
+-            elif not wants_enable and is_enabled != "FALSE":
+-                api_command(ansible_module, "dnsforwardzone_disable",
+-                            name, {})
++            # if command is set then run it with the args
++            if command is not None:
++                api_command(ansible_module, command, name, args)
+                 changed = True
+ 
++            # does the enabled state match what we want (if we care)
++            if is_enabled != "IGNORE":
++                if wants_enable and is_enabled != "TRUE":
++                    api_command(ansible_module, "dnsforwardzone_enable",
++                                name, {})
++                    changed = True
++                elif not wants_enable and is_enabled != "FALSE":
++                    api_command(ansible_module, "dnsforwardzone_disable",
++                                name, {})
++                    changed = True
++
+     except Exception as e:
+         ansible_module.fail_json(msg=str(e))
+ 
+From 3f785bc0e9fe1ab3ad874ce4f26e6897189db8aa Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Wed, 10 Jun 2020 22:20:20 -0300
+Subject: [PATCH] Fix error message when adding dnsforwardzone without
+ forwarders.
+
+---
+ plugins/modules/ipadnsforwardzone.py         |  5 +++++
+ tests/dnsforwardzone/test_dnsforwardzone.yml | 13 +++++++++++--
+ 2 files changed, 16 insertions(+), 2 deletions(-)
+
+diff --git a/plugins/modules/ipadnsforwardzone.py b/plugins/modules/ipadnsforwardzone.py
+index b28f28db..3968e6a1 100644
+--- a/plugins/modules/ipadnsforwardzone.py
++++ b/plugins/modules/ipadnsforwardzone.py
+@@ -217,6 +217,11 @@ def main():
+             # Make sure forwardzone exists
+             existing_resource = find_dnsforwardzone(ansible_module, name)
+ 
++            # validate parameters
++            if state == 'present':
++                if existing_resource is None and not forwarders:
++                    ansible_module.fail_json(msg='No forwarders specified.')
++
+             if existing_resource is None and operation == "update":
+                 # does not exist and is updating
+                 # trying to update something that doesn't exist, so error
+diff --git a/tests/dnsforwardzone/test_dnsforwardzone.yml b/tests/dnsforwardzone/test_dnsforwardzone.yml
+index ac08a48f..d94db9e5 100644
+--- a/tests/dnsforwardzone/test_dnsforwardzone.yml
++++ b/tests/dnsforwardzone/test_dnsforwardzone.yml
+@@ -5,10 +5,12 @@
+   gather_facts: false
+ 
+   tasks:
+-  - name: ensure forwardzone example.com is absent - prep
++  - name: ensure test forwardzones are absent - prep
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+-      name: example.com
++      name:
++      - example.com
++      - newfailzone.com
+       state: absent
+ 
+   - name: ensure forwardzone example.com is created
+@@ -207,6 +209,13 @@
+     register: result
+     failed_when: result.changed
+ 
++  - name: Ensure forwardzone is not added without forwarders, with correct message.
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: newfailzone.com
++    register: result
++    failed_when: not result.failed or "No forwarders specified" not in result.msg
++
+   - name: ensure forwardzone example.com is absent - tidy up
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+From 1d223c2b63634abe86f7702a64dd83c4fbc272ce Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Mon, 15 Jun 2020 16:14:25 -0300
+Subject: [PATCH] Add support for attributes `ip_address` and `port` to
+ `forwarders`.
+
+This patch modify the was forwarders are configured, using two attributes,
+`ip_address` and `port`, instead of IPA API internal string representation
+of `IP port PORT`.
+---
+ README-dnsforwardzone.md                     |  6 ++-
+ plugins/modules/ipadnsforwardzone.py         | 37 ++++++++++++++---
+ tests/dnsforwardzone/test_dnsforwardzone.yml | 43 ++++++++++++--------
+ 3 files changed, 62 insertions(+), 24 deletions(-)
+
+diff --git a/README-dnsforwardzone.md b/README-dnsforwardzone.md
+index 81919295..15b2b574 100644
+--- a/README-dnsforwardzone.md
++++ b/README-dnsforwardzone.md
+@@ -99,8 +99,10 @@ 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` | Zone name (FQDN). | yes if `state` == `present`
+-`forwarders` \| `idnsforwarders` |  Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`) | no
+-`forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
++`forwarders` \| `idnsforwarders` |  Per-zone forwarders. A custom port can be specified for each forwarder. Options | no
++&nbsp; | `ip_address`: The forwarder IP address. | yes
++&nbsp; | `port`: The forwarder IP port. | no
++`forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
+ `skip_overlap_check` | Force DNS zone creation even if it will overlap with an existing zone. Defaults to False. | no
+ `action` | Work on group or member level. It can be on of `member` or `dnsforwardzone` and defaults to `dnsforwardzone`. | no
+ `state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | yes
+diff --git a/plugins/modules/ipadnsforwardzone.py b/plugins/modules/ipadnsforwardzone.py
+index 3968e6a1..8e5c3464 100644
+--- a/plugins/modules/ipadnsforwardzone.py
++++ b/plugins/modules/ipadnsforwardzone.py
+@@ -54,9 +54,16 @@
+   forwarders:
+     description:
+     - List of the DNS servers to forward to
+-    required: true
+-    type: list
+     aliases: ["idnsforwarders"]
++    options:
++      ip_address:
++        description: Forwarder IP address (either IPv4 or IPv6).
++        required: false
++        type: string
++      port:
++        description: Forwarder port.
++        required: false
++        type: int
+   forwardpolicy:
+     description: Per-zone conditional forwarding policy
+     required: false
+@@ -128,6 +135,20 @@ def gen_args(forwarders, forwardpolicy, skip_overlap_check):
+     return _args
+ 
+ 
++def forwarder_list(forwarders):
++    """Convert the forwarder dict into a list compatible with IPA API."""
++    if forwarders is None:
++        return None
++    fwd_list = []
++    for forwarder in forwarders:
++        if forwarder.get('port', None) is not None:
++            formatter = "{ip_address} port {port}"
++        else:
++            formatter = "{ip_address}"
++        fwd_list.append(formatter.format(**forwarder))
++    return fwd_list
++
++
+ def main():
+     ansible_module = AnsibleModule(
+         argument_spec=dict(
+@@ -136,8 +157,13 @@ def main():
+             ipaadmin_password=dict(type="str", required=False, no_log=True),
+             name=dict(type="list", aliases=["cn"], default=None,
+                       required=True),
+-            forwarders=dict(type='list', aliases=["idnsforwarders"],
+-                            required=False),
++            forwarders=dict(type="list", default=None, required=False,
++                            aliases=["idnsforwarders"], elements='dict',
++                            options=dict(
++                                 ip_address=dict(type='str', required=True),
++                                 port=dict(type='int', required=False,
++                                           default=None),
++                            )),
+             forwardpolicy=dict(type='str', aliases=["idnsforwardpolicy"],
+                                required=False,
+                                choices=['only', 'first', 'none']),
+@@ -160,7 +186,8 @@ def main():
+                                           "ipaadmin_password")
+     names = module_params_get(ansible_module, "name")
+     action = module_params_get(ansible_module, "action")
+-    forwarders = module_params_get(ansible_module, "forwarders")
++    forwarders = forwarder_list(
++        module_params_get(ansible_module, "forwarders"))
+     forwardpolicy = module_params_get(ansible_module, "forwardpolicy")
+     skip_overlap_check = module_params_get(ansible_module,
+                                            "skip_overlap_check")
+diff --git a/tests/dnsforwardzone/test_dnsforwardzone.yml b/tests/dnsforwardzone/test_dnsforwardzone.yml
+index d94db9e5..468cd4ce 100644
+--- a/tests/dnsforwardzone/test_dnsforwardzone.yml
++++ b/tests/dnsforwardzone/test_dnsforwardzone.yml
+@@ -5,7 +5,7 @@
+   gather_facts: false
+ 
+   tasks:
+-  - name: ensure test forwardzones are absent - prep
++  - name: ensure test forwardzones are absent
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name:
+@@ -19,7 +19,7 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
++        - ip_address: 8.8.8.8
+       forwardpolicy: first
+       skip_overlap_check: true
+     register: result
+@@ -31,7 +31,7 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
++        - ip_address: 8.8.8.8
+       forwardpolicy: first
+       skip_overlap_check: true
+     register: result
+@@ -43,19 +43,22 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
+-        - 4.4.4.4
++        - ip_address: 8.8.8.8
++        - ip_address: 4.4.4.4
++          port: 8053
+       forwardpolicy: first
+       skip_overlap_check: true
+     register: result
+     failed_when: not result.changed
+ 
++  - pause:
++
+   - name: ensure forwardzone example.com has one forwarder again
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
++        - ip_address: 8.8.8.8
+       forwardpolicy: first
+       skip_overlap_check: true
+       state: present
+@@ -67,7 +70,7 @@
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
++        - ip_address: 8.8.8.8
+       forwardpolicy: first
+       skip_overlap_check: false
+       state: present
+@@ -80,8 +83,9 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
+-        - 4.4.4.4
++        - ip_address: 8.8.8.8
++        - ip_address: 4.4.4.4
++          port: 8053
+       forwardpolicy: only
+       skip_overlap_check: false
+     register: result
+@@ -100,7 +104,7 @@
+       name: example.com
+       skip_overlap_check: true
+       forwarders:
+-        - 8.8.8.8
++        - ip_address: 8.8.8.8
+     register: result
+     failed_when: not result.changed
+ 
+@@ -110,7 +114,8 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 4.4.4.4
++        - ip_address: 4.4.4.4
++          port: 8053
+       action: member
+     register: result
+     failed_when: not result.changed
+@@ -121,8 +126,9 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 4.4.4.4
+-        - 8.8.8.8
++        - ip_address: 4.4.4.4
++          port: 8053
++        - ip_address: 8.8.8.8
+       action: member
+     register: result
+     failed_when: result.changed
+@@ -133,7 +139,7 @@
+       state: absent
+       name: example.com
+       forwarders:
+-        - 8.8.8.8
++        - ip_address: 8.8.8.8
+       action: member
+     register: result
+     failed_when: not result.changed
+@@ -144,7 +150,8 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 4.4.4.4
++        - ip_address: 4.4.4.4
++          port: 8053
+       action: member
+     register: result
+     failed_when: result.changed
+@@ -161,7 +168,8 @@
+       state: present
+       name: example.com
+       forwarders:
+-        - 4.4.4.4
++        - ip_address: 4.4.4.4
++          port: 8053
+       action: member
+       skip_overlap_check: true
+     register: result
+@@ -179,7 +187,8 @@
+       state: disabled
+       name: example.com
+       forwarders:
+-        - 4.4.4.4
++        - ip_address: 4.4.4.4
++          port: 8053
+       skip_overlap_check: true
+     register: result
+     failed_when: not result.changed
+From bf864469a1da81c6b23e9726562b21408764ac8f Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Mon, 15 Jun 2020 20:42:23 -0300
+Subject: [PATCH] Add support for attribute `permission` on dnsforwardzone
+ module.
+
+Adds missing attribute `permission to dnsforwardzone module, that
+enable setting `manageby` for the DNS Forwar Zone.
+---
+ README-dnsforwardzone.md                     |   1 +
+ plugins/modules/ipadnsforwardzone.py         |  71 ++++++++----
+ tests/dnsforwardzone/test_dnsforwardzone.yml | 110 +++++++++++++++----
+ 3 files changed, 136 insertions(+), 46 deletions(-)
+
+diff --git a/README-dnsforwardzone.md b/README-dnsforwardzone.md
+index 15b2b574..175e6f8b 100644
+--- a/README-dnsforwardzone.md
++++ b/README-dnsforwardzone.md
+@@ -104,6 +104,7 @@ Variable | Description | Required
+ &nbsp; | `port`: The forwarder IP port. | no
+ `forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
+ `skip_overlap_check` | Force DNS zone creation even if it will overlap with an existing zone. Defaults to False. | no
++`permission` | Allow DNS Forward Zone to be managed. (bool) | no
+ `action` | Work on group or member level. It can be on of `member` or `dnsforwardzone` and defaults to `dnsforwardzone`. | no
+ `state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | yes
+ 
+diff --git a/plugins/modules/ipadnsforwardzone.py b/plugins/modules/ipadnsforwardzone.py
+index 8e5c3464..a729197b 100644
+--- a/plugins/modules/ipadnsforwardzone.py
++++ b/plugins/modules/ipadnsforwardzone.py
+@@ -75,6 +75,11 @@
+     - Force DNS zone creation even if it will overlap with an existing zone.
+     required: false
+     default: false
++  permission:
++    description:
++    - Allow DNS Forward Zone to be managed.
++    required: false
++    type: bool
+ '''
+ 
+ EXAMPLES = '''
+@@ -168,6 +173,8 @@ def main():
+                                required=False,
+                                choices=['only', 'first', 'none']),
+             skip_overlap_check=dict(type='bool', required=False),
++            permission=dict(type='bool', required=False,
++                            aliases=['managedby']),
+             action=dict(type="str", default="dnsforwardzone",
+                         choices=["member", "dnsforwardzone"]),
+             # state
+@@ -191,6 +198,7 @@ def main():
+     forwardpolicy = module_params_get(ansible_module, "forwardpolicy")
+     skip_overlap_check = module_params_get(ansible_module,
+                                            "skip_overlap_check")
++    permission = module_params_get(ansible_module, "permission")
+     state = module_params_get(ansible_module, "state")
+ 
+     if state == 'present' and len(names) != 1:
+@@ -215,7 +223,9 @@ def main():
+         wants_enable = True
+ 
+     if operation == "del":
+-        invalid = ["forwarders", "forwardpolicy", "skip_overlap_check"]
++        invalid = [
++            "forwarders", "forwardpolicy", "skip_overlap_check", "permission"
++        ]
+         for x in invalid:
+             if vars()[x] is not None:
+                 ansible_module.fail_json(
+@@ -241,6 +251,9 @@ def main():
+         api_connect()
+ 
+         for name in names:
++            commands = []
++            command = None
++
+             # Make sure forwardzone exists
+             existing_resource = find_dnsforwardzone(ansible_module, name)
+ 
+@@ -249,6 +262,18 @@ def main():
+                 if existing_resource is None and not forwarders:
+                     ansible_module.fail_json(msg='No forwarders specified.')
+ 
++            if existing_resource is not None:
++                if state != "absent":
++                    if forwarders:
++                        forwarders = list(
++                            set(existing_resource["idnsforwarders"]
++                                + forwarders))
++                else:
++                    if forwarders:
++                        forwarders = list(
++                            set(existing_resource["idnsforwarders"])
++                            - set(forwarders))
++
+             if existing_resource is None and operation == "update":
+                 # does not exist and is updating
+                 # trying to update something that doesn't exist, so error
+@@ -256,20 +281,17 @@ def main():
+                                                          valid""" % (name))
+             elif existing_resource is None and operation == "del":
+                 # does not exists and should be absent
+-                # set command
+-                command = None
+                 # enabled or disabled?
+                 is_enabled = "IGNORE"
+             elif existing_resource is not None and operation == "del":
+                 # exists but should be absent
+                 # set command
+                 command = "dnsforwardzone_del"
++                args = {}
+                 # enabled or disabled?
+                 is_enabled = "IGNORE"
+             elif forwarders is None:
+                 # forwarders are not defined its not a delete, update state?
+-                # set command
+-                command = None
+                 # enabled or disabled?
+                 if existing_resource is not None:
+                     is_enabled = existing_resource["idnszoneactive"][0]
+@@ -278,23 +300,13 @@ def main():
+             elif existing_resource is not None and operation == "update":
+                 # exists and is updating
+                 # calculate the new forwarders and mod
+-                # determine args
+-                if state != "absent":
+-                    forwarders = list(set(existing_resource["idnsforwarders"]
+-                                          + forwarders))
+-                else:
+-                    forwarders = list(set(existing_resource["idnsforwarders"])
+-                                      - set(forwarders))
+-                args = gen_args(forwarders, forwardpolicy,
+-                                skip_overlap_check)
+-                if skip_overlap_check is not None:
++                args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
++                if "skip_overlap_check" in args:
+                     del args['skip_overlap_check']
+ 
+                 # command
+                 if not compare_args_ipa(ansible_module, args, existing_resource):
+                     command = "dnsforwardzone_mod"
+-                else:
+-                    command = None
+ 
+                 # enabled or disabled?
+                 is_enabled = existing_resource["idnszoneactive"][0]
+@@ -313,21 +325,36 @@ def main():
+                 # exists and should be present, has it changed?
+                 # determine args
+                 args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
+-                if skip_overlap_check is not None:
++                if 'skip_overlap_check' in args:
+                     del args['skip_overlap_check']
+ 
+                 # set command
+                 if not compare_args_ipa(ansible_module, args, existing_resource):
+                     command = "dnsforwardzone_mod"
+-                else:
+-                    command = None
+ 
+                 # enabled or disabled?
+                 is_enabled = existing_resource["idnszoneactive"][0]
+ 
+-            # if command is set then run it with the args
++            # if command is set...
+             if command is not None:
+-                api_command(ansible_module, command, name, args)
++                commands.append([name, command, args])
++
++            if permission is not None:
++                if existing_resource is None:
++                    managedby = None
++                else:
++                    managedby = existing_resource.get('managedby', None)
++                if permission and managedby is None:
++                    commands.append(
++                        [name, 'dnsforwardzone_add_permission', {}]
++                    )
++                elif not permission and managedby is not None:
++                    commands.append(
++                        [name, 'dnsforwardzone_remove_permission', {}]
++                    )
++
++            for name, command, args in commands:
++                result = api_command(ansible_module, command, name, args)
+                 changed = True
+ 
+             # does the enabled state match what we want (if we care)
+diff --git a/tests/dnsforwardzone/test_dnsforwardzone.yml b/tests/dnsforwardzone/test_dnsforwardzone.yml
+index 468cd4ce..0386bd48 100644
+--- a/tests/dnsforwardzone/test_dnsforwardzone.yml
++++ b/tests/dnsforwardzone/test_dnsforwardzone.yml
+@@ -51,8 +51,6 @@
+     register: result
+     failed_when: not result.changed
+ 
+-  - pause:
+-
+   - name: ensure forwardzone example.com has one forwarder again
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+@@ -63,7 +61,7 @@
+       skip_overlap_check: true
+       state: present
+     register: result
+-    failed_when: not result.changed
++    failed_when: result.changed
+ 
+   - name: skip_overlap_check can only be set on creation so change nothing
+     ipadnsforwardzone:
+@@ -77,6 +75,22 @@
+     register: result
+     failed_when: result.changed
+ 
++  - name: ensure forwardzone example.com is absent.
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      state: absent
++    register: result
++    failed_when: not result.changed
++
++  - name: ensure forwardzone example.com is absent, again.
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      state: absent
++    register: result
++    failed_when: result.changed
++
+   - name: change all the things at once
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+@@ -87,11 +101,12 @@
+         - ip_address: 4.4.4.4
+           port: 8053
+       forwardpolicy: only
+-      skip_overlap_check: false
++      skip_overlap_check: true
++      permission: yes
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: ensure forwardzone example.com is absent for next testset
++  - name: ensure forwardzone example.com is absent.
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+@@ -156,43 +171,58 @@
+     register: result
+     failed_when: result.changed
+ 
+-  - name: ensure forwardzone example.com is absent again
++  - name: Add a permission for per-forward zone access delegation.
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+-      state: absent
++      permission: yes
++      action: member
++    register: result
++    failed_when: not result.changed
+ 
+-  - name: try to create a new forwarder with action=member
++  - name: Add a permission for per-forward zone access delegation, again.
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+-      state: present
+       name: example.com
+-      forwarders:
+-        - ip_address: 4.4.4.4
+-          port: 8053
++      permission: yes
+       action: member
+-      skip_overlap_check: true
+     register: result
+     failed_when: result.changed
+ 
+-  - name: ensure forwardzone example.com is absent - tidy up
++  - name: Remove a permission for per-forward zone access delegation.
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+-      state: absent
++      permission: no
++      action: member
++    register: result
++    failed_when: not result.changed
+ 
+-  - name: try to create a new forwarder is disabled state
++  - name: Remove a permission for per-forward zone access delegation, again.
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+-      state: disabled
+       name: example.com
+-      forwarders:
+-        - ip_address: 4.4.4.4
+-          port: 8053
+-      skip_overlap_check: true
++      permission: no
++      action: member
++    register: result
++    failed_when: result.changed
++
++  - name: disable the forwarder
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      state: disabled
+     register: result
+     failed_when: not result.changed
+ 
++  - name: disable the forwarder again
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      state: disabled
++    register: result
++    failed_when: result.changed
++
+   - name: enable the forwarder
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+@@ -201,12 +231,42 @@
+     register: result
+     failed_when: not result.changed
+ 
+-  - name: disable the forwarder again
++  - name: enable the forwarder, again
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+-      state: disabled
++      state: enabled
++    register: result
++    failed_when: result.changed
++
++  - name: ensure forwardzone example.com is absent again
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      state: absent
++
++  - name: try to create a new forwarder with action=member
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      state: present
++      name: example.com
++      forwarders:
++        - ip_address: 4.4.4.4
++          port: 8053
+       action: member
++      skip_overlap_check: true
++    register: result
++    failed_when: result.changed
++
++  - name: try to create a new forwarder with disabled state
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      state: disabled
++      name: example.com
++      forwarders:
++        - ip_address: 4.4.4.4
++          port: 8053
++      skip_overlap_check: yes
+     register: result
+     failed_when: not result.changed
+ 
+@@ -228,5 +288,7 @@
+   - name: ensure forwardzone example.com is absent - tidy up
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+-      name: example.com
++      name:
++      - example.com
++      - newfailzone.com
+       state: absent
+From 857fb82eb9141a44ffb91331653e1c30b43f671e Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Mon, 15 Jun 2020 23:40:35 -0300
+Subject: [PATCH] Allows modification of forward policy in existing DNS Forward
+ Zone.
+
+This patch allows the modification of the forward zone policy in
+an existing DNS Forward Zone, and fixes some issues with `enable`
+and `disable` state that prevented correct behavior of `forwardpolicy`.
+---
+ plugins/modules/ipadnsforwardzone.py         | 154 ++++++++++---------
+ tests/dnsforwardzone/test_dnsforwardzone.yml |  32 ++--
+ 2 files changed, 97 insertions(+), 89 deletions(-)
+
+diff --git a/plugins/modules/ipadnsforwardzone.py b/plugins/modules/ipadnsforwardzone.py
+index a729197b..1f1e85ec 100644
+--- a/plugins/modules/ipadnsforwardzone.py
++++ b/plugins/modules/ipadnsforwardzone.py
+@@ -217,10 +217,20 @@ def main():
+     else:
+         operation = "add"
+ 
+-    if state == "disabled":
+-        wants_enable = False
+-    else:
+-        wants_enable = True
++    if state in ["enabled", "disabled"]:
++        if action == "member":
++            ansible_module.fail_json(
++                msg="Action `member` cannot be used with state `%s`"
++                    % (state))
++        invalid = [
++            "forwarders", "forwardpolicy", "skip_overlap_check", "permission"
++        ]
++        for x in invalid:
++            if vars()[x] is not None:
++                ansible_module.fail_json(
++                    msg="Argument '%s' can not be used with action "
++                    "'%s', state `%s`" % (x, action, state))
++        wants_enable = (state == "enabled")
+ 
+     if operation == "del":
+         invalid = [
+@@ -230,7 +240,7 @@ def main():
+             if vars()[x] is not None:
+                 ansible_module.fail_json(
+                     msg="Argument '%s' can not be used with action "
+-                    "'%s'" % (x, action))
++                    "'%s', state `%s`" % (x, action, state))
+ 
+     changed = False
+     exit_args = {}
+@@ -262,7 +272,27 @@ def main():
+                 if existing_resource is None and not forwarders:
+                     ansible_module.fail_json(msg='No forwarders specified.')
+ 
+-            if existing_resource is not None:
++            if existing_resource is None:
++                if operation == "add":
++                    # does not exist but should be present
++                    # determine args
++                    args = gen_args(forwarders, forwardpolicy,
++                                    skip_overlap_check)
++                    # set command
++                    command = "dnsforwardzone_add"
++                    # enabled or disabled?
++
++                elif operation == "update":
++                    # does not exist and is updating
++                    # trying to update something that doesn't exist, so error
++                    ansible_module.fail_json(
++                        msg="dnsforwardzone '%s' not found." % (name))
++
++                elif operation == "del":
++                    # there's nothnig to do.
++                    continue
++
++            else:   # existing_resource is not None
+                 if state != "absent":
+                     if forwarders:
+                         forwarders = list(
+@@ -274,66 +304,51 @@ def main():
+                             set(existing_resource["idnsforwarders"])
+                             - set(forwarders))
+ 
+-            if existing_resource is None and operation == "update":
+-                # does not exist and is updating
+-                # trying to update something that doesn't exist, so error
+-                ansible_module.fail_json(msg="""dnsforwardzone '%s' is not
+-                                                         valid""" % (name))
+-            elif existing_resource is None and operation == "del":
+-                # does not exists and should be absent
+-                # enabled or disabled?
+-                is_enabled = "IGNORE"
+-            elif existing_resource is not None and operation == "del":
+-                # exists but should be absent
+-                # set command
+-                command = "dnsforwardzone_del"
+-                args = {}
+-                # enabled or disabled?
+-                is_enabled = "IGNORE"
+-            elif forwarders is None:
+-                # forwarders are not defined its not a delete, update state?
+-                # enabled or disabled?
++                if operation == "add":
++                    # exists and should be present, has it changed?
++                    # determine args
++                    args = gen_args(
++                        forwarders, forwardpolicy, skip_overlap_check)
++                    if 'skip_overlap_check' in args:
++                        del args['skip_overlap_check']
++
++                    # set command
++                    if not compare_args_ipa(
++                            ansible_module, args, existing_resource):
++                        command = "dnsforwardzone_mod"
++
++                elif operation == "del":
++                    # exists but should be absent
++                    # set command
++                    command = "dnsforwardzone_del"
++                    args = {}
++
++                elif operation == "update":
++                    # exists and is updating
++                    # calculate the new forwarders and mod
++                    args = gen_args(
++                        forwarders, forwardpolicy, skip_overlap_check)
++                    if "skip_overlap_check" in args:
++                        del args['skip_overlap_check']
++
++                    # command
++                    if not compare_args_ipa(
++                            ansible_module, args, existing_resource):
++                        command = "dnsforwardzone_mod"
++
++            if state in ['enabled', 'disabled']:
+                 if existing_resource is not None:
+                     is_enabled = existing_resource["idnszoneactive"][0]
+                 else:
+-                    is_enabled = "IGNORE"
+-            elif existing_resource is not None and operation == "update":
+-                # exists and is updating
+-                # calculate the new forwarders and mod
+-                args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
+-                if "skip_overlap_check" in args:
+-                    del args['skip_overlap_check']
+-
+-                # command
+-                if not compare_args_ipa(ansible_module, args, existing_resource):
+-                    command = "dnsforwardzone_mod"
+-
+-                # enabled or disabled?
+-                is_enabled = existing_resource["idnszoneactive"][0]
+-
+-            elif existing_resource is None and operation == "add":
+-                # does not exist but should be present
+-                # determine args
+-                args = gen_args(forwarders, forwardpolicy,
+-                                skip_overlap_check)
+-                # set command
+-                command = "dnsforwardzone_add"
+-                # enabled or disabled?
+-                is_enabled = "TRUE"
+-
+-            elif existing_resource is not None and operation == "add":
+-                # exists and should be present, has it changed?
+-                # determine args
+-                args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
+-                if 'skip_overlap_check' in args:
+-                    del args['skip_overlap_check']
+-
+-                # set command
+-                if not compare_args_ipa(ansible_module, args, existing_resource):
+-                    command = "dnsforwardzone_mod"
+-
+-                # enabled or disabled?
+-                is_enabled = existing_resource["idnszoneactive"][0]
++                    ansible_module.fail_json(
++                        msg="dnsforwardzone '%s' not found." % (name))
++
++            # does the enabled state match what we want (if we care)
++            if is_enabled != "IGNORE":
++                if wants_enable and is_enabled != "TRUE":
++                    commands.append([name, "dnsforwardzone_enable", {}])
++                elif not wants_enable and is_enabled != "FALSE":
++                    commands.append([name, "dnsforwardzone_disable", {}])
+ 
+             # if command is set...
+             if command is not None:
+@@ -354,20 +369,9 @@ def main():
+                     )
+ 
+             for name, command, args in commands:
+-                result = api_command(ansible_module, command, name, args)
++                api_command(ansible_module, command, name, args)
+                 changed = True
+ 
+-            # does the enabled state match what we want (if we care)
+-            if is_enabled != "IGNORE":
+-                if wants_enable and is_enabled != "TRUE":
+-                    api_command(ansible_module, "dnsforwardzone_enable",
+-                                name, {})
+-                    changed = True
+-                elif not wants_enable and is_enabled != "FALSE":
+-                    api_command(ansible_module, "dnsforwardzone_disable",
+-                                name, {})
+-                    changed = True
+-
+     except Exception as e:
+         ansible_module.fail_json(msg=str(e))
+ 
+diff --git a/tests/dnsforwardzone/test_dnsforwardzone.yml b/tests/dnsforwardzone/test_dnsforwardzone.yml
+index 0386bd48..223cf3d0 100644
+--- a/tests/dnsforwardzone/test_dnsforwardzone.yml
++++ b/tests/dnsforwardzone/test_dnsforwardzone.yml
+@@ -106,6 +106,22 @@
+     register: result
+     failed_when: not result.changed
+ 
++  - name: change zone forward policy
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      forwardpolicy: first
++    register: result
++    failed_when: not result.changed
++
++  - name: change zone forward policy, again
++    ipadnsforwardzone:
++      ipaadmin_password: SomeADMINpassword
++      name: example.com
++      forwardpolicy: first
++    register: result
++    failed_when: result.changed
++
+   - name: ensure forwardzone example.com is absent.
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+@@ -256,27 +272,15 @@
+       action: member
+       skip_overlap_check: true
+     register: result
+-    failed_when: result.changed
++    failed_when: not result.failed or "not found" not in result.msg
+ 
+   - name: try to create a new forwarder with disabled state
+-    ipadnsforwardzone:
+-      ipaadmin_password: SomeADMINpassword
+-      state: disabled
+-      name: example.com
+-      forwarders:
+-        - ip_address: 4.4.4.4
+-          port: 8053
+-      skip_overlap_check: yes
+-    register: result
+-    failed_when: not result.changed
+-
+-  - name: ensure it stays disabled
+     ipadnsforwardzone:
+       ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: disabled
+     register: result
+-    failed_when: result.changed
++    failed_when: not result.failed or "not found" not in result.msg
+ 
+   - name: Ensure forwardzone is not added without forwarders, with correct message.
+     ipadnsforwardzone:
+From 8da6a6937919d0c390b870113fb557649c39c815 Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Fri, 26 Jun 2020 11:28:15 -0300
+Subject: [PATCH] Change password values in README to keep consistency with
+ other modules.
+
+---
+ README-dnsforwardzone.md | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/README-dnsforwardzone.md b/README-dnsforwardzone.md
+index 175e6f8b..32de7bfe 100644
+--- a/README-dnsforwardzone.md
++++ b/README-dnsforwardzone.md
+@@ -49,7 +49,7 @@ Example playbook to ensure presence of a forwardzone to ipa DNS:
+   tasks:
+   - name: ensure presence of forwardzone for DNS requests for example.com to 8.8.8.8
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -59,13 +59,13 @@ Example playbook to ensure presence of a forwardzone to ipa DNS:
+ 
+   - name: ensure the forward zone is disabled
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: disabled
+ 
+   - name: ensure presence of multiple upstream DNS servers for example.com
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -74,7 +74,7 @@ Example playbook to ensure presence of a forwardzone to ipa DNS:
+ 
+   - name: ensure presence of another forwarder to any existing ones for example.com
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       state: present
+       name: example.com
+       forwarders:
+@@ -83,7 +83,7 @@ Example playbook to ensure presence of a forwardzone to ipa DNS:
+ 
+   - name: ensure the forwarder for example.com does not exists (delete it if needed)
+     ipadnsforwardzone:
+-      ipaadmin_password: password01
++      ipaadmin_password: SomeADMINpassword
+       name: example.com
+       state: absent
+ ```
diff --git a/SOURCES/ansible-freeipa-0.1.12-Fixes-service-disable-when-service-has-no-certificates-attached_rhbz#1836294.patch b/SOURCES/ansible-freeipa-0.1.12-Fixes-service-disable-when-service-has-no-certificates-attached_rhbz#1836294.patch
new file mode 100644
index 0000000..b4b8e6e
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-Fixes-service-disable-when-service-has-no-certificates-attached_rhbz#1836294.patch
@@ -0,0 +1,112 @@
+From e57e4908f936c524085fb5853fe4493c7711ab3f Mon Sep 17 00:00:00 2001
+From: Rafael Guterres Jeffman <rjeffman@redhat.com>
+Date: Thu, 25 Jun 2020 16:26:30 -0300
+Subject: [PATCH] Fixes service disable when service has no certificates
+ attached.
+
+Services without certificates, but with keytabs were not being
+disabled. This change allows execution of service_disable if
+there is a certificate or if has_keytab is true.
+
+A new test was added to verify the issue:
+
+    tests/service/test_service_disable.yml
+---
+ plugins/modules/ipaservice.py          |  8 +--
+ tests/service/test_service_disable.yml | 68 ++++++++++++++++++++++++++
+ 2 files changed, 73 insertions(+), 3 deletions(-)
+ create mode 100644 tests/service/test_service_disable.yml
+
+diff --git a/plugins/modules/ipaservice.py b/plugins/modules/ipaservice.py
+index 23a0d6b3..b0d25355 100644
+--- a/plugins/modules/ipaservice.py
++++ b/plugins/modules/ipaservice.py
+@@ -812,9 +812,11 @@ def main():
+ 
+             elif state == "disabled":
+                 if action == "service":
+-                    if res_find is not None and \
+-                       len(res_find.get('usercertificate', [])) > 0:
+-                        commands.append([name, 'service_disable', {}])
++                    if res_find is not None:
++                        has_cert = bool(res_find.get('usercertificate'))
++                        has_keytab = res_find.get('has_keytab', False)
++                        if has_cert or has_keytab:
++                            commands.append([name, 'service_disable', {}])
+                 else:
+                     ansible_module.fail_json(
+                         msg="Invalid action '%s' for state '%s'" %
+diff --git a/tests/service/test_service_disable.yml b/tests/service/test_service_disable.yml
+new file mode 100644
+index 00000000..3b4a88fb
+--- /dev/null
++++ b/tests/service/test_service_disable.yml
+@@ -0,0 +1,68 @@
++---
++- name: Playbook to manage IPA service.
++  hosts: ipaserver
++  become: yes
++  gather_facts: yes
++
++  tasks:
++  - name: Ensure service is absent
++    ipaservice:
++      ipaadmin_password: SomeADMINpassword
++      name: "mysvc1/{{ ansible_fqdn }}"
++
++  - name: Ensure service is present
++    ipaservice:
++      ipaadmin_password: SomeADMINpassword
++      name: "mysvc1/{{ ansible_fqdn }}"
++      certificate:
++        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
++      force: no
++    register: result
++    failed_when: not result.changed
++
++  - name: Obtain keytab
++    shell: ipa-getkeytab -s "{{ ansible_fqdn }}" -p "mysvc1/{{ ansible_fqdn }}" -k mysvc1.keytab
++
++  - name: Verify keytab
++    shell: ipa service-find "mysvc1/{{ ansible_fqdn }}"
++    register: result
++    failed_when: result.failed or result.stdout | regex_search(" Keytab. true")
++
++  - name: Ensure service is disabled
++    ipaservice:
++      ipaadmin_password: SomeADMINpassword
++      name: "mysvc1/{{ ansible_fqdn }}"
++      state: disabled
++    register: result
++    failed_when: not result.changed
++
++  - name: Verify keytab
++    shell: ipa service-find "mysvc1/{{ ansible_fqdn }}"
++    register: result
++    failed_when: result.failed or result.stdout | regex_search(" Keytab. true")
++
++  - name: Obtain keytab
++    shell: ipa-getkeytab -s "{{ ansible_fqdn }}" -p "mysvc1/{{ ansible_fqdn }}" -k mysvc1.keytab
++
++  - name: Verify keytab
++    shell: ipa service-find "mysvc1/{{ ansible_fqdn }}"
++    register: result
++    failed_when: result.failed or result.stdout | regex_search(" Keytab. true")
++
++  - name: Ensure service is disabled
++    ipaservice:
++      ipaadmin_password: SomeADMINpassword
++      name: "mysvc1/{{ ansible_fqdn }}"
++      state: disabled
++    register: result
++    failed_when: not result.changed
++
++  - name: Verify keytab
++    shell: ipa service-find "mysvc1/{{ ansible_fqdn }}"
++    register: result
++    failed_when: result.failed or result.stdout | regex_search(" Keytab. true")
++
++  - name: Ensure service is absent
++    ipaservice:
++      ipaadmin_password: SomeADMINpassword
++      name: "mysvc1/{{ ansible_fqdn }}"
diff --git a/SOURCES/ansible-freeipa-0.1.12-action_plugins-ipaclient_get_otp-Discovered-python-n_rhbz#1852714.patch b/SOURCES/ansible-freeipa-0.1.12-action_plugins-ipaclient_get_otp-Discovered-python-n_rhbz#1852714.patch
new file mode 100644
index 0000000..14dbf4e
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-action_plugins-ipaclient_get_otp-Discovered-python-n_rhbz#1852714.patch
@@ -0,0 +1,49 @@
+From 80aac15de9026055ae2b9972859939cf7925b813 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Tue, 30 Jun 2020 17:32:19 +0200
+Subject: [PATCH] action_plugins/ipaclient_get_otp: Discovered python needed in
+ task_vars
+
+Ansible is now also supporting discovered_python_interpreter for
+action_plugins. task_vars needs to be non Null and contain a setting for
+discovered_python_interpreter. The ipaclient_get_otp action_plugin
+therefore needed to be adapted.
+---
+ roles/ipaclient/action_plugins/ipaclient_get_otp.py | 4 ++--
+ roles/ipaclient/tasks/install.yml                   | 1 -
+ 2 files changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/roles/ipaclient/action_plugins/ipaclient_get_otp.py b/roles/ipaclient/action_plugins/ipaclient_get_otp.py
+index dcddc0a..8e04ad9 100644
+--- a/roles/ipaclient/action_plugins/ipaclient_get_otp.py
++++ b/roles/ipaclient/action_plugins/ipaclient_get_otp.py
+@@ -164,7 +164,8 @@ class ActionModule(ActionBase):
+             return result
+ 
+         data = self._execute_module(module_name='ipaclient_get_facts',
+-                                    module_args=dict(), task_vars=None)
++                                    module_args=dict(), task_vars=task_vars)
++
+         try:
+             domain = data['ansible_facts']['ipa']['domain']
+             realm = data['ansible_facts']['ipa']['realm']
+@@ -245,4 +246,3 @@ class ActionModule(ActionBase):
+         finally:
+             # delete the local temp directory
+             shutil.rmtree(local_temp_dir, ignore_errors=True)
+-            run_cmd(['/usr/bin/kdestroy', '-c', tmp_ccache])
+diff --git a/roles/ipaclient/tasks/install.yml b/roles/ipaclient/tasks/install.yml
+index 0de3dea..4421f0c 100644
+--- a/roles/ipaclient/tasks/install.yml
++++ b/roles/ipaclient/tasks/install.yml
+@@ -134,7 +134,6 @@
+                    "Password cannot be set on enrolled host" not
+                        in result_ipaclient_get_otp.msg
+       delegate_to: "{{ result_ipaclient_test.servers[0] }}"
+-      delegate_facts: yes
+       ignore_errors: yes
+ 
+     - name: Install - Report error for OTP generation
+-- 
+2.26.2
+
diff --git a/SOURCES/ansible-freeipa-0.1.12-ipa-host-group-Fix-membermanager-unknow-user-issue_rhbz#1848426.patch b/SOURCES/ansible-freeipa-0.1.12-ipa-host-group-Fix-membermanager-unknow-user-issue_rhbz#1848426.patch
new file mode 100644
index 0000000..69069b2
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-ipa-host-group-Fix-membermanager-unknow-user-issue_rhbz#1848426.patch
@@ -0,0 +1,132 @@
+From 6132a947e65fb9c3a1ec5c059aed34afb06a67df Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Mon, 29 Jun 2020 13:12:12 +0200
+Subject: [PATCH] ipa[host]group: Fix membermanager unknow user issue
+
+If a unknown membermanager user presence will be ensured, the unknown user
+error was ignored. This has been fixed in ipagroup. The code for the error
+handling in ipagroup and ipahostgroup has been adapted because of this.
+
+New tests for tests/[host]group/test_[host]group_membermnager.yml have been
+added.
+---
+ plugins/modules/ipagroup.py                   | 19 +++++++++----------
+ plugins/modules/ipahostgroup.py               | 13 +++++++------
+ tests/group/test_group_membermanager.yml      | 11 ++++++++++-
+ .../test_hostgroup_membermanager.yml          | 11 ++++++++++-
+ 4 files changed, 36 insertions(+), 18 deletions(-)
+
+diff --git a/plugins/modules/ipagroup.py b/plugins/modules/ipagroup.py
+index 915bc499..903c256d 100644
+--- a/plugins/modules/ipagroup.py
++++ b/plugins/modules/ipagroup.py
+@@ -507,16 +507,15 @@ def main():
+             # All "already a member" and "not a member" failures in the
+             # result are ignored. All others are reported.
+             errors = []
+-            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))
++            for failed_item in result.get("failed", []):
++                failed = result["failed"][failed_item]
++                for member_type in failed:
++                    for member, failure in failed[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))
+             if len(errors) > 0:
+                 ansible_module.fail_json(msg=", ".join(errors))
+ 
+diff --git a/plugins/modules/ipahostgroup.py b/plugins/modules/ipahostgroup.py
+index 4c18e940..5f615160 100644
+--- a/plugins/modules/ipahostgroup.py
++++ b/plugins/modules/ipahostgroup.py
+@@ -423,14 +423,15 @@ def main():
+             # All "already a member" and "not a member" failures in the
+             # result are ignored. All others are reported.
+             errors = []
+-            if "failed" in result and "member" in result["failed"]:
+-                failed = result["failed"]["member"]
++            for failed_item in result.get("failed", []):
++                failed = result["failed"][failed_item]
+                 for member_type in failed:
+                     for member, failure in failed[member_type]:
+-                        if "already a member" not in failure \
+-                           and "not a member" not in failure:
+-                            errors.append("%s: %s %s: %s" % (
+-                                command, member_type, member, failure))
++                        if "already a member" in failure \
++                           or "not a member" in failure:
++                            continue
++                        errors.append("%s: %s %s: %s" % (
++                            command, member_type, member, failure))
+             if len(errors) > 0:
+                 ansible_module.fail_json(msg=", ".join(errors))
+ 
+diff --git a/tests/group/test_group_membermanager.yml b/tests/group/test_group_membermanager.yml
+index 1d38654f..661f26d6 100644
+--- a/tests/group/test_group_membermanager.yml
++++ b/tests/group/test_group_membermanager.yml
+@@ -8,7 +8,7 @@
+   - name: Ensure user manangeruser1 and manageruser2 is absent
+     ipauser:
+       ipaadmin_password: SomeADMINpassword
+-      name: manageruser1,manageruser2
++      name: manageruser1,manageruser2,unknown_user
+       state: absent
+ 
+   - name: Ensure group testgroup, managergroup1 and managergroup2 are absent
+@@ -185,6 +185,15 @@
+     register: result
+     failed_when: not result.changed
+ 
++  - name: Ensure unknown membermanager_user member failure
++    ipagroup:
++      ipaadmin_password: SomeADMINpassword
++      name: testgroup
++      membermanager_user: unknown_user
++      action: member
++    register: result
++    failed_when: result.changed or "no such entry" not in result.msg
++
+   - name: Ensure group testgroup, managergroup1 and managergroup2 are absent
+     ipagroup:
+       ipaadmin_password: SomeADMINpassword
+diff --git a/tests/hostgroup/test_hostgroup_membermanager.yml b/tests/hostgroup/test_hostgroup_membermanager.yml
+index c32d1088..c0f65460 100644
+--- a/tests/hostgroup/test_hostgroup_membermanager.yml
++++ b/tests/hostgroup/test_hostgroup_membermanager.yml
+@@ -15,7 +15,7 @@
+   - name: Ensure user manangeruser1 and manageruser2 is absent
+     ipauser:
+       ipaadmin_password: SomeADMINpassword
+-      name: manageruser1,manageruser2
++      name: manageruser1,manageruser2,unknown_user
+       state: absent
+ 
+   - name: Ensure group managergroup1 and managergroup2 are absent
+@@ -200,6 +200,15 @@
+     register: result
+     failed_when: not result.changed
+ 
++  - name: Ensure unknown membermanager_user member failure
++    ipahostgroup:
++      ipaadmin_password: SomeADMINpassword
++      name: testhostgroup
++      membermanager_user: unknown_user
++      action: member
++    register: result
++    failed_when: result.changed or "no such entry" not in result.msg
++
+   - name: Ensure host-group testhostgroup is absent
+     ipahostgroup:
+       ipaadmin_password: SomeADMINpassword
diff --git a/SOURCES/ansible-freeipa-0.1.12-ipa-server-replica-Fix-pkcs12-info-regressions-intro_rhbz#1853284.patch b/SOURCES/ansible-freeipa-0.1.12-ipa-server-replica-Fix-pkcs12-info-regressions-intro_rhbz#1853284.patch
new file mode 100644
index 0000000..ab45af0
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-ipa-server-replica-Fix-pkcs12-info-regressions-intro_rhbz#1853284.patch
@@ -0,0 +1,150 @@
+From 8ce5fd147aafc34e43dbe4246565c48eace2e115 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Thu, 2 Jul 2020 12:02:33 +0200
+Subject: [PATCH] ipa[server,replica]: Fix pkcs12 info regressions introduced
+ with CA-less
+
+With the CA-less patches the types for the pkcs12 infos have been changed
+to lists in the modules. This is resulting in a bad conversion from None
+to [''] for the parameters. Because of this a normal replica deployment is
+failing as [''] is not a valid value.
+
+The install.yml files for ipareplica and also ipaserver have been changed
+in the way that the pkcs12 values are checked if they are None. The
+parameter will simply be omitted in this case and the parameter in the
+module will become None by default.
+---
+ roles/ipareplica/tasks/install.yml | 18 +++++++++---------
+ roles/ipaserver/tasks/install.yml  | 10 +++++-----
+ 2 files changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/roles/ipareplica/tasks/install.yml b/roles/ipareplica/tasks/install.yml
+index fc7f83e..c2a6222 100644
+--- a/roles/ipareplica/tasks/install.yml
++++ b/roles/ipareplica/tasks/install.yml
+@@ -281,7 +281,7 @@
+       ccache: "{{ result_ipareplica_prepare.ccache }}"
+       installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
+       _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+-      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
++      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info  if result_ipareplica_prepare._dirsrv_pkcs12_info != None else omit }}"
+       subject_base: "{{ result_ipareplica_prepare.subject_base }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       _add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
+@@ -345,7 +345,7 @@
+       config_master_host_name:
+         "{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
+       ccache: "{{ result_ipareplica_prepare.ccache }}"
+-      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
++      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info  if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+ 
+   # We need to point to the master in ipa default conf when certmonger
+@@ -407,8 +407,8 @@
+       ccache: "{{ result_ipareplica_prepare.ccache }}"
+       _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+       _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+-      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
+-      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
++      _dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info if result_ipareplica_prepare._dirsrv_pkcs12_info != None else omit }}"
++      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       dirman_password: "{{ ipareplica_dirman_password }}"
+       ds_ca_subject: "{{ result_ipareplica_setup_ds.ds_ca_subject }}"
+@@ -429,7 +429,7 @@
+       ccache: "{{ result_ipareplica_prepare.ccache }}"
+       _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+       _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+-      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info }}"
++      _http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info if result_ipareplica_prepare._http_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       dirman_password: "{{ ipareplica_dirman_password }}"
+ 
+@@ -507,7 +507,7 @@
+       _kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
+       _kra_host_name: "{{ result_ipareplica_prepare.config_kra_host_name }}"
+       _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+-      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
++      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       dirman_password: "{{ ipareplica_dirman_password }}"
+ 
+@@ -529,7 +529,7 @@
+       _kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
+       _kra_host_name: "{{ result_ipareplica_prepare.config_kra_host_name }}"
+       _subject_base: "{{ result_ipareplica_prepare._subject_base }}"
+-      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
++      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       dirman_password: "{{ ipareplica_dirman_password }}"
+       config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
+@@ -554,7 +554,7 @@
+       ccache: "{{ result_ipareplica_prepare.ccache }}"
+       _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+       _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+-      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
++      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       dirman_password: "{{ ipareplica_dirman_password }}"
+ 
+@@ -574,7 +574,7 @@
+       ccache: "{{ result_ipareplica_prepare.ccache }}"
+       _ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
+       _ca_file: "{{ result_ipareplica_prepare._ca_file }}"
+-      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
++      _pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
+       _top_dir: "{{ result_ipareplica_prepare._top_dir }}"
+       dirman_password: "{{ ipareplica_dirman_password }}"
+       ds_ca_subject: "{{ result_ipareplica_setup_ds.ds_ca_subject }}"
+diff --git a/roles/ipaserver/tasks/install.yml b/roles/ipaserver/tasks/install.yml
+index 30f9da2..687f72d 100644
+--- a/roles/ipaserver/tasks/install.yml
++++ b/roles/ipaserver/tasks/install.yml
+@@ -203,7 +203,7 @@
+       # no_host_dns: "{{ result_ipaserver_test.no_host_dns }}"
+       dirsrv_config_file: "{{ ipaserver_dirsrv_config_file | default(omit) }}"
+       dirsrv_cert_files: "{{ ipaserver_dirsrv_cert_files | default(omit) }}"
+-      _dirsrv_pkcs12_info: "{{ result_ipaserver_test._dirsrv_pkcs12_info }}"
++      _dirsrv_pkcs12_info: "{{ result_ipaserver_test._dirsrv_pkcs12_info if result_ipaserver_test._dirsrv_pkcs12_info != None else omit }}"
+       external_cert_files:
+         "{{ ipaserver_external_cert_files | default(omit) }}"
+       subject_base: "{{ result_ipaserver_prepare.subject_base }}"
+@@ -240,7 +240,7 @@
+       no_hbac_allow: "{{ ipaserver_no_hbac_allow }}"
+       idstart: "{{ result_ipaserver_test.idstart }}"
+       idmax: "{{ result_ipaserver_test.idmax }}"
+-      _pkinit_pkcs12_info: "{{ result_ipaserver_test._pkinit_pkcs12_info }}"
++      _pkinit_pkcs12_info: "{{ result_ipaserver_test._pkinit_pkcs12_info if result_ipaserver_test._pkinit_pkcs12_info != None else omit }}"
+ 
+   - name: Install - Setup custodia
+     ipaserver_setup_custodia:
+@@ -270,7 +270,7 @@
+       no_pkinit: "{{ result_ipaserver_test.no_pkinit }}"
+       dirsrv_config_file: "{{ ipaserver_dirsrv_config_file | default(omit) }}"
+       dirsrv_cert_files: "{{ ipaserver_dirsrv_cert_files | default([]) }}"
+-      _dirsrv_pkcs12_info: "{{ result_ipaserver_test._dirsrv_pkcs12_info }}"
++      _dirsrv_pkcs12_info: "{{ result_ipaserver_test._dirsrv_pkcs12_info if result_ipaserver_test._dirsrv_pkcs12_info != None else omit }}"
+       external_ca: "{{ ipaserver_external_ca }}"
+       external_ca_type: "{{ ipaserver_external_ca_type | default(omit) }}"
+       external_ca_profile:
+@@ -334,7 +334,7 @@
+         idmax: "{{ result_ipaserver_test.idmax }}"
+         http_cert_files: "{{ ipaserver_http_cert_files | default([]) }}"
+         no_ui_redirect: "{{ ipaserver_no_ui_redirect }}"
+-        _http_pkcs12_info: "{{ result_ipaserver_test._http_pkcs12_info }}"
++        _http_pkcs12_info: "{{ result_ipaserver_test._http_pkcs12_info if result_ipaserver_test._http_pkcs12_info != None else omit }}"
+ 
+     - name: Install - Setup KRA
+       ipaserver_setup_kra:
+@@ -394,7 +394,7 @@
+         idstart: "{{ result_ipaserver_test.idstart }}"
+         idmax: "{{ result_ipaserver_test.idmax }}"
+         dirsrv_config_file: "{{ ipaserver_dirsrv_config_file | default(omit) }}"
+-        _dirsrv_pkcs12_info: "{{ result_ipaserver_test._dirsrv_pkcs12_info }}"
++        _dirsrv_pkcs12_info: "{{ result_ipaserver_test._dirsrv_pkcs12_info if result_ipaserver_test._dirsrv_pkcs12_info != None else omit }}"
+ 
+     - name: Install - Setup client
+       include_role:
+-- 
+2.26.2
+
diff --git a/SOURCES/ansible-freeipa-0.1.12-ipa-user,host-Fail-on-duplucate-names-in-the-users-and-hosts-lists_rhbz#1822683.patch b/SOURCES/ansible-freeipa-0.1.12-ipa-user,host-Fail-on-duplucate-names-in-the-users-and-hosts-lists_rhbz#1822683.patch
new file mode 100644
index 0000000..1bf52b7
--- /dev/null
+++ b/SOURCES/ansible-freeipa-0.1.12-ipa-user,host-Fail-on-duplucate-names-in-the-users-and-hosts-lists_rhbz#1822683.patch
@@ -0,0 +1,132 @@
+From 1d7fb31b8bfa00babd7c753b354d7344b531cd77 Mon Sep 17 00:00:00 2001
+From: Thomas Woerner <twoerner@redhat.com>
+Date: Mon, 29 Jun 2020 14:50:56 +0200
+Subject: [PATCH] ipa[user,host]: Fail on duplucate names in the users and
+ hosts lists
+
+It was possible to have several entries for names with the hosts and users
+lists. This resulted sometimes in errors but also unexpected changes. A new
+check has been added to make sure that the names in the users and hosts
+lists are unique.
+
+New tests have been added to verify this in the existing files:
+- tests/host/test_hosts.yml
+- tests/user/test_users.yml
+---
+ plugins/modules/ipahost.py |  7 +++++++
+ plugins/modules/ipauser.py |  7 +++++++
+ tests/host/test_hosts.yml  | 15 +++++++++++++++
+ tests/user/test_users.yml  | 19 +++++++++++++++++++
+ 4 files changed, 48 insertions(+)
+
+diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
+index 7a981f16..1fe11dc5 100644
+--- a/plugins/modules/ipahost.py
++++ b/plugins/modules/ipahost.py
+@@ -799,10 +799,15 @@ def main():
+         server_realm = api_get_realm()
+ 
+         commands = []
++        host_set = set()
+ 
+         for host in names:
+             if isinstance(host, dict):
+                 name = host.get("name")
++                if name in host_set:
++                    ansible_module.fail_json(
++                        msg="host '%s' is used more than once" % name)
++                host_set.add(name)
+                 description = host.get("description")
+                 locality = host.get("locality")
+                 location = host.get("location")
+@@ -1337,6 +1342,8 @@ def main():
+             else:
+                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
+ 
++        del host_set
++
+         # Execute commands
+ 
+         errors = []
+diff --git a/plugins/modules/ipauser.py b/plugins/modules/ipauser.py
+index b8152ee4..03713a41 100644
+--- a/plugins/modules/ipauser.py
++++ b/plugins/modules/ipauser.py
+@@ -958,10 +958,15 @@ def main():
+         # commands
+ 
+         commands = []
++        user_set = set()
+ 
+         for user in names:
+             if isinstance(user, dict):
+                 name = user.get("name")
++                if name in user_set:
++                    ansible_module.fail_json(
++                        msg="user '%s' is used more than once" % name)
++                user_set.add(name)
+                 # present
+                 first = user.get("first")
+                 last = user.get("last")
+@@ -1370,6 +1375,8 @@ def main():
+             else:
+                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
+ 
++        del user_set
++
+         # Execute commands
+ 
+         errors = []
+diff --git a/tests/host/test_hosts.yml b/tests/host/test_hosts.yml
+index 30fd6538..f82cc612 100644
+--- a/tests/host/test_hosts.yml
++++ b/tests/host/test_hosts.yml
+@@ -96,3 +96,18 @@
+       state: absent
+     register: result
+     failed_when: result.changed
++
++  - name: Duplicate names in hosts failure test
++    ipahost:
++      ipaadmin_password: SomeADMINpassword
++      hosts:
++      - name: "{{ host1_fqdn }}"
++        force: yes
++      - name: "{{ host2_fqdn }}"
++        force: yes
++      - name: "{{ host3_fqdn }}"
++        force: yes
++      - name: "{{ host3_fqdn }}"
++        force: yes
++    register: result
++    failed_when: result.changed or "is used more than once" not in result.msg
+diff --git a/tests/user/test_users.yml b/tests/user/test_users.yml
+index 5b5d4538..81c7b608 100644
+--- a/tests/user/test_users.yml
++++ b/tests/user/test_users.yml
+@@ -85,6 +85,25 @@
+     register: result
+     failed_when: result.changed
+ 
++  - name: Duplicate names in users failure test
++    ipauser:
++      ipaadmin_password: SomeADMINpassword
++      users:
++      - name: user1
++        givenname: user1
++        last: Last
++      - name: user2
++        first: user2
++        last: Last
++      - name: user3
++        first: user3
++        last: Last
++      - name: user3
++        first: user3
++        last: Last
++    register: result
++    failed_when: result.changed or "is used more than once" not in result.msg
++
+   - name: Remove test users
+     ipauser:
+       ipaadmin_password: SomeADMINpassword
diff --git a/SPECS/ansible-freeipa.spec b/SPECS/ansible-freeipa.spec
index 9ef5da6..c7862d2 100644
--- a/SPECS/ansible-freeipa.spec
+++ b/SPECS/ansible-freeipa.spec
@@ -5,11 +5,18 @@
 
 Summary: Roles and playbooks to deploy FreeIPA servers, replicas and clients
 Name: ansible-freeipa
-Version: 0.1.10
-Release: 1%{?dist}
+Version: 0.1.12
+Release: 4%{?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.12-Fixes-service-disable-when-service-has-no-certificates-attached_rhbz#1836294.patch
+Patch2: ansible-freeipa-0.1.12-Add-suppport-for-changing-password-of-symmetric-vaults_rhbz#1839197.patch
+Patch3: ansible-freeipa-0.1.12-Fix-forwardzone-issues_rhbz#1843826,1843828,1843829,1843830,1843831.patch
+Patch4: ansible-freeipa-0.1.12-ipa-host-group-Fix-membermanager-unknow-user-issue_rhbz#1848426.patch
+Patch5: ansible-freeipa-0.1.12-ipa-user,host-Fail-on-duplucate-names-in-the-users-and-hosts-lists_rhbz#1822683.patch
+Patch6: ansible-freeipa-0.1.12-action_plugins-ipaclient_get_otp-Discovered-python-n_rhbz#1852714.patch
+Patch7: ansible-freeipa-0.1.12-ipa-server-replica-Fix-pkcs12-info-regressions-intro_rhbz#1853284.patch
 BuildArch: noarch
 
 #Requires: ansible
@@ -30,6 +37,7 @@ Features
 - One-time-password (OTP) support for client installation
 - Repair mode for clients
 - Modules for dns forwarder management
+- Modules for dns record management
 - Modules for dns zone management
 - Modules for group management
 - Modules for hbacrule management
@@ -87,6 +95,13 @@ a separate step before starting the server installation.
 %prep
 %setup -q
 # Do not create backup files with patches
+%patch1 -p1
+%patch2 -p1
+%patch3 -p1
+%patch4 -p1
+%patch5 -p1
+%patch6 -p1
+%patch7 -p1
 # Fix python modules and module utils:
 # - Remove shebang
 # - Remove execute flag
@@ -121,6 +136,38 @@ cp -rp plugins/* %{buildroot}%{_datadir}/ansible/plugins/
 %doc playbooks
 
 %changelog
+* Thu Jul 02 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.12-4
+- ipa[server,replica]: Fix pkcs12 info regressions introduced with CA-less
+  Resolves: RHBZ#1853284
+
+* Wed Jul 01 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.12-3
+- action_plugins/ipaclient_get_otp: Discovered python needed in task_vars
+  Resolves: RHBZ#1852714
+
+* Mon Jun 29 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.12-2
+- Fixes service disable when service has no certificates attached
+  Resolves: RHBZ#1836294
+- Add suppport for changing password of symmetric vaults
+  Resolves: RHBZ#1839197
+- Fix forwardzone issues
+  Resolves: RHBZ#1843826
+  Resolves: RHBZ#1843828
+  Resolves: RHBZ#1843829
+  Resolves: RHBZ#1843830
+  Resolves: RHBZ#1843831
+- ipa[host]group: Fix membermanager unknow user issue
+  Resolves: RHBZ#1848426
+- ipa[user,host]: Fail on duplucate names in the users and hosts lists
+  Resolves: RHBZ#1822683
+
+* Mon Jun 15 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.12-1
+- Update to version 0.1.12 bug fix only release
+  Related: RHBZ#1818768
+
+* Thu Jun 11 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.11-1
+- Update to version 0.1.11
+  Related: RHBZ#1818768
+
 * Mon Apr 27 2020 Thomas Woerner <twoerner@redhat.com> - 0.1.10-1
 - Update to version 0.1.10:
   - ipaclient: Not delete keytab when ipaclient_on_master is true