Skip to content

Commit 74b3d44

Browse files
committed
proxmox_firewall & proxmox_firewall_info - Added tests and fixed sanity issues
1 parent bfd5e5c commit 74b3d44

File tree

6 files changed

+196
-52
lines changed

6 files changed

+196
-52
lines changed

meta/runtime.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ action_groups:
2020
- proxmox_disk
2121
- proxmox_domain_info
2222
- proxmox_firewall
23+
- proxmox_firewall_info
2324
- proxmox_group
2425
- proxmox_group_info
2526
- proxmox_kvm

plugins/module_utils/proxmox_sdn.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,6 @@
88

99
__metaclass__ = type
1010

11-
import traceback
12-
13-
PROXMOXER_IMP_ERR = None
14-
try:
15-
from proxmoxer import ProxmoxResource
16-
from proxmoxer import __version__ as proxmoxer_version
17-
HAS_PROXMOXER = True
18-
except ImportError:
19-
HAS_PROXMOXER = False
20-
PROXMOXER_IMP_ERR = traceback.format_exc()
21-
2211
from typing import List, Dict
2312

2413
from ansible_collections.community.proxmox.plugins.module_utils.proxmox import (
@@ -111,7 +100,7 @@ def get_zones(self, zone_type: str = None) -> List[Dict]:
111100
msg=f'Failed to retrieve zone information from cluster: {e}'
112101
)
113102

114-
def get_aliases(self, firewall_obj: ProxmoxResource | None) -> List[Dict]:
103+
def get_aliases(self, firewall_obj):
115104
"""Get aliases for IP/CIDR at given firewall endpoint level
116105
117106
:param firewall_obj: Firewall endpoint as a ProxmoxResource e.g. self.proxmox_api.cluster().firewall
@@ -127,7 +116,7 @@ def get_aliases(self, firewall_obj: ProxmoxResource | None) -> List[Dict]:
127116
msg='Failed to retrieve aliases'
128117
)
129118

130-
def get_fw_rules(self, rules_obj: ProxmoxResource, pos: int = None) -> List[Dict]:
119+
def get_fw_rules(self, rules_obj, pos=None):
131120
"""Get firewall rules at given rules endpoint level
132121
133122
:param rules_obj: Firewall Rules endpoint as a ProxmoxResource e.g. self.proxmox_api.cluster().firewall().rules
@@ -143,7 +132,7 @@ def get_fw_rules(self, rules_obj: ProxmoxResource, pos: int = None) -> List[Dict
143132
msg=f'Failed to retrieve firewall rules: {e}'
144133
)
145134

146-
def get_groups(self) -> List:
135+
def get_groups(self):
147136
"""Get firewall security groups
148137
149138
:return: list of groups

plugins/modules/proxmox_firewall.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
DOCUMENTATION = r"""
1313
module: proxmox_firewall
1414
short_description: Manage firewall rules in Proxmox
15+
version_added: "1.4.0"
1516
description:
1617
- create/update/delete FW rules at cluster/group/vnet/node/vm level
1718
- Create/delete firewall security groups
@@ -36,7 +37,7 @@
3637
description:
3738
- If O(state=present) and if 1 or more rule/alias already exists it will update them
3839
type: bool
39-
default: truw
40+
default: true
4041
level:
4142
description:
4243
- Level at which the firewall rule applies.
@@ -348,7 +349,7 @@
348349

349350
def get_proxmox_args():
350351
return dict(
351-
state=dict(type="str", choices=["present", "absent"], default="present"),
352+
state=dict(type="str", choices=["present", "absent"], default="present"),
352353
update=dict(type="bool", default=True),
353354
level=dict(type="str", choices=["cluster", "node", "vm", "vnet", "group"], default="cluster", required=False),
354355
node=dict(type="str", required=False),
@@ -490,7 +491,6 @@ def run(self):
490491
if aliases is not None:
491492
self.aliases_absent(firewall_obj=firewall_obj, aliases=aliases)
492493

493-
494494
def aliases_present(self, firewall_obj, level, aliases, update):
495495
if firewall_obj is None or level not in ['cluster', 'vm']:
496496
self.module.fail_json(

plugins/modules/proxmox_firewall_info.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
__metaclass__ = type
1111

1212
DOCUMENTATION = r"""
13-
module: proxmox_firewall
13+
module: proxmox_firewall_info
1414
short_description: Manage firewall rules in Proxmox
15+
version_added: "1.4.0"
1516
description:
1617
- create/update/delete FW rules at cluster/group/vnet/node/vm level
1718
- Create/delete firewall security groups
@@ -58,6 +59,7 @@
5859
- community.proxmox.proxmox.actiongroup_proxmox
5960
- community.proxmox.proxmox.documentation
6061
- community.proxmox.attributes
62+
- community.proxmox.attributes.info_module
6163
"""
6264

6365
EXAMPLES = r"""
@@ -73,7 +75,7 @@
7375

7476
RETURN = r"""
7577
groups:
76-
description:
78+
description:
7779
- List of firewall security groups.
7880
- This will always be given for cluster level regardless of the level passed.
7981
- Because only at cluster level we can have firewall security groups
@@ -238,6 +240,7 @@ def get_ansible_module():
238240

239241
return AnsibleModule(
240242
argument_spec=module_args,
243+
supports_check_mode=True,
241244
required_if=[
242245
('level', 'vm', ['vmid']),
243246
('level', 'node', ['node']),
@@ -257,7 +260,7 @@ def run(self):
257260

258261
if level == "vm":
259262
vm = self.get_vm(vmid=self.params.get('vmid'))
260-
node = self.proxmox_api.nodes( vm['node'])
263+
node = self.proxmox_api.nodes(vm['node'])
261264
virt = node(vm['type'])
262265
firewall_obj = virt(str(vm['vmid'])).firewall
263266
rules_obj = firewall_obj().rules

tests/unit/plugins/modules/test_proxmox_firewall.py

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,6 @@ def fail_json(*args, **kwargs):
6969
raise SystemExit(kwargs)
7070

7171

72-
def get_module_args_state_none(level="cluster", vmid=None, node=None, vnet=None, group=None):
73-
return {
74-
"api_host": "host",
75-
"api_user": "user",
76-
"api_password": "password",
77-
"level": level,
78-
"vmid": vmid,
79-
"node": node,
80-
"vnet": vnet,
81-
"group": group
82-
}
83-
84-
8572
def get_module_args_group_conf(group, level="cluster", state="present"):
8673
return {
8774
"api_host": "host",
@@ -144,16 +131,6 @@ def tearDown(self):
144131
self.mock_module_helper.stop()
145132
super(TestProxmoxFirewallModule, self).tearDown()
146133

147-
def test_get_fw_state_none(self):
148-
with pytest.raises(SystemExit) as exc_info:
149-
with set_module_args(get_module_args_state_none()):
150-
self.module.main()
151-
result = exc_info.value.args[0]
152-
assert result["changed"] is False
153-
assert result["msg"] == "successfully retrieved firewall rules and groups"
154-
assert result["firewall_rules"] == RAW_FIREWALL_RULES
155-
assert result["groups"] == ['test1', 'test2']
156-
157134
def test_create_group(self):
158135
with pytest.raises(SystemExit) as exc_info:
159136
with set_module_args(get_module_args_group_conf(group='test')):
@@ -172,21 +149,13 @@ def test_delete_group(self):
172149
assert result["msg"] == 'successfully deleted security group test1'
173150
assert result['group'] == 'test1'
174151

175-
def test_update_fw_rules(self):
176-
with pytest.raises(SystemExit) as exc_info:
177-
with set_module_args(get_module_args_rules(state='update')):
178-
self.module.main()
179-
result = exc_info.value.args[0]
180-
assert result['changed'] is True
181-
assert result["msg"] == 'successfully updated firewall rules'
182-
183152
def test_create_fw_rules(self):
184153
with pytest.raises(SystemExit) as exc_info:
185154
with set_module_args(get_module_args_rules(state='present', pos=2)):
186155
self.module.main()
187156
result = exc_info.value.args[0]
188157
assert result['changed'] is True
189-
assert result["msg"] == 'successfully created firewall rules'
158+
assert result["msg"] == 'successfully created/updated firewall rules'
190159

191160
def test_delete_fw_rule(self):
192161
with pytest.raises(SystemExit) as exc_info:
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) 2025, Jana Hoch <[email protected]>
4+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
# SPDX-License-Identifier: GPL-3.0-or-later
6+
7+
from __future__ import absolute_import, division, print_function
8+
9+
__metaclass__ = type
10+
11+
from unittest.mock import patch
12+
13+
import pytest
14+
15+
proxmoxer = pytest.importorskip("proxmoxer")
16+
17+
from ansible.module_utils import basic
18+
from ansible_collections.community.proxmox.plugins.modules import proxmox_firewall_info
19+
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import (
20+
ModuleTestCase,
21+
set_module_args,
22+
)
23+
import ansible_collections.community.proxmox.plugins.module_utils.proxmox as proxmox_utils
24+
25+
RAW_FIREWALL_RULES = [
26+
{
27+
"ipversion": 4,
28+
"digest": "245f9fb31d5f59543dedc5a84ba7cd6afa4dbcc0",
29+
"log": "nolog",
30+
"action": "ACCEPT",
31+
"enable": 1,
32+
"type": "out",
33+
"source": "1.1.1.1",
34+
"pos": 0
35+
},
36+
{
37+
"enable": 1,
38+
"pos": 1,
39+
"source": "1.0.0.1",
40+
"type": "out",
41+
"action": "ACCEPT",
42+
"digest": "245f9fb31d5f59543dedc5a84ba7cd6afa4dbcc0",
43+
"ipversion": 4
44+
}
45+
]
46+
47+
RAW_GROUPS = [
48+
{
49+
"digest": "fdb62dec01018d4f35c83ecc2ae3f110a8b3bd62",
50+
"group": "test1"
51+
},
52+
{
53+
"group": "test2",
54+
"digest": "fdb62dec01018d4f35c83ecc2ae3f110a8b3bd62"
55+
}
56+
]
57+
58+
RAW_ALIASES = [
59+
{
60+
"name": "test1",
61+
"cidr": "10.10.1.0/24",
62+
"digest": "978391f460484e8d4fb3ca785cfe5a9d16fe8b1f",
63+
"ipversion": 4
64+
},
65+
{
66+
"name": "test2",
67+
"cidr": "10.10.2.0/24",
68+
"digest": "978391f460484e8d4fb3ca785cfe5a9d16fe8b1f",
69+
"ipversion": 4
70+
},
71+
{
72+
"name": "test3",
73+
"cidr": "10.10.3.0/24",
74+
"digest": "978391f460484e8d4fb3ca785cfe5a9d16fe8b1f",
75+
"ipversion": 4
76+
}
77+
]
78+
79+
RAW_CLUSTER_RESOURCES = [
80+
{
81+
"vmid": 100,
82+
"maxcpu": 8,
83+
"memhost": 860138496,
84+
"type": "qemu",
85+
"id": "qemu/100",
86+
"diskread": 127452302,
87+
"netin": 42,
88+
"netout": 0,
89+
"cpu": 0.0046731498237984,
90+
"uptime": 119787,
91+
"template": 0,
92+
"disk": 0,
93+
"name": "nextcloud",
94+
"maxdisk": 644245094400,
95+
"mem": 445415424,
96+
"status": "running",
97+
"diskwrite": 1024,
98+
"maxmem": 8589934592,
99+
"node": "pve"
100+
}
101+
]
102+
103+
104+
def exit_json(*args, **kwargs):
105+
"""function to patch over exit_json; package return data into an exception"""
106+
if 'changed' not in kwargs:
107+
kwargs['changed'] = False
108+
raise SystemExit(kwargs)
109+
110+
111+
def fail_json(*args, **kwargs):
112+
"""function to patch over fail_json; package return data into an exception"""
113+
kwargs['failed'] = True
114+
raise SystemExit(kwargs)
115+
116+
117+
def get_module_args(level="cluster", vmid=None, node=None, vnet=None, group=None):
118+
return {
119+
"api_host": "host",
120+
"api_user": "user",
121+
"api_password": "password",
122+
"level": level,
123+
"vmid": vmid,
124+
"node": node,
125+
"vnet": vnet,
126+
"group": group
127+
}
128+
129+
130+
class TestProxmoxFirewallModule(ModuleTestCase):
131+
def setUp(self):
132+
super(TestProxmoxFirewallModule, self).setUp()
133+
proxmox_utils.HAS_PROXMOXER = True
134+
self.module = proxmox_firewall_info
135+
self.mock_module_helper = patch.multiple(basic.AnsibleModule,
136+
exit_json=exit_json,
137+
fail_json=fail_json)
138+
self.mock_module_helper.start()
139+
self.connect_mock = patch(
140+
"ansible_collections.community.proxmox.plugins.module_utils.proxmox.ProxmoxAnsible._connect",
141+
).start()
142+
143+
self.connect_mock.return_value.cluster.resources.get.return_value = (
144+
RAW_CLUSTER_RESOURCES
145+
)
146+
147+
mock_cluster_fw = self.connect_mock.return_value.cluster.return_value.firewall.return_value
148+
mock_vm100_fw = self.connect_mock.return_value.nodes.return_value.return_value.return_value.firewall.return_value
149+
150+
mock_cluster_fw.rules.get.return_value = RAW_FIREWALL_RULES
151+
mock_cluster_fw.groups.return_value.get.return_value = RAW_GROUPS
152+
mock_cluster_fw.aliases.return_value.get.return_value = RAW_ALIASES
153+
154+
mock_vm100_fw.rules.get.return_value = RAW_FIREWALL_RULES
155+
mock_vm100_fw.aliases.return_value.get.return_value = RAW_ALIASES
156+
157+
def tearDown(self):
158+
self.connect_mock.stop()
159+
self.mock_module_helper.stop()
160+
super(TestProxmoxFirewallModule, self).tearDown()
161+
162+
def test_cluster_level_info(self):
163+
with pytest.raises(SystemExit) as exc_info:
164+
with set_module_args(get_module_args()):
165+
self.module.main()
166+
result = exc_info.value.args[0]
167+
assert result["changed"] is False
168+
assert result["msg"] == "successfully retrieved firewall rules and groups"
169+
assert result["firewall_rules"] == RAW_FIREWALL_RULES
170+
assert result["groups"] == ['test1', 'test2']
171+
assert result["aliases"] == RAW_ALIASES
172+
173+
def test_vm_level_info(self):
174+
with pytest.raises(SystemExit) as exc_info:
175+
with set_module_args(get_module_args(level='vm', vmid=100)):
176+
self.module.main()
177+
result = exc_info.value.args[0]
178+
assert result["changed"] is False
179+
assert result["msg"] == "successfully retrieved firewall rules and groups"
180+
assert result["firewall_rules"] == RAW_FIREWALL_RULES
181+
assert result["groups"] == ['test1', 'test2']
182+
assert result["aliases"] == RAW_ALIASES

0 commit comments

Comments
 (0)