-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add: Implement organization based GitHub billing API
This API is very limited but easy to implement.
- Loading branch information
1 parent
472ec14
commit c32c192
Showing
5 changed files
with
325 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# SPDX-FileCopyrightText: 2023 Greenbone AG | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
from pontos.github.api.client import GitHubAsyncREST | ||
from pontos.github.models.billing import ( | ||
ActionsBilling, | ||
PackagesBilling, | ||
StorageBilling, | ||
) | ||
|
||
|
||
class GitHubAsyncRESTBilling(GitHubAsyncREST): | ||
async def actions(self, organization: str) -> ActionsBilling: | ||
""" | ||
Get the summary of the free and paid GitHub Actions minutes used | ||
https://docs.github.com/en/rest/billing/billing#get-github-actions-billing-for-an-organization | ||
Args: | ||
organization: The organization name | ||
Raises: | ||
HTTPStatusError: A httpx.HTTPStatusError is raised if the request | ||
failed. | ||
Returns: | ||
Information about the Actions billing | ||
Example: | ||
.. code-block:: python | ||
from pontos.github.api import GitHubAsyncRESTApi | ||
async with GitHubAsyncRESTApi(token) as api: | ||
billing = await api.billing.actions("foo") | ||
print(billing) | ||
""" | ||
api = f"/orgs/{organization}/settings/billing/actions" | ||
response = await self._client.get(api) | ||
response.raise_for_status() | ||
return ActionsBilling.from_dict(response.json()) | ||
|
||
async def packages(self, organization: str) -> PackagesBilling: | ||
""" | ||
Get the free and paid storage used for GitHub Packages in gigabytes | ||
https://docs.github.com/en/rest/billing/billing#get-github-packages-billing-for-an-organization | ||
Args: | ||
organization: The organization name | ||
Raises: | ||
HTTPStatusError: A httpx.HTTPStatusError is raised if the request | ||
failed. | ||
Returns: | ||
Information about the Packages billing | ||
Example: | ||
.. code-block:: python | ||
from pontos.github.api import GitHubAsyncRESTApi | ||
async with GitHubAsyncRESTApi(token) as api: | ||
billing = await api.billing.packages("foo") | ||
print(billing) | ||
""" | ||
api = f"/orgs/{organization}/settings/billing/packages" | ||
response = await self._client.get(api) | ||
response.raise_for_status() | ||
return PackagesBilling.from_dict(response.json()) | ||
|
||
async def storage(self, organization: str) -> StorageBilling: | ||
""" | ||
Get the estimated paid and estimated total storage used for GitHub | ||
Actions and GitHub Packages | ||
https://docs.github.com/en/rest/billing/billing#get-shared-storage-billing-for-an-organization | ||
Args: | ||
organization: The organization name | ||
Raises: | ||
HTTPStatusError: A httpx.HTTPStatusError is raised if the request | ||
failed. | ||
Returns: | ||
Information about the storage billing | ||
Example: | ||
.. code-block:: python | ||
from pontos.github.api import GitHubAsyncRESTApi | ||
async with GitHubAsyncRESTApi(token) as api: | ||
billing = await api.billing.storage("foo") | ||
print(billing) | ||
""" | ||
api = f"/orgs/{organization}/settings/billing/shared-storage" | ||
response = await self._client.get(api) | ||
response.raise_for_status() | ||
return StorageBilling.from_dict(response.json()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# SPDX-FileCopyrightText: 2023 Greenbone AG | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
from dataclasses import dataclass | ||
from typing import Optional | ||
|
||
from pontos.github.models.base import GitHubModel | ||
|
||
|
||
@dataclass | ||
class ActionsMinutesUsedBreakdown(GitHubModel): | ||
""" | ||
Attributes: | ||
UBUNTU: Total minutes used on Ubuntu runner machines | ||
MACOS: Total minutes used on macOS runner machines | ||
WINDOWS: Total minutes used on Windows runner machines | ||
total: Total minutes used on all runner machines | ||
""" | ||
|
||
UBUNTU: Optional[int] = None | ||
MACOS: Optional[int] = None | ||
WINDOWS: Optional[int] = None | ||
total: Optional[int] = None | ||
|
||
|
||
@dataclass | ||
class ActionsBilling(GitHubModel): | ||
""" | ||
Billing Information for using GitHub Actions | ||
Attributes: | ||
total_minutes_used: The sum of the free and paid GitHub Actions minutes | ||
used | ||
total_paid_minutes_used: The total paid GitHub Actions minutes used | ||
included_minutes: The amount of free GitHub Actions minutes available | ||
minutes_used_breakdown: | ||
""" | ||
|
||
total_minutes_used: int | ||
total_paid_minutes_used: int | ||
included_minutes: int | ||
minutes_used_breakdown: ActionsMinutesUsedBreakdown | ||
|
||
|
||
@dataclass | ||
class PackagesBilling(GitHubModel): | ||
""" | ||
Billing Information for using GitHub Packages | ||
Attributes: | ||
total_gigabytes_bandwidth_used: Sum of the free and paid storage space | ||
(GB) for GitHub Packages | ||
total_paid_gigabytes_bandwidth_used: Total paid storage space (GB) for | ||
GitHub Packages | ||
included_gigabytes_bandwidth: Free storage space (GB) for GitHub | ||
Packages | ||
""" | ||
|
||
total_gigabytes_bandwidth_used: int | ||
total_paid_gigabytes_bandwidth_used: int | ||
included_gigabytes_bandwidth: int | ||
|
||
|
||
@dataclass | ||
class StorageBilling(GitHubModel): | ||
""" | ||
Billing Information for using GitHub storage | ||
Attributes: | ||
days_left_in_billing_cycle: Numbers of days left in billing cycle | ||
estimated_paid_storage_for_month: Estimated storage space (GB) used in | ||
billing cycle | ||
estimated_storage_for_month: Estimated sum of free and paid storage | ||
space (GB) used in billing cycle | ||
""" | ||
|
||
days_left_in_billing_cycle: int | ||
estimated_paid_storage_for_month: int | ||
estimated_storage_for_month: int |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# SPDX-FileCopyrightText: 2023 Greenbone AG | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
# ruff: noqa:E501 | ||
|
||
from pontos.github.api.billing import GitHubAsyncRESTBilling | ||
from tests.github.api import GitHubAsyncRESTTestCase, create_response | ||
|
||
|
||
class GitHubAsyncRESTBillingTestCase(GitHubAsyncRESTTestCase): | ||
api_cls = GitHubAsyncRESTBilling | ||
|
||
async def test_actions(self): | ||
response = create_response() | ||
response.json.return_value = { | ||
"total_minutes_used": 305, | ||
"total_paid_minutes_used": 0, | ||
"included_minutes": 3000, | ||
"minutes_used_breakdown": { | ||
"UBUNTU": 205, | ||
"MACOS": 10, | ||
"WINDOWS": 90, | ||
}, | ||
} | ||
self.client.get.return_value = response | ||
|
||
billing = await self.api.actions("foo") | ||
|
||
self.client.get.assert_awaited_once_with( | ||
"/orgs/foo/settings/billing/actions", | ||
) | ||
|
||
self.assertEqual(billing.total_minutes_used, 305) | ||
|
||
async def test_packages(self): | ||
response = create_response() | ||
response.json.return_value = { | ||
"total_gigabytes_bandwidth_used": 50, | ||
"total_paid_gigabytes_bandwidth_used": 40, | ||
"included_gigabytes_bandwidth": 10, | ||
} | ||
self.client.get.return_value = response | ||
|
||
billing = await self.api.packages("foo") | ||
|
||
self.client.get.assert_awaited_once_with( | ||
"/orgs/foo/settings/billing/packages", | ||
) | ||
|
||
self.assertEqual(billing.total_gigabytes_bandwidth_used, 50) | ||
|
||
async def test_storage(self): | ||
response = create_response() | ||
response.json.return_value = { | ||
"days_left_in_billing_cycle": 20, | ||
"estimated_paid_storage_for_month": 15, | ||
"estimated_storage_for_month": 40, | ||
} | ||
self.client.get.return_value = response | ||
|
||
billing = await self.api.storage("foo") | ||
|
||
self.client.get.assert_awaited_once_with( | ||
"/orgs/foo/settings/billing/shared-storage", | ||
) | ||
|
||
self.assertEqual(billing.days_left_in_billing_cycle, 20) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# SPDX-FileCopyrightText: 2023 Greenbone AG | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
# ruff: noqa:E501 | ||
|
||
import unittest | ||
|
||
from pontos.github.models.billing import ( | ||
ActionsBilling, | ||
PackagesBilling, | ||
StorageBilling, | ||
) | ||
|
||
|
||
class ActionsBillingTestCase(unittest.TestCase): | ||
def test_from_dict(self): | ||
billing = ActionsBilling.from_dict( | ||
{ | ||
"total_minutes_used": 305, | ||
"total_paid_minutes_used": 0, | ||
"included_minutes": 3000, | ||
"minutes_used_breakdown": { | ||
"UBUNTU": 205, | ||
"MACOS": 10, | ||
"WINDOWS": 90, | ||
}, | ||
} | ||
) | ||
|
||
self.assertEqual(billing.total_minutes_used, 305) | ||
self.assertEqual(billing.total_paid_minutes_used, 0) | ||
self.assertEqual(billing.included_minutes, 3000) | ||
self.assertEqual(billing.minutes_used_breakdown.UBUNTU, 205) | ||
self.assertEqual(billing.minutes_used_breakdown.MACOS, 10) | ||
self.assertEqual(billing.minutes_used_breakdown.WINDOWS, 90) | ||
self.assertIsNone(billing.minutes_used_breakdown.total) | ||
|
||
|
||
class PackagesBillingTestCase(unittest.TestCase): | ||
def test_from_dict(self): | ||
billing = PackagesBilling.from_dict( | ||
{ | ||
"total_gigabytes_bandwidth_used": 50, | ||
"total_paid_gigabytes_bandwidth_used": 40, | ||
"included_gigabytes_bandwidth": 10, | ||
} | ||
) | ||
|
||
self.assertEqual(billing.total_gigabytes_bandwidth_used, 50) | ||
self.assertEqual(billing.total_paid_gigabytes_bandwidth_used, 40) | ||
self.assertEqual(billing.included_gigabytes_bandwidth, 10) | ||
|
||
|
||
class StorageBillingTestCase(unittest.TestCase): | ||
def test_from_dict(self): | ||
billing = StorageBilling.from_dict( | ||
{ | ||
"days_left_in_billing_cycle": 20, | ||
"estimated_paid_storage_for_month": 15, | ||
"estimated_storage_for_month": 40, | ||
} | ||
) | ||
self.assertEqual(billing.days_left_in_billing_cycle, 20) | ||
self.assertEqual(billing.estimated_paid_storage_for_month, 15) | ||
self.assertEqual(billing.estimated_storage_for_month, 40) |