From 8cc03eea14a20ae02fe8c6d8b21ddbdccbd536a2 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 7 May 2025 03:19:38 -0400 Subject: [PATCH 1/4] Trusted publisher for PyPI (#536) --- .github/workflows/publish-pypi.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-pypi.yaml b/.github/workflows/publish-pypi.yaml index d5338b7a7..027ac5298 100644 --- a/.github/workflows/publish-pypi.yaml +++ b/.github/workflows/publish-pypi.yaml @@ -5,7 +5,11 @@ on: types: [ published ] jobs: pypi-release: + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write runs-on: ubuntu-latest + environment: pypi-release steps: - name: Checkout uses: actions/checkout@v4 @@ -25,5 +29,3 @@ jobs: - name: Publish the release artifacts to PyPI uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # pin@release/v1.12.4 - with: - password: ${{ secrets.PYPI_API_TOKEN }} From 751180e2a70b363cc94a5c2382b5af518231ab8e Mon Sep 17 00:00:00 2001 From: Ye Chen <127243817+yec-akamai@users.noreply.github.com> Date: Mon, 12 May 2025 12:20:55 -0400 Subject: [PATCH 2/4] Project: Limits Visibility M1 (#544) * Support Object Storage Quota Limits Visibility (#531) * obj quota * add comment * build json object * add obj quotas int tests (#535) * Update Object Storage quota doc link (#543) --------- Co-authored-by: Youjung Kim <126618609+ykim-akamai@users.noreply.github.com> --- linode_api4/groups/object_storage.py | 16 +++++ linode_api4/objects/object_storage.py | 49 ++++++++++++++++ test/fixtures/object-storage_quotas.json | 25 ++++++++ ...t-storage_quotas_obj-objects-us-ord-1.json | 9 +++ ...age_quotas_obj-objects-us-ord-1_usage.json | 4 ++ .../models/object_storage/test_obj_quotas.py | 58 +++++++++++++++++++ test/unit/objects/object_storage_test.py | 51 ++++++++++++++++ 7 files changed, 212 insertions(+) create mode 100644 test/fixtures/object-storage_quotas.json create mode 100644 test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json create mode 100644 test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json create mode 100644 test/integration/models/object_storage/test_obj_quotas.py diff --git a/linode_api4/groups/object_storage.py b/linode_api4/groups/object_storage.py index eb6a296b7..5ffab3ffc 100644 --- a/linode_api4/groups/object_storage.py +++ b/linode_api4/groups/object_storage.py @@ -21,6 +21,7 @@ ObjectStorageCluster, ObjectStorageKeyPermission, ObjectStorageKeys, + ObjectStorageQuota, ) from linode_api4.util import drop_null_keys @@ -517,3 +518,18 @@ def object_url_create( ) return MappedObject(**result) + + def quotas(self, *filters): + """ + Lists the active ObjectStorage-related quotas applied to your account. + + API Documentation: https://techdocs.akamai.com/linode-api/reference/get-object-storage-quotas + + :param filters: Any number of filters to apply to this query. + See :doc:`Filtering Collections` + for more details on filtering. + + :returns: A list of Object Storage Quotas that matched the query. + :rtype: PaginatedList of ObjectStorageQuota + """ + return self.client._get_and_filter(ObjectStorageQuota, *filters) diff --git a/linode_api4/objects/object_storage.py b/linode_api4/objects/object_storage.py index be1fd0cc7..29eba2b06 100644 --- a/linode_api4/objects/object_storage.py +++ b/linode_api4/objects/object_storage.py @@ -51,6 +51,16 @@ class ObjectStorageEndpoint(JSONObject): s3_endpoint: Optional[str] = None +@dataclass +class ObjectStorageQuotaUsage(JSONObject): + """ + ObjectStorageQuotaUsage contains the fields of an object storage quota usage information. + """ + + quota_limit: int = 0 + usage: int = 0 + + class ObjectStorageType(Base): """ An ObjectStorageType represents the structure of a valid Object Storage type. @@ -566,3 +576,42 @@ class ObjectStorageKeys(Base): "limited": Property(), "regions": Property(unordered=True), } + + +class ObjectStorageQuota(Base): + """ + An Object Storage related quota information on your account. + Object Storage Quota related features are under v4beta and may not currently be available to all users. + + API documentation: https://techdocs.akamai.com/linode-api/reference/get-object-storage-quota + """ + + api_endpoint = "/object-storage/quotas/{quota_id}" + id_attribute = "quota_id" + + properties = { + "quota_id": Property(identifier=True), + "quota_name": Property(), + "endpoint_type": Property(), + "s3_endpoint": Property(), + "description": Property(), + "quota_limit": Property(), + "resource_metric": Property(), + } + + def usage(self): + """ + Gets usage data for a specific ObjectStorage Quota resource you can have on your account and the current usage for that resource. + + API documentation: https://techdocs.akamai.com/linode-api/reference/get-object-storage-quota-usage + + :returns: The Object Storage Quota usage. + :rtype: ObjectStorageQuotaUsage + """ + + result = self._client.get( + f"{type(self).api_endpoint}/usage", + model=self, + ) + + return ObjectStorageQuotaUsage.from_json(result) diff --git a/test/fixtures/object-storage_quotas.json b/test/fixtures/object-storage_quotas.json new file mode 100644 index 000000000..e831d7303 --- /dev/null +++ b/test/fixtures/object-storage_quotas.json @@ -0,0 +1,25 @@ +{ + "data": [ + { + "quota_id": "obj-objects-us-ord-1", + "quota_name": "Object Storage Maximum Objects", + "description": "Maximum number of Objects this customer is allowed to have on this endpoint.", + "endpoint_type": "E1", + "s3_endpoint": "us-iad-1.linodeobjects.com", + "quota_limit": 50, + "resource_metric": "object" + }, + { + "quota_id": "obj-bucket-us-ord-1", + "quota_name": "Object Storage Maximum Buckets", + "description": "Maximum number of buckets this customer is allowed to have on this endpoint.", + "endpoint_type": "E1", + "s3_endpoint": "us-iad-1.linodeobjects.com", + "quota_limit": 50, + "resource_metric": "bucket" + } + ], + "page": 1, + "pages": 1, + "results": 2 +} \ No newline at end of file diff --git a/test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json new file mode 100644 index 000000000..e01d743c3 --- /dev/null +++ b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json @@ -0,0 +1,9 @@ +{ + "quota_id": "obj-objects-us-ord-1", + "quota_name": "Object Storage Maximum Objects", + "description": "Maximum number of Objects this customer is allowed to have on this endpoint.", + "endpoint_type": "E1", + "s3_endpoint": "us-iad-1.linodeobjects.com", + "quota_limit": 50, + "resource_metric": "object" +} \ No newline at end of file diff --git a/test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json new file mode 100644 index 000000000..59b306044 --- /dev/null +++ b/test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json @@ -0,0 +1,4 @@ +{ + "quota_limit": 100, + "usage": 10 +} diff --git a/test/integration/models/object_storage/test_obj_quotas.py b/test/integration/models/object_storage/test_obj_quotas.py new file mode 100644 index 000000000..b1beade44 --- /dev/null +++ b/test/integration/models/object_storage/test_obj_quotas.py @@ -0,0 +1,58 @@ +from linode_api4.objects.object_storage import ( + ObjectStorageQuota, + ObjectStorageQuotaUsage, +) + + +def test_list_obj_storage_quotas(test_linode_client): + quotas = test_linode_client.object_storage.quotas() + + target_quota_id = "obj-buckets-us-sea-1.linodeobjects.com" + + found_quota = None + for quota in quotas: + if quota.quota_id == target_quota_id: + found_quota = quota + break + + assert ( + found_quota is not None + ), f"Quota with ID {target_quota_id} not found." + + assert found_quota.quota_id == "obj-buckets-us-sea-1.linodeobjects.com" + assert found_quota.quota_name == "max_buckets" + assert found_quota.endpoint_type == "E1" + assert found_quota.s3_endpoint == "us-sea-1.linodeobjects.com" + assert ( + found_quota.description + == "Maximum number of buckets this customer is allowed to have on this endpoint" + ) + assert found_quota.quota_limit == 1000 + assert found_quota.resource_metric == "bucket" + + +def test_get_obj_storage_quota(test_linode_client): + quota_id = "obj-objects-us-ord-1.linodeobjects.com" + quota = test_linode_client.load(ObjectStorageQuota, quota_id) + + assert quota.quota_id == "obj-objects-us-ord-1.linodeobjects.com" + assert quota.quota_name == "max_objects" + assert quota.endpoint_type == "E1" + assert quota.s3_endpoint == "us-ord-1.linodeobjects.com" + assert ( + quota.description + == "Maximum number of objects this customer is allowed to have on this endpoint" + ) + assert quota.quota_limit == 100000000 + assert quota.resource_metric == "object" + + +def test_get_obj_storage_quota_usage(test_linode_client): + quota_id = "obj-objects-us-ord-1.linodeobjects.com" + quota = test_linode_client.load(ObjectStorageQuota, quota_id) + + quota_usage = quota.usage() + + assert isinstance(quota_usage, ObjectStorageQuotaUsage) + assert quota_usage.quota_limit == 100000000 + assert quota_usage.usage >= 0 diff --git a/test/unit/objects/object_storage_test.py b/test/unit/objects/object_storage_test.py index 396813b3d..b7ff7e49c 100644 --- a/test/unit/objects/object_storage_test.py +++ b/test/unit/objects/object_storage_test.py @@ -6,6 +6,7 @@ ObjectStorageACL, ObjectStorageBucket, ObjectStorageCluster, + ObjectStorageQuota, ) @@ -284,3 +285,53 @@ def test_object_acl_config_update(self): "name": "example", }, ) + + def test_quota_get_and_list(self): + """ + Test that you can get and list an Object storage quota and usage information. + """ + quota = ObjectStorageQuota( + self.client, + "obj-objects-us-ord-1", + ) + + self.assertIsNotNone(quota) + self.assertEqual(quota.quota_id, "obj-objects-us-ord-1") + self.assertEqual(quota.quota_name, "Object Storage Maximum Objects") + self.assertEqual( + quota.description, + "Maximum number of Objects this customer is allowed to have on this endpoint.", + ) + self.assertEqual(quota.endpoint_type, "E1") + self.assertEqual(quota.s3_endpoint, "us-iad-1.linodeobjects.com") + self.assertEqual(quota.quota_limit, 50) + self.assertEqual(quota.resource_metric, "object") + + quota_usage_url = "/object-storage/quotas/obj-objects-us-ord-1/usage" + with self.mock_get(quota_usage_url) as m: + usage = quota.usage() + self.assertIsNotNone(usage) + self.assertEqual(m.call_url, quota_usage_url) + self.assertEqual(usage.quota_limit, 100) + self.assertEqual(usage.usage, 10) + + quota_list_url = "/object-storage/quotas" + with self.mock_get(quota_list_url) as m: + quotas = self.client.object_storage.quotas() + self.assertIsNotNone(quotas) + self.assertEqual(m.call_url, quota_list_url) + self.assertEqual(len(quotas), 2) + self.assertEqual(quotas[0].quota_id, "obj-objects-us-ord-1") + self.assertEqual( + quotas[0].quota_name, "Object Storage Maximum Objects" + ) + self.assertEqual( + quotas[0].description, + "Maximum number of Objects this customer is allowed to have on this endpoint.", + ) + self.assertEqual(quotas[0].endpoint_type, "E1") + self.assertEqual( + quotas[0].s3_endpoint, "us-iad-1.linodeobjects.com" + ) + self.assertEqual(quotas[0].quota_limit, 50) + self.assertEqual(quotas[0].resource_metric, "object") From ace528b2bed8957ffb9371e563f78459ced3f718 Mon Sep 17 00:00:00 2001 From: Youjung Kim <126618609+ykim-akamai@users.noreply.github.com> Date: Mon, 12 May 2025 10:52:43 -0700 Subject: [PATCH 3/4] Update test assertion (#546) --- test/integration/models/lke/test_lke.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/integration/models/lke/test_lke.py b/test/integration/models/lke/test_lke.py index e0a9eafb1..3486485d6 100644 --- a/test/integration/models/lke/test_lke.py +++ b/test/integration/models/lke/test_lke.py @@ -208,7 +208,10 @@ def _to_comparable(p: LKENodePool) -> Dict[str, Any]: assert _to_comparable(cluster.pools[0]) == _to_comparable(pool) - assert pool.disk_encryption == InstanceDiskEncryptionType.disabled + assert pool.disk_encryption in ( + InstanceDiskEncryptionType.enabled, + InstanceDiskEncryptionType.disabled, + ) def test_cluster_dashboard_url_view(lke_cluster): From a8fa1d7ca3bd95fc88c67391fc56992a8d1fccec Mon Sep 17 00:00:00 2001 From: Ye Chen <127243817+yec-akamai@users.noreply.github.com> Date: Mon, 12 May 2025 13:53:36 -0400 Subject: [PATCH 4/4] improve test (#547) --- .../models/object_storage/test_obj_quotas.py | 65 ++++++++----------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/test/integration/models/object_storage/test_obj_quotas.py b/test/integration/models/object_storage/test_obj_quotas.py index b1beade44..10a546bc7 100644 --- a/test/integration/models/object_storage/test_obj_quotas.py +++ b/test/integration/models/object_storage/test_obj_quotas.py @@ -1,58 +1,45 @@ +import pytest + from linode_api4.objects.object_storage import ( ObjectStorageQuota, ObjectStorageQuotaUsage, ) -def test_list_obj_storage_quotas(test_linode_client): +def test_list_and_get_obj_storage_quotas(test_linode_client): quotas = test_linode_client.object_storage.quotas() - target_quota_id = "obj-buckets-us-sea-1.linodeobjects.com" - - found_quota = None - for quota in quotas: - if quota.quota_id == target_quota_id: - found_quota = quota - break - - assert ( - found_quota is not None - ), f"Quota with ID {target_quota_id} not found." - - assert found_quota.quota_id == "obj-buckets-us-sea-1.linodeobjects.com" - assert found_quota.quota_name == "max_buckets" - assert found_quota.endpoint_type == "E1" - assert found_quota.s3_endpoint == "us-sea-1.linodeobjects.com" - assert ( - found_quota.description - == "Maximum number of buckets this customer is allowed to have on this endpoint" - ) - assert found_quota.quota_limit == 1000 - assert found_quota.resource_metric == "bucket" + if len(quotas) < 1: + pytest.skip("No available quota for testing. Skipping now...") + found_quota = quotas[0] -def test_get_obj_storage_quota(test_linode_client): - quota_id = "obj-objects-us-ord-1.linodeobjects.com" - quota = test_linode_client.load(ObjectStorageQuota, quota_id) - - assert quota.quota_id == "obj-objects-us-ord-1.linodeobjects.com" - assert quota.quota_name == "max_objects" - assert quota.endpoint_type == "E1" - assert quota.s3_endpoint == "us-ord-1.linodeobjects.com" - assert ( - quota.description - == "Maximum number of objects this customer is allowed to have on this endpoint" + get_quota = test_linode_client.load( + ObjectStorageQuota, found_quota.quota_id ) - assert quota.quota_limit == 100000000 - assert quota.resource_metric == "object" + + assert found_quota.quota_id == get_quota.quota_id + assert found_quota.quota_name == get_quota.quota_name + assert found_quota.endpoint_type == get_quota.endpoint_type + assert found_quota.s3_endpoint == get_quota.s3_endpoint + assert found_quota.description == get_quota.description + assert found_quota.quota_limit == get_quota.quota_limit + assert found_quota.resource_metric == get_quota.resource_metric def test_get_obj_storage_quota_usage(test_linode_client): - quota_id = "obj-objects-us-ord-1.linodeobjects.com" + quotas = test_linode_client.object_storage.quotas() + + if len(quotas) < 1: + pytest.skip("No available quota for testing. Skipping now...") + + quota_id = quotas[0].quota_id quota = test_linode_client.load(ObjectStorageQuota, quota_id) quota_usage = quota.usage() assert isinstance(quota_usage, ObjectStorageQuotaUsage) - assert quota_usage.quota_limit == 100000000 - assert quota_usage.usage >= 0 + assert quota_usage.quota_limit >= 0 + + if quota_usage.usage is not None: + assert quota_usage.usage >= 0