Blame SOURCES/ansible-freeipa-0.1.12-Add-support-for-option-name_from_ip-in-ipadnszone-mo_rhbz#1845056.patch

01db47
From abbd15e6f50718119b4dd0380913d2d646eb7638 Mon Sep 17 00:00:00 2001
01db47
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
01db47
Date: Mon, 3 Aug 2020 19:23:07 -0300
01db47
Subject: [PATCH] Add support for option `name_from_ip` in ipadnszone module.
01db47
01db47
IPA CLI has an option `name_from_ip` that provide a name for a zone
01db47
from the reverse IP address, so that it can be used to, for example,
01db47
manage PTR DNS records.
01db47
01db47
This patch adds a similar attribute to ipadnszone module, where it
01db47
will try to find the proper zone name, using DNS resolve, or provide
01db47
a sane default, if a the zone name cannot be resolved.
01db47
01db47
The option `name_from_ip` must be used instead of `name` in playbooks,
01db47
and it is a string, and not a list.
01db47
01db47
A new example playbook was added:
01db47
01db47
    playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
01db47
A new test playbook was added:
01db47
01db47
    tests/dnszone/test_dnszone_name_from_ip.yml
01db47
---
01db47
 README-dnszone.md                             |   3 +-
01db47
 playbooks/dnszone/dnszone-reverse-from-ip.yml |  10 ++
01db47
 plugins/modules/ipadnszone.py                 |  65 +++++++++-
01db47
 tests/dnszone/test_dnszone_name_from_ip.yml   | 112 ++++++++++++++++++
01db47
 4 files changed, 186 insertions(+), 4 deletions(-)
01db47
 create mode 100644 playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
 create mode 100644 tests/dnszone/test_dnszone_name_from_ip.yml
01db47
01db47
diff --git a/README-dnszone.md b/README-dnszone.md
01db47
index 9c9b12c..48b019a 100644
01db47
--- a/README-dnszone.md
01db47
+++ b/README-dnszone.md
01db47
@@ -163,7 +163,8 @@ Variable | Description | Required
01db47
 -------- | ----------- | --------
01db47
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
01db47
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
01db47
-`name` \| `zone_name` | The zone name string or list of strings. | yes
01db47
+`name` \| `zone_name` | The zone name string or list of strings. | no
01db47
+`name_from_ip` | Derive zone name from reverse of IP (PTR). | no
01db47
 `forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
01db47
   | `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes
01db47
   | `port` - The custom port that should be used on this server. | no
01db47
diff --git a/playbooks/dnszone/dnszone-reverse-from-ip.yml b/playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
new file mode 100644
01db47
index 0000000..5693872
01db47
--- /dev/null
01db47
+++ b/playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
@@ -0,0 +1,10 @@
01db47
+---
01db47
+- name: Playbook to ensure DNS zone exist
01db47
+  hosts: ipaserver
01db47
+  become: true
01db47
+
01db47
+  tasks:
01db47
+  - name: Ensure zone exist, finding zone name from IP address.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 10.1.2.3
01db47
diff --git a/plugins/modules/ipadnszone.py b/plugins/modules/ipadnszone.py
01db47
index c5e812a..901bfef 100644
01db47
--- a/plugins/modules/ipadnszone.py
01db47
+++ b/plugins/modules/ipadnszone.py
01db47
@@ -43,6 +43,10 @@ options:
01db47
     required: true
01db47
     type: list
01db47
     alises: ["zone_name"]
01db47
+  name_from_ip:
01db47
+    description: Derive zone name from reverse of IP (PTR).
01db47
+    required: false
01db47
+    type: str
01db47
   forwarders:
01db47
     description: The list of global DNS forwarders.
01db47
     required: false
01db47
@@ -197,6 +201,12 @@ from ansible.module_utils.ansible_freeipa_module import (
01db47
     is_ipv6_addr,
01db47
     is_valid_port,
01db47
 )  # noqa: E402
01db47
+import netaddr
01db47
+import six
01db47
+
01db47
+
01db47
+if six.PY3:
01db47
+    unicode = str
01db47
 
01db47
 
01db47
 class DNSZoneModule(FreeIPABaseModule):
01db47
@@ -354,6 +364,31 @@ class DNSZoneModule(FreeIPABaseModule):
01db47
         if not zone and self.ipa_params.skip_nameserver_check is not None:
01db47
             return self.ipa_params.skip_nameserver_check
01db47
 
01db47
+    def __reverse_zone_name(self, ipaddress):
01db47
+        """
01db47
+        Infer reverse zone name from an ip address.
01db47
+
01db47
+        This function uses the same heuristics as FreeIPA to infer the zone
01db47
+        name from ip.
01db47
+        """
01db47
+        try:
01db47
+            ip = netaddr.IPAddress(str(ipaddress))
01db47
+        except (netaddr.AddrFormatError, ValueError):
01db47
+            net = netaddr.IPNetwork(ipaddress)
01db47
+            items = net.ip.reverse_dns.split('.')
01db47
+            prefixlen = net.prefixlen
01db47
+            ip_version = net.version
01db47
+        else:
01db47
+            items = ip.reverse_dns.split('.')
01db47
+            prefixlen = 24 if ip.version == 4 else 64
01db47
+            ip_version = ip.version
01db47
+        if ip_version == 4:
01db47
+            return u'.'.join(items[4 - prefixlen // 8:])
01db47
+        elif ip_version == 6:
01db47
+            return u'.'.join(items[32 - prefixlen // 4:])
01db47
+        else:
01db47
+            self.fail_json(msg="Invalid IP version for reverse zone.")
01db47
+
01db47
     def get_zone(self, zone_name):
01db47
         get_zone_args = {"idnsname": zone_name, "all": True}
01db47
         response = self.api_command("dnszone_find", args=get_zone_args)
01db47
@@ -368,14 +403,33 @@ class DNSZoneModule(FreeIPABaseModule):
01db47
         return zone, is_zone_active
01db47
 
01db47
     def get_zone_names(self):
01db47
-        if len(self.ipa_params.name) > 1 and self.ipa_params.state != "absent":
01db47
+        zone_names = self.__get_zone_names_from_params()
01db47
+        if len(zone_names) > 1 and self.ipa_params.state != "absent":
01db47
             self.fail_json(
01db47
                 msg=("Please provide a single name. Multiple values for 'name'"
01db47
                      "can only be supplied for state 'absent'.")
01db47
             )
01db47
 
01db47
+        return zone_names
01db47
+
01db47
+    def __get_zone_names_from_params(self):
01db47
+        if not self.ipa_params.name:
01db47
+            return [self.__reverse_zone_name(self.ipa_params.name_from_ip)]
01db47
         return self.ipa_params.name
01db47
 
01db47
+    def check_ipa_params(self):
01db47
+        if not self.ipa_params.name and not self.ipa_params.name_from_ip:
01db47
+            self.fail_json(
01db47
+                msg="Either `name` or `name_from_ip` must be provided."
01db47
+            )
01db47
+        if self.ipa_params.state != "present" and self.ipa_params.name_from_ip:
01db47
+            self.fail_json(
01db47
+                msg=(
01db47
+                    "Cannot use argument `name_from_ip` with state `%s`."
01db47
+                    % self.ipa_params.state
01db47
+                )
01db47
+            )
01db47
+
01db47
     def define_ipa_commands(self):
01db47
         for zone_name in self.get_zone_names():
01db47
             # Look for existing zone in IPA
01db47
@@ -434,8 +488,9 @@ def get_argument_spec():
01db47
         ipaadmin_principal=dict(type="str", default="admin"),
01db47
         ipaadmin_password=dict(type="str", required=False, no_log=True),
01db47
         name=dict(
01db47
-            type="list", default=None, required=True, aliases=["zone_name"]
01db47
+            type="list", default=None, required=False, aliases=["zone_name"]
01db47
         ),
01db47
+        name_from_ip=dict(type="str", default=None, required=False),
01db47
         forwarders=dict(
01db47
             type="list",
01db47
             default=None,
01db47
@@ -475,7 +530,11 @@ def get_argument_spec():
01db47
 
01db47
 
01db47
 def main():
01db47
-    DNSZoneModule(argument_spec=get_argument_spec()).ipa_run()
01db47
+    DNSZoneModule(
01db47
+        argument_spec=get_argument_spec(),
01db47
+        mutually_exclusive=[["name", "name_from_ip"]],
01db47
+        required_one_of=[["name", "name_from_ip"]],
01db47
+    ).ipa_run()
01db47
 
01db47
 
01db47
 if __name__ == "__main__":
01db47
diff --git a/tests/dnszone/test_dnszone_name_from_ip.yml b/tests/dnszone/test_dnszone_name_from_ip.yml
01db47
new file mode 100644
01db47
index 0000000..9bd2eb0
01db47
--- /dev/null
01db47
+++ b/tests/dnszone/test_dnszone_name_from_ip.yml
01db47
@@ -0,0 +1,112 @@
01db47
+---
01db47
+- name: Test dnszone
01db47
+  hosts: ipaserver
01db47
+  become: yes
01db47
+  gather_facts: yes
01db47
+
01db47
+  tasks:
01db47
+
01db47
+  # Setup
01db47
+  - name: Ensure zone is absent.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name: "{{ item }}"
01db47
+      state: absent
01db47
+    with_items:
01db47
+      - 2.0.192.in-addr.arpa.
01db47
+      - 0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f.ip6.arpa.
01db47
+      - 1.0.0.0.e.f.a.c.8.b.d.0.1.0.0.2.ip6.arpa.
01db47
+
01db47
+  # tests
01db47
+  - name: Ensure zone exists for reverse IP.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 192.0.2.3/24
01db47
+    register: ipv4_zone
01db47
+    failed_when: not ipv4_zone.changed or ipv4_zone.failed
01db47
+
01db47
+  - name: Ensure zone exists for reverse IP, again.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 192.0.2.3/24
01db47
+    register: result
01db47
+    failed_when: result.changed or result.failed
01db47
+
01db47
+  - name: Ensure zone exists for reverse IP, given the zone name.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name: "{{ ipv4_zone.dnszone.name }}"
01db47
+    register: result
01db47
+    failed_when: result.changed or result.failed
01db47
+
01db47
+  - name: Modify existing zone, using `name_from_ip`.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 192.0.2.3/24
01db47
+      default_ttl: 1234
01db47
+    register: result
01db47
+    failed_when: not result.changed
01db47
+
01db47
+  - name: Modify existing zone, using `name_from_ip`, again.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 192.0.2.3/24
01db47
+      default_ttl: 1234
01db47
+    register: result
01db47
+    failed_when: result.changed or result.failed
01db47
+
01db47
+  - name: Ensure ipv6 zone exists for reverse IPv6.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: fd00::0001
01db47
+    register: ipv6_zone
01db47
+    failed_when: not ipv6_zone.changed or ipv6_zone.failed
01db47
+
01db47
+  # - debug:
01db47
+  #     msg: "{{ipv6_zone}}"
01db47
+
01db47
+  - name: Ensure ipv6 zone was created.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name: "{{ ipv6_zone.dnszone.name }}"
01db47
+    register: result
01db47
+    failed_when: result.changed or result.failed
01db47
+
01db47
+  - name: Ensure ipv6 zone exists for reverse IPv6, again.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: fd00::0001
01db47
+    register: result
01db47
+    failed_when: result.changed
01db47
+
01db47
+  - name: Ensure second ipv6 zone exists for reverse IPv6.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 2001:db8:cafe:1::1
01db47
+    register: ipv6_sec_zone
01db47
+    failed_when: not ipv6_sec_zone.changed or ipv6_zone.failed
01db47
+
01db47
+  - name: Ensure second ipv6 zone was created.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name: "{{ ipv6_sec_zone.dnszone.name }}"
01db47
+    register: result
01db47
+    failed_when: result.changed or result.failed
01db47
+
01db47
+  - name: Ensure second ipv6 zone exists for reverse IPv6, again.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 2001:db8:cafe:1::1
01db47
+    register: result
01db47
+    failed_when: result.changed
01db47
+
01db47
+  # Cleanup
01db47
+  - name: Ensure zone is absent.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name: "{{ item }}"
01db47
+      state: absent
01db47
+    with_items:
01db47
+      - "{{ ipv6_zone.dnszone.name }}"
01db47
+      - "{{ ipv6_sec_zone.dnszone.name }}"
01db47
+      - "{{ ipv4_zone.dnszone.name }}"
01db47
-- 
01db47
2.26.2
01db47
01db47
From 531e544b30e69f436d14c4ce18c67998c1a0774b Mon Sep 17 00:00:00 2001
01db47
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
01db47
Date: Wed, 5 Aug 2020 15:13:46 -0300
01db47
Subject: [PATCH] Added support for client defined result data in
01db47
 FReeIPABaseModule
01db47
01db47
Modified support for processing result of IPA API commands so that
01db47
client code can define its own processing and add return values to
01db47
self.exit_args based on command result.
01db47
01db47
If a subclass need to process the result of IPA API commands it should
01db47
override the method `process_command_result`. The default implementation
01db47
will simply evaluate if `changed` should be true.
01db47
---
01db47
 .../module_utils/ansible_freeipa_module.py    | 22 +++++++++++++------
01db47
 plugins/modules/ipadnszone.py                 |  8 +++++++
01db47
 2 files changed, 23 insertions(+), 7 deletions(-)
01db47
01db47
diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
01db47
index 4799e5a..30302b4 100644
01db47
--- a/plugins/module_utils/ansible_freeipa_module.py
01db47
+++ b/plugins/module_utils/ansible_freeipa_module.py
01db47
@@ -619,7 +619,7 @@ class FreeIPABaseModule(AnsibleModule):
01db47
         if exc_val:
01db47
             self.fail_json(msg=str(exc_val))
01db47
 
01db47
-        self.exit_json(changed=self.changed, user=self.exit_args)
01db47
+        self.exit_json(changed=self.changed, **self.exit_args)
01db47
 
01db47
     def get_command_errors(self, command, result):
01db47
         """Look for erros into command results."""
01db47
@@ -658,14 +658,22 @@ class FreeIPABaseModule(AnsibleModule):
01db47
             except Exception as excpt:
01db47
                 self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
01db47
             else:
01db47
-                if "completed" in result:
01db47
-                    if result["completed"] > 0:
01db47
-                        self.changed = True
01db47
-                else:
01db47
-                    self.changed = True
01db47
-
01db47
+                self.process_command_result(name, command, args, result)
01db47
             self.get_command_errors(command, result)
01db47
 
01db47
+    def process_command_result(self, name, command, args, result):
01db47
+        """
01db47
+        Process an API command result.
01db47
+
01db47
+        This method can be overriden in subclasses, and change self.exit_values
01db47
+        to return data in the result for the controller.
01db47
+        """
01db47
+        if "completed" in result:
01db47
+            if result["completed"] > 0:
01db47
+                self.changed = True
01db47
+        else:
01db47
+            self.changed = True
01db47
+
01db47
     def require_ipa_attrs_change(self, command_args, ipa_attrs):
01db47
         """
01db47
         Compare given args with current object attributes.
01db47
diff --git a/plugins/modules/ipadnszone.py b/plugins/modules/ipadnszone.py
01db47
index 901bfef..6a90fa2 100644
01db47
--- a/plugins/modules/ipadnszone.py
01db47
+++ b/plugins/modules/ipadnszone.py
01db47
@@ -472,6 +472,14 @@ class DNSZoneModule(FreeIPABaseModule):
01db47
                 }
01db47
                 self.add_ipa_command("dnszone_mod", zone_name, args)
01db47
 
01db47
+    def process_command_result(self, name, command, args, result):
01db47
+        super(DNSZoneModule, self).process_command_result(
01db47
+            name, command, args, result
01db47
+        )
01db47
+        if command == "dnszone_add" and self.ipa_params.name_from_ip:
01db47
+            dnszone_exit_args = self.exit_args.setdefault('dnszone', {})
01db47
+            dnszone_exit_args['name'] = name
01db47
+
01db47
 
01db47
 def get_argument_spec():
01db47
     forwarder_spec = dict(
01db47
-- 
01db47
2.26.2
01db47
01db47
From 41e8226d0c03e06816626d78cecbc2aebf547691 Mon Sep 17 00:00:00 2001
01db47
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
01db47
Date: Wed, 5 Aug 2020 15:14:43 -0300
01db47
Subject: [PATCH] Return the zone_name when adding a zone with name_from_ip.
01db47
01db47
When adding a zone using the option name_from_ip, the user have
01db47
little control over the final name of the zone, and if this name
01db47
is to be used in further processing in a playbook it might lead to
01db47
errors if the inferred name does not match what the user wanted to.
01db47
01db47
By returning the actual inferred zone name, the name can be safely
01db47
used for other tasks in the playbook.
01db47
---
01db47
 README-dnszone.md                             | 11 +++++++++++
01db47
 playbooks/dnszone/dnszone-reverse-from-ip.yml |  7 ++++++-
01db47
 plugins/modules/ipadnszone.py                 |  8 ++++++++
01db47
 3 files changed, 25 insertions(+), 1 deletion(-)
01db47
01db47
diff --git a/README-dnszone.md b/README-dnszone.md
01db47
index 48b019a..3f4827b 100644
01db47
--- a/README-dnszone.md
01db47
+++ b/README-dnszone.md
01db47
@@ -190,6 +190,17 @@ Variable | Description | Required
01db47
 `skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no
01db47
 
01db47
 
01db47
+Return Values
01db47
+=============
01db47
+
01db47
+ipadnszone
01db47
+----------
01db47
+
01db47
+Variable | Description | Returned When
01db47
+-------- | ----------- | -------------
01db47
+`dnszone` | DNS Zone dict with zone name infered from `name_from_ip`. 
Options: | If `state` is `present`, `name_from_ip` is used, and a zone was created.
01db47
+  | `name` - The name of the zone created, inferred from `name_from_ip`. | Always
01db47
+
01db47
 Authors
01db47
 =======
01db47
 
01db47
diff --git a/playbooks/dnszone/dnszone-reverse-from-ip.yml b/playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
index 5693872..218a318 100644
01db47
--- a/playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
+++ b/playbooks/dnszone/dnszone-reverse-from-ip.yml
01db47
@@ -7,4 +7,9 @@
01db47
   - name: Ensure zone exist, finding zone name from IP address.
01db47
     ipadnszone:
01db47
       ipaadmin_password: SomeADMINpassword
01db47
-      name_from_ip: 10.1.2.3
01db47
+      name_from_ip: 10.1.2.3/24
01db47
+    register: result
01db47
+
01db47
+  - name: Zone name inferred from `name_from_ip`
01db47
+    debug:
01db47
+      msg: "Zone created: {{ result.dnszone.name }}"
01db47
diff --git a/plugins/modules/ipadnszone.py b/plugins/modules/ipadnszone.py
01db47
index 6a90fa2..93eac07 100644
01db47
--- a/plugins/modules/ipadnszone.py
01db47
+++ b/plugins/modules/ipadnszone.py
01db47
@@ -192,6 +192,14 @@ EXAMPLES = """
01db47
 """
01db47
 
01db47
 RETURN = """
01db47
+dnszone:
01db47
+  description: DNS Zone dict with zone name infered from `name_from_ip`.
01db47
+  returned:
01db47
+    If `state` is `present`, `name_from_ip` is used, and a zone was created.
01db47
+  options:
01db47
+    name:
01db47
+      description: The name of the zone created, inferred from `name_from_ip`.
01db47
+      returned: always
01db47
 """
01db47
 
01db47
 from ipapython.dnsutil import DNSName  # noqa: E402
01db47
-- 
01db47
2.26.2
01db47
01db47
From 46bbc7bbd7a4e01d07b0390aee8c799aaa5ac895 Mon Sep 17 00:00:00 2001
01db47
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
01db47
Date: Mon, 17 Aug 2020 15:52:38 -0300
01db47
Subject: [PATCH] Document usage of `name_from_ip`.
01db47
01db47
Since `name_from_ip` has a similar, but not equal, behavior to `name`,
01db47
and as the inferred DNS zone might depend on DNS configuration and
01db47
can be different than the user expects, it has some limited usage,
01db47
and the user must be aware of its effects.
01db47
01db47
This change to the documentation enhance the documentation including
01db47
more details on the attribute usage.
01db47
---
01db47
 README-dnszone.md             | 42 ++++++++++++++++++++++++++++++++++-
01db47
 plugins/modules/ipadnszone.py |  4 +++-
01db47
 2 files changed, 44 insertions(+), 2 deletions(-)
01db47
01db47
diff --git a/README-dnszone.md b/README-dnszone.md
01db47
index 3f4827b..c5a7ab3 100644
01db47
--- a/README-dnszone.md
01db47
+++ b/README-dnszone.md
01db47
@@ -152,6 +152,46 @@ Example playbook to remove a zone:
01db47
 
01db47
 ```
01db47
 
01db47
+Example playbook to create a zone for reverse DNS lookup, from an IP address:
01db47
+
01db47
+```yaml
01db47
+
01db47
+---
01db47
+- name: dnszone present
01db47
+  hosts: ipaserver
01db47
+  become: true
01db47
+
01db47
+  tasks:
01db47
+  - name: Ensure zone for reverse DNS lookup is present.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 192.168.1.2
01db47
+      state: present
01db47
+```
01db47
+
01db47
+Note that, on the previous example the zone created with `name_from_ip` might be "1.168.192.in-addr.arpa.", "168.192.in-addr.arpa.", or "192.in-addr.arpa.", depending on the DNS response the system get while querying for zones, and for this reason, when creating a zone using `name_from_ip`, the inferred zone name is returned to the controller, in the attribute `dnszone.name`. Since the zone inferred might not be what a user expects, `name_from_ip` can only be used with `state: present`. To have more control over the zone name, the prefix length for the IP address can be provided.
01db47
+
01db47
+Example playbook to create a zone for reverse DNS lookup, from an IP address, given the prefix length and displaying the resulting zone name:
01db47
+
01db47
+```yaml
01db47
+
01db47
+---
01db47
+- name: dnszone present
01db47
+  hosts: ipaserver
01db47
+  become: true
01db47
+
01db47
+  tasks:
01db47
+      - name: Ensure zone for reverse DNS lookup is present.
01db47
+    ipadnszone:
01db47
+      ipaadmin_password: SomeADMINpassword
01db47
+      name_from_ip: 192.168.1.2/24
01db47
+      state: present
01db47
+    register: result
01db47
+  - name: Display inferred zone name.
01db47
+    debug:
01db47
+      msg: "Zone name: {{ result.dnszone.name }}"
01db47
+```
01db47
+
01db47
 
01db47
 Variables
01db47
 =========
01db47
@@ -164,7 +204,7 @@ Variable | Description | Required
01db47
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
01db47
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
01db47
 `name` \| `zone_name` | The zone name string or list of strings. | no
01db47
-`name_from_ip` | Derive zone name from reverse of IP (PTR). | no
01db47
+`name_from_ip` | Derive zone name from reverse of IP (PTR). Can only be used with `state: present`. | no
01db47
 `forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
01db47
   | `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes
01db47
   | `port` - The custom port that should be used on this server. | no
01db47
diff --git a/plugins/modules/ipadnszone.py b/plugins/modules/ipadnszone.py
01db47
index 93eac07..ff6bfff 100644
01db47
--- a/plugins/modules/ipadnszone.py
01db47
+++ b/plugins/modules/ipadnszone.py
01db47
@@ -44,7 +44,9 @@ options:
01db47
     type: list
01db47
     alises: ["zone_name"]
01db47
   name_from_ip:
01db47
-    description: Derive zone name from reverse of IP (PTR).
01db47
+    description: |
01db47
+      Derive zone name from reverse of IP (PTR).
01db47
+      Can only be used with `state: present`.
01db47
     required: false
01db47
     type: str
01db47
   forwarders:
01db47
-- 
01db47
2.26.2
01db47