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.
Files changed (106) hide show
  1. {cgcsdk-1.2.5/cgcsdk.egg-info → cgcsdk-1.3.0}/PKG-INFO +76 -76
  2. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/.env +2 -2
  3. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/CHANGELOG.md +20 -0
  4. cgcsdk-1.3.0/cgc/commands/compute/billing/billing_cmd.py +118 -0
  5. cgcsdk-1.3.0/cgc/commands/compute/billing/billing_responses.py +65 -0
  6. cgcsdk-1.3.0/cgc/commands/compute/billing/billing_utils.py +241 -0
  7. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_responses.py +18 -0
  8. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_utils.py +12 -2
  9. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/debug/debug_cmd.py +18 -18
  10. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/resource/resource_cmd.py +58 -0
  11. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_responses.py +1 -1
  12. cgcsdk-1.3.0/cgc/tests/test.py +110 -0
  13. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/click_group.py +13 -0
  14. cgcsdk-1.3.0/cgc/utils/custom_exceptions.py +59 -0
  15. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/message_utils.py +4 -0
  16. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/prepare_headers.py +3 -3
  17. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/requests_helper.py +4 -0
  18. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/response_utils.py +10 -3
  19. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/version_control.py +1 -1
  20. {cgcsdk-1.2.5 → cgcsdk-1.3.0/cgcsdk.egg-info}/PKG-INFO +76 -76
  21. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/SOURCES.txt +1 -1
  22. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/setup.cfg +4 -4
  23. cgcsdk-1.2.5/cgc/commands/compute/billing/billing_cmd.py +0 -159
  24. cgcsdk-1.2.5/cgc/commands/compute/billing/billing_responses.py +0 -55
  25. cgcsdk-1.2.5/cgc/commands/compute/billing/billing_utils.py +0 -163
  26. cgcsdk-1.2.5/cgc/server.crt +0 -24
  27. cgcsdk-1.2.5/cgc/utils/custom_exceptions.py +0 -47
  28. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/LICENSE +0 -0
  29. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/MANIFEST.in +0 -0
  30. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/README.md +0 -0
  31. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/__init__.py +0 -0
  32. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/cgc.py +0 -0
  33. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/__init__.py +0 -0
  34. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/__init__.py +0 -0
  35. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_cmd.py +0 -0
  36. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_logic.py +0 -0
  37. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_responses.py +0 -0
  38. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/auth/auth_utils.py +0 -0
  39. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_cmd.py +0 -0
  40. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_cmd_responses.py +0 -0
  41. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_helpers.py +0 -0
  42. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/cgc_models.py +0 -0
  43. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/__init__.py +0 -0
  44. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/billing/__init__.py +0 -0
  45. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_cmd.py +0 -0
  46. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/compute/compute_models.py +0 -0
  47. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/db/__init__.py +0 -0
  48. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/db/db_cmd.py +0 -0
  49. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/db/db_models.py +0 -0
  50. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/debug/__init__.py +0 -0
  51. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/exceptions.py +0 -0
  52. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/__init__.py +0 -0
  53. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/job_utils.py +0 -0
  54. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/jobs_cmd.py +0 -0
  55. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/jobs/jobs_responses.py +0 -0
  56. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/resource/__init__.py +0 -0
  57. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/resource/resource_responses.py +0 -0
  58. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/__init__.py +0 -0
  59. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_cmd.py +0 -0
  60. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_models.py +0 -0
  61. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_responses.py +0 -0
  62. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/keys_utils.py +0 -0
  63. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_cmd.py +0 -0
  64. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_models.py +0 -0
  65. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_responses.py +0 -0
  66. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/user/secret_utils.py +0 -0
  67. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/__init__.py +0 -0
  68. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/data_model.py +0 -0
  69. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_cmd.py +0 -0
  70. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_models.py +0 -0
  71. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/commands/volume/volume_utils.py +0 -0
  72. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/config.py +0 -0
  73. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/__init__.py +0 -0
  74. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/exceptions.py +0 -0
  75. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/job.py +0 -0
  76. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/postgresql.py +0 -0
  77. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/sdk/resource.py +0 -0
  78. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/telemetry/__init__.py +0 -0
  79. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/telemetry/basic.py +0 -0
  80. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/__init__.py +0 -0
  81. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_invoice.txt +0 -0
  82. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_status.txt +0 -0
  83. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_stop_events_compute.txt +0 -0
  84. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_billing_stop_events_volume.txt +0 -0
  85. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_compute_list.txt +0 -0
  86. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_compute_list_no_labels.txt +0 -0
  87. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_tabulate_response.txt +0 -0
  88. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/desired_responses/test_volume_list.txt +0 -0
  89. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/tests/responses_tests.py +0 -0
  90. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/__init__.py +0 -0
  91. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/config_utils.py +0 -0
  92. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/consts/__init__.py +0 -0
  93. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/consts/env_consts.py +0 -0
  94. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/consts/message_consts.py +0 -0
  95. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/__init__.py +0 -0
  96. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/aes_crypto.py +0 -0
  97. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/encryption_module.py +0 -0
  98. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/cryptography/rsa_crypto.py +0 -0
  99. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/get_headers_data.py +0 -0
  100. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgc/utils/update.py +0 -0
  101. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/dependency_links.txt +0 -0
  102. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/entry_points.txt +0 -0
  103. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/requires.txt +0 -0
  104. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/cgcsdk.egg-info/top_level.txt +0 -0
  105. {cgcsdk-1.2.5 → cgcsdk-1.3.0}/pyproject.toml +0 -0
  106. {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.2.5
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)
@@ -5,5 +5,5 @@ API_SECURE_CONNECTION=yes
5
5
  CONFIG_FILE_NAME = cfg.json
6
6
  TMP_DIR = .tmp
7
7
  RELEASE = 1
8
- MAJOR_VERSION = 2
9
- MINOR_VERSION = 5
8
+ MAJOR_VERSION = 3
9
+ MINOR_VERSION = 0
@@ -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["mounts"])
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
- output_data.append(pod_data)
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