Skip to content

Commit 72b4f13

Browse files
committed
Lock group and tests
1 parent ddcdbe7 commit 72b4f13

File tree

6 files changed

+391
-0
lines changed

6 files changed

+391
-0
lines changed

linode_api4/groups/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .linode import *
1010
from .lke import *
1111
from .lke_tier import *
12+
from .lock import *
1213
from .longview import *
1314
from .maintenance import *
1415
from .monitor import *

linode_api4/groups/lock.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from typing import Union
2+
3+
from linode_api4.errors import UnexpectedResponseError
4+
from linode_api4.groups import Group
5+
from linode_api4.objects import Lock, LockType
6+
7+
__all__ = ["LockGroup"]
8+
9+
10+
class LockGroup(Group):
11+
"""
12+
Encapsulates methods for interacting with Resource Locks.
13+
14+
Resource locks prevent deletion or modification of resources.
15+
Currently, only Linode instances can be locked.
16+
"""
17+
18+
def __call__(self, *filters):
19+
"""
20+
Returns a list of all Resource Locks on the account.
21+
22+
This is intended to be called off of the :any:`LinodeClient`
23+
class, like this::
24+
25+
locks = client.locks()
26+
27+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-locks
28+
29+
:param filters: Any number of filters to apply to this query.
30+
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
31+
for more details on filtering.
32+
33+
:returns: A list of Resource Locks on the account.
34+
:rtype: PaginatedList of Lock
35+
"""
36+
return self.client._get_and_filter(Lock, *filters)
37+
38+
def create(
39+
self,
40+
entity_type: str,
41+
entity_id: Union[int, str],
42+
lock_type: Union[LockType, str] = LockType.cannot_delete,
43+
) -> Lock:
44+
"""
45+
Creates a new Resource Lock for the specified entity.
46+
47+
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-lock
48+
49+
:param entity_type: The type of entity to lock (e.g., "linode").
50+
:type entity_type: str
51+
:param entity_id: The ID of the entity to lock.
52+
:type entity_id: int or str
53+
:param lock_type: The type of lock to create. Defaults to "cannot_delete".
54+
:type lock_type: LockType or str
55+
56+
:returns: The newly created Resource Lock.
57+
:rtype: Lock
58+
"""
59+
params = {
60+
"entity_type": entity_type,
61+
"entity_id": entity_id,
62+
"lock_type": lock_type,
63+
}
64+
65+
result = self.client.post("/locks", data=params)
66+
67+
if "id" not in result:
68+
raise UnexpectedResponseError(
69+
"Unexpected response when creating lock!", json=result
70+
)
71+
72+
return Lock(self.client, result["id"], result)
73+
74+
def delete(self, lock: Union[Lock, int]) -> bool:
75+
"""
76+
Deletes a Resource Lock.
77+
78+
API Documentation: https://techdocs.akamai.com/linode-api/reference/delete-lock
79+
80+
:param lock: The Lock to delete, or its ID.
81+
:type lock: Lock or int
82+
83+
:returns: True if the lock was successfully deleted.
84+
:rtype: bool
85+
"""
86+
lock_id = lock.id if isinstance(lock, Lock) else lock
87+
88+
self.client.delete(f"/locks/{lock_id}")
89+
return True

linode_api4/linode_client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ImageGroup,
1919
LinodeGroup,
2020
LKEGroup,
21+
LockGroup,
2122
LongviewGroup,
2223
MaintenanceGroup,
2324
MetricsGroup,
@@ -454,6 +455,9 @@ def __init__(
454455

455456
self.monitor = MonitorGroup(self)
456457

458+
#: Access methods related to Resource Locks - See :any:`LockGroup` for more information.
459+
self.locks = LockGroup(self)
460+
457461
super().__init__(
458462
token=token,
459463
base_url=base_url,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file is intentionally left empty to make the directory a Python package.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
from test.integration.conftest import get_region
2+
from test.integration.helpers import (
3+
get_test_label,
4+
send_request_when_resource_available,
5+
)
6+
7+
import pytest
8+
9+
from linode_api4.objects import Lock, LockType
10+
11+
12+
@pytest.fixture(scope="function")
13+
def linode_for_lock(test_linode_client, e2e_test_firewall):
14+
"""
15+
Create a Linode instance for testing locks.
16+
"""
17+
client = test_linode_client
18+
region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core")
19+
label = get_test_label(length=8)
20+
21+
linode_instance, _ = client.linode.instance_create(
22+
"g6-nanode-1",
23+
region,
24+
image="linode/debian12",
25+
label=label,
26+
firewall=e2e_test_firewall,
27+
)
28+
29+
yield linode_instance
30+
31+
# Clean up any locks on the Linode before deleting it
32+
locks = client.locks()
33+
for lock in locks:
34+
if (
35+
lock.entity.id == linode_instance.id
36+
and lock.entity.type == "linode"
37+
):
38+
lock.delete()
39+
40+
send_request_when_resource_available(
41+
timeout=100, func=linode_instance.delete
42+
)
43+
44+
45+
@pytest.fixture(scope="function")
46+
def test_lock(test_linode_client, linode_for_lock):
47+
"""
48+
Create a lock for testing.
49+
"""
50+
lock = test_linode_client.locks.create(
51+
entity_type="linode",
52+
entity_id=linode_for_lock.id,
53+
lock_type=LockType.cannot_delete,
54+
)
55+
56+
yield lock
57+
58+
# Clean up lock if it still exists
59+
try:
60+
lock.delete()
61+
except Exception:
62+
pass # Lock may have been deleted by the test
63+
64+
65+
@pytest.mark.smoke
66+
def test_get_lock(test_linode_client, test_lock):
67+
"""
68+
Test that a lock can be retrieved by ID.
69+
"""
70+
lock = test_linode_client.load(Lock, test_lock.id)
71+
72+
assert lock.id == test_lock.id
73+
assert lock.lock_type == "cannot_delete"
74+
assert lock.entity is not None
75+
assert lock.entity.type == "linode"
76+
77+
78+
def test_list_locks(test_linode_client, test_lock):
79+
"""
80+
Test that locks can be listed.
81+
"""
82+
locks = test_linode_client.locks()
83+
84+
assert len(locks) > 0
85+
86+
# Verify our test lock is in the list
87+
lock_ids = [lock.id for lock in locks]
88+
assert test_lock.id in lock_ids
89+
90+
91+
def test_create_lock_cannot_delete(test_linode_client, linode_for_lock):
92+
"""
93+
Test creating a cannot_delete lock.
94+
"""
95+
lock = test_linode_client.locks.create(
96+
entity_type="linode",
97+
entity_id=linode_for_lock.id,
98+
lock_type=LockType.cannot_delete,
99+
)
100+
101+
assert lock.id is not None
102+
assert lock.lock_type == "cannot_delete"
103+
assert lock.entity.id == linode_for_lock.id
104+
assert lock.entity.type == "linode"
105+
assert lock.entity.label == linode_for_lock.label
106+
107+
# Clean up
108+
lock.delete()
109+
110+
111+
def test_create_lock_cannot_delete_with_subresources(
112+
test_linode_client, linode_for_lock
113+
):
114+
"""
115+
Test creating a cannot_delete_with_subresources lock.
116+
"""
117+
lock = test_linode_client.locks.create(
118+
entity_type="linode",
119+
entity_id=linode_for_lock.id,
120+
lock_type=LockType.cannot_delete_with_subresources,
121+
)
122+
123+
assert lock.id is not None
124+
assert lock.lock_type == "cannot_delete_with_subresources"
125+
assert lock.entity.id == linode_for_lock.id
126+
assert lock.entity.type == "linode"
127+
128+
# Clean up
129+
lock.delete()
130+
131+
132+
def test_delete_lock(test_linode_client, linode_for_lock):
133+
"""
134+
Test that a lock can be deleted.
135+
"""
136+
# Create a lock
137+
lock = test_linode_client.locks.create(
138+
entity_type="linode",
139+
entity_id=linode_for_lock.id,
140+
lock_type=LockType.cannot_delete,
141+
)
142+
143+
lock_id = lock.id
144+
145+
# Delete the lock using the group method
146+
result = test_linode_client.locks.delete(lock)
147+
148+
assert result is True
149+
150+
# Verify the lock no longer exists
151+
locks = test_linode_client.locks()
152+
lock_ids = [l.id for l in locks]
153+
assert lock_id not in lock_ids
154+
155+
156+
def test_delete_lock_by_id(test_linode_client, linode_for_lock):
157+
"""
158+
Test that a lock can be deleted by ID.
159+
"""
160+
# Create a lock
161+
lock = test_linode_client.locks.create(
162+
entity_type="linode",
163+
entity_id=linode_for_lock.id,
164+
lock_type=LockType.cannot_delete,
165+
)
166+
167+
lock_id = lock.id
168+
169+
# Delete the lock by ID
170+
result = test_linode_client.locks.delete(lock_id)
171+
172+
assert result is True
173+
174+
# Verify the lock no longer exists
175+
locks = test_linode_client.locks()
176+
lock_ids = [l.id for l in locks]
177+
assert lock_id not in lock_ids
178+
179+
180+
def test_lock_object_delete(test_linode_client, linode_for_lock):
181+
"""
182+
Test that a lock can be deleted using the Lock object's delete method.
183+
"""
184+
# Create a lock
185+
lock = test_linode_client.locks.create(
186+
entity_type="linode",
187+
entity_id=linode_for_lock.id,
188+
lock_type=LockType.cannot_delete,
189+
)
190+
191+
lock_id = lock.id
192+
193+
# Delete the lock using the object method
194+
lock.delete()
195+
196+
# Verify the lock no longer exists
197+
locks = test_linode_client.locks()
198+
lock_ids = [l.id for l in locks]
199+
assert lock_id not in lock_ids

0 commit comments

Comments
 (0)