From e64f81abc17ebac3254c07f1ac0c774e293f732e Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 03 2020 10:11:48 +0000 Subject: Apply patch ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch patch_name: ansible-freeipa-ipahost-Add-support-for-several-IP-addresses-and-also-to-change-them_rhbz#1783979,1783976.patch present_in_specfile: true --- diff --git a/README-host.md b/README-host.md index be5ad79..ecc59a9 100644 --- a/README-host.md +++ b/README-host.md @@ -65,6 +65,79 @@ Example playbook to ensure host presence: - "52:54:00:BD:97:1E" state: present ``` +Compared to `ipa host-add` command no IP address conflict check is done as the ipahost module supports to have several IPv4 and IPv6 addresses for a host. + + +Example playbook to ensure host presence with several IP addresses: + +```yaml +--- +- name: Playbook to handle hosts + hosts: ipaserver + become: true + + tasks: + # Ensure host is present + - ipahost: + ipaadmin_password: MyPassword123 + name: host01.example.com + description: Example host + ip_address: + - 192.168.0.123 + - 192.168.0.124 + - fe80::20c:29ff:fe02:a1b3 + - fe80::20c:29ff:fe02:a1b4 + locality: Lab + ns_host_location: Lab + ns_os_version: CentOS 7 + ns_hardware_platform: Lenovo T61 + mac_address: + - "08:00:27:E3:B1:2D" + - "52:54:00:BD:97:1E" + state: present +``` + + +Example playbook to ensure IP addresses are present for a host: + +```yaml +--- +- name: Playbook to handle hosts + hosts: ipaserver + become: true + + tasks: + # Ensure host is present + - ipahost: + ipaadmin_password: MyPassword123 + name: host01.example.com + ip_address: + - 192.168.0.124 + - fe80::20c:29ff:fe02:a1b4 + action: member + state: present +``` + + +Example playbook to ensure IP addresses are absent for a host: + +```yaml +--- +- name: Playbook to handle hosts + hosts: ipaserver + become: true + + tasks: + # Ensure host is present + - ipahost: + ipaadmin_password: MyPassword123 + name: host01.example.com + ip_address: + - 192.168.0.124 + - fe80::20c:29ff:fe02:a1b4 + action: member + state: absent +``` Example playbook to ensure host presence without DNS: @@ -215,7 +288,7 @@ Example playbook to disable a host: update_dns: yes state: disabled ``` -`update_dns` controls if the DNS entries will be updated. +`update_dns` controls if the DNS entries will be updated in this case. For `state` present it is controlling the update of the DNS SSHFP records, but not the the other DNS records. Example playbook to ensure a host is absent: @@ -286,8 +359,8 @@ Variable | Description | Required `ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no `force` | Force host name even if not in DNS. | no `reverse` | Reverse DNS detection. | no -`ip_address` \| `ipaddress` | The host IP address. | no -`update_dns` | Update DNS entries. | no +`ip_address` \| `ipaddress` | The host IP address list. It can contain IPv4 and IPv6 addresses. No conflict check for IP addresses is done. | no +`update_dns` | For existing hosts: DNS SSHFP records are updated with `state` present and all DNS entries for a host removed with `state` absent. | no Return Values diff --git a/playbooks/host/host-member-ipaddresses-absent.yml b/playbooks/host/host-member-ipaddresses-absent.yml new file mode 100644 index 0000000..2466dbd --- /dev/null +++ b/playbooks/host/host-member-ipaddresses-absent.yml @@ -0,0 +1,17 @@ +--- +- name: Host member IP addresses absent + hosts: ipaserver + become: true + + tasks: + - name: Ensure host01.example.com IP addresses absent + ipahost: + ipaadmin_password: MyPassword123 + name: host01.example.com + ip_address: + - 192.168.0.123 + - fe80::20c:29ff:fe02:a1b3 + - 192.168.0.124 + - fe80::20c:29ff:fe02:a1b4 + action: member + state: absent diff --git a/playbooks/host/host-member-ipaddresses-present.yml b/playbooks/host/host-member-ipaddresses-present.yml new file mode 100644 index 0000000..f473993 --- /dev/null +++ b/playbooks/host/host-member-ipaddresses-present.yml @@ -0,0 +1,16 @@ +--- +- name: Host member IP addresses present + hosts: ipaserver + become: true + + tasks: + - name: Ensure host01.example.com IP addresses present + ipahost: + ipaadmin_password: MyPassword123 + name: host01.example.com + ip_address: + - 192.168.0.123 + - fe80::20c:29ff:fe02:a1b3 + - 192.168.0.124 + - fe80::20c:29ff:fe02:a1b4 + action: member diff --git a/playbooks/host/host-present-with-several-ip-addresses.yml b/playbooks/host/host-present-with-several-ip-addresses.yml new file mode 100644 index 0000000..4956562 --- /dev/null +++ b/playbooks/host/host-present-with-several-ip-addresses.yml @@ -0,0 +1,24 @@ +--- +- name: Host present with several IP addresses + hosts: ipaserver + become: true + + tasks: + - name: Ensure host is present + ipahost: + ipaadmin_password: MyPassword123 + name: host01.example.com + description: Example host + ip_address: + - 192.168.0.123 + - fe80::20c:29ff:fe02:a1b3 + - 192.168.0.124 + - fe80::20c:29ff:fe02:a1b4 + locality: Lab + ns_host_location: Lab + ns_os_version: CentOS 7 + ns_hardware_platform: Lenovo T61 + mac_address: + - "08:00:27:E3:B1:2D" + - "52:54:00:BD:97:1E" + state: present diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py index 9e97b88..6acdbef 100644 --- a/plugins/module_utils/ansible_freeipa_module.py +++ b/plugins/module_utils/ansible_freeipa_module.py @@ -42,6 +42,7 @@ try: from ipalib.x509 import Encoding except ImportError: from cryptography.hazmat.primitives.serialization import Encoding +import socket import base64 import six @@ -285,3 +286,25 @@ def encode_certificate(cert): if not six.PY2: encoded = encoded.decode('ascii') return encoded + + +def is_ipv4_addr(ipaddr): + """ + Test if figen IP address is a valid IPv4 address + """ + try: + socket.inet_pton(socket.AF_INET, ipaddr) + except socket.error: + return False + return True + + +def is_ipv6_addr(ipaddr): + """ + Test if figen IP address is a valid IPv6 address + """ + try: + socket.inet_pton(socket.AF_INET6, ipaddr) + except socket.error: + return False + return True diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py index dba4181..a5fd482 100644 --- a/plugins/modules/ipahost.py +++ b/plugins/modules/ipahost.py @@ -176,11 +176,16 @@ options: default: true required: false ip_address: - description: The host IP address + description: + The host IP address list (IPv4 and IPv6). No IP address conflict + check will be done. aliases: ["ipaddress"] required: false update_dns: - description: Update DNS entries + description: + Controls the update of the DNS SSHFP records for existing hosts and + the removal of all DNS entries if a host gets removed with state + absent. required: false description: description: The host description @@ -306,11 +311,16 @@ options: default: true required: false ip_address: - description: The host IP address + description: + The host IP address list (IPv4 and IPv6). No IP address conflict + check will be done. aliases: ["ipaddress"] required: false update_dns: - description: Update DNS entries + description: + Controls the update of the DNS SSHFP records for existing hosts and + the removal of all DNS entries if a host gets removed with state + absent. required: false update_password: description: @@ -398,7 +408,8 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_text from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \ - module_params_get, gen_add_del_lists, encode_certificate, api_get_realm + module_params_get, gen_add_del_lists, encode_certificate, api_get_realm, \ + is_ipv4_addr, is_ipv6_addr import six @@ -428,6 +439,32 @@ def find_host(module, name): return None +def find_dnsrecord(module, name): + domain_name = name[name.find(".")+1:] + host_name = name[:name.find(".")] + + _args = { + "all": True, + "idnsname": to_text(host_name), + } + + _result = api_command(module, "dnsrecord_find", to_text(domain_name), + _args) + + if len(_result["result"]) > 1: + module.fail_json( + msg="There is more than one host '%s'" % (name)) + elif len(_result["result"]) == 1: + _res = _result["result"][0] + certs = _res.get("usercertificate") + if certs is not None: + _res["usercertificate"] = [encode_certificate(cert) for + cert in certs] + return _res + else: + return None + + def show_host(module, name): _result = api_command(module, "host_show", to_text(name), {}) return _result["result"] @@ -470,16 +507,34 @@ def gen_args(description, locality, location, platform, os, password, random, _args["ipakrboktoauthasdelegate"] = ok_to_auth_as_delegate if force is not None: _args["force"] = force - if reverse is not None: - _args["no_reverse"] = not reverse if ip_address is not None: - _args["ip_address"] = ip_address + # IP addresses are handed extra, therefore it is needed to set + # the force option here to make sure that host-add is able to + # add a host without IP address. + _args["force"] = True if update_dns is not None: _args["updatedns"] = update_dns return _args +def gen_dnsrecord_args(module, ip_address, reverse): + _args = {} + if reverse is not None: + _args["a_extra_create_reverse"] = reverse + _args["aaaa_extra_create_reverse"] = reverse + if ip_address is not None: + for ip in ip_address: + if is_ipv4_addr(ip): + _args.setdefault("arecord", []).append(ip) + elif is_ipv6_addr(ip): + _args.setdefault("aaaarecord", []).append(ip) + else: + module.fail_json(msg="'%s' is not a valid IP address." % ip) + + return _args + + def check_parameters( module, state, action, description, locality, location, platform, os, password, random, @@ -499,8 +554,7 @@ def check_parameters( "os", "password", "random", "mac_address", "sshpubkey", "userclass", "auth_ind", "requires_pre_auth", "ok_as_delegate", "ok_to_auth_as_delegate", "force", - "reverse", "ip_address", "update_dns", - "update_password"] + "reverse", "update_dns", "update_password"] for x in invalid: if vars()[x] is not None: module.fail_json( @@ -512,7 +566,7 @@ def check_parameters( "password", "random", "mac_address", "sshpubkey", "userclass", "auth_ind", "requires_pre_auth", "ok_as_delegate", "ok_to_auth_as_delegate", "force", - "reverse", "ip_address", "update_password"] + "reverse", "update_password"] for x in invalid: if vars()[x] is not None: module.fail_json( @@ -549,9 +603,6 @@ def main(): default=None, no_log=True), random=dict(type="bool", aliases=["random_password"], default=None), - - - certificate=dict(type="list", aliases=["usercertificate"], default=None), managedby_host=dict(type="list", @@ -608,7 +659,7 @@ def main(): default=None), force=dict(type='bool', default=None), reverse=dict(type='bool', default=None), - ip_address=dict(type="str", aliases=["ipaddress"], + ip_address=dict(type="list", aliases=["ipaddress"], default=None), update_dns=dict(type="bool", aliases=["updatedns"], default=None), @@ -820,6 +871,7 @@ def main(): # Make sure host exists res_find = find_host(ansible_module, name) + res_find_dnsrecord = find_dnsrecord(ansible_module, name) # Create command if state == "present": @@ -829,6 +881,8 @@ def main(): random, mac_address, sshpubkey, userclass, auth_ind, requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate, force, reverse, ip_address, update_dns) + dnsrecord_args = gen_dnsrecord_args( + ansible_module, ip_address, reverse) if action == "host": # Found the host @@ -938,39 +992,20 @@ def main(): res_find.get( "ipaallowedtoperform_read_keys_hostgroup")) - else: - certificate_add = certificate or [] - certificate_del = [] - managedby_host_add = managedby_host or [] - managedby_host_del = [] - principal_add = principal or [] - principal_del = [] - allow_create_keytab_user_add = \ - allow_create_keytab_user or [] - allow_create_keytab_user_del = [] - allow_create_keytab_group_add = \ - allow_create_keytab_group or [] - allow_create_keytab_group_del = [] - allow_create_keytab_host_add = \ - allow_create_keytab_host or [] - allow_create_keytab_host_del = [] - allow_create_keytab_hostgroup_add = \ - allow_create_keytab_hostgroup or [] - allow_create_keytab_hostgroup_del = [] - allow_retrieve_keytab_user_add = \ - allow_retrieve_keytab_user or [] - allow_retrieve_keytab_user_del = [] - allow_retrieve_keytab_group_add = \ - allow_retrieve_keytab_group or [] - allow_retrieve_keytab_group_del = [] - allow_retrieve_keytab_host_add = \ - allow_retrieve_keytab_host or [] - allow_retrieve_keytab_host_del = [] - allow_retrieve_keytab_hostgroup_add = \ - allow_retrieve_keytab_hostgroup or [] - allow_retrieve_keytab_hostgroup_del = [] + # IP addresses are not really a member of hosts, but + # we will simply treat it as this to enable the + # addition and removal of IPv4 and IPv6 addresses in + # a simple way. + _dnsrec = res_find_dnsrecord or {} + dnsrecord_a_add, dnsrecord_a_del = gen_add_del_lists( + dnsrecord_args.get("arecord"), + _dnsrec.get("arecord")) + dnsrecord_aaaa_add, dnsrecord_aaaa_del = \ + gen_add_del_lists( + dnsrecord_args.get("aaaarecord"), + _dnsrec.get("aaaarecord")) - else: + if action != "host" or (action == "host" and res_find is None): certificate_add = certificate or [] certificate_del = [] managedby_host_add = managedby_host or [] @@ -1001,6 +1036,10 @@ def main(): allow_retrieve_keytab_hostgroup_add = \ allow_retrieve_keytab_hostgroup or [] allow_retrieve_keytab_hostgroup_del = [] + dnsrecord_a_add = dnsrecord_args.get("arecord") or [] + dnsrecord_a_del = [] + dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or [] + dnsrecord_aaaa_del = [] # Remove canonical principal from principal_del canonical_principal = "host/" + name + "@" + server_realm @@ -1135,6 +1174,36 @@ def main(): "hostgroup": allow_retrieve_keytab_hostgroup_del, }]) + if len(dnsrecord_a_add) > 0 or len(dnsrecord_aaaa_add) > 0: + domain_name = name[name.find(".")+1:] + host_name = name[:name.find(".")] + + commands.append([domain_name, + "dnsrecord_add", + { + "idnsname": host_name, + "arecord": dnsrecord_a_add, + "a_extra_create_reverse": reverse, + "aaaarecord": dnsrecord_aaaa_add, + "aaaa_extra_create_reverse": reverse + }]) + + if len(dnsrecord_a_del) > 0 or len(dnsrecord_aaaa_del) > 0: + domain_name = name[name.find(".")+1:] + host_name = name[:name.find(".")] + + # There seems to be an issue with dnsrecord_del (not + # for dnsrecord_add) if aaaarecord is an empty list. + # Therefore this is done differently here: + _args = {"idnsname": host_name} + if len(dnsrecord_a_del) > 0: + _args["arecord"] = dnsrecord_a_del + if len(dnsrecord_aaaa_del) > 0: + _args["aaaarecord"] = dnsrecord_aaaa_del + + commands.append([domain_name, + "dnsrecord_del", _args]) + elif state == "absent": if action == "host": @@ -1215,6 +1284,17 @@ def main(): "hostgroup": allow_retrieve_keytab_hostgroup, }]) + dnsrecord_args = gen_dnsrecord_args(ansible_module, + ip_address, reverse) + if "arecord" in dnsrecord_args or \ + "aaaarecord" in dnsrecord_args: + domain_name = name[name.find(".")+1:] + host_name = name[:name.find(".")] + dnsrecord_args["idnsname"] = host_name + + commands.append([domain_name, "dnsrecord_del", + dnsrecord_args]) + elif state == "disabled": if res_find is not None: commands.append([name, "host_disable", {}]) @@ -1259,6 +1339,11 @@ def main(): # Host is already disabled, ignore error if "This entry is already disabled" in msg: continue + + # Ignore no modification error. + if "no modifications to be performed" in msg: + continue + ansible_module.fail_json(msg="%s: %s: %s" % (command, name, msg)) diff --git a/tests/host/test_host.yml b/tests/host/test_host.yml index 1a555a1..f3ec11d 100644 --- a/tests/host/test_host.yml +++ b/tests/host/test_host.yml @@ -129,7 +129,7 @@ - name: Host "{{ host5_fqdn }}" present again ipahost: ipaadmin_password: MyPassword123 - name: "{{ host1_fqdn }}" + name: "{{ host5_fqdn }}" ip_address: "{{ ipv4_prefix + '.205' }}" update_dns: yes reverse: no diff --git a/tests/host/test_host_ipaddresses.yml b/tests/host/test_host_ipaddresses.yml new file mode 100644 index 0000000..0a97dd5 --- /dev/null +++ b/tests/host/test_host_ipaddresses.yml @@ -0,0 +1,312 @@ +--- +- name: Test host IP addresses + hosts: ipaserver + become: true + + tasks: + - name: Get Domain from server name + set_fact: + ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}" + when: ipaserver_domain is not defined + + - name: Set host1_fqdn .. host6_fqdn + set_fact: + host1_fqdn: "{{ 'host1.' + ipaserver_domain }}" + host2_fqdn: "{{ 'host2.' + ipaserver_domain }}" + host3_fqdn: "{{ 'host3.' + ipaserver_domain }}" + + - name: Get IPv4 address prefix from server node + set_fact: + ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] | + join('.') }}" + + - name: Host absent + ipahost: + ipaadmin_password: MyPassword123 + name: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + - "{{ host3_fqdn }}" + update_dns: yes + state: absent + + - name: Host "{{ host1_fqdn }}" present + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.201' }}" + - fe80::20c:29ff:fe02:a1b2 + update_dns: yes + reverse: no + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" present again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.201' }}" + - fe80::20c:29ff:fe02:a1b2 + update_dns: yes + reverse: no + register: result + failed_when: result.changed + + - name: Host "{{ host1_fqdn }}" present again with new IP address + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + update_dns: yes + reverse: no + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" present again with new IP address again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + update_dns: yes + reverse: no + register: result + failed_when: result.changed + + - name: Host "{{ host1_fqdn }}" member IPv4 address present + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: "{{ ipv4_prefix + '.201' }}" + action: member + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" member IPv4 address present again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: "{{ ipv4_prefix + '.201' }}" + action: member + register: result + failed_when: result.changed + + - name: Host "{{ host1_fqdn }}" member IPv4 address absent + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: "{{ ipv4_prefix + '.201' }}" + action: member + state: absent + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" member IPv4 address absent again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: "{{ ipv4_prefix + '.201' }}" + action: member + state: absent + register: result + failed_when: result.changed + + - name: Host "{{ host1_fqdn }}" member IPv6 address present + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: fe80::20c:29ff:fe02:a1b2 + action: member + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" member IPv6 address present again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: fe80::20c:29ff:fe02:a1b2 + action: member + register: result + failed_when: result.changed + + - name: Host "{{ host1_fqdn }}" member IPv6 address absent + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: fe80::20c:29ff:fe02:a1b2 + action: member + state: absent + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" member IPv6 address absent again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: fe80::20c:29ff:fe02:a1b2 + action: member + state: absent + register: result + + - name: Host "{{ host1_fqdn }}" member all ip-addresses absent + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + action: member + state: absent + register: result + failed_when: not result.changed + + - name: Host "{{ host1_fqdn }}" all member ip-addresses absent again + ipahost: + ipaadmin_password: MyPassword123 + name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + action: member + state: absent + register: result + failed_when: result.changed + + - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with same IP addresses + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + - name: "{{ host2_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + register: result + failed_when: not result.changed + + - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with same IP addresses again + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host1_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + - name: "{{ host2_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + register: result + failed_when: result.changed + + - name: Hosts "{{ host3_fqdn }}" present with same IP addresses + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host3_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + register: result + failed_when: not result.changed + + - name: Hosts "{{ host3_fqdn }}" present with same IP addresses again + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host3_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + register: result + failed_when: result.changed + + - name: Host "{{ host3_fqdn }}" present with differnt IP addresses + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host3_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.111' }}" + - fe80::20c:29ff:fe02:a1b1 + - "{{ ipv4_prefix + '.121' }}" + - fe80::20c:29ff:fe02:a1b2 + register: result + failed_when: not result.changed + + - name: Host "{{ host3_fqdn }}" present with different IP addresses again + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host3_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.111' }}" + - fe80::20c:29ff:fe02:a1b1 + - "{{ ipv4_prefix + '.121' }}" + - fe80::20c:29ff:fe02:a1b2 + register: result + failed_when: result.changed + + - name: Host "{{ host3_fqdn }}" present with old IP addresses + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host3_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + register: result + failed_when: not result.changed + + - name: Host "{{ host3_fqdn }}" present with old IP addresses again + ipahost: + ipaadmin_password: MyPassword123 + hosts: + - name: "{{ host3_fqdn }}" + ip_address: + - "{{ ipv4_prefix + '.211' }}" + - fe80::20c:29ff:fe02:a1b3 + - "{{ ipv4_prefix + '.221' }}" + - fe80::20c:29ff:fe02:a1b4 + register: result + failed_when: result.changed + + - name: Host absent + ipahost: + ipaadmin_password: MyPassword123 + name: + - "{{ host1_fqdn }}" + - "{{ host2_fqdn }}" + - "{{ host3_fqdn }}" + update_dns: yes + state: absent