Skip to content
Merged
56 changes: 56 additions & 0 deletions linode_api4/groups/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ChildAccount,
Event,
Invoice,
Lock,
Login,
MappedObject,
OAuthClient,
Expand Down Expand Up @@ -510,3 +511,58 @@ def child_accounts(self, *filters):
:rtype: PaginatedList of ChildAccount
"""
return self.client._get_and_filter(ChildAccount, *filters)

def locks(self, *filters):
"""
Returns a list of all resource locks on the account.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-locks

:param filters: Any number of filters to apply to this query.
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
for more details on filtering.

:returns: A list of resource locks on the account.
:rtype: PaginatedList of Lock
"""
return self.client._get_and_filter(Lock, *filters)

def lock_create(self, entity_type, entity_id, lock_type, **kwargs):
"""
Creates a resource lock to prevent deletion or modification.

API Documentation: https://techdocs.akamai.com/linode-api/reference/post-lock

:param entity_type: The type of entity to lock (e.g., "linode").
:type entity_type: str
:param entity_id: The ID of the entity to lock.
:type entity_id: int
:param lock_type: The type of lock (e.g., "cannot_delete").
:type lock_type: str or LockType

:returns: The created resource lock.
:rtype: Lock
"""
from linode_api4.objects.lock import ( # pylint: disable=import-outside-toplevel
LockType,
)

params = {
"entity_type": entity_type,
"entity_id": entity_id,
"lock_type": (
lock_type.value
if isinstance(lock_type, LockType)
else lock_type
),
}
params.update(kwargs)

result = self.client.post("/locks", data=params)

if "id" not in result:
raise UnexpectedResponseError(
"Unexpected response when creating lock!", json=result
)

return Lock(self.client, result["id"], result)
1 change: 1 addition & 0 deletions linode_api4/objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
from .placement import *
from .monitor import *
from .monitor_api import *
from .lock import *
Comment thread Fixed
1 change: 1 addition & 0 deletions linode_api4/objects/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ class Instance(Base):
"maintenance_policy": Property(
mutable=True
), # Note: This field is only available when using v4beta.
"locks": Property(unordered=True),
}

@property
Expand Down
45 changes: 45 additions & 0 deletions linode_api4/objects/lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from dataclasses import dataclass

from linode_api4.objects.base import Base, Property
from linode_api4.objects.serializable import JSONObject, StrEnum


class LockType(StrEnum):
"""
LockType defines valid values for resource lock types.

API Documentation: https://techdocs.akamai.com/linode-api/reference/post-lock
"""

cannot_delete = "cannot_delete"
cannot_delete_with_subresources = "cannot_delete_with_subresources"


@dataclass
class LockEntity(JSONObject):
"""
Represents the entity that is locked.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-lock
"""

id: int = 0
type: str = ""
label: str = ""
url: str = ""


class Lock(Base):
"""
A resource lock that prevents deletion or modification of a resource.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-lock
"""

api_endpoint = "/locks/{id}"

properties = {
"id": Property(identifier=True),
"lock_type": Property(),
"entity": Property(json_object=LockEntity),
}
27 changes: 27 additions & 0 deletions test/fixtures/locks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"data": [
{
"id": 1,
"lock_type": "cannot_delete",
"entity": {
"id": 123,
"type": "linode",
"label": "test-linode",
"url": "/v4/linode/instances/123"
}
},
{
"id": 2,
"lock_type": "cannot_delete_with_subresources",
"entity": {
"id": 456,
"type": "linode",
"label": "another-linode",
"url": "/v4/linode/instances/456"
}
}
],
"page": 1,
"pages": 1,
"results": 2
}
10 changes: 10 additions & 0 deletions test/fixtures/locks_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": 1,
"lock_type": "cannot_delete",
"entity": {
"id": 123,
"type": "linode",
"label": "test-linode",
"url": "/v4/linode/instances/123"
}
}
63 changes: 63 additions & 0 deletions test/unit/objects/lock_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from test.unit.base import ClientBaseCase

from linode_api4.objects.lock import Lock, LockEntity, LockType


class LockTest(ClientBaseCase):
"""
Tests methods of the Lock class
"""

def test_get_lock(self):
"""
Tests that a lock is loaded correctly by ID
"""
lock = Lock(self.client, 1)

self.assertEqual(lock.id, 1)
self.assertEqual(lock.lock_type, "cannot_delete")
self.assertIsInstance(lock.entity, LockEntity)
self.assertEqual(lock.entity.id, 123)
self.assertEqual(lock.entity.type, "linode")
self.assertEqual(lock.entity.label, "test-linode")
self.assertEqual(lock.entity.url, "/v4/linode/instances/123")

def test_get_locks(self):
"""
Tests that locks can be retrieved
"""
locks = self.client.account.locks()

self.assertEqual(len(locks), 2)
self.assertEqual(locks[0].id, 1)
self.assertEqual(locks[0].lock_type, "cannot_delete")
self.assertEqual(locks[1].id, 2)
self.assertEqual(locks[1].lock_type, "cannot_delete_with_subresources")

def test_create_lock(self):
"""
Tests that a lock can be created
"""
with self.mock_post("/locks/1") as m:
lock = self.client.account.lock_create(
"linode", 123, LockType.cannot_delete
)

self.assertEqual(m.call_url, "/locks")
self.assertEqual(m.call_data["entity_type"], "linode")
self.assertEqual(m.call_data["entity_id"], 123)
self.assertEqual(m.call_data["lock_type"], "cannot_delete")

self.assertEqual(lock.id, 1)
self.assertEqual(lock.lock_type, "cannot_delete")

def test_delete_lock(self):
"""
Tests that a lock can be deleted
"""
lock = Lock(self.client, 1)

with self.mock_delete() as m:
lock.delete()

self.assertEqual(m.call_url, "/locks/1")
Loading