Skip to content

Commit 70169d7

Browse files
Added support for firewall rule version endpoints (#520)
* Added support for firewall rule version endpoints * Updated get_rule_versions to be a property method
1 parent e1d8881 commit 70169d7

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

linode_api4/objects/networking.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,37 @@ def get_rules(self):
244244
"{}/rules".format(self.api_endpoint), model=self
245245
)
246246

247+
@property
248+
def rule_versions(self):
249+
"""
250+
Gets the JSON rule versions for this Firewall.
251+
252+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-firewall-rule-versions
253+
254+
:returns: Lists the current and historical rules of the firewall (that is not deleted),
255+
using version. Whenever the rules update, the version increments from 1.
256+
:rtype: dict
257+
"""
258+
return self._client.get(
259+
"{}/history".format(self.api_endpoint), model=self
260+
)
261+
262+
def get_rule_version(self, version):
263+
"""
264+
Gets the JSON for a specific rule version for this Firewall.
265+
266+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-firewall-rule-version
267+
268+
:param version: The firewall rule version to view.
269+
:type version: int
270+
271+
:returns: Gets a specific firewall rule version for an enabled or disabled firewall.
272+
:rtype: dict
273+
"""
274+
return self._client.get(
275+
"{}/history/rules/{}".format(self.api_endpoint, version), model=self
276+
)
277+
247278
def device_create(self, id, type="linode", **kwargs):
248279
"""
249280
Creates and attaches a device to this Firewall
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"data": [
3+
{
4+
"updated": "2025-03-07T17:06:36",
5+
"status": "enabled",
6+
"rules": {
7+
"version": 1
8+
}
9+
},
10+
{
11+
"updated": "2025-03-07T17:06:36",
12+
"status": "enabled",
13+
"rules": {
14+
"version": 2
15+
}
16+
}
17+
],
18+
"page": 1,
19+
"pages": 1,
20+
"results": 2
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"inbound": [
3+
{
4+
"action": "ACCEPT",
5+
"addresses": {
6+
"ipv4": [
7+
"0.0.0.0/0"
8+
],
9+
"ipv6": [
10+
"ff00::/8"
11+
]
12+
},
13+
"description": "A really cool firewall rule.",
14+
"label": "really-cool-firewall-rule",
15+
"ports": "80",
16+
"protocol": "TCP"
17+
}
18+
],
19+
"inbound_policy": "ACCEPT",
20+
"outbound": [],
21+
"outbound_policy": "DROP",
22+
"version": 2,
23+
"fingerprint": "96c9568c"
24+
}

test/integration/models/networking/test_networking.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import time
12
from test.integration.conftest import (
23
get_api_ca_file,
34
get_api_url,
@@ -72,6 +73,59 @@ def test_get_networking_rules(test_linode_client, test_firewall):
7273
assert "outbound_policy" in str(rules)
7374

7475

76+
def test_get_networking_rule_versions(test_linode_client, test_firewall):
77+
firewall = test_linode_client.load(Firewall, test_firewall.id)
78+
79+
# Update the firewall's rules
80+
new_rules = {
81+
"inbound": [
82+
{
83+
"action": "ACCEPT",
84+
"addresses": {
85+
"ipv4": ["0.0.0.0/0"],
86+
"ipv6": ["ff00::/8"],
87+
},
88+
"description": "A really cool firewall rule.",
89+
"label": "really-cool-firewall-rule",
90+
"ports": "80",
91+
"protocol": "TCP",
92+
}
93+
],
94+
"inbound_policy": "ACCEPT",
95+
"outbound": [],
96+
"outbound_policy": "DROP",
97+
}
98+
firewall.update_rules(new_rules)
99+
time.sleep(1)
100+
101+
rule_versions = firewall.rule_versions
102+
103+
# Original firewall rules
104+
old_rule_version = firewall.get_rule_version(1)
105+
106+
# Updated firewall rules
107+
new_rule_version = firewall.get_rule_version(2)
108+
109+
assert "rules" in str(rule_versions)
110+
assert "version" in str(rule_versions)
111+
assert rule_versions["results"] == 2
112+
113+
assert old_rule_version["inbound"] == []
114+
assert old_rule_version["inbound_policy"] == "ACCEPT"
115+
assert old_rule_version["outbound"] == []
116+
assert old_rule_version["outbound_policy"] == "DROP"
117+
assert old_rule_version["version"] == 1
118+
119+
assert (
120+
new_rule_version["inbound"][0]["description"]
121+
== "A really cool firewall rule."
122+
)
123+
assert new_rule_version["inbound_policy"] == "ACCEPT"
124+
assert new_rule_version["outbound"] == []
125+
assert new_rule_version["outbound_policy"] == "DROP"
126+
assert new_rule_version["version"] == 2
127+
128+
75129
@pytest.mark.smoke
76130
def test_ip_addresses_share(
77131
test_linode_client,

test/unit/objects/networking_test.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,54 @@ def test_get_rules(self):
4747
self.assertEqual(result["inbound_policy"], "DROP")
4848
self.assertEqual(result["outbound_policy"], "DROP")
4949

50+
def test_get_rule_versions(self):
51+
"""
52+
Tests that you can submit a correct firewall rule versions view api request.
53+
"""
54+
55+
firewall = Firewall(self.client, 123)
56+
57+
with self.mock_get("/networking/firewalls/123/history") as m:
58+
result = firewall.rule_versions
59+
self.assertEqual(m.call_url, "/networking/firewalls/123/history")
60+
self.assertEqual(result["data"][0]["status"], "enabled")
61+
self.assertEqual(result["data"][0]["rules"]["version"], 1)
62+
self.assertEqual(result["data"][0]["status"], "enabled")
63+
self.assertEqual(result["data"][1]["rules"]["version"], 2)
64+
65+
def test_get_rule_version(self):
66+
"""
67+
Tests that you can submit a correct firewall rule version view api request.
68+
"""
69+
70+
firewall = Firewall(self.client, 123)
71+
72+
with self.mock_get("/networking/firewalls/123/history/rules/2") as m:
73+
result = firewall.get_rule_version(2)
74+
self.assertEqual(
75+
m.call_url, "/networking/firewalls/123/history/rules/2"
76+
)
77+
self.assertEqual(result["inbound"][0]["action"], "ACCEPT")
78+
self.assertEqual(
79+
result["inbound"][0]["addresses"]["ipv4"][0], "0.0.0.0/0"
80+
)
81+
self.assertEqual(
82+
result["inbound"][0]["addresses"]["ipv6"][0], "ff00::/8"
83+
)
84+
self.assertEqual(
85+
result["inbound"][0]["description"],
86+
"A really cool firewall rule.",
87+
)
88+
self.assertEqual(
89+
result["inbound"][0]["label"], "really-cool-firewall-rule"
90+
)
91+
self.assertEqual(result["inbound"][0]["ports"], "80")
92+
self.assertEqual(result["inbound"][0]["protocol"], "TCP")
93+
self.assertEqual(result["outbound"], [])
94+
self.assertEqual(result["inbound_policy"], "ACCEPT")
95+
self.assertEqual(result["outbound_policy"], "DROP")
96+
self.assertEqual(result["version"], 2)
97+
5098
def test_rdns_reset(self):
5199
"""
52100
Tests that the RDNS of an IP and be reset using an explicit null value.

0 commit comments

Comments
 (0)