cgcsdk 1.2.5__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cgcsdk-1.2.5/cgcsdk.egg-info → cgcsdk-1.3.0}/PKG-INFO +76 -76
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/.env +2 -2
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/CHANGELOG.md +20 -0
- cgcsdk-1.3.0/cgc/commands/compute/billing/billing_cmd.py +118 -0
- cgcsdk-1.3.0/cgc/commands/compute/billing/billing_responses.py +65 -0
- cgcsdk-1.3.0/cgc/commands/compute/billing/billing_utils.py +241 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_responses.py +18 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_utils.py +12 -2
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/debug/debug_cmd.py +18 -18
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/resource/resource_cmd.py +58 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_responses.py +1 -1
- cgcsdk-1.3.0/cgc/tests/test.py +110 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/click_group.py +13 -0
- cgcsdk-1.3.0/cgc/utils/custom_exceptions.py +59 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/message_utils.py +4 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/prepare_headers.py +3 -3
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/requests_helper.py +4 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/response_utils.py +10 -3
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/version_control.py +1 -1
- {cgcsdk-1.2.5 → cgcsdk-1.3.0/cgcsdk.egg-info}/PKG-INFO +76 -76
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/SOURCES.txt +1 -1
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/setup.cfg +4 -4
- cgcsdk-1.2.5/cgc/commands/compute/billing/billing_cmd.py +0 -159
- cgcsdk-1.2.5/cgc/commands/compute/billing/billing_responses.py +0 -55
- cgcsdk-1.2.5/cgc/commands/compute/billing/billing_utils.py +0 -163
- cgcsdk-1.2.5/cgc/server.crt +0 -24
- cgcsdk-1.2.5/cgc/utils/custom_exceptions.py +0 -47
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/LICENSE +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/MANIFEST.in +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/README.md +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/cgc.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_logic.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_responses.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_utils.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_cmd_responses.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_helpers.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_models.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/billing/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_models.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/db/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/db/db_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/db/db_models.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/debug/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/exceptions.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/job_utils.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/jobs_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/jobs_responses.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/resource/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/resource/resource_responses.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_models.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_responses.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_utils.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_models.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_responses.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_utils.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/data_model.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_cmd.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_models.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_utils.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/config.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/exceptions.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/job.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/postgresql.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/resource.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/telemetry/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/telemetry/basic.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_invoice.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_status.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_stop_events_compute.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_stop_events_volume.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_compute_list.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_compute_list_no_labels.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_tabulate_response.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_volume_list.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/responses_tests.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/config_utils.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/consts/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/consts/env_consts.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/consts/message_consts.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/__init__.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/aes_crypto.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/encryption_module.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/rsa_crypto.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/get_headers_data.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/update.py +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/dependency_links.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/entry_points.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/requires.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/top_level.txt +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/pyproject.toml +0 -0
- {cgcsdk-1.2.5 → cgcsdk-1.3.0}/setup.py +0 -0
@@ -1,76 +1,76 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: cgcsdk
|
3
|
-
Version: 1.
|
4
|
-
Summary: CGC Core REST API client
|
5
|
-
Home-page: https://cgc.comtegra.cloud/
|
6
|
-
Author: Comtegra AI Team
|
7
|
-
Author-email: ai@comtegra.pl
|
8
|
-
License: BSD 2-clause
|
9
|
-
Project-URL: Documentation, https://docs.cgc.comtegra.cloud/
|
10
|
-
Project-URL: GitHub, https://git.comtegra.pl/k8s/cgc-client-k8s-cloud
|
11
|
-
Project-URL: Changelog, https://git.comtegra.pl/k8s/cgc-client-k8s-cloud/-/blob/main/cgc/CHANGELOG.md
|
12
|
-
Keywords: cloud,sdk,orchestrator,kubernetes,jupyter-notebook,cgc-core
|
13
|
-
Classifier: Development Status :: 5 - Production/Stable
|
14
|
-
Classifier: Intended Audience :: Science/Research
|
15
|
-
Classifier: License :: OSI Approved :: BSD License
|
16
|
-
Classifier: Operating System :: POSIX :: Linux
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
20
|
-
Description-Content-Type: text/markdown
|
21
|
-
License-File: LICENSE
|
22
|
-
Requires-Dist: click
|
23
|
-
Requires-Dist: python-dotenv
|
24
|
-
Requires-Dist: tabulate
|
25
|
-
Requires-Dist: pycryptodomex
|
26
|
-
Requires-Dist: paramiko>=2.11
|
27
|
-
Requires-Dist: statsd
|
28
|
-
Requires-Dist: requests
|
29
|
-
Requires-Dist: setuptools
|
30
|
-
Requires-Dist: colorama
|
31
|
-
Requires-Dist: psycopg2-binary
|
32
|
-
Dynamic: author
|
33
|
-
Dynamic: author-email
|
34
|
-
Dynamic: classifier
|
35
|
-
Dynamic: description
|
36
|
-
Dynamic: description-content-type
|
37
|
-
Dynamic: home-page
|
38
|
-
Dynamic: keywords
|
39
|
-
Dynamic: license
|
40
|
-
Dynamic: license-file
|
41
|
-
Dynamic: project-url
|
42
|
-
Dynamic: requires-dist
|
43
|
-
Dynamic: summary
|
44
|
-
|
45
|
-
# Comtegra GPU Cloud CLI Client
|
46
|
-
|
47
|
-
## Basic info
|
48
|
-
|
49
|
-
CGC Clinet is complete solution to create and manage your compute resources through CLI interface and python code. It incorporates CLI and SDK in one package.
|
50
|
-
|
51
|
-
CGC CLI is a command line interface for Comtegra GPU Cloud. CGC CLI enables management of your Comtegra GPU Cloud resources. Current version of the app provides support for compute, storage and network resurces to be created, listed and deleted. Every compute resource is given to you as an URL, which is accessible from open Internet.
|
52
|
-
|
53
|
-
To enable better access to your storage resources, every account has the ability to spawn free of charge filebrowser which is local implementation of dropbox. Remember to mount newely created volumes to it.
|
54
|
-
|
55
|
-
For now, we provide the ability to spawn compute resources like:
|
56
|
-
|
57
|
-
1. [Jupyter notebook](https://jupyter.org/) with tensorflow or pytorch installed as default
|
58
|
-
2. [Triton inferencing server](https://docs.nvidia.com/deeplearning/triton-inference-server/) for large scale inferencing
|
59
|
-
3. [Label studio](https://labelstud.io/) for easy management of your data annotation tasks with variety of modes
|
60
|
-
4. [Rapids](https://rapids.ai/) suite of accelerated libraries for data processing
|
61
|
-
|
62
|
-
Notebooks are equiped with all CUDA libraries and GPU drivers which enables the usage of GPU for accelerated computations.
|
63
|
-
Apart from compute resources, we provide the database engines accessible from within your namespace:
|
64
|
-
|
65
|
-
1. [PostgreSQL](https://www.postgresql.org/)
|
66
|
-
2. [Weaviate](https://weaviate.io/)
|
67
|
-
|
68
|
-
More are coming!
|
69
|
-
Please follow instructions to get started.
|
70
|
-
|
71
|
-
## More info
|
72
|
-
|
73
|
-
If you'd like to know more visit:
|
74
|
-
|
75
|
-
- [Comtegra GPU Website](https://cgc.comtegra.cloud)
|
76
|
-
- [Docs](https://docs.cgc.comtegra.cloud)
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: cgcsdk
|
3
|
+
Version: 1.3.0
|
4
|
+
Summary: CGC Core REST API client
|
5
|
+
Home-page: https://cgc.comtegra.cloud/
|
6
|
+
Author: Comtegra AI Team
|
7
|
+
Author-email: ai@comtegra.pl
|
8
|
+
License: BSD 2-clause
|
9
|
+
Project-URL: Documentation, https://docs.cgc.comtegra.cloud/
|
10
|
+
Project-URL: GitHub, https://git.comtegra.pl/k8s/cgc-client-k8s-cloud
|
11
|
+
Project-URL: Changelog, https://git.comtegra.pl/k8s/cgc-client-k8s-cloud/-/blob/main/cgc/CHANGELOG.md
|
12
|
+
Keywords: cloud,sdk,orchestrator,kubernetes,jupyter-notebook,cgc-core
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Description-Content-Type: text/markdown
|
21
|
+
License-File: LICENSE
|
22
|
+
Requires-Dist: click
|
23
|
+
Requires-Dist: python-dotenv
|
24
|
+
Requires-Dist: tabulate
|
25
|
+
Requires-Dist: pycryptodomex
|
26
|
+
Requires-Dist: paramiko>=2.11
|
27
|
+
Requires-Dist: statsd
|
28
|
+
Requires-Dist: requests
|
29
|
+
Requires-Dist: setuptools
|
30
|
+
Requires-Dist: colorama
|
31
|
+
Requires-Dist: psycopg2-binary
|
32
|
+
Dynamic: author
|
33
|
+
Dynamic: author-email
|
34
|
+
Dynamic: classifier
|
35
|
+
Dynamic: description
|
36
|
+
Dynamic: description-content-type
|
37
|
+
Dynamic: home-page
|
38
|
+
Dynamic: keywords
|
39
|
+
Dynamic: license
|
40
|
+
Dynamic: license-file
|
41
|
+
Dynamic: project-url
|
42
|
+
Dynamic: requires-dist
|
43
|
+
Dynamic: summary
|
44
|
+
|
45
|
+
# Comtegra GPU Cloud CLI Client
|
46
|
+
|
47
|
+
## Basic info
|
48
|
+
|
49
|
+
CGC Clinet is complete solution to create and manage your compute resources through CLI interface and python code. It incorporates CLI and SDK in one package.
|
50
|
+
|
51
|
+
CGC CLI is a command line interface for Comtegra GPU Cloud. CGC CLI enables management of your Comtegra GPU Cloud resources. Current version of the app provides support for compute, storage and network resurces to be created, listed and deleted. Every compute resource is given to you as an URL, which is accessible from open Internet.
|
52
|
+
|
53
|
+
To enable better access to your storage resources, every account has the ability to spawn free of charge filebrowser which is local implementation of dropbox. Remember to mount newely created volumes to it.
|
54
|
+
|
55
|
+
For now, we provide the ability to spawn compute resources like:
|
56
|
+
|
57
|
+
1. [Jupyter notebook](https://jupyter.org/) with tensorflow or pytorch installed as default
|
58
|
+
2. [Triton inferencing server](https://docs.nvidia.com/deeplearning/triton-inference-server/) for large scale inferencing
|
59
|
+
3. [Label studio](https://labelstud.io/) for easy management of your data annotation tasks with variety of modes
|
60
|
+
4. [Rapids](https://rapids.ai/) suite of accelerated libraries for data processing
|
61
|
+
|
62
|
+
Notebooks are equiped with all CUDA libraries and GPU drivers which enables the usage of GPU for accelerated computations.
|
63
|
+
Apart from compute resources, we provide the database engines accessible from within your namespace:
|
64
|
+
|
65
|
+
1. [PostgreSQL](https://www.postgresql.org/)
|
66
|
+
2. [Weaviate](https://weaviate.io/)
|
67
|
+
|
68
|
+
More are coming!
|
69
|
+
Please follow instructions to get started.
|
70
|
+
|
71
|
+
## More info
|
72
|
+
|
73
|
+
If you'd like to know more visit:
|
74
|
+
|
75
|
+
- [Comtegra GPU Website](https://cgc.comtegra.cloud)
|
76
|
+
- [Docs](https://docs.cgc.comtegra.cloud)
|
@@ -1,5 +1,25 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 1.3.0
|
4
|
+
|
5
|
+
Release on July 08, 2025
|
6
|
+
|
7
|
+
* stop events are not longer supported
|
8
|
+
* billing endpoints v1 are not longer supported
|
9
|
+
* added support for v2 billing endpoints
|
10
|
+
* added `cgc billing pricing` command
|
11
|
+
* error messages are now more descriptive
|
12
|
+
|
13
|
+
## 1.2.6
|
14
|
+
|
15
|
+
Release on Apr 30, 2025
|
16
|
+
|
17
|
+
* `cgc resource scale up` scales up the resource replicas, with the given name
|
18
|
+
* `cgc resource scale down` scales down the resource replicas, with the given name
|
19
|
+
* modified error message displayed - uses custom_exceptions
|
20
|
+
* listing of `TurnedOff` resources is now available
|
21
|
+
* `TurnedOn` available if the resource is not running, but it is scaled up
|
22
|
+
|
3
23
|
## 1.2.5
|
4
24
|
|
5
25
|
Release on Apr 16, 2025
|
@@ -0,0 +1,118 @@
|
|
1
|
+
import click
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
|
5
|
+
from cgc.commands.compute.billing.billing_responses import (
|
6
|
+
billing_pricing_response,
|
7
|
+
billing_status_response,
|
8
|
+
billing_invoice_response,
|
9
|
+
)
|
10
|
+
from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
|
11
|
+
from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
|
12
|
+
from cgc.utils.click_group import CustomGroup, CustomCommand
|
13
|
+
from cgc.utils.requests_helper import call_api, EndpointTypes
|
14
|
+
|
15
|
+
|
16
|
+
@click.group("billing", cls=CustomGroup)
|
17
|
+
def billing_group():
|
18
|
+
"""
|
19
|
+
Access and manage billing information.
|
20
|
+
"""
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
@billing_group.command("status", cls=CustomCommand)
|
25
|
+
@click.option(
|
26
|
+
"--detailed",
|
27
|
+
"-d",
|
28
|
+
"detailed",
|
29
|
+
prompt=True,
|
30
|
+
type=bool,
|
31
|
+
default=False,
|
32
|
+
help="If true, returns detailed invoice information",
|
33
|
+
is_flag=True,
|
34
|
+
)
|
35
|
+
def billing_status(detailed: bool):
|
36
|
+
"""
|
37
|
+
Shows billing status for user namespace
|
38
|
+
"""
|
39
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
40
|
+
url = f"{api_url}/v2/api/billing/status?details={detailed}"
|
41
|
+
metric = "billing.status"
|
42
|
+
__res = call_api(request=EndpointTypes.get, url=url, headers=headers)
|
43
|
+
click.echo(
|
44
|
+
billing_status_response(
|
45
|
+
retrieve_and_validate_response_send_metric(__res, metric)
|
46
|
+
)
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
def _get_previous_month():
|
51
|
+
return datetime.now().month - 1 if datetime.now().month > 1 else 12
|
52
|
+
|
53
|
+
|
54
|
+
def _get_previous_year_if_required():
|
55
|
+
return datetime.now().year - 1 if datetime.now().month == 1 else datetime.now().year
|
56
|
+
|
57
|
+
|
58
|
+
@billing_group.command("invoice", cls=CustomCommand)
|
59
|
+
@click.option(
|
60
|
+
"--year",
|
61
|
+
"-y",
|
62
|
+
"year",
|
63
|
+
prompt=True,
|
64
|
+
type=int,
|
65
|
+
default=_get_previous_year_if_required(),
|
66
|
+
)
|
67
|
+
@click.option(
|
68
|
+
"--month",
|
69
|
+
"-m",
|
70
|
+
"month",
|
71
|
+
prompt=True,
|
72
|
+
type=click.IntRange(1, 12),
|
73
|
+
default=_get_previous_month(),
|
74
|
+
)
|
75
|
+
@click.option(
|
76
|
+
"--detailed",
|
77
|
+
"-d",
|
78
|
+
"detailed",
|
79
|
+
prompt=True,
|
80
|
+
type=bool,
|
81
|
+
default=False,
|
82
|
+
help="If true, returns detailed invoice information",
|
83
|
+
is_flag=True,
|
84
|
+
)
|
85
|
+
def billing_invoice(year: int, month: int, detailed: bool):
|
86
|
+
"""
|
87
|
+
Opens invoice from given year and month
|
88
|
+
"""
|
89
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
90
|
+
url = (
|
91
|
+
f"{api_url}/v2/api/billing/invoice?year={year}&month={month}&details={detailed}"
|
92
|
+
)
|
93
|
+
metric = "billing.invoice"
|
94
|
+
__res = call_api(request=EndpointTypes.get, url=url, headers=headers)
|
95
|
+
|
96
|
+
click.echo(
|
97
|
+
billing_invoice_response(
|
98
|
+
year,
|
99
|
+
month,
|
100
|
+
retrieve_and_validate_response_send_metric(__res, metric),
|
101
|
+
)
|
102
|
+
)
|
103
|
+
|
104
|
+
|
105
|
+
@billing_group.command("pricing", cls=CustomCommand)
|
106
|
+
def billing_pricing():
|
107
|
+
"""
|
108
|
+
Shows billing pricing information for user
|
109
|
+
"""
|
110
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
111
|
+
url = f"{api_url}/v2/api/billing/user_pricing"
|
112
|
+
metric = "billing.pricing"
|
113
|
+
__res = call_api(request=EndpointTypes.get, url=url, headers=headers)
|
114
|
+
click.echo(
|
115
|
+
billing_pricing_response(
|
116
|
+
retrieve_and_validate_response_send_metric(__res, metric)
|
117
|
+
)
|
118
|
+
)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import calendar
|
2
|
+
import click
|
3
|
+
from decimal import Decimal
|
4
|
+
from tabulate import tabulate
|
5
|
+
from cgc.commands.compute.billing import (
|
6
|
+
NoCostsFound,
|
7
|
+
NoInvoiceFoundForSelectedMonth,
|
8
|
+
)
|
9
|
+
from cgc.commands.compute.billing.billing_utils import get_billing_status_message
|
10
|
+
from cgc.utils.message_utils import key_error_decorator_for_helpers
|
11
|
+
|
12
|
+
|
13
|
+
@key_error_decorator_for_helpers
|
14
|
+
def billing_status_response(data: dict) -> str:
|
15
|
+
total_cost = data["details"]["cost_total"]
|
16
|
+
namespace = data["details"]["namespace"]
|
17
|
+
billing_records = data["details"]["billing_records"]
|
18
|
+
details = data["details"].get("details", [])
|
19
|
+
if not billing_records:
|
20
|
+
raise NoCostsFound()
|
21
|
+
message = get_billing_status_message(billing_records, details)
|
22
|
+
message += f"Total cost for namespace {namespace}: {total_cost:.2f} pln"
|
23
|
+
return message
|
24
|
+
|
25
|
+
|
26
|
+
@key_error_decorator_for_helpers
|
27
|
+
def billing_invoice_response(year: int, month: int, data: dict) -> str:
|
28
|
+
total_cost = float(data["details"]["cost_total"])
|
29
|
+
namespace = data["details"]["namespace"]
|
30
|
+
billing_records = data["details"]["billing_records"]
|
31
|
+
details = data["details"].get("details", [])
|
32
|
+
if (
|
33
|
+
not billing_records or total_cost == 0
|
34
|
+
): # TODO: total_cost == 0 is it correct thinking?
|
35
|
+
raise NoInvoiceFoundForSelectedMonth(year, month)
|
36
|
+
message = get_billing_status_message(billing_records, details)
|
37
|
+
message += f"Total cost for namespace {namespace} in {calendar.month_name[month]} {year}: {total_cost:.2f} pln"
|
38
|
+
return message
|
39
|
+
|
40
|
+
|
41
|
+
@key_error_decorator_for_helpers
|
42
|
+
def billing_pricing_response(data: dict) -> str:
|
43
|
+
"""Create response string for billing pricing command.
|
44
|
+
|
45
|
+
:return: Response string.
|
46
|
+
:rtype: str
|
47
|
+
"""
|
48
|
+
pricing_details = data["details"]["pricing_details"]
|
49
|
+
if not pricing_details:
|
50
|
+
return "No pricing details available."
|
51
|
+
if pricing_details.get("tier"):
|
52
|
+
tier = pricing_details["tier"] or "DEFAULT"
|
53
|
+
click.echo(f"Current pricing tier: {tier}")
|
54
|
+
if not pricing_details.get("resources"):
|
55
|
+
return "No resources costs available."
|
56
|
+
headers = ["Resource", "Price per unit (pln) / (second OR token)"]
|
57
|
+
pricing_data = [
|
58
|
+
(resource, f"{Decimal(str(price)):.7f}")
|
59
|
+
for resource, price in pricing_details.get("resources").items()
|
60
|
+
]
|
61
|
+
click.echo(
|
62
|
+
"Pricing values displayed are approximated due to float representation. For exact values, refer to the billing system dashboard."
|
63
|
+
)
|
64
|
+
|
65
|
+
return tabulate(pricing_data, headers=headers)
|
@@ -0,0 +1,241 @@
|
|
1
|
+
import click
|
2
|
+
import datetime
|
3
|
+
import sys
|
4
|
+
from tabulate import tabulate
|
5
|
+
from cgc.utils.message_utils import (
|
6
|
+
prepare_error_message,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
def verify_input_datetime(*args):
|
11
|
+
try:
|
12
|
+
for arg in args:
|
13
|
+
datetime.datetime.strptime(arg, "%d-%m-%Y")
|
14
|
+
except ValueError:
|
15
|
+
click.echo(prepare_error_message("Incorrect date format, should be DD-MM-YYYY"))
|
16
|
+
sys.exit()
|
17
|
+
|
18
|
+
|
19
|
+
def _get_costs_list_for_user(costs_list: list):
|
20
|
+
"""Format data in costs list to be displayed in a table and calculate user cost
|
21
|
+
|
22
|
+
:param costs_list: list of costs for user
|
23
|
+
:type costs_list: list
|
24
|
+
:return: formatted list of costs and total cost for user
|
25
|
+
:rtype: user_costs_list_to_print: list, total_user_cost: float
|
26
|
+
"""
|
27
|
+
user_costs_list_to_print = []
|
28
|
+
total_user_cost = 0
|
29
|
+
|
30
|
+
for cost in costs_list:
|
31
|
+
if "resource_cost" in cost:
|
32
|
+
if cost.get("type", "") == "oneoff":
|
33
|
+
for resource, value in cost["resource_cost"].items():
|
34
|
+
user_costs_list_to_print.append(
|
35
|
+
[
|
36
|
+
cost.get("type", ""),
|
37
|
+
cost.get("namespace", ""),
|
38
|
+
f"{resource}: {float(value):.2f} pln",
|
39
|
+
"-",
|
40
|
+
"-",
|
41
|
+
"<<-",
|
42
|
+
]
|
43
|
+
)
|
44
|
+
total_user_cost += float(cost.get("cost_total", 0))
|
45
|
+
else:
|
46
|
+
resource_cost_str = ", ".join(
|
47
|
+
f"{k}: {float(v):.2f}" for k, v in cost["resource_cost"].items()
|
48
|
+
)
|
49
|
+
start, end = "-", "-"
|
50
|
+
if "datetime_range" in cost and len(cost["datetime_range"]) == 2:
|
51
|
+
start, end = cost["datetime_range"]
|
52
|
+
user_costs_list_to_print.append(
|
53
|
+
[
|
54
|
+
cost.get("type", ""),
|
55
|
+
cost.get("namespace", ""),
|
56
|
+
resource_cost_str,
|
57
|
+
start,
|
58
|
+
end,
|
59
|
+
f"{float(cost.get('cost_total', 0)):.2f} pln",
|
60
|
+
]
|
61
|
+
)
|
62
|
+
total_user_cost += float(cost.get("cost_total", 0))
|
63
|
+
|
64
|
+
if costs_list:
|
65
|
+
user_costs_list_to_print.sort(key=lambda d: f"{d[0]} {d[1]}")
|
66
|
+
return user_costs_list_to_print, total_user_cost
|
67
|
+
|
68
|
+
|
69
|
+
def _get_costs_list_for_user_with_details(costs_list: list):
|
70
|
+
"""Format data in costs list to be displayed in a table and calculate user cost with details
|
71
|
+
|
72
|
+
:param costs_list: list of costs for user
|
73
|
+
:type costs_list: list
|
74
|
+
:return: formatted list of costs and total cost for user
|
75
|
+
:rtype: user_costs_list_to_print: list, total_user_cost: float
|
76
|
+
"""
|
77
|
+
user_costs_list_to_print = []
|
78
|
+
total_user_cost = 0
|
79
|
+
|
80
|
+
for cost in costs_list:
|
81
|
+
# Support both old and new structure
|
82
|
+
record = cost.get("record", {})
|
83
|
+
name = record.get("name", cost.get("name", ""))
|
84
|
+
# id_ = record.get("id", cost.get("id", ""))
|
85
|
+
user_id = record.get("user_id", cost.get("user_id", ""))
|
86
|
+
namespace = record.get("namespace", cost.get("namespace", ""))
|
87
|
+
type_ = record.get("type", cost.get("type", ""))
|
88
|
+
resource_cost = cost.get("resource_cost", {})
|
89
|
+
if type_ == "oneoff":
|
90
|
+
for resource, value in resource_cost.items():
|
91
|
+
user_costs_list_to_print.append(
|
92
|
+
[
|
93
|
+
name,
|
94
|
+
user_id,
|
95
|
+
namespace,
|
96
|
+
type_,
|
97
|
+
f"{resource}: {float(value):.2f} pln",
|
98
|
+
"-",
|
99
|
+
"-",
|
100
|
+
"<<-",
|
101
|
+
]
|
102
|
+
)
|
103
|
+
total_user_cost += float(cost.get("cost_total", 0))
|
104
|
+
else:
|
105
|
+
resource_cost_str = ", ".join(
|
106
|
+
f"{k}: {float(v):.2f}" for k, v in resource_cost.items()
|
107
|
+
)
|
108
|
+
start = cost.get("calculation_start_time", "-")
|
109
|
+
end = cost.get("calculation_end_time", "-")
|
110
|
+
cost_total = cost.get("cost_total", 0)
|
111
|
+
|
112
|
+
user_costs_list_to_print.append(
|
113
|
+
[
|
114
|
+
name,
|
115
|
+
# id_,
|
116
|
+
user_id,
|
117
|
+
namespace,
|
118
|
+
type_,
|
119
|
+
resource_cost_str,
|
120
|
+
start,
|
121
|
+
end,
|
122
|
+
f"{float(cost_total):.2f} pln",
|
123
|
+
]
|
124
|
+
)
|
125
|
+
total_user_cost += float(cost_total)
|
126
|
+
|
127
|
+
if costs_list:
|
128
|
+
user_costs_list_to_print.sort(key=lambda d: f"{d[0]} {d[1]}")
|
129
|
+
return user_costs_list_to_print, total_user_cost
|
130
|
+
|
131
|
+
|
132
|
+
def get_billing_status_message(billing_records: list, details: list = []):
|
133
|
+
"""Prints billing status for all users in a pretty table
|
134
|
+
|
135
|
+
:param user_list: list of users with costs
|
136
|
+
:type user_list: list
|
137
|
+
"""
|
138
|
+
message = ""
|
139
|
+
users = set(
|
140
|
+
record.get("user_id", "") for record in billing_records
|
141
|
+
) # Get unique user IDs
|
142
|
+
if not users:
|
143
|
+
return "No billing records found."
|
144
|
+
for user in users:
|
145
|
+
user_records = [
|
146
|
+
record for record in billing_records if record["user_id"] == user
|
147
|
+
]
|
148
|
+
costs_list_to_print, _user_cost = _get_costs_list_for_user(user_records)
|
149
|
+
list_headers = _get_status_list_headers()
|
150
|
+
message += f"Billing status for user: {user}\n"
|
151
|
+
message += tabulate(costs_list_to_print, headers=list_headers)
|
152
|
+
message += f"\n\nSummary user cost: {float(_user_cost):.2f} pln\n\n"
|
153
|
+
if details:
|
154
|
+
message += "Detailed billing records:\n"
|
155
|
+
costs_list_to_print, _ = _get_costs_list_for_user_with_details(details)
|
156
|
+
if not costs_list_to_print:
|
157
|
+
message += "No detailed billing records found.\n"
|
158
|
+
else:
|
159
|
+
list_headers = _get_billing_details_list_headers()
|
160
|
+
message += tabulate(costs_list_to_print, headers=list_headers)
|
161
|
+
message += "\n\n"
|
162
|
+
return message
|
163
|
+
|
164
|
+
|
165
|
+
def _get_status_list_headers():
|
166
|
+
"""Generates headers for billing status command
|
167
|
+
|
168
|
+
:return: list of headers
|
169
|
+
:rtype: list
|
170
|
+
"""
|
171
|
+
return ["type", "namespace", "resource breakdown", "start", "end", "cost"]
|
172
|
+
|
173
|
+
|
174
|
+
def _get_billing_details_list_headers():
|
175
|
+
"""Generates headers for billing details command
|
176
|
+
|
177
|
+
:return: list of headers
|
178
|
+
:rtype: list
|
179
|
+
"""
|
180
|
+
return [
|
181
|
+
"name",
|
182
|
+
# "id",
|
183
|
+
"user_id",
|
184
|
+
"namespace",
|
185
|
+
"type",
|
186
|
+
"resource breakdown",
|
187
|
+
"start",
|
188
|
+
"end",
|
189
|
+
"cost_total",
|
190
|
+
]
|
191
|
+
|
192
|
+
|
193
|
+
# TODO: refactor to use: tabulate_a_response(data: list) -> str:
|
194
|
+
def get_table_compute_stop_events_message(event_list: list):
|
195
|
+
"""Prints compute stop events info
|
196
|
+
|
197
|
+
:param event_list: raw list of events
|
198
|
+
:type event_list: list
|
199
|
+
"""
|
200
|
+
message = "Compute stop events:"
|
201
|
+
event_list_headers = ["id", "name", "entity", "date created"]
|
202
|
+
event_list_to_print = []
|
203
|
+
for event in event_list:
|
204
|
+
event_id = event["event_id"]
|
205
|
+
event_name = event["event_name"]
|
206
|
+
event_date = event["date_created"]
|
207
|
+
event_entity = event["entity"]
|
208
|
+
row_list = [event_id, event_name, event_entity, event_date]
|
209
|
+
event_list_to_print.append(row_list)
|
210
|
+
message += tabulate(event_list_to_print, headers=event_list_headers)
|
211
|
+
return message
|
212
|
+
|
213
|
+
|
214
|
+
# TODO: refactor to use: tabulate_a_response(data: list) -> str:
|
215
|
+
def get_table_volume_stop_events_message(event_list: list):
|
216
|
+
"""Prints volume stop events info
|
217
|
+
|
218
|
+
:param event_list: raw list of events
|
219
|
+
:type event_list: list
|
220
|
+
"""
|
221
|
+
message = "Volume stop events:"
|
222
|
+
event_list_headers = [
|
223
|
+
"id",
|
224
|
+
"name",
|
225
|
+
"disks type",
|
226
|
+
"access type",
|
227
|
+
"size",
|
228
|
+
"date created",
|
229
|
+
]
|
230
|
+
event_list_to_print = []
|
231
|
+
for event in event_list:
|
232
|
+
event_id = event["event_id"]
|
233
|
+
volume_name = event["volume_name"]
|
234
|
+
event_date = event["date_created"]
|
235
|
+
disks_type = event["disks_type"]
|
236
|
+
access_type = event["access_type"]
|
237
|
+
size = event["size"]
|
238
|
+
row_list = [event_id, volume_name, disks_type, access_type, size, event_date]
|
239
|
+
event_list_to_print.append(row_list)
|
240
|
+
message += tabulate(event_list_to_print, headers=event_list_headers)
|
241
|
+
return message
|
@@ -62,6 +62,10 @@ def compute_list_response(detailed: bool, data: dict) -> str:
|
|
62
62
|
pod_list = data["details"]["pods_list"]
|
63
63
|
setup_gauge(f"{get_namespace()}.compute.count", len(pod_list))
|
64
64
|
|
65
|
+
# disabled resources pod list
|
66
|
+
other_pods_list = data["details"].get("other_pods_list", [])
|
67
|
+
pod_list.extend(other_pods_list)
|
68
|
+
|
65
69
|
if not pod_list:
|
66
70
|
raise NoAppsToList()
|
67
71
|
|
@@ -156,6 +160,20 @@ def compute_delete_response(data: dict) -> str:
|
|
156
160
|
return f"App {name} and its service successfully deleted."
|
157
161
|
|
158
162
|
|
163
|
+
@key_error_decorator_for_helpers
|
164
|
+
def compute_scale_response(data: dict) -> str:
|
165
|
+
"""Create response string for compute scale command.
|
166
|
+
|
167
|
+
:param response: dict object from API response.
|
168
|
+
:type response: requests.Response
|
169
|
+
:return: Response string.
|
170
|
+
:rtype: str
|
171
|
+
"""
|
172
|
+
name = data["details"]["template_name"]
|
173
|
+
replicas = data["details"]["replicas"]
|
174
|
+
return f"App {name} has been successfully scaled to {replicas} replicas."
|
175
|
+
|
176
|
+
|
159
177
|
@key_error_decorator_for_helpers
|
160
178
|
def compute_restart_response(data: dict) -> str:
|
161
179
|
"""Create response string for compute restart command.
|
@@ -83,6 +83,13 @@ def get_app_list(pod_list: list, detailed: bool) -> list:
|
|
83
83
|
"""
|
84
84
|
output_data = []
|
85
85
|
|
86
|
+
def update_output_data_count(pod_name):
|
87
|
+
for pod in output_data:
|
88
|
+
if pod["name"] == pod_name:
|
89
|
+
pod["count"] += 1
|
90
|
+
return True
|
91
|
+
return False
|
92
|
+
|
86
93
|
for pod in pod_list:
|
87
94
|
try:
|
88
95
|
main_container_name = pod["labels"]["entity"]
|
@@ -94,7 +101,7 @@ def get_app_list(pod_list: list, detailed: bool) -> list:
|
|
94
101
|
raise Exception(
|
95
102
|
"Parser was unable to find main container in server output in container list"
|
96
103
|
)
|
97
|
-
volumes_mounted = list_get_mounted_volumes(main_container
|
104
|
+
volumes_mounted = list_get_mounted_volumes(main_container.get("mounts", []))
|
98
105
|
limits = main_container["resources"].get("limits")
|
99
106
|
cpu = limits.get("cpu") if limits is not None else 0
|
100
107
|
ram = limits.get("memory") if limits is not None else "0Gi"
|
@@ -106,6 +113,7 @@ def get_app_list(pod_list: list, detailed: bool) -> list:
|
|
106
113
|
"volumes_mounted": volumes_mounted,
|
107
114
|
"cpu": cpu,
|
108
115
|
"ram": ram,
|
116
|
+
"count": pod.get("replicas", 1), # first in the list are always pods
|
109
117
|
}
|
110
118
|
# getting rid of unwanted and used values
|
111
119
|
if "pod-template-hash" in pod["labels"].keys():
|
@@ -139,7 +147,9 @@ def get_app_list(pod_list: list, detailed: bool) -> list:
|
|
139
147
|
|
140
148
|
# appending the rest of labels
|
141
149
|
pod_data.update(pod["labels"])
|
142
|
-
|
150
|
+
|
151
|
+
if not update_output_data_count(pod_data["name"]):
|
152
|
+
output_data.append(pod_data)
|
143
153
|
except KeyError:
|
144
154
|
pass
|
145
155
|
|