Skip to content

Commit 3946514

Browse files
committed
Add support for resource lock
1 parent 5d395d4 commit 3946514

File tree

7 files changed

+203
-0
lines changed

7 files changed

+203
-0
lines changed

linode_api4/groups/account.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
ChildAccount,
1212
Event,
1313
Invoice,
14+
Lock,
1415
Login,
1516
MappedObject,
1617
OAuthClient,
@@ -510,3 +511,58 @@ def child_accounts(self, *filters):
510511
:rtype: PaginatedList of ChildAccount
511512
"""
512513
return self.client._get_and_filter(ChildAccount, *filters)
514+
515+
def locks(self, *filters):
516+
"""
517+
Returns a list of all resource locks on the account.
518+
519+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-locks
520+
521+
:param filters: Any number of filters to apply to this query.
522+
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
523+
for more details on filtering.
524+
525+
:returns: A list of resource locks on the account.
526+
:rtype: PaginatedList of Lock
527+
"""
528+
return self.client._get_and_filter(Lock, *filters)
529+
530+
def lock_create(self, entity_type, entity_id, lock_type, **kwargs):
531+
"""
532+
Creates a resource lock to prevent deletion or modification.
533+
534+
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-lock
535+
536+
:param entity_type: The type of entity to lock (e.g., "linode").
537+
:type entity_type: str
538+
:param entity_id: The ID of the entity to lock.
539+
:type entity_id: int
540+
:param lock_type: The type of lock (e.g., "cannot_delete").
541+
:type lock_type: str or LockType
542+
543+
:returns: The created resource lock.
544+
:rtype: Lock
545+
"""
546+
from linode_api4.objects.lock import ( # pylint: disable=import-outside-toplevel
547+
LockType,
548+
)
549+
550+
params = {
551+
"entity_type": entity_type,
552+
"entity_id": entity_id,
553+
"lock_type": (
554+
lock_type.value
555+
if isinstance(lock_type, LockType)
556+
else lock_type
557+
),
558+
}
559+
params.update(kwargs)
560+
561+
result = self.client.post("/locks", data=params)
562+
563+
if "id" not in result:
564+
raise UnexpectedResponseError(
565+
"Unexpected response when creating lock!", json=result
566+
)
567+
568+
return Lock(self.client, result["id"], result)

linode_api4/objects/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
from .placement import *
2525
from .monitor import *
2626
from .monitor_api import *
27+
from .lock import *

linode_api4/objects/linode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ class Instance(Base):
803803
"maintenance_policy": Property(
804804
mutable=True
805805
), # Note: This field is only available when using v4beta.
806+
"locks": Property(unordered=True),
806807
}
807808

808809
@property

linode_api4/objects/lock.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from dataclasses import dataclass
2+
3+
from linode_api4.objects.base import Base, Property
4+
from linode_api4.objects.serializable import JSONObject, StrEnum
5+
6+
7+
class LockType(StrEnum):
8+
"""
9+
LockType defines valid values for resource lock types.
10+
11+
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-lock
12+
"""
13+
14+
cannot_delete = "cannot_delete"
15+
cannot_delete_with_subresources = "cannot_delete_with_subresources"
16+
17+
18+
@dataclass
19+
class LockEntity(JSONObject):
20+
"""
21+
Represents the entity that is locked.
22+
23+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-lock
24+
"""
25+
26+
id: int = 0
27+
type: str = ""
28+
label: str = ""
29+
url: str = ""
30+
31+
32+
class Lock(Base):
33+
"""
34+
A resource lock that prevents deletion or modification of a resource.
35+
36+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-lock
37+
"""
38+
39+
api_endpoint = "/locks/{id}"
40+
41+
properties = {
42+
"id": Property(identifier=True),
43+
"lock_type": Property(),
44+
"entity": Property(json_object=LockEntity),
45+
}

test/fixtures/locks.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"data": [
3+
{
4+
"id": 1,
5+
"lock_type": "cannot_delete",
6+
"entity": {
7+
"id": 123,
8+
"type": "linode",
9+
"label": "test-linode",
10+
"url": "/v4/linode/instances/123"
11+
}
12+
},
13+
{
14+
"id": 2,
15+
"lock_type": "cannot_delete_with_subresources",
16+
"entity": {
17+
"id": 456,
18+
"type": "linode",
19+
"label": "another-linode",
20+
"url": "/v4/linode/instances/456"
21+
}
22+
}
23+
],
24+
"page": 1,
25+
"pages": 1,
26+
"results": 2
27+
}

test/fixtures/locks_1.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"id": 1,
3+
"lock_type": "cannot_delete",
4+
"entity": {
5+
"id": 123,
6+
"type": "linode",
7+
"label": "test-linode",
8+
"url": "/v4/linode/instances/123"
9+
}
10+
}

test/unit/objects/lock_test.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from test.unit.base import ClientBaseCase
2+
3+
from linode_api4.objects.lock import Lock, LockEntity, LockType
4+
5+
6+
class LockTest(ClientBaseCase):
7+
"""
8+
Tests methods of the Lock class
9+
"""
10+
11+
def test_get_lock(self):
12+
"""
13+
Tests that a lock is loaded correctly by ID
14+
"""
15+
lock = Lock(self.client, 1)
16+
17+
self.assertEqual(lock.id, 1)
18+
self.assertEqual(lock.lock_type, "cannot_delete")
19+
self.assertIsInstance(lock.entity, LockEntity)
20+
self.assertEqual(lock.entity.id, 123)
21+
self.assertEqual(lock.entity.type, "linode")
22+
self.assertEqual(lock.entity.label, "test-linode")
23+
self.assertEqual(lock.entity.url, "/v4/linode/instances/123")
24+
25+
def test_get_locks(self):
26+
"""
27+
Tests that locks can be retrieved
28+
"""
29+
locks = self.client.account.locks()
30+
31+
self.assertEqual(len(locks), 2)
32+
self.assertEqual(locks[0].id, 1)
33+
self.assertEqual(locks[0].lock_type, "cannot_delete")
34+
self.assertEqual(locks[1].id, 2)
35+
self.assertEqual(locks[1].lock_type, "cannot_delete_with_subresources")
36+
37+
def test_create_lock(self):
38+
"""
39+
Tests that a lock can be created
40+
"""
41+
with self.mock_post("/locks/1") as m:
42+
lock = self.client.account.lock_create(
43+
"linode", 123, LockType.cannot_delete
44+
)
45+
46+
self.assertEqual(m.call_url, "/locks")
47+
self.assertEqual(m.call_data["entity_type"], "linode")
48+
self.assertEqual(m.call_data["entity_id"], 123)
49+
self.assertEqual(m.call_data["lock_type"], "cannot_delete")
50+
51+
self.assertEqual(lock.id, 1)
52+
self.assertEqual(lock.lock_type, "cannot_delete")
53+
54+
def test_delete_lock(self):
55+
"""
56+
Tests that a lock can be deleted
57+
"""
58+
lock = Lock(self.client, 1)
59+
60+
with self.mock_delete() as m:
61+
lock.delete()
62+
63+
self.assertEqual(m.call_url, "/locks/1")

0 commit comments

Comments
 (0)