Skip to content

Commit 79eefe7

Browse files
committed
proxmox_firewall: Add force condition
- state=present: + check if fw rules already exists and if needed update them instead of creating + check if grooup exists and if so don't do anything - state=update: + check if fw rules don't existsand if needed create them instead of updating - make rules.pos as required this is to handle above conditions - add method to get security groups and list them withfirewall rules when state is not provided
1 parent 47d52ca commit 79eefe7

File tree

1 file changed

+85
-9
lines changed

1 file changed

+85
-9
lines changed

plugins/modules/proxmox_firewall.py

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
- present
3333
- update
3434
- absent
35+
force:
36+
description:
37+
- If state is present and if 1 or more rule already exists at given pos force will update them
38+
- If state is update and if 1 or more rule doesn't exist force will create
39+
type: bool
40+
default: false
3541
level:
3642
description:
3743
- Level at which the firewall rule applies.
@@ -157,6 +163,7 @@
157163
description:
158164
- Position of the rule in the list.
159165
type: int
166+
required: true
160167
proto:
161168
description:
162169
- IP protocol. You can use protocol names ('tcp'/'udp') or simple numbers, as defined in '/etc/protocols'.
@@ -276,6 +283,14 @@
276283
sample:
277284
test
278285
286+
groups:
287+
description: list of firewall security groups
288+
returned: on success
289+
type: list
290+
elements: str
291+
sample:
292+
[ "test" ]
293+
279294
firewall_rules:
280295
description: List of firewall rules.
281296
returned: on success
@@ -391,6 +406,7 @@
391406
def get_proxmox_args():
392407
return dict(
393408
state=dict(type="str", choices=["present", "absent", "update"], required=False),
409+
force=dict(type="bool", default=False),
394410
level=dict(type="str", choices=["cluster", "node", "vm", "vnet", "group"], default="cluster", required=False),
395411
node=dict(type="str", required=False),
396412
vmid=dict(type="int", required=False),
@@ -417,7 +433,7 @@ def get_proxmox_args():
417433
choices=["emerg", "alert", "crit", "err", "warning", "notice", "info", "debug", "nolog"],
418434
required=False),
419435
macro=dict(type="str", required=False),
420-
pos=dict(type="int", required=False),
436+
pos=dict(type="int", required=True),
421437
proto=dict(type="str", required=False),
422438
source=dict(type="str", required=False),
423439
sport=dict(type="str", required=False)
@@ -464,6 +480,7 @@ def run(self):
464480
)
465481

466482
state = self.params.get("state")
483+
force = self.params.get("force")
467484
level = self.params.get("level")
468485
rules = self.params.get("rules")
469486

@@ -496,24 +513,32 @@ def run(self):
496513
if self.params.get('group_conf'):
497514
self.create_group(group=self.params.get('group'), comment=self.params.get('comment'))
498515
if rules is not None:
499-
self.create_fw_rules(rules_obj=rules_obj, rules=rules)
516+
self.create_fw_rules(rules_obj=rules_obj, rules=rules, force=force)
500517
elif state == "update":
501518
if self.params.get('group_conf'):
502519
self.create_group(group=self.params.get('group'), comment=self.params.get('comment'))
503520
if rules is not None:
504-
self.update_fw_rules(rules_obj=rules_obj, rules=rules)
521+
self.update_fw_rules(rules_obj=rules_obj, rules=rules, force=force)
505522
elif state == "absent":
506523
if self.params.get('pos'):
507524
self.delete_fw_rule(rules_obj=rules_obj, pos=self.params.get('pos'))
508525
if self.params.get('group_conf'):
509526
self.delete_group(group_name=self.params.get('group'))
510527
else:
511528
rules = self.get_fw_rules(rules_obj, pos=self.params.get('pos'))
529+
groups = self.get_groups()
512530
self.module.exit_json(
513-
changed=False, firewall_rules=rules, msg='successfully retrieved firewall rules'
531+
changed=False,
532+
firewall_rules=rules,
533+
groups=groups,
534+
msg='successfully retrieved firewall rules and groups'
514535
)
515536

516537
def create_group(self, group, comment=None):
538+
if group in self.get_groups():
539+
self.module.exit_json(
540+
changed=False, group=group, msg=f"security group {group} already exists"
541+
)
517542
try:
518543
self.proxmox_api.cluster().firewall().groups.post(group=group, comment=comment)
519544
self.module.exit_json(
@@ -525,6 +550,10 @@ def create_group(self, group, comment=None):
525550
)
526551

527552
def delete_group(self, group_name):
553+
if group_name not in self.get_groups():
554+
self.module.exit_json(
555+
changed=False, group=group_name, msg=f"security group {group_name} already doesn't exists"
556+
)
528557
try:
529558
group = getattr(self.proxmox_api.cluster().firewall().groups(), group_name)
530559
group.delete()
@@ -546,8 +575,23 @@ def get_fw_rules(self, rules_obj, pos=None):
546575
msg=f'Failed to retrieve firewall rules: {e}'
547576
)
548577

578+
def get_groups(self):
579+
try:
580+
return [x['group'] for x in self.proxmox_api.cluster().firewall().groups().get()]
581+
except Exception as e:
582+
self.module.fail_json(
583+
msg=f'Failed to retrieve firewall security groups: {e}'
584+
)
585+
549586
def delete_fw_rule(self, rules_obj, pos):
550587
try:
588+
for item in self.get_fw_rules(rules_obj):
589+
if item.get('pos') == pos:
590+
break
591+
else:
592+
self.module.exit_json(
593+
changed=False, msg="Firewall rule already doesn't exist"
594+
)
551595
rule_obj = getattr(rules_obj(), str(pos))
552596
digest = rule_obj.get().get('digest')
553597
rule_obj.delete(pos=pos, digest=digest)
@@ -560,8 +604,21 @@ def delete_fw_rule(self, rules_obj, pos):
560604
msg=f'Failed to delete firewall rule at pos {pos}: {e}'
561605
)
562606

563-
def update_fw_rules(self, rules_obj, rules):
564-
for rule in rules:
607+
def update_fw_rules(self, rules_obj, rules, force):
608+
existing_rules = self.get_fw_rules(rules_obj)
609+
rules_to_create = []
610+
if len(existing_rules) > 0:
611+
existing_pos = [x['pos'] for x in existing_rules]
612+
for rule in rules:
613+
if rule.get('pos') not in existing_pos:
614+
if force:
615+
rules_to_create.append(rule)
616+
else:
617+
self.module.fail_json(
618+
msg=f"Rule doesn't exists at pos - {rule.get('pos')} and force is false."
619+
)
620+
rules_to_update = [rule for rule in rules if rule not in rules_to_create]
621+
for rule in rules_to_update:
565622
rule['icmp-type'] = rule.get('icmp_type')
566623
rule['enable'] = ansible_to_proxmox_bool(rule.get('enable'))
567624
del rule['icmp_type']
@@ -574,12 +631,29 @@ def update_fw_rules(self, rules_obj, rules):
574631
self.module.fail_json(
575632
msg=f'Failed to update firewall rule at pos {rule["pos"]}: {e}'
576633
)
634+
635+
if len(rules_to_create) > 0:
636+
self.create_fw_rules(rules_obj=rules_obj, rules=rules_to_create, force=False)
577637
self.module.exit_json(
578-
changed=True, msg='successfully created firewall rules'
638+
changed=True, msg='successfully updated firewall rules'
579639
)
580640

581-
def create_fw_rules(self, rules_obj, rules):
582-
for rule in rules:
641+
def create_fw_rules(self, rules_obj, rules, force):
642+
existing_rules = self.get_fw_rules(rules_obj)
643+
rules_to_update = []
644+
if len(existing_rules) > 0:
645+
existing_pos = [x['pos'] for x in existing_rules]
646+
for rule in rules:
647+
if rule.get('pos') in existing_pos:
648+
if force:
649+
rules_to_update.append(rule)
650+
else:
651+
self.module.fail_json(
652+
msg=f"Rule already exists at pos - {rule.get('pos')} and force is false."
653+
)
654+
rules_to_create = [rule for rule in rules if rule not in rules_to_update]
655+
656+
for rule in rules_to_create:
583657
rule['icmp-type'] = rule.get('icmp_type')
584658
rule['enable'] = ansible_to_proxmox_bool(rule.get('enable'))
585659
del rule['icmp_type']
@@ -591,6 +665,8 @@ def create_fw_rules(self, rules_obj, rules):
591665
self.module.fail_json(
592666
msg=f'Failed to create firewall rule {rule}: {e}'
593667
)
668+
if len(rules_to_update) > 0:
669+
self.update_fw_rules(rules_obj=rules_obj, rules=rules_to_update, force=False)
594670
self.module.exit_json(
595671
changed=True, msg='successfully created firewall rules'
596672
)

0 commit comments

Comments
 (0)