Skip to content

Commit 4bc4a21

Browse files
project: MultiCluster Object Storage (#416)
* Add warnings to deprecated OBJ API usage (#410) * add deprecated * add dependency * fix deprecated * Update OBJ Group and Objects for MultiCluster Object Storage API Changes (#426) --------- Co-authored-by: Ye Chen <[email protected]>
1 parent 53a2c07 commit 4bc4a21

File tree

10 files changed

+362
-47
lines changed

10 files changed

+362
-47
lines changed

linode_api4/groups/object_storage.py

+145-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import re
2+
import warnings
13
from typing import List, Optional, Union
24
from urllib import parse
35

6+
from deprecated import deprecated
7+
48
from linode_api4.errors import UnexpectedResponseError
59
from linode_api4.groups import Group
610
from linode_api4.objects import (
@@ -9,6 +13,7 @@
913
ObjectStorageACL,
1014
ObjectStorageBucket,
1115
ObjectStorageCluster,
16+
ObjectStorageKeyPermission,
1217
ObjectStorageKeys,
1318
)
1419
from linode_api4.util import drop_null_keys
@@ -20,8 +25,14 @@ class ObjectStorageGroup(Group):
2025
available clusters, buckets, and managing keys and TLS/SSL certs, etc.
2126
"""
2227

28+
@deprecated(
29+
reason="deprecated to use regions list API for listing available OJB clusters"
30+
)
2331
def clusters(self, *filters):
2432
"""
33+
This endpoint will be deprecated to use the regions list API to list available OBJ clusters,
34+
and a new access key API will directly expose the S3 endpoint hostname.
35+
2536
Returns a list of available Object Storage Clusters. You may filter
2637
this query to return only Clusters that are available in a specific region::
2738
@@ -58,6 +69,7 @@ def keys_create(
5869
self,
5970
label: str,
6071
bucket_access: Optional[Union[dict, List[dict]]] = None,
72+
regions: Optional[List[str]] = None,
6173
):
6274
"""
6375
Creates a new Object Storage keypair that may be used to interact directly
@@ -97,14 +109,16 @@ def keys_create(
97109
98110
:param label: The label for this keypair, for identification only.
99111
:type label: str
100-
:param bucket_access: One or a list of dicts with keys "cluster,"
101-
"permissions", and "bucket_name". If given, the
102-
resulting Object Storage keys will only have the
103-
requested level of access to the requested buckets,
104-
if they exist and are owned by you. See the provided
105-
:any:`bucket_access` function for a convenient way
106-
to create these dicts.
107-
:type bucket_access: dict or list of dict
112+
:param bucket_access: One or a list of dicts with keys "cluster," "region",
113+
"permissions", and "bucket_name". "cluster" key is
114+
deprecated because multiple cluster can be placed
115+
in the same region. Please consider switching to
116+
regions. If given, the resulting Object Storage keys
117+
will only have the requested level of access to the
118+
requested buckets, if they exist and are owned by
119+
you. See the provided :any:`bucket_access` function
120+
for a convenient way to create these dicts.
121+
:type bucket_access: Optional[Union[dict, List[dict]]]
108122
109123
:returns: The new keypair, with the secret key populated.
110124
:rtype: ObjectStorageKeys
@@ -115,22 +129,35 @@ def keys_create(
115129
if not isinstance(bucket_access, list):
116130
bucket_access = [bucket_access]
117131

118-
ba = [
119-
{
120-
"permissions": c.get("permissions"),
121-
"bucket_name": c.get("bucket_name"),
122-
"cluster": (
123-
c.id
124-
if "cluster" in c
125-
and issubclass(type(c["cluster"]), Base)
126-
else c.get("cluster")
127-
),
132+
ba = []
133+
for access_rule in bucket_access:
134+
access_rule_json = {
135+
"permissions": access_rule.get("permissions"),
136+
"bucket_name": access_rule.get("bucket_name"),
128137
}
129-
for c in bucket_access
130-
]
138+
139+
if "region" in access_rule:
140+
access_rule_json["region"] = access_rule.get("region")
141+
elif "cluster" in access_rule:
142+
warnings.warn(
143+
"'cluster' is a deprecated attribute, "
144+
"please consider using 'region' instead.",
145+
DeprecationWarning,
146+
)
147+
access_rule_json["cluster"] = (
148+
access_rule.id
149+
if "cluster" in access_rule
150+
and issubclass(type(access_rule["cluster"]), Base)
151+
else access_rule.get("cluster")
152+
)
153+
154+
ba.append(access_rule_json)
131155

132156
params["bucket_access"] = ba
133157

158+
if regions is not None:
159+
params["regions"] = regions
160+
134161
result = self.client.post("/object-storage/keys", data=params)
135162

136163
if not "id" in result:
@@ -142,9 +169,74 @@ def keys_create(
142169
ret = ObjectStorageKeys(self.client, result["id"], result)
143170
return ret
144171

145-
def bucket_access(self, cluster, bucket_name, permissions):
146-
return ObjectStorageBucket.access(
147-
self, cluster, bucket_name, permissions
172+
@classmethod
173+
def bucket_access(
174+
cls,
175+
cluster_or_region: str,
176+
bucket_name: str,
177+
permissions: Union[str, ObjectStorageKeyPermission],
178+
):
179+
"""
180+
Returns a dict formatted to be included in the `bucket_access` argument
181+
of :any:`keys_create`. See the docs for that method for an example of
182+
usage.
183+
184+
:param cluster_or_region: The region or Object Storage cluster to grant access in.
185+
:type cluster_or_region: str
186+
:param bucket_name: The name of the bucket to grant access to.
187+
:type bucket_name: str
188+
:param permissions: The permissions to grant. Should be one of "read_only"
189+
or "read_write".
190+
:type permissions: Union[str, ObjectStorageKeyPermission]
191+
:param use_region: Whether to use region mode.
192+
:type use_region: bool
193+
194+
:returns: A dict formatted correctly for specifying bucket access for
195+
new keys.
196+
:rtype: dict
197+
"""
198+
199+
result = {
200+
"bucket_name": bucket_name,
201+
"permissions": permissions,
202+
}
203+
204+
if cls.is_cluster(cluster_or_region):
205+
warnings.warn(
206+
"Cluster ID for Object Storage APIs has been deprecated. "
207+
"Please consider switch to a region ID (e.g., from `us-mia-1` to `us-mia`)",
208+
DeprecationWarning,
209+
)
210+
result["cluster"] = cluster_or_region
211+
else:
212+
result["region"] = cluster_or_region
213+
214+
return result
215+
216+
def buckets_in_region(self, region: str, *filters):
217+
"""
218+
Returns a list of Buckets in the region belonging to this Account.
219+
220+
This endpoint is available for convenience.
221+
It is recommended that instead you use the more fully-featured S3 API directly.
222+
223+
API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-buckets-in-cluster-list
224+
225+
:param filters: Any number of filters to apply to this query.
226+
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
227+
for more details on filtering.
228+
229+
:param region: The ID of an object storage region (e.g. `us-mia-1`).
230+
:type region: str
231+
232+
:returns: A list of Object Storage Buckets that in the requested cluster.
233+
:rtype: PaginatedList of ObjectStorageBucket
234+
"""
235+
236+
return self.client._get_and_filter(
237+
ObjectStorageBucket,
238+
*filters,
239+
endpoint=f"/object-storage/buckets/{region}",
148240
)
149241

150242
def cancel(self):
@@ -197,10 +289,14 @@ def buckets(self, *filters):
197289
"""
198290
return self.client._get_and_filter(ObjectStorageBucket, *filters)
199291

292+
@staticmethod
293+
def is_cluster(cluster_or_region: str):
294+
return bool(re.match(r"^[a-z]{2}-[a-z]+-[0-9]+$", cluster_or_region))
295+
200296
def bucket_create(
201297
self,
202-
cluster,
203-
label,
298+
cluster_or_region: Union[str, ObjectStorageCluster],
299+
label: str,
204300
acl: ObjectStorageACL = ObjectStorageACL.PRIVATE,
205301
cors_enabled=False,
206302
):
@@ -240,17 +336,30 @@ def bucket_create(
240336
:returns: A Object Storage Buckets that created by user.
241337
:rtype: ObjectStorageBucket
242338
"""
243-
cluster_id = (
244-
cluster.id if isinstance(cluster, ObjectStorageCluster) else cluster
339+
cluster_or_region_id = (
340+
cluster_or_region.id
341+
if isinstance(cluster_or_region, ObjectStorageCluster)
342+
else cluster_or_region
245343
)
246344

247345
params = {
248-
"cluster": cluster_id,
249346
"label": label,
250347
"acl": acl,
251348
"cors_enabled": cors_enabled,
252349
}
253350

351+
if self.is_cluster(cluster_or_region_id):
352+
warnings.warn(
353+
"The cluster parameter has been deprecated for creating a object "
354+
"storage bucket. Please consider switching to a region value. For "
355+
"example, a cluster value of `us-mia-1` can be translated to a "
356+
"region value of `us-mia`.",
357+
DeprecationWarning,
358+
)
359+
params["cluster"] = cluster_or_region_id
360+
else:
361+
params["region"] = cluster_or_region_id
362+
254363
result = self.client.post("/object-storage/buckets", data=params)
255364

256365
if not "label" in result or not "cluster" in result:
@@ -263,21 +372,21 @@ def bucket_create(
263372
self.client, result["label"], result["cluster"], result
264373
)
265374

266-
def object_acl_config(self, cluster_id, bucket, name=None):
375+
def object_acl_config(self, cluster_or_region_id: str, bucket, name=None):
267376
return ObjectStorageBucket(
268-
self.client, bucket, cluster_id
377+
self.client, bucket, cluster_or_region_id
269378
).object_acl_config(name)
270379

271380
def object_acl_config_update(
272-
self, cluster_id, bucket, acl: ObjectStorageACL, name
381+
self, cluster_or_region_id, bucket, acl: ObjectStorageACL, name
273382
):
274383
return ObjectStorageBucket(
275-
self.client, bucket, cluster_id
384+
self.client, bucket, cluster_or_region_id
276385
).object_acl_config_update(acl, name)
277386

278387
def object_url_create(
279388
self,
280-
cluster_id,
389+
cluster_or_region_id,
281390
bucket,
282391
method,
283392
name,
@@ -294,8 +403,8 @@ def object_url_create(
294403
295404
API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-object-url-create
296405
297-
:param cluster_id: The ID of the cluster this bucket exists in.
298-
:type cluster_id: str
406+
:param cluster_or_region_id: The ID of the cluster or region this bucket exists in.
407+
:type cluster_or_region_id: str
299408
300409
:param bucket: The bucket name.
301410
:type bucket: str
@@ -337,7 +446,7 @@ def object_url_create(
337446

338447
result = self.client.post(
339448
"/object-storage/buckets/{}/{}/object-url".format(
340-
parse.quote(str(cluster_id)), parse.quote(str(bucket))
449+
parse.quote(str(cluster_or_region_id)), parse.quote(str(bucket))
341450
),
342451
data=drop_null_keys(params),
343452
)

linode_api4/objects/object_storage.py

+37-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from typing import Optional
22
from urllib import parse
33

4+
from deprecated import deprecated
5+
46
from linode_api4.errors import UnexpectedResponseError
57
from linode_api4.objects import (
68
Base,
@@ -21,19 +23,25 @@ class ObjectStorageACL(StrEnum):
2123
CUSTOM = "custom"
2224

2325

26+
class ObjectStorageKeyPermission(StrEnum):
27+
READ_ONLY = "read_only"
28+
READ_WRITE = "read_write"
29+
30+
2431
class ObjectStorageBucket(DerivedBase):
2532
"""
2633
A bucket where objects are stored in.
2734
2835
API documentation: https://www.linode.com/docs/api/object-storage/#object-storage-bucket-view
2936
"""
3037

31-
api_endpoint = "/object-storage/buckets/{cluster}/{label}"
32-
parent_id_name = "cluster"
38+
api_endpoint = "/object-storage/buckets/{region}/{label}"
39+
parent_id_name = "region"
3340
id_attribute = "label"
3441

3542
properties = {
36-
"cluster": Property(identifier=True),
43+
"region": Property(identifier=True),
44+
"cluster": Property(),
3745
"created": Property(is_datetime=True),
3846
"hostname": Property(),
3947
"label": Property(identifier=True),
@@ -57,8 +65,11 @@ def make_instance(cls, id, client, parent_id=None, json=None):
5765
"""
5866
if json is None:
5967
return None
60-
if parent_id is None and json["cluster"]:
61-
parent_id = json["cluster"]
68+
69+
cluster_or_region = json.get("region") or json.get("cluster")
70+
71+
if parent_id is None and cluster_or_region:
72+
parent_id = cluster_or_region
6273

6374
if parent_id:
6475
return super().make(id, client, cls, parent_id=parent_id, json=json)
@@ -386,6 +397,13 @@ def object_acl_config_update(self, acl: ObjectStorageACL, name):
386397

387398
return MappedObject(**result)
388399

400+
@deprecated(
401+
reason=(
402+
"'access' method has been deprecated in favor of the class method "
403+
"'bucket_access' in ObjectStorageGroup, which can be accessed by "
404+
"'client.object_storage.access'"
405+
)
406+
)
389407
def access(self, cluster, bucket_name, permissions):
390408
"""
391409
Returns a dict formatted to be included in the `bucket_access` argument
@@ -411,8 +429,14 @@ def access(self, cluster, bucket_name, permissions):
411429
}
412430

413431

432+
@deprecated(
433+
reason="deprecated to use regions list API for viewing available OJB clusters"
434+
)
414435
class ObjectStorageCluster(Base):
415436
"""
437+
This class will be deprecated to use the regions list to view available OBJ clusters,
438+
and a new access key API will directly expose the S3 endpoint hostname.
439+
416440
A cluster where Object Storage is available.
417441
418442
API documentation: https://www.linode.com/docs/api/object-storage/#cluster-view
@@ -428,6 +452,13 @@ class ObjectStorageCluster(Base):
428452
"static_site_domain": Property(),
429453
}
430454

455+
@deprecated(
456+
reason=(
457+
"'buckets_in_cluster' method has been deprecated, please consider "
458+
"switching to 'buckets_in_region' in the object storage group (can "
459+
"be accessed via 'client.object_storage.buckets_in_cluster')."
460+
)
461+
)
431462
def buckets_in_cluster(self, *filters):
432463
"""
433464
Returns a list of Buckets in this cluster belonging to this Account.
@@ -470,4 +501,5 @@ class ObjectStorageKeys(Base):
470501
"secret_key": Property(),
471502
"bucket_access": Property(),
472503
"limited": Property(),
504+
"regions": Property(unordered=True),
473505
}

0 commit comments

Comments
 (0)