From 7cbd9a2604a1b58595fa74bf86d65cce42ecf2ca Mon Sep 17 00:00:00 2001 From: Julien MOROT Date: Fri, 10 Jan 2025 16:54:05 +0100 Subject: [PATCH 1/5] add initial functions to generate openiaas datasources for cloud temple terraform provider --- libs/ShivaApi.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/libs/ShivaApi.py b/libs/ShivaApi.py index ba454ec..ad82c40 100644 --- a/libs/ShivaApi.py +++ b/libs/ShivaApi.py @@ -43,7 +43,41 @@ def get_esxi_datastores(self): if response.status_code == HTTPStatus.OK: return self.json_response(response) return self.error_response(response) + + def get_all_contentlibs(self): + """Find all content libraries""" + response = self.get(self.base_url + "/v1/vcenters/content_libraries") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) + def get_all_contentlibsitems(self): + """Find all content libraries items""" + response = self.get(self.base_url + "/v1/vcenters/content_libraries/items") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) + + def get_openiaas_templates(self): + """Find all openiaas templates""" + response = self.get(self.base_url + "/v1/open_iaas/templates") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) + + def get_all_vcnetworks(self): + """Find all vc networks""" + response = self.get(self.base_url + "/v1/vcenters/networks") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) + + def get_openiaas_networks(self): + """Find all vc networks""" + response = self.get(self.base_url + "/v1/open_iaas/networks") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) def get_vm_network_adapters(self, vm_id: str): """Find network adapters for virtual machine""" @@ -88,6 +122,13 @@ def get_hosts(self): if response.status_code == HTTPStatus.OK: return self.json_response(response) return self.error_response(response) + + def get_openiaas_hosts(self): + """Find Hosts""" + response = self.get(self.base_url + "/v1/open_iaas/hosts") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) def get_hosts_by_vdc(self, vdc_id): """Find Hots with Virtual Datacenter ID Filter""" From 81600e681fb1b0e0db87aac5f810ef82f3d08fc3 Mon Sep 17 00:00:00 2001 From: Julien MOROT Date: Fri, 10 Jan 2025 17:58:17 +0100 Subject: [PATCH 2/5] Add storage repo --- libs/ShivaApi.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/ShivaApi.py b/libs/ShivaApi.py index ad82c40..4c09205 100644 --- a/libs/ShivaApi.py +++ b/libs/ShivaApi.py @@ -178,6 +178,13 @@ def get_datastore_clusters_by_dc(self, id_dc): if response.status_code == HTTPStatus.OK: return self.json_response(response) return self.error_response(response) + + def get_openiaas_storage_repositories(self): + """Find all storage_repositories""" + response = self.get(self.base_url + "/v1/open_iaas/storage_repositories") + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) def get_vcenters(self): """Find vCenter servers""" From b6c11d9a4ebd59427a5233779c84ee7baa55b197 Mon Sep 17 00:00:00 2001 From: kcn Date: Mon, 27 Jan 2025 14:47:14 +0100 Subject: [PATCH 3/5] Here's my updates of the "normal" expectations of the refresh token flow --- libs/httpClient.py | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/libs/httpClient.py b/libs/httpClient.py index f18d351..c20bcd9 100644 --- a/libs/httpClient.py +++ b/libs/httpClient.py @@ -67,11 +67,7 @@ def auth(self): exp_epoch = datetime.fromtimestamp(self.jwt_decoded['exp']) time_now = datetime.now() diff = (exp_epoch - time_now).total_seconds() / 60 - if 10 > diff > 1: - if hasattr(self, 'logger'): - self.logger.debug("10 > diff > 1 - token is valide") - self.__refresh() - elif diff < 1: + if diff < 10: self.__login() def __login(self): @@ -100,32 +96,6 @@ def __login(self): self.logger.error("request exception %s", err) raise HttpClientException("request exception", err) from err - def __refresh(self): - try: - headers = { - 'Content-Type': 'application/json', - 'accept': 'application/json', - 'Authorization': "Bearer " + self.bearer_token - } - - refreshToken = { - 'refreshToken': self.bearer_token - } - - # Retrieve bearer - bearer_request = requests.post(url=self.url + "/api/iam/v2/auth/refresh", headers=headers, - json=refreshToken) - - if bearer_request.status_code != 200: - raise HttpClientException("Cannot generate bearer token", bearer_request.reason) - else: - self.bearer_token = bearer_request.json()['refresh_token'] - - except RequestException as err: - if hasattr(self, 'logger'): - self.logger.error("request exception %s", err) - raise HttpClientException("request exception", err) from err - def __execute(self, req: Request) -> Response: self.auth() try: From 5ffb7c3dd7a6fb1dd949a3962e780d54b1916bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o?= <33425208+Theoooooo@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:57:14 +0100 Subject: [PATCH 4/5] Update policies call --- libs/ShivaApi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ShivaApi.py b/libs/ShivaApi.py index 4c09205..f412ab1 100644 --- a/libs/ShivaApi.py +++ b/libs/ShivaApi.py @@ -301,7 +301,7 @@ def get_jobs(self): def get_policies(self): """Find SLA Policies""" - response = self.get(self.base_url + "/v1/policies") + response = self.get(self.base_url + "/v1/spp/policies") if response.status_code == HTTPStatus.OK: return self.json_response(response) return self.error_response(response) From 0d8cf39c86b69f74965e6dbeb21e05c1c1d47129 Mon Sep 17 00:00:00 2001 From: Alexandre LEPETIT <10155402+aelttil@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:36:43 +0100 Subject: [PATCH 5/5] feat: Add Order API client and consumption retrieval script - Add Order class in libs/ShivaApi.py with get_consumptions() method - Support date format YYYY-MM-DD with automatic conversion to timestamps - Add tests/get_consumption.py script to fetch and display consumption data - Display SKU usage with descriptions in French --- libs/ShivaApi.py | 38 +++++++++++++++++++++++++++++++++ tests/get_consumption.py | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/get_consumption.py diff --git a/libs/ShivaApi.py b/libs/ShivaApi.py index f412ab1..9ea7db8 100644 --- a/libs/ShivaApi.py +++ b/libs/ShivaApi.py @@ -342,6 +342,44 @@ def get_tags(self): return self.error_response(response) +class Order(HttpClient): + def __init__(self, url: str, token_id: str, token_secret: str): + super().__init__(url=url, token_id=token_id, token_secret=token_secret) + self.base_url = "/api/order" + self.auth() + + def get_consumptions(self, start_date: str, end_date: str): + """Get consumptions for a time period + + Args: + start_date: Start date in format YYYY-MM-DD (e.g., "2025-01-15") + end_date: End date in format YYYY-MM-DD (e.g., "2025-01-17") + + Returns: + List of consumption items with usage and SKU information + """ + from datetime import datetime + + # Convert date strings to timestamp in milliseconds + try: + start_timestamp = int(datetime.strptime(start_date, "%Y-%m-%d").timestamp() * 1000) + end_timestamp = int(datetime.strptime(end_date, "%Y-%m-%d").timestamp() * 1000) + except ValueError as e: + raise ValueError(f"Invalid date format. Expected YYYY-MM-DD: {e}") + + # Validate that end_date >= start_date + if end_timestamp < start_timestamp: + raise ValueError("end_date must be greater than or equal to start_date") + + response = self.get( + self.base_url + f"/v1/consumptions?exportTimeStart={start_timestamp}&exportTimeEnd={end_timestamp}" + ) + + if response.status_code == HTTPStatus.OK: + return self.json_response(response) + return self.error_response(response) + + class Observability(HttpClient): def __init__(self, url: str, token_id: str, token_secret: str): super().__init__(url=url, token_id=token_id, token_secret=token_secret) diff --git a/tests/get_consumption.py b/tests/get_consumption.py new file mode 100644 index 0000000..de817fa --- /dev/null +++ b/tests/get_consumption.py @@ -0,0 +1,46 @@ +import sys +import os +# Add parent directory to path to allow imports from libs +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from libs.ShivaApi import Order + +# Init Order client +order_client = Order( + url="https://shiva.cloud-temple.com", + token_id="", + token_secret="" +) + +# Get consumptions for a specific period +# Format: YYYY-MM-DD +start_date = "2026-01-15" +end_date = "2026-01-17" + +print(f"Récupération des consommations du {start_date} au {end_date}...\n", flush=True) + +try: + consumptions = order_client.get_consumptions( + start_date=start_date, + end_date=end_date + ) + + print(f"Nombre total de SKU: {len(consumptions)}\n", flush=True) + print("=" * 80, flush=True) + + for item in consumptions: + usage = item.get('usage') + sku = item.get('sku', {}) + sku_name = sku.get('name', 'N/A') + sku_unit = sku.get('unit', 'N/A') + sku_desc_fr = sku.get('descriptionFr', 'N/A') + + print(f"\n📊 SKU: {sku_name}") + print(f" Usage: {usage} {sku_unit}") + print(f" Description: {sku_desc_fr}") + print("-" * 80) + +except ValueError as e: + print(f"❌ Erreur de validation: {e}") +except Exception as e: + print(f"❌ Erreur lors de la récupération des consommations: {e}")