From 809e41540304a14e7f867782b7772978467dd888 Mon Sep 17 00:00:00 2001
From: Klaus Zerwes <zerwes@users.noreply.github.com>
Date: Wed, 11 Sep 2024 17:26:18 +0200
Subject: [PATCH] task #38 - generic unboundplus cfg with sections and
 uuid-subsections

---
 tasks/unbound.yml         | 232 ++++----------------------------------
 tasks/unboundplus.yml     |  43 ++++---
 tasks/unboundplusuuid.yml |  14 +++
 3 files changed, 56 insertions(+), 233 deletions(-)
 create mode 100644 tasks/unboundplusuuid.yml

diff --git a/tasks/unbound.yml b/tasks/unbound.yml
index fc96f5b..b7a8de0 100644
--- a/tasks/unbound.yml
+++ b/tasks/unbound.yml
@@ -12,24 +12,20 @@
 #     active_interface: lan,wan  # default: empty => all
 #     outgoing_interface:
 #     local_zone_type: transparent
+#   hosts:
+#     04ac0d40-ecd0-4a1c-8603-91ce9aed08ad:  # uuid
+#       enabled: 1
+#       hostname: "*"
+#       domain: test.de
+#       rr: A
+#       mxprio:
+#       mx:
+#       server: 10.11.12.13
+#       description: test de
+#   aliases:
 #   ...
-#   domainoverrides: # list of domainoverrides dicts
-#     - domain: example.xor
-#       ip: 127.0..1.1
-#       descr: xtra domain  # descr is optional, but in order to work as expected
-#                           # please do yourself a favor and be consistent and use
-#                           # either in all entries or none of them
-#     ...
-#   acls: # list of acl dicts
-#     - aclname: dnsaclx # !!! please use uniq names !!!
-#       aclaction: allow
-#       description: dns acl for xtra
-#       rows:
-#         - acl_network: 172.0.0.0
-#           mask: 24
-#           description: this network # same restrictions as for domainoverrides descr
-#                                   # entries apply here
-#     ...
+#
+# see tests/unbound*.yml for more examples
 
 - name: fail if we have a deprecated config
   delegate_to: localhost
@@ -37,16 +33,15 @@
     msg: "found deprectated 'opn_unbound' setting! please use 'opn_unboundplus'!"
   when: opn_unbound is defined
 
-- name: unbound general settings
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/OPNsense/unboundplus/general/{{ item.key }}
-    value: "{{ item.value }}"
-    pretty_print: true
-  register: _unbound_general_settings
-  with_dict:
-    - "{{ opn_unboundplus.general | default({}) }}"
+- name: unboundplus
+  ansible.builtin.include_tasks: unboundplus.yml
+  vars:
+    unboundplussection: "{{ _opnunbndsettings.key }}"
+    unboundplussectionsettings: "{{ _opnunbndsettings.value }}"
+  when: opn_unboundplus is defined
+  with_dict: "{{ opn_unboundplus }}"
+  loop_control:
+    loop_var: _opnunbndsettings
 
 # this is just a hackish fix for the strange empty tag handling in community.general.xml
 # https://github.com/ansible-collections/community.general/issues/8361
@@ -60,187 +55,6 @@
   with_items:
     - present
     - absent
-  when: _unbound_general_settings is changed
-
-# unbound/domainoverrides
-
-- name: count unbound/domainoverrides
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/unbound/domainoverrides/domain
-    count: true
-  register: configured_unbound_domainoverrides_count
-
-- name: debug configured_unbound_domainoverrides_count
-  ansible.builtin.debug:
-    var: configured_unbound_domainoverrides_count
-    verbosity: 1
-
-- name: get unbound/domainoverrides/domain entries
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/unbound/domainoverrides/domain
-    content: "text"
-  register: configured_unbound_domainoverrides_domain
-  when: configured_unbound_domainoverrides_count.count > 0
-
-- name: debug configured_unbound_domainoverrides_domain
-  ansible.builtin.debug:
-    var: configured_unbound_domainoverrides_domain
-    verbosity: 1
-
-- name: get unbound/domainoverrides/ip entries
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/unbound/domainoverrides/ip
-    content: "text"
-  register: configured_unbound_domainoverrides_ip
-  when: configured_unbound_domainoverrides_count.count > 0
-
-- name: denug configured_unbound_domainoverrides_ip
-  ansible.builtin.debug:
-    var: configured_unbound_domainoverrides_ip
-    verbosity: 1
-
-# descr is optional
-# FIXME: here the detection is not working in all cases
-# i.e. if you do not use descr in the first element of the
-# domainoverrides list
-- name: get unbound/domainoverrides/descr entries
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/unbound/domainoverrides/descr
-    content: "text"
-  ignore_errors: true
-  register: configured_unbound_domainoverrides_descr
-  when: configured_unbound_domainoverrides_count.count > 0
-
-- name: debug configured_unbound_domainoverrides_descr
-  ansible.builtin.debug:
-    var: configured_unbound_domainoverrides_descr|default([])
-    verbosity: 1
-
-- name: init configured_unbound_domainoverrides
-  ansible.builtin.set_fact:
-    configured_unbound_domainoverrides: []
-
-- name: populate configured_unbound_domainoverrides
-  ansible.builtin.set_fact:
-    configured_unbound_domainoverrides: "[
-        {% for domaindict in configured_unbound_domainoverrides_domain.matches %}
-          {
-            'domain':'{{ domaindict.domain }}',
-            'ip':'{{ configured_unbound_domainoverrides_ip.matches[loop.index0].ip }}'
-            {% if configured_unbound_domainoverrides_descr.matches[loop.index0].descr is defined
-              and configured_unbound_domainoverrides_descr.matches[loop.index0].descr %}
-              ,'descr':'{{ configured_unbound_domainoverrides_descr.matches[loop.index0].descr }}'
-            {% endif %}
-          },
-        {% endfor %}
-      ]"
-  when: configured_unbound_domainoverrides_count.count > 0
-
-- name: debug configured_unbound_domainoverrides
-  ansible.builtin.debug:
-    var: configured_unbound_domainoverrides
-    verbosity: 1
-- name: debug opn_unbound.domainoverrides
-  ansible.builtin.debug:
-    var: opn_unbound.domainoverrides|default([])
-    verbosity: 1
-
-- name: compare configured and defined unbound domainoverrides
-  ansible.builtin.set_fact:
-    unbound_domainoverrides_delta: "{{ configured_unbound_domainoverrides | symmetric_difference(opn_unbound.domainoverrides | default([])) }}"
-- name: debug delta between configured and defined unbound domainoverrides
-  ansible.builtin.debug:
-    var: unbound_domainoverrides_delta
-    verbosity: 1
-- name: check if a update of unbound domainoverrides is required
-  ansible.builtin.set_fact:
-    unbound_domainoverrides_update: true
-  when: unbound_domainoverrides_delta | length > 0
-
-# opnsense uses one or more domainoverrides entries for one forward-zone
-# in unbound multiple entries for the same zone are merged into one forward-zone
-# with multiple forward-addr entries
-# so here we just have one chance: remove all domainoverrides and recreate them in a bulk
-- name: unbound domainoverrides
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/unbound/domainoverrides
-    state: absent
-    pretty_print: true
-  when: unbound_domainoverrides_update | default(False)
-
-- name: unbound domainoverrides  # noqa jinja[spacing]
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: /opnsense/unbound
-    add_children: "[
-      {% for domainoverrides in opn_unbound.domainoverrides -%}
-        '<domainoverrides>
-          {% for k, v in domainoverrides.items() | list -%}
-            <{{ k }}>{{ v }}</{{ k }}>
-          {%- endfor %}
-        </domainoverrides>',
-      {%- endfor %}
-      ]"
-    input_type: xml
-    pretty_print: true
-  when:
-    - opn_unbound.domainoverrides is defined
-    - unbound_domainoverrides_update | default(False)
-
-# END unbound/domainoverrides
-
-# unbound/acls
-# assumes uniq names and doesn't clean up undefined ACLs
-# in order to remove stuff from the xml, use opn_unset
-
-- name: unbound acls aclaction
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: "/opnsense/unbound/acls[aclname/text()='{{ item.aclname }}']/aclaction"
-    value: "{{ item.aclaction }}"
-    pretty_print: true
-  with_items:
-    - "{{ opn_unbound.acls | default([]) }}"
-
-- name: unbound acls description
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: "/opnsense/unbound/acls[aclname/text()='{{ item.aclname }}']/description"
-    value: "{{ item.description }}"
-    pretty_print: true
-  with_items:
-    - "{{ opn_unbound.acls | default([]) }}"
-  when: item.description is defined
-
-# Invalid variable name in 'register' specified: 'configured_unbound_acls_rows_count['{{ item.0.aclname }}']'
-# FIXME use block with_items and nested loop in block
-
-- name: unbound acls rows
-  ansible.builtin.include_tasks: unboundaclsrows.yml
-  vars:
-    aclname: "{{ item.aclname }}"
-    rows: "{{ item.rows }}"
-  when: item.rows is defined
-  with_items:
-    - "{{ opn_unbound.acls | default([]) }}"
-
-# END unbound/acls
-
-- name: unboundplus
-  ansible.builtin.include_tasks: unboundplus.yml
-  when: opn_unboundplus is defined
+  when: _unbound_settings is changed or _unbound_settings_uuid is changed
 
 ...
diff --git a/tasks/unboundplus.yml b/tasks/unboundplus.yml
index 5447077..de5f856 100644
--- a/tasks/unboundplus.yml
+++ b/tasks/unboundplus.yml
@@ -1,36 +1,31 @@
 # vim: tabstop=2 expandtab shiftwidth=2 softtabstop=2 smartindent nu ft=yaml
 ---
 
-# example definition:
-#
-# opn_unboundplus:
-#   dnsbl:
-#     enabled: 1
-#     type: "aa,ag,bla0,bla"
-#     whitelists: "*.example.com,*.another.xyz"
-#   dots: DNSoverTLS is NOT YET IMPLEMENTED!
-#   miscellaneous:
-#     privatedomain: "..."
-#     insecuredomain: "..."
+# handle different unboundlus sections
 
-- name: unboundplus dnsbl
+- name: unboundplus settings {{ unboundplussection }}
   delegate_to: localhost
   community.general.xml:
     path: "{{ local_config_path }}"
-    xpath: "/opnsense/OPNsense/unboundplus/dnsbl/{{ item.key }}"
+    xpath: "/opnsense/OPNsense/unboundplus/{{ unboundplussection }}/{{ item.key }}"
     value: "{{ item.value }}"
     pretty_print: true
-  with_dict: "{{ opn_unboundplus.dnsbl }}"
-  when: opn_unboundplus.dnsbl is defined
+  register: _unbound_settings
+  with_dict: "{{ unboundplussectionsettings }}"
+  when:
+    - unboundplussectionsettings is defined
+    - unboundplussection not in opn_unboundplus_uuid_sections.keys()
 
-- name: unboundplus miscellaneous
-  delegate_to: localhost
-  community.general.xml:
-    path: "{{ local_config_path }}"
-    xpath: "/opnsense/OPNsense/unboundplus/miscellaneous/{{ item.key }}"
-    value: "{{ item.value }}"
-    pretty_print: true
-  with_dict: "{{ opn_unboundplus.miscellaneous }}"
-  when: opn_unboundplus.miscellaneous is defined
+- name: unboundplus uuid settings for {{ unboundplussection }}
+  ansible.builtin.include_tasks: unboundplusuuid.yml
+  vars:
+    _uuid: "{{ _opnunbndsettingsuuid.key }}"
+    _uuidvalues: "{{  _opnunbndsettingsuuid.value }}"
+  with_dict: "{{ unboundplussectionsettings }}"
+  loop_control:
+    loop_var: _opnunbndsettingsuuid
+  when:
+    - unboundplussectionsettings is defined
+    - unboundplussection in opn_unboundplus_uuid_sections.keys()
 
 ...
diff --git a/tasks/unboundplusuuid.yml b/tasks/unboundplusuuid.yml
new file mode 100644
index 0000000..8fab5bc
--- /dev/null
+++ b/tasks/unboundplusuuid.yml
@@ -0,0 +1,14 @@
+---
+
+- name: unboundplus settings {{ unboundplussection }}
+  delegate_to: localhost
+  community.general.xml:
+    path: "{{ local_config_path }}"
+    xpath: "/opnsense/OPNsense/unboundplus/{{ unboundplussection }}/{{ opn_unboundplus_uuid_sections[unboundplussection] }}[@uuid='{{ _uuid }}']/{{ item.key }}"
+    value: "{{ item.value }}"
+    pretty_print: true
+  register: _unbound_settings_uuid
+  with_dict: "{{ _uuidvalues }}"
+
+
+...