Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 *
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