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 }} 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/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): 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..10a546bc7 --- /dev/null +++ b/test/integration/models/object_storage/test_obj_quotas.py @@ -0,0 +1,45 @@ +import pytest + +from linode_api4.objects.object_storage import ( + ObjectStorageQuota, + ObjectStorageQuotaUsage, +) + + +def test_list_and_get_obj_storage_quotas(test_linode_client): + quotas = test_linode_client.object_storage.quotas() + + if len(quotas) < 1: + pytest.skip("No available quota for testing. Skipping now...") + + found_quota = quotas[0] + + get_quota = test_linode_client.load( + ObjectStorageQuota, found_quota.quota_id + ) + + 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): + 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 >= 0 + + if quota_usage.usage is not None: + 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")