From 6bfcfcdc810125e5fa05a56dcceda9d5c84fe158 Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Wed, 19 Jan 2022 10:57:23 -0300
Subject: [PATCH] dnsconfig: add support for 'action: member'.

This patch adds support for 'action: member' for ipadnsconfig plugin,
impacting management of DNS forwarders setting.

Use of 'state: absent' now requires 'action: member'. With 'state:
present', orwarders can be either defined through 'action: dnsconfig'
or added using 'action: member'.

Tests have been updated to reflec the new behavior.
---
 README-dnsconfig.md                |  4 +-
 plugins/modules/ipadnsconfig.py    | 42 +++++++++++++-----
 tests/dnsconfig/test_dnsconfig.yml | 71 +++++++++++++++++++++++++++++-
 3 files changed, 104 insertions(+), 13 deletions(-)

diff --git a/README-dnsconfig.md b/README-dnsconfig.md
index 976251bb..02544082 100644
--- a/README-dnsconfig.md
+++ b/README-dnsconfig.md
@@ -71,6 +71,7 @@ Example playbook to ensure a global forwarder, with a custom port, is absent:
       forwarders:
           - ip_address: 2001:4860:4860::8888
             port: 53
+      action: member
       state: absent
 ```
 
@@ -130,7 +131,8 @@ Variable | Description | Required
 &nbsp; | `port` - The custom port that should be used on this server. | no
 `forward_policy` | The global forwarding policy. It can be one of `only`, `first`, or `none`.  | no
 `allow_sync_ptr` | Allow synchronization of forward (A, AAAA) and reverse (PTR) records (bool). | yes
-`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes
+`action` | Work on dnsconfig or member level. It can be one of `member` or `dnsconfig` and defaults to `dnsconfig`. Only `forwarders` can be managed with `action: member`. | no
+`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. `absent` can only be used with `action: member` and `forwarders`. | yes
 
 
 Authors
diff --git a/plugins/modules/ipadnsconfig.py b/plugins/modules/ipadnsconfig.py
index 6ca4aff5..020b9bcd 100644
--- a/plugins/modules/ipadnsconfig.py
+++ b/plugins/modules/ipadnsconfig.py
@@ -59,8 +59,16 @@ options:
       Allow synchronization of forward (A, AAAA) and reverse (PTR) records.
     required: false
     type: bool
+  action:
+    description: |
+      Work on dnsconfig or member level. It can be one of `member` or
+      `dnsconfig`. Only `forwarders` can be managed with `action: member`.
+    default: "dnsconfig"
+    choices: ["member", "dnsconfig"]
   state:
-    description: State to ensure
+    description: |
+      The state to ensure. It can be one of `present` or `absent`.
+      `absent` can only be used with `action: member` and `forwarders`.
     default: present
     choices: ["present", "absent"]
 """
@@ -83,6 +91,7 @@ EXAMPLES = """
       - ip_address: 2001:4860:4860::8888
         port: 53
     state: absent
+    action: member
 
 # Disable PTR record synchronization.
 - ipadnsconfig:
@@ -118,7 +127,7 @@ def find_dnsconfig(module):
     return None
 
 
-def gen_args(module, state, dnsconfig, forwarders, forward_policy,
+def gen_args(module, state, action, dnsconfig, forwarders, forward_policy,
              allow_sync_ptr):
     _args = {}
 
@@ -137,15 +146,20 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
 
         global_forwarders = dnsconfig.get('idnsforwarders', [])
         if state == 'absent':
-            _args['idnsforwarders'] = [
-                fwd for fwd in global_forwarders if fwd not in _forwarders]
-            # When all forwarders should be excluded, use an empty string ('').
-            if not _args['idnsforwarders']:
-                _args['idnsforwarders'] = ['']
+            if action == "member":
+                _args['idnsforwarders'] = [
+                    fwd for fwd in global_forwarders if fwd not in _forwarders]
+                # When all forwarders should be excluded,
+                # use an empty string ('').
+                if not _args['idnsforwarders']:
+                    _args['idnsforwarders'] = ['']
 
         elif state == 'present':
-            _args['idnsforwarders'] = \
-                list(set(list(_forwarders) + list(global_forwarders)))
+            if action == "member":
+                _args['idnsforwarders'] = \
+                    list(set(list(_forwarders) + list(global_forwarders)))
+            else:
+                _args['idnsforwarders'] = _forwarders
             # If no forwarders should be added, remove argument.
             if not _args['idnsforwarders']:
                 del _args['idnsforwarders']
@@ -179,6 +193,8 @@ def main():
             allow_sync_ptr=dict(type='bool', required=False, default=None),
 
             # general
+            action=dict(type="str", default="dnsconfig",
+                        choices=["member", "dnsconfig"]),
             state=dict(type="str", default="present",
                        choices=["present", "absent"]),
         )
@@ -191,11 +207,17 @@ def main():
     forward_policy = ansible_module.params_get('forward_policy')
     allow_sync_ptr = ansible_module.params_get('allow_sync_ptr')
 
+    action = ansible_module.params_get('action')
     state = ansible_module.params_get('state')
 
     # Check parameters.
     invalid = []
+    if state == "present" and action == "member":
+        invalid = ['forward_policy', 'allow_sync_ptr']
     if state == 'absent':
+        if action != "member":
+            ansible_module.fail_json(
+                msg="State 'absent' is only valid with action 'member'.")
         invalid = ['forward_policy', 'allow_sync_ptr']
 
     ansible_module.params_fail_used_invalid(invalid, state)
@@ -208,7 +230,7 @@ def main():
     with ansible_module.ipa_connect():
 
         res_find = find_dnsconfig(ansible_module)
-        args = gen_args(ansible_module, state, res_find, forwarders,
+        args = gen_args(ansible_module, state, action, res_find, forwarders,
                         forward_policy, allow_sync_ptr)
 
         # Execute command only if configuration changes.
diff --git a/tests/dnsconfig/test_dnsconfig.yml b/tests/dnsconfig/test_dnsconfig.yml
index 408c0596..b12d56fb 100644
--- a/tests/dnsconfig/test_dnsconfig.yml
+++ b/tests/dnsconfig/test_dnsconfig.yml
@@ -17,6 +17,7 @@
         - ip_address: 2001:4860:4860::8888
           port: 53
       state: absent
+      action: member
 
   # Tests.
   - name: Set config to invalid IPv4.
@@ -74,23 +75,72 @@
     register: result
     failed_when: result.changed or result.failed
 
-  - name: Ensure forwarder is absent.
+  - name: Ensure forwarder 8.8.8.8 is absent.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
       ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
       state: absent
+      action: member
     register: result
     failed_when: not result.changed or result.failed
 
-  - name: Ensure forwarder is absent, again.
+  - name: Ensure forwarder 8.8.8.8 is absent, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
       ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
       state: absent
+      action: member
+    register: result
+    failed_when: result.changed or result.failed
+
+  - name: Ensure forwarder 8.8.4.4 is present.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      forwarders:
+        - ip_address: 8.8.4.4
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure forwarder 8.8.8.8 is present.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      forwarders:
+        - ip_address: 8.8.8.8
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure forwarder 8.8.4.4 is present.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      forwarders:
+        - ip_address: 8.8.4.4
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure forwarders are absent.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      forwarders:
+        - ip_address: 8.8.4.4
+        - ip_address: 8.8.8.8
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure forwarders are absent, again.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      forwarders:
+        - ip_address: 8.8.4.4
+        - ip_address: 8.8.8.8
     register: result
     failed_when: result.changed or result.failed
 
@@ -168,6 +218,7 @@
         - ip_address: 2001:4860:4860::8888
           port: 53
       state: absent
+      action: member
     register: result
     failed_when: not result.changed or result.failed
 
@@ -181,6 +232,7 @@
         - ip_address: 2001:4860:4860::8888
           port: 53
       state: absent
+      action: member
     register: result
     failed_when: result.changed or result.failed
 
@@ -193,6 +245,16 @@
     register: result
     failed_when: not result.changed or result.failed
 
+  - name: Ensure forwarders is not present.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      forwarders:
+        - ip_address: 8.8.4.4
+    check_mode: yes
+    register: result
+    failed_when: not result.changed or result.failed
+
   - name: Ensure forwarders are present.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
@@ -200,6 +262,7 @@
       forwarders:
         - ip_address: 8.8.4.4
         - ip_address: 8.8.8.8
+      action: member
     register: result
     failed_when: not result.changed or result.failed
 
@@ -210,6 +273,7 @@
       forwarders:
         - ip_address: 8.8.4.4
         - ip_address: 8.8.8.8
+      action: member
     register: result
     failed_when: result.changed or result.failed
 
@@ -219,6 +283,7 @@
       ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.4.4
+      action: member
     register: result
     failed_when: result.changed or result.failed
 
@@ -229,6 +294,7 @@
       forwarders:
         - ip_address: 8.8.4.4
         - ip_address: 8.8.8.8
+      action: member
     register: result
     failed_when: result.changed or result.failed
 
@@ -244,3 +310,4 @@
         - ip_address: 2001:4860:4860::8888
           port: 53
       state: absent
+      action: member
-- 
GitLab