Skip to content

Commit 1adb2ca

Browse files
committed
proxmox_firewall & proxmox module_utils
- Move check_rules() to proxmox module_utils and rename to compare_list_of_dicts() - Generalize the implemnetation as this is usefull in multiple places. - e.g. filtering out which fw rules, aliases, etc needs to be created/updated
1 parent a7cb2df commit 1adb2ca

File tree

2 files changed

+74
-49
lines changed

2 files changed

+74
-49
lines changed

plugins/module_utils/proxmox.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,53 @@ def ansible_to_proxmox_bool(value):
7373
return 1 if value else 0
7474

7575

76+
def compare_list_of_dicts(existing_list, new_list, uid, params_to_ignore=None):
77+
""" Compare 2 list of dicts
78+
Use case - for firewall rules we will be getting a list of rules from user.
79+
We want to filter out which rules needs to be updated and which rules are completely new and needs to be created
80+
81+
@param existing_list: Existing values example - list of existing rules
82+
@param new_list: New values example - list of rules passed to module
83+
@param uid: unique identifier in dict. It should always be present in both lists - in case of firewall rules it's pos
84+
@param params_to_ignore: list of params we want to ignore which are present in existing_list's dict.
85+
In case of firewall rules we want to ignore ['digest', 'ipversion']
86+
87+
@return: returns 2 list items 1st is the list of items which are completely new and needs to be created
88+
2nd is a list of items which needs to be updated
89+
"""
90+
if params_to_ignore is None:
91+
params_to_ignore = list()
92+
items_to_update = []
93+
new_list = [{k: v for k, v in item.items() if v is not None} for item in new_list]
94+
95+
if existing_list is None:
96+
items_to_create = new_list
97+
items_to_update = list()
98+
return items_to_create, items_to_update
99+
100+
existing_list = {x[uid]: x for x in existing_list}
101+
new_list = {x[uid]: x for x in new_list}
102+
103+
common_uids = set(existing_list.keys()).intersection(set(new_list.keys()))
104+
missing_uids = set(new_list.keys()) - set(existing_list.keys())
105+
items_to_create = [new_list[id] for id in missing_uids]
106+
107+
for id in common_uids:
108+
# If new rule has a parameter that is not present in existing rule we need to update
109+
if set(new_list[id].keys()) - set(existing_list[id].keys()) != set():
110+
items_to_update.append(new_list[id])
111+
continue
112+
113+
# If existing rule param value doesn't match new rule param OR
114+
# If existing rule has a param that is not present in new rule except for params in params_to_ignore
115+
for existing_rule_param, existing_parm_value in existing_list[id].items():
116+
if (existing_rule_param not in params_to_ignore and
117+
new_list[id].get(existing_rule_param) != existing_parm_value):
118+
items_to_update.append(new_list[id])
119+
120+
return items_to_create, items_to_update
121+
122+
76123
class ProxmoxAnsible(object):
77124
"""Base class for Proxmox modules"""
78125
TASK_TIMED_OUT = 'timeout expired'

plugins/modules/proxmox_firewall.py

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@
399399
from ansible_collections.community.proxmox.plugins.module_utils.proxmox import (
400400
proxmox_auth_argument_spec,
401401
ansible_to_proxmox_bool,
402+
compare_list_of_dicts,
402403
ProxmoxAnsible
403404
)
404405

@@ -491,10 +492,11 @@ def run(self):
491492
group = self.params.get("group")
492493
group_conf = self.params.get("group_conf")
493494

494-
for rule in rules:
495-
rule['icmp-type'] = rule.get('icmp_type')
496-
rule['enable'] = ansible_to_proxmox_bool(rule.get('enable'))
497-
del rule['icmp_type']
495+
if rules is not None:
496+
for rule in rules:
497+
rule['icmp-type'] = rule.get('icmp_type')
498+
rule['enable'] = ansible_to_proxmox_bool(rule.get('enable'))
499+
del rule['icmp_type']
498500

499501
if level == "vm":
500502
vm = self.get_vm(vmid=self.params.get('vmid'))
@@ -616,54 +618,25 @@ def delete_fw_rule(self, rules_obj, pos):
616618
msg=f'Failed to delete firewall rule at pos {pos}: {e}'
617619
)
618620

619-
def check_rules(self, existing_rules, new_rules):
620-
rules_to_update = []
621-
new_rules = [{k: v for k, v in item.items() if v is not None} for item in new_rules]
622-
623-
if existing_rules is None:
624-
rules_to_create = new_rules
625-
rules_to_update = list()
626-
return rules_to_create, rules_to_update
627-
628-
existing_rules = {x['pos']: x for x in existing_rules}
629-
new_rules = {x['pos']: x for x in new_rules}
630-
631-
common_pos = set(existing_rules.keys()).intersection(set(new_rules.keys()))
632-
pos_to_create = set(new_rules.keys()) - set(existing_rules.keys())
633-
rules_to_create = [new_rules[pos] for pos in pos_to_create]
634-
635-
params_to_ignore = ['digest', 'ipversion']
636-
637-
for pos in common_pos:
638-
# If new rule has a parameter that is not present in existing rule we need to update
639-
if set(new_rules[pos].keys()) - set(existing_rules[pos].keys()) != set():
640-
rules_to_update.append(new_rules[pos])
641-
continue
642-
643-
# If existing rule param value doesn't match new rule param OR
644-
# If existing rule has a param that is not present in new rule except for params in params_to_ignore
645-
for existing_rule_param, existing_parm_value in existing_rules[pos].items():
646-
if (existing_rule_param not in params_to_ignore and
647-
new_rules[pos].get(existing_rule_param) != existing_parm_value):
648-
rules_to_update.append(new_rules[pos])
649-
650-
return rules_to_create, rules_to_update
651-
652621
def update_fw_rules(self, rules_obj, rules, force):
653622
existing_rules = self.get_fw_rules(rules_obj)
654-
rules_to_create, rules_to_update = self.check_rules(existing_rules=existing_rules, new_rules=rules)
623+
rules_to_create, rules_to_update = compare_list_of_dicts(
624+
existing_list=existing_rules,
625+
new_list=rules,
626+
uid='pos',
627+
params_to_ignore=['digest', 'ipversion']
628+
)
655629

656-
if len(rules_to_update) == 0:
657-
if len(rules_to_create) == 0:
658-
self.module.exit_json(
659-
changed=False,
660-
msg='No need to update any FW rules.'
630+
if len(rules_to_update) == 0 and len(rules_to_create) == 0:
631+
self.module.exit_json(
632+
changed=False,
633+
msg='No need to update any FW rules.'
661634

662-
)
663-
elif len(rules_to_create) > 0 and not force:
664-
self.module.fail_json(
665-
msg=f"Need to create new rules for pos - {[x['pos'] for x in rules_to_create]} But force is false"
666-
)
635+
)
636+
elif len(rules_to_create) > 0 and not force:
637+
self.module.fail_json(
638+
msg=f"Need to create new rules for pos - {[x['pos'] for x in rules_to_create]} But force is false"
639+
)
667640

668641
for rule in rules_to_update:
669642
try:
@@ -684,7 +657,12 @@ def update_fw_rules(self, rules_obj, rules, force):
684657

685658
def create_fw_rules(self, rules_obj, rules, force):
686659
existing_rules = self.get_fw_rules(rules_obj=rules_obj)
687-
rules_to_create, rules_to_update = self.check_rules(existing_rules=existing_rules, new_rules=rules)
660+
rules_to_create, rules_to_update = compare_list_of_dicts(
661+
existing_list=existing_rules,
662+
new_list=rules,
663+
uid='pos',
664+
params_to_ignore=['digest', 'ipversion']
665+
)
688666

689667
if len(rules_to_create) == 0 and len(rules_to_update) == 0:
690668
self.module.exit_json(

0 commit comments

Comments
 (0)