main
py 227 lines 8.83 KB
Raw
1 import json
2 from pathlib import Path
3
4 from fastapi import HTTPException
5 from loguru import logger
6
7 from app.connectors.grafana.schema.dashboards import BitdefenderDashboard
8 from app.connectors.grafana.schema.dashboards import CarbonBlackDashboard
9 from app.connectors.grafana.schema.dashboards import CatoDashboard
10 from app.connectors.grafana.schema.dashboards import CrowdstrikeDashboard
11 from app.connectors.grafana.schema.dashboards import DarktraceDashboard
12 from app.connectors.grafana.schema.dashboards import DashboardProvisionRequest
13 from app.connectors.grafana.schema.dashboards import DefenderForEndpointDashboard
14 from app.connectors.grafana.schema.dashboards import DuoDashboard
15 from app.connectors.grafana.schema.dashboards import FortinetDashboard
16 from app.connectors.grafana.schema.dashboards import GrafanaDashboard
17 from app.connectors.grafana.schema.dashboards import GrafanaDashboardResponse
18 from app.connectors.grafana.schema.dashboards import HuntressDashboard
19 from app.connectors.grafana.schema.dashboards import MimecastDashboard
20 from app.connectors.grafana.schema.dashboards import Office365Dashboard
21 from app.connectors.grafana.schema.dashboards import SapSiemDashboard
22 from app.connectors.grafana.schema.dashboards import SentinelOneDashboard
23 from app.connectors.grafana.schema.dashboards import SonicwallDashboard
24 from app.connectors.grafana.schema.dashboards import WazuhDashboard
25 from app.connectors.grafana.utils.universal import create_grafana_client
26
27
28 def get_dashboard_path(dashboard_info: tuple) -> Path:
29 """
30 Returns the path to the dashboard JSON file.
31
32 Parameters:
33 - dashboard_info (tuple): A tuple containing the folder name and file name of the dashboard.
34
35 Returns:
36 - Path: The path to the dashboard JSON file.
37 """
38 folder_name, file_name = dashboard_info
39 current_file = Path(__file__) # Path to the current file
40 base_dir = current_file.parent.parent # Move up two levels to the 'grafana' directory
41 return base_dir / "dashboards" / folder_name / file_name
42
43
44 def load_dashboard_json(dashboard_info: tuple, datasource_uid: str, grafana_url: str) -> dict:
45 """
46 Load the JSON data of a dashboard from a file and replace the 'uid' value with the provided datasource UID.
47
48 Args:
49 dashboard_info (tuple): Information about the dashboard (e.g., file name, directory).
50 datasource_uid (str): The UID of the datasource to replace in the dashboard JSON.
51
52 Returns:
53 dict: The loaded dashboard data with the replaced 'uid' value.
54
55 Raises:
56 FileNotFoundError: If the dashboard JSON file is not found.
57 HTTPException: If there is an error decoding the JSON from the file.
58 """
59 file_path = get_dashboard_path(dashboard_info)
60 try:
61 with open(file_path, "r") as file:
62 dashboard_data = json.load(file)
63
64 # Search for 'uid' with 'wazuh_datasource_uid' and replace it
65 replace_uid_value(dashboard_data, datasource_uid)
66 replace_grafana_url(dashboard_data, grafana_url)
67
68 return dashboard_data
69
70 except FileNotFoundError:
71 logger.error(f"Dashboard JSON file not found at {file_path}")
72 raise HTTPException(status_code=404, detail="Dashboard JSON file not found")
73 except json.JSONDecodeError:
74 logger.error("Error decoding JSON from file")
75 raise HTTPException(status_code=500, detail="Error decoding JSON from file")
76
77
78 def replace_uid_value(
79 obj,
80 new_value,
81 key_to_replace="uid",
82 old_value="replace_datasource_uid",
83 ):
84 """
85 Recursively replaces the value of a specified key in a nested dictionary or list.
86
87 Args:
88 obj (dict or list): The object to be traversed and modified.
89 new_value: The new value to replace the old value with.
90 key_to_replace (str): The key to be replaced. Defaults to "uid".
91 old_value: The old value to be replaced. Defaults to "replace_datasource_uid".
92 """
93 if isinstance(obj, dict):
94 for k, v in obj.items():
95 if k == key_to_replace and v == old_value:
96 obj[k] = new_value
97 elif isinstance(v, (dict, list)):
98 replace_uid_value(v, new_value, key_to_replace, old_value)
99 elif isinstance(obj, list):
100 for item in obj:
101 replace_uid_value(item, new_value, key_to_replace, old_value)
102
103
104 def replace_grafana_url(obj, new_value, key_to_replace="url", old_value="https://grafana.company.local"):
105 """
106 Recursively replaces the value of a specified key in a nested dictionary or list.
107
108 Args:
109 obj (dict or list): The object to be traversed and modified.
110 new_value: The new value to replace the old value with.
111 key_to_replace (str): The key to be replaced. Defaults to "url".
112 old_value: The old value to be replaced. Defaults to "https://grafana.company.local".
113 """
114 if isinstance(obj, dict):
115 for k, v in obj.items():
116 if k == key_to_replace and isinstance(v, str) and v.startswith(old_value):
117 obj[k] = v.replace(old_value, new_value)
118 elif isinstance(v, (dict, list)):
119 replace_grafana_url(v, new_value, key_to_replace, old_value)
120 elif isinstance(obj, list):
121 for item in obj:
122 replace_grafana_url(item, new_value, key_to_replace, old_value)
123
124
125 async def update_dashboard(
126 dashboard_json: dict,
127 organization_id: int,
128 folder_id: int,
129 ) -> dict:
130 """
131 Update a dashboard in Grafana.
132
133 Args:
134 dashboard_json (dict): The updated dashboard JSON.
135 organization_id (int): The ID of the organization.
136 folder_id (int): The ID of the folder.
137
138 Returns:
139 dict: The updated dashboard response.
140
141 Raises:
142 HTTPException: If there is an error updating the dashboard.
143 """
144 logger.info(
145 f"Updating dashboards for organization {organization_id} and folder {folder_id}",
146 )
147 try:
148 grafana_client = await create_grafana_client("Grafana")
149 # Switch to the newly created organization
150 grafana_client.user.switch_actual_user_organisation(organization_id)
151 logger.info(
152 f"Updating dashboards for organization {organization_id} and folder {folder_id}",
153 )
154 dashboard_update_payload = {
155 "dashboard": dashboard_json,
156 "folderId": folder_id,
157 "overwrite": True,
158 }
159 return grafana_client.dashboard.update_dashboard(dashboard_update_payload)
160 except Exception as e:
161 logger.error(f"Error updating dashboard: {e}")
162 raise HTTPException(status_code=500, detail=f"Error updating dashboard: {e}")
163
164
165 async def provision_dashboards(
166 dashboard_request: DashboardProvisionRequest,
167 ) -> GrafanaDashboardResponse:
168 """
169 Provisions dashboards in Grafana.
170
171 Args:
172 dashboard_request (DashboardProvisionRequest): The request object containing the details of the dashboards to provision.
173
174 Returns:
175 GrafanaDashboardResponse: The response object containing the provisioned dashboards, success status, and message.
176 """
177 logger.info(f"Received dashboard provision request: {dashboard_request}")
178 provisioned_dashboards = []
179 errors = []
180
181 valid_dashboards = {
182 item.name: item
183 for item in list(WazuhDashboard)
184 + list(Office365Dashboard)
185 + list(MimecastDashboard)
186 + list(SapSiemDashboard)
187 + list(HuntressDashboard)
188 + list(CarbonBlackDashboard)
189 + list(FortinetDashboard)
190 + list(CrowdstrikeDashboard)
191 + list(DuoDashboard)
192 + list(DarktraceDashboard)
193 + list(BitdefenderDashboard)
194 + list(CatoDashboard)
195 + list(DefenderForEndpointDashboard)
196 + list(SonicwallDashboard)
197 + list(SentinelOneDashboard)
198 }
199
200 for dashboard_name in dashboard_request.dashboards:
201 dashboard_enum = valid_dashboards[dashboard_name]
202 try:
203 dashboard_json = load_dashboard_json(
204 dashboard_enum.value,
205 datasource_uid=dashboard_request.datasourceUid,
206 grafana_url=dashboard_request.grafana_url,
207 )
208 updated_dashboard = await update_dashboard(
209 dashboard_json=dashboard_json,
210 organization_id=dashboard_request.organizationId,
211 folder_id=dashboard_request.folderId,
212 )
213 provisioned_dashboards.append(GrafanaDashboard(**updated_dashboard))
214 except HTTPException as e:
215 errors.append(f"Failed to update dashboard {dashboard_name}: {e.detail}")
216 raise HTTPException(
217 status_code=500,
218 detail=f"Error updating dashboard: {e}",
219 )
220
221 success = len(errors) == 0
222 message = "All dashboards provisioned successfully" if success else "Some dashboards failed to provision"
223 return GrafanaDashboardResponse(
224 provisioned_dashboards=provisioned_dashboards,
225 success=success,
226 message=message,
227 )