|
| 1 | +from linode_api4.errors import UnexpectedResponseError |
| 2 | +from linode_api4.groups import Group |
| 3 | +from linode_api4.objects import ( |
| 4 | + Base, |
| 5 | + MappedObject, |
| 6 | + ObjectStorageACL, |
| 7 | + ObjectStorageBucket, |
| 8 | + ObjectStorageCluster, |
| 9 | + ObjectStorageKeys, |
| 10 | +) |
| 11 | +from linode_api4.util import drop_null_keys |
| 12 | + |
| 13 | + |
| 14 | +class ObjectStorageGroup(Group): |
| 15 | + """ |
| 16 | + This group encapsulates all endpoints under /object-storage, including viewing |
| 17 | + available clusters, buckets, and managing keys and TLS/SSL certs, etc. |
| 18 | + """ |
| 19 | + |
| 20 | + def clusters(self, *filters): |
| 21 | + """ |
| 22 | + Returns a list of available Object Storage Clusters. You may filter |
| 23 | + this query to return only Clusters that are available in a specific region:: |
| 24 | +
|
| 25 | + us_east_clusters = client.object_storage.clusters(ObjectStorageCluster.region == "us-east") |
| 26 | +
|
| 27 | + API Documentation: https://www.linode.com/docs/api/object-storage/#clusters-list |
| 28 | +
|
| 29 | + :param filters: Any number of filters to apply to this query. |
| 30 | +
|
| 31 | + :returns: A list of Object Storage Clusters that matched the query. |
| 32 | + :rtype: PaginatedList of ObjectStorageCluster |
| 33 | + """ |
| 34 | + return self.client._get_and_filter(ObjectStorageCluster, *filters) |
| 35 | + |
| 36 | + def keys(self, *filters): |
| 37 | + """ |
| 38 | + Returns a list of Object Storage Keys active on this account. These keys |
| 39 | + allow third-party applications to interact directly with Linode Object Storage. |
| 40 | +
|
| 41 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-keys-list |
| 42 | +
|
| 43 | + :param filters: Any number of filters to apply to this query. |
| 44 | +
|
| 45 | + :returns: A list of Object Storage Keys that matched the query. |
| 46 | + :rtype: PaginatedList of ObjectStorageKeys |
| 47 | + """ |
| 48 | + return self.client._get_and_filter(ObjectStorageKeys, *filters) |
| 49 | + |
| 50 | + def keys_create(self, label, bucket_access=None): |
| 51 | + """ |
| 52 | + Creates a new Object Storage keypair that may be used to interact directly |
| 53 | + with Linode Object Storage in third-party applications. This response is |
| 54 | + the only time that "secret_key" will be populated - be sure to capture its |
| 55 | + value or it will be lost forever. |
| 56 | +
|
| 57 | + If given, `bucket_access` will cause the new keys to be restricted to only |
| 58 | + the specified level of access for the specified buckets. For example, to |
| 59 | + create a keypair that can only access the "example" bucket in all clusters |
| 60 | + (and assuming you own that bucket in every cluster), you might do this:: |
| 61 | +
|
| 62 | + client = LinodeClient(TOKEN) |
| 63 | +
|
| 64 | + # look up clusters |
| 65 | + all_clusters = client.object_storage.clusters() |
| 66 | +
|
| 67 | + new_keys = client.object_storage.keys_create( |
| 68 | + "restricted-keys", |
| 69 | + bucket_access=[ |
| 70 | + client.object_storage.bucket_access(cluster, "example", "read_write") |
| 71 | + for cluster in all_clusters |
| 72 | + ], |
| 73 | + ) |
| 74 | +
|
| 75 | + To create a keypair that can only read from the bucket "example2" in the |
| 76 | + "us-east-1" cluster (an assuming you own that bucket in that cluster), |
| 77 | + you might do this:: |
| 78 | +
|
| 79 | + client = LinodeClient(TOKEN) |
| 80 | + new_keys_2 = client.object_storage.keys_create( |
| 81 | + "restricted-keys-2", |
| 82 | + bucket_access=client.object_storage.bucket_access("us-east-1", "example2", "read_only"), |
| 83 | + ) |
| 84 | +
|
| 85 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-key-create |
| 86 | +
|
| 87 | + :param label: The label for this keypair, for identification only. |
| 88 | + :type label: str |
| 89 | + :param bucket_access: One or a list of dicts with keys "cluster," |
| 90 | + "permissions", and "bucket_name". If given, the |
| 91 | + resulting Object Storage keys will only have the |
| 92 | + requested level of access to the requested buckets, |
| 93 | + if they exist and are owned by you. See the provided |
| 94 | + :any:`bucket_access` function for a convenient way |
| 95 | + to create these dicts. |
| 96 | + :type bucket_access: dict or list of dict |
| 97 | +
|
| 98 | + :returns: The new keypair, with the secret key populated. |
| 99 | + :rtype: ObjectStorageKeys |
| 100 | + """ |
| 101 | + params = {"label": label} |
| 102 | + |
| 103 | + if bucket_access is not None: |
| 104 | + if not isinstance(bucket_access, list): |
| 105 | + bucket_access = [bucket_access] |
| 106 | + |
| 107 | + ba = [ |
| 108 | + { |
| 109 | + "permissions": c.get("permissions"), |
| 110 | + "bucket_name": c.get("bucket_name"), |
| 111 | + "cluster": c.id |
| 112 | + if "cluster" in c and issubclass(type(c["cluster"]), Base) |
| 113 | + else c.get("cluster"), |
| 114 | + } |
| 115 | + for c in bucket_access |
| 116 | + ] |
| 117 | + |
| 118 | + params["bucket_access"] = ba |
| 119 | + |
| 120 | + result = self.client.post("/object-storage/keys", data=params) |
| 121 | + |
| 122 | + if not "id" in result: |
| 123 | + raise UnexpectedResponseError( |
| 124 | + "Unexpected response when creating Object Storage Keys!", |
| 125 | + json=result, |
| 126 | + ) |
| 127 | + |
| 128 | + ret = ObjectStorageKeys(self.client, result["id"], result) |
| 129 | + return ret |
| 130 | + |
| 131 | + def bucket_access(self, cluster, bucket_name, permissions): |
| 132 | + return ObjectStorageBucket.access( |
| 133 | + self, cluster, bucket_name, permissions |
| 134 | + ) |
| 135 | + |
| 136 | + def cancel(self): |
| 137 | + """ |
| 138 | + Cancels Object Storage service. This may be a destructive operation. Once |
| 139 | + cancelled, you will no longer receive the transfer for or be billed for |
| 140 | + Object Storage, and all keys will be invalidated. |
| 141 | +
|
| 142 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-cancel |
| 143 | + """ |
| 144 | + self.client.post("/object-storage/cancel", data={}) |
| 145 | + return True |
| 146 | + |
| 147 | + def transfer(self): |
| 148 | + """ |
| 149 | + The amount of outbound data transfer used by your account’s Object Storage buckets, |
| 150 | + in bytes, for the current month’s billing cycle. Object Storage adds 1 terabyte |
| 151 | + of outbound data transfer to your data transfer pool. |
| 152 | +
|
| 153 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-transfer-view |
| 154 | +
|
| 155 | + :returns: The amount of outbound data transfer used by your account’s Object |
| 156 | + Storage buckets, in bytes, for the current month’s billing cycle. |
| 157 | + :rtype: MappedObject |
| 158 | + """ |
| 159 | + result = self.client.get("/object-storage/transfer") |
| 160 | + |
| 161 | + if not "used" in result: |
| 162 | + raise UnexpectedResponseError( |
| 163 | + "Unexpected response when getting Transfer Pool!", |
| 164 | + json=result, |
| 165 | + ) |
| 166 | + |
| 167 | + return MappedObject(**result) |
| 168 | + |
| 169 | + def buckets(self, *filters): |
| 170 | + """ |
| 171 | + Returns a paginated list of all Object Storage Buckets that you own. |
| 172 | + This endpoint is available for convenience. |
| 173 | + It is recommended that instead you use the more fully-featured S3 API directly. |
| 174 | +
|
| 175 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-buckets-list |
| 176 | +
|
| 177 | + :returns: A list of Object Storage Buckets that matched the query. |
| 178 | + :rtype: PaginatedList of ObjectStorageBucket |
| 179 | + """ |
| 180 | + return self.client._get_and_filter(ObjectStorageBucket, *filters) |
| 181 | + |
| 182 | + def bucket_create( |
| 183 | + self, |
| 184 | + cluster, |
| 185 | + label, |
| 186 | + acl: ObjectStorageACL = ObjectStorageACL.PRIVATE, |
| 187 | + cors_enabled=False, |
| 188 | + ): |
| 189 | + """ |
| 190 | + Creates an Object Storage Bucket in the specified cluster. Accounts with |
| 191 | + negative balances cannot access this command. If the bucket already exists |
| 192 | + and is owned by you, this endpoint returns a 200 response with that bucket |
| 193 | + as if it had just been created. |
| 194 | +
|
| 195 | + This endpoint is available for convenience. |
| 196 | + It is recommended that instead you use the more fully-featured S3 API directly. |
| 197 | +
|
| 198 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-bucket-create |
| 199 | +
|
| 200 | + :param acl: The Access Control Level of the bucket using a canned ACL string. |
| 201 | + For more fine-grained control of ACLs, use the S3 API directly. |
| 202 | + :type acl: str |
| 203 | + Enum: private,public-read,authenticated-read,public-read-write |
| 204 | +
|
| 205 | + :param cluster: The ID of the Object Storage Cluster where this bucket |
| 206 | + should be created. |
| 207 | + :type cluster: str |
| 208 | +
|
| 209 | + :param cors_enabled: If true, the bucket will be created with CORS enabled for |
| 210 | + all origins. For more fine-grained controls of CORS, use |
| 211 | + the S3 API directly. |
| 212 | + :type cors_enabled: bool |
| 213 | +
|
| 214 | + :param label: The name for this bucket. Must be unique in the cluster you are |
| 215 | + creating the bucket in, or an error will be returned. Labels will |
| 216 | + be reserved only for the cluster that active buckets are created |
| 217 | + and stored in. If you want to reserve this bucket’s label in |
| 218 | + another cluster, you must create a new bucket with the same label |
| 219 | + in the new cluster. |
| 220 | + :type label: str |
| 221 | +
|
| 222 | + :returns: A Object Storage Buckets that created by user. |
| 223 | + :rtype: ObjectStorageBucket |
| 224 | + """ |
| 225 | + cluster_id = ( |
| 226 | + cluster.id if isinstance(cluster, ObjectStorageCluster) else cluster |
| 227 | + ) |
| 228 | + |
| 229 | + params = { |
| 230 | + "cluster": cluster_id, |
| 231 | + "label": label, |
| 232 | + "acl": acl, |
| 233 | + "cors_enabled": cors_enabled, |
| 234 | + } |
| 235 | + |
| 236 | + result = self.client.post("/object-storage/buckets", data=params) |
| 237 | + |
| 238 | + if not "label" in result or not "cluster" in result: |
| 239 | + raise UnexpectedResponseError( |
| 240 | + "Unexpected response when creating Object Storage Bucket!", |
| 241 | + json=result, |
| 242 | + ) |
| 243 | + |
| 244 | + return ObjectStorageBucket( |
| 245 | + self.client, result["label"], result["cluster"], result |
| 246 | + ) |
| 247 | + |
| 248 | + def object_acl_config(self, cluster_id, bucket, name=None): |
| 249 | + return ObjectStorageBucket( |
| 250 | + self.client, bucket, cluster_id |
| 251 | + ).object_acl_config(name) |
| 252 | + |
| 253 | + def object_acl_config_update( |
| 254 | + self, cluster_id, bucket, acl: ObjectStorageACL, name |
| 255 | + ): |
| 256 | + return ObjectStorageBucket( |
| 257 | + self.client, bucket, cluster_id |
| 258 | + ).object_acl_config_update(acl, name) |
| 259 | + |
| 260 | + def object_url_create( |
| 261 | + self, |
| 262 | + cluster_id, |
| 263 | + bucket, |
| 264 | + method, |
| 265 | + name, |
| 266 | + content_type=None, |
| 267 | + expires_in=3600, |
| 268 | + ): |
| 269 | + """ |
| 270 | + Creates a pre-signed URL to access a single Object in a bucket. |
| 271 | + This can be used to share objects, and also to create/delete objects by using |
| 272 | + the appropriate HTTP method in your request body’s method parameter. |
| 273 | +
|
| 274 | + This endpoint is available for convenience. |
| 275 | + It is recommended that instead you use the more fully-featured S3 API directly. |
| 276 | +
|
| 277 | + API Documentation: https://www.linode.com/docs/api/object-storage/#object-storage-object-url-create |
| 278 | +
|
| 279 | + :param cluster_id: The ID of the cluster this bucket exists in. |
| 280 | + :type cluster_id: str |
| 281 | +
|
| 282 | + :param bucket: The bucket name. |
| 283 | + :type bucket: str |
| 284 | +
|
| 285 | + :param content_type: The expected Content-type header of the request this |
| 286 | + signed URL will be valid for. If provided, the |
| 287 | + Content-type header must be sent with the request when |
| 288 | + this URL is used, and must be the same as it was when |
| 289 | + the signed URL was created. |
| 290 | + Required for all methods except “GET” or “DELETE”. |
| 291 | + :type content_type: str |
| 292 | +
|
| 293 | + :param expires_in: How long this signed URL will be valid for, in seconds. |
| 294 | + If omitted, the URL will be valid for 3600 seconds (1 hour). Defaults to 3600. |
| 295 | + :type expires_in: int 360..86400 |
| 296 | +
|
| 297 | + :param method: The HTTP method allowed to be used with the pre-signed URL. |
| 298 | + :type method: str |
| 299 | +
|
| 300 | + :param name: The name of the object that will be accessed with the pre-signed |
| 301 | + URL. This object need not exist, and no error will be returned |
| 302 | + if it doesn’t. This behavior is useful for generating pre-signed |
| 303 | + URLs to upload new objects to by setting the method to “PUT”. |
| 304 | + :type name: str |
| 305 | +
|
| 306 | + :returns: The signed URL to perform the request at. |
| 307 | + :rtype: MappedObject |
| 308 | + """ |
| 309 | + if method not in ("GET", "DELETE") and content_type is None: |
| 310 | + raise ValueError( |
| 311 | + "Content-type header is missing for the current method! It's required for all methods except GET or DELETE." |
| 312 | + ) |
| 313 | + params = { |
| 314 | + "method": method, |
| 315 | + "name": name, |
| 316 | + "expires_in": expires_in, |
| 317 | + "content_type": content_type, |
| 318 | + } |
| 319 | + |
| 320 | + result = self.client.post( |
| 321 | + "/object-storage/buckets/{}/{}/object-url".format( |
| 322 | + cluster_id, bucket |
| 323 | + ), |
| 324 | + data=drop_null_keys(params), |
| 325 | + ) |
| 326 | + |
| 327 | + if not "url" in result: |
| 328 | + raise UnexpectedResponseError( |
| 329 | + "Unexpected response when creating the access url of an object!", |
| 330 | + json=result, |
| 331 | + ) |
| 332 | + |
| 333 | + return MappedObject(**result) |
0 commit comments