brynq-sdk-elastic 1.0.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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: brynq_sdk_elastic
3
+ Version: 1.0.0
4
+ Summary: elastic wrapper from BrynQ
5
+ Home-page: UNKNOWN
6
+ Author: BrynQ
7
+ Author-email: support@brynq.com
8
+ License: BrynQ License
9
+ Description: elastic wrapper from BrynQ
10
+ Platform: UNKNOWN
@@ -0,0 +1 @@
1
+ from brynq_sdk.elastic.elastic import Elastic
@@ -0,0 +1,328 @@
1
+ import requests
2
+ import json
3
+ import datetime
4
+ import string
5
+ import random
6
+ import pandas as pd
7
+ import os
8
+
9
+
10
+ class Elastic:
11
+ def __init__(self, api_key: str = None, customer_name: str = None, space_name: str = None):
12
+ """
13
+ A package to create indexes, users, roles, getting data, etc.
14
+ :param api_key: The api key to connect to elasticsearch if not provided in the .env file
15
+ """
16
+ try:
17
+ self.verify = False
18
+ if os.getenv('BRYNQ_ENVIRONMENT') == 'prod':
19
+ self.elasticsearch_host = f'https://{os.getenv("ELASTIC_HOST_LIVE", "localhost")}:{os.getenv("ELASTIC_PORT_LIVE", "9200")}'
20
+ self.kibana_host = f'http://{os.getenv("ELASTIC_HOST_LIVE", "localhost")}:{os.getenv("KIBANA_PORT_LIVE", "5601")}'
21
+ self.elastic_token = os.getenv('ELASTIC_API_KEY_LIVE', api_key)
22
+ else:
23
+ self.elasticsearch_host = f'https://{os.getenv("ELASTIC_HOST_STAGING", "localhost")}:{os.getenv("ELASTIC_PORT_STAGING", "9200")}'
24
+ self.kibana_host = f'http://{os.getenv("ELASTIC_HOST_STAGING", "localhost")}:{os.getenv("KIBANA_PORT_STAGING", "5601")}'
25
+ self.elastic_token = os.getenv('ELASTIC_API_KEY_STAGING', api_key)
26
+
27
+ self.client_user = os.getenv('BRYNQ_CUSTOMER_NAME', 'default').lower().replace(' ', '_') if customer_name is None else customer_name.lower().replace(' ', '_')
28
+ self.space_name = os.getenv('ELASTIC_SPACE', 'default') if space_name is None else space_name
29
+ self.timestamp = int(datetime.datetime.now().timestamp())
30
+ self.elastic_headers = {
31
+ 'Content-Type': 'application/json',
32
+ 'Authorization': f'ApiKey {self.elastic_token}'
33
+ }
34
+ self.kibana_headers = {
35
+ 'Content-Type': 'application/json',
36
+ 'Authorization': f'ApiKey {self.elastic_token}',
37
+ 'kbn-xsrf': 'true'
38
+ }
39
+ self.get_health()
40
+ self.create_space(space_name=self.space_name)
41
+ except Exception as e:
42
+ raise ConnectionError('Could not establish a connection: {}'.format(str(e)))
43
+
44
+ def get_health(self) -> str:
45
+ """
46
+ Check if a there is a connection with elasticsearch
47
+ :return: if the connection is established or not
48
+ """
49
+ # Get the health of the database connection
50
+ health = requests.get(url=f'{self.elasticsearch_host}/_cat/health?', headers=self.elastic_headers, verify=self.verify).status_code
51
+ if health != 200:
52
+ raise ConnectionError('Elasticsearch cluster health check failed with status code: {}'.format(health))
53
+ else:
54
+ return 'Healthy connection established with elasticsearch!'
55
+
56
+ def initialize_customer(self):
57
+ # Creates the index for the user if it does not exist yet
58
+ self.create_index(index_name=f'task_execution_log_{self.client_user}')
59
+
60
+ # creates the data view for the space and index if it does not exist yet
61
+ self.create_data_view(space_name=self.space_name, view_name=f'task_execution_log_{self.client_user}', name=f'Task execution log {self.client_user}', time_field='started_at')
62
+
63
+ def create_space(self, space_name: str) -> str:
64
+ """
65
+ This function creates a space in elasticsearch for the current customer
66
+ :param space_name: The name of the space
67
+ :return: The status of the creation of the space
68
+ """
69
+ url = f'{self.kibana_host}/api/spaces/space'
70
+ data = {
71
+ "id": space_name,
72
+ "name": space_name,
73
+ "description": f"This is the space for {space_name}",
74
+ "color": "#aabbcc",
75
+ "initials": space_name[0:2].upper(),
76
+ "disabledFeatures": [],
77
+ }
78
+
79
+ response = requests.head(url=url + fr'/{space_name}', headers=self.kibana_headers, verify=self.verify)
80
+
81
+ if response.status_code == 200:
82
+ return f'Index \'{space_name}\' already exists'
83
+ else:
84
+ response = requests.post(url=url, headers=self.kibana_headers, data=json.dumps(data), verify=self.verify)
85
+ if response.status_code == 200:
86
+ return f'space {space_name} created'
87
+ else:
88
+ raise ConnectionError(f'Could not create space {space_name} with status code: {response.status_code}. Response: {response.text}')
89
+
90
+ def create_data_view(self, space_name: str, view_name: str, name: str, time_field: str) -> str:
91
+ """
92
+ This function creates a data view in elasticsearch for the current customer
93
+ :param space_name: The name of the space
94
+ :param view_name: The name of the data view
95
+ :param time_field: The name of the time field
96
+ :return: The status of the creation of the data view
97
+ """
98
+ url = f'{self.kibana_host}/s/{space_name}/api/data_views/data_view'
99
+ data = {
100
+ "data_view": {
101
+ "title": f'{view_name}*',
102
+ "id": f'{view_name}',
103
+ "name": f'{name}',
104
+ "timeFieldName": time_field
105
+ }
106
+ }
107
+
108
+ response = requests.head(url=url + fr'/{view_name}', headers=self.kibana_headers, verify=self.verify)
109
+
110
+ if response.status_code == 200:
111
+ return f'Data view \'{view_name}\' already exists'
112
+ else:
113
+ response = requests.post(url=url, headers=self.kibana_headers, data=json.dumps(data), verify=self.verify)
114
+ if response.status_code == 200:
115
+ return f'data view {view_name} created'
116
+ else:
117
+ raise ConnectionError(f'Could not create data view {view_name} with status code: {response.status_code}. Response: {response.text}')
118
+
119
+ def get_all_docs_from_index(self, index: str) -> pd.DataFrame:
120
+ """
121
+ Get all the documents from a certain index
122
+ :param index: the name of the index
123
+ :return: The response of the request to elasticsearch
124
+ """
125
+ size = 10000
126
+
127
+ # Get all indices with the given index from the function parameter. For each day a new index.
128
+ indices = requests.get(url=self.elasticsearch_host + '/' + index + '*/_settings', headers=self.elastic_headers, verify=self.verify).json()
129
+ index_list = {}
130
+
131
+ for index in indices:
132
+ index_date = datetime.date(2023, 4, 3)
133
+ index_list[str(index_date)] = index
134
+
135
+ url = f'{self.elasticsearch_host}/{index}/_search'
136
+
137
+ # initial request
138
+ params = {"size": size, "scroll": "10m"}
139
+ response = requests.get(url=url, headers=self.elastic_headers, params=params, verify=self.verify).json()
140
+
141
+ # next requests until finished
142
+ scroll_id = response['_scroll_id']
143
+ total = response['hits']['total']['value']
144
+ response = pd.json_normalize(response['hits']['hits'])
145
+ response.drop(['_id', '_index', '_score'], axis=1, inplace=True)
146
+
147
+ # start all the request to elastic based on the scroll_id and add to the initial response
148
+ loop_boolean = True
149
+ body = json.dumps({"scroll": "10m", "scroll_id": scroll_id})
150
+ url = f'{self.elasticsearch_host}/_search/scroll'
151
+
152
+ while loop_boolean and total > size:
153
+ next_response = pd.json_normalize(requests.post(url=url, data=body, headers=self.elastic_headers, verify=self.verify).json()["hits"]["hits"])
154
+ next_response.drop(['_id', '_index', '_score'], axis=1, inplace=True)
155
+ response = pd.concat([response, next_response], ignore_index=True)
156
+ print(f'Received {len(next_response)} documents from index {index}')
157
+ if len(next_response) != size:
158
+ loop_boolean = False
159
+ return response
160
+
161
+ def delete_index(self, index_name) -> str:
162
+ """
163
+ Deletes an existing index if it exists. Documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html
164
+ :param index_name: The index you want to delete
165
+ :return: The response of the request to elasticsearch
166
+ """
167
+ # Check if index exists
168
+ url = f'{self.elasticsearch_host}/{index_name}'
169
+ response = requests.head(url=url, headers=self.elastic_headers, verify=self.verify)
170
+
171
+ # Delete index if it exists
172
+ if response.status_code == 404:
173
+ return f'Index \'{index_name}\' does not exist'
174
+ else:
175
+ response = requests.delete(url=url, headers=self.elastic_headers, verify=self.verify)
176
+ if response.status_code == 200:
177
+ return f'Index \'{index_name}\' deleted'
178
+ else:
179
+ raise ConnectionError(f'Could not delete index {index_name} with status code: {response.status_code}. Response: {response.text}')
180
+
181
+ def create_index(self, index_name: str) -> str:
182
+ """
183
+ Creates a new index in the elasticsearch instance. Documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html
184
+ :param index_name: The name of the desired index
185
+ :return: The response of the request to elasticsearch
186
+ """
187
+ url = f'{self.elasticsearch_host}/{index_name}'
188
+ response = requests.head(url=url, headers=self.elastic_headers, verify=self.verify)
189
+
190
+ if response.status_code == 200:
191
+ return f'Index \'{index_name}\' already exists'
192
+ else:
193
+ response = requests.put(url=url, headers=self.elastic_headers, verify=self.verify)
194
+ if response.status_code == 200:
195
+ return f'Index {index_name} created'
196
+ else:
197
+ raise ConnectionError(f'Could not create index {index_name} with status code: {response.status_code}. Response: {response.text}')
198
+
199
+ def create_or_update_role(self, role_name: str, index: str) -> str:
200
+ """
201
+ Creates or updates a role. All the indexes which start with the same constraint as the role_name, are added to the role
202
+ :param role_name: The name of the desired role. Most often the username which also is used for the mysql database user (sc_customer)
203
+ :param index: one or more index names in a list.
204
+ :return: The response of the request to elasticsearch
205
+ """
206
+ url = f'{self.kibana_host}/api/security/role/{role_name}'
207
+ # Set the body
208
+ body = {
209
+ 'elasticsearch': {
210
+ 'cluster': ['transport_client'],
211
+ 'indices': [
212
+ {
213
+ 'names': [index],
214
+ 'privileges': ['read', 'write', 'read_cross_cluster', 'view_index_metadata', 'index']
215
+ }
216
+ ]
217
+ },
218
+ 'kibana': [{
219
+ 'feature': {
220
+ 'dashboard': ['read'],
221
+ 'discover': ['read']
222
+ },
223
+ 'spaces': [role_name],
224
+ }],
225
+ 'metadata': {
226
+ 'version': 1
227
+ }
228
+ }
229
+ body = json.dumps(body)
230
+
231
+ response = requests.head(url=url, headers=self.kibana_headers, verify=self.verify)
232
+
233
+ if response.status_code == 200:
234
+ return f'Role \'{role_name}\' already exists'
235
+ else:
236
+ response = requests.put(url=url, data=body, headers=self.kibana_headers, verify=self.verify)
237
+ if response.status_code == 204:
238
+ return f'Role {role_name} created'
239
+ else:
240
+ raise ConnectionError(f'Could not create role {role_name} with status code: {response.status_code}. Response: {response.text}')
241
+
242
+ def get_indices(self) -> dict:
243
+ """
244
+ Get all the indices in the elasticsearch instance
245
+ :return: A dictionary with all the indices
246
+ """
247
+ indices = requests.get(url=f'{self.elasticsearch_host}/_cat/indices?format=json', headers=self.elastic_headers, verify=self.verify).json()
248
+ return indices
249
+
250
+ def create_user(self, user_name: str, password: str, user_description: str, roles: list) -> str:
251
+ """
252
+ Creates a user if it doesn't exist.
253
+ :param user_name: The username. Most often the username which also is used for the mysql database user (sc_customer)
254
+ :param password: Choose a safe password. At least 8 characters long
255
+ :param user_description: A readable description. Often the customer name
256
+ :param roles: Give the roles to which the user belongs in a list. Most often the same role_name as the user_name
257
+ :return: The response of the request to elasticsearch
258
+ """
259
+ url = f'{self.elasticsearch_host}/_security/user/{user_name}'
260
+ body = {
261
+ 'password': f'{password}',
262
+ 'roles': roles,
263
+ 'full_name': f'{user_description}'
264
+ }
265
+ body = json.dumps(body)
266
+
267
+ response = requests.head(url=url, headers=self.elastic_headers, verify=self.verify)
268
+
269
+ if response.status_code == 200:
270
+ return f'user {user_name} already exists'
271
+ else:
272
+ response = requests.put(url=url, data=body, headers=self.elastic_headers, verify=self.verify)
273
+ if response.status_code == 200:
274
+ return f'user {user_name}, with password: {password} has been created'
275
+ else:
276
+ raise ConnectionError(f'Could not create user {user_name} with status code: {response.status_code}. Response: {response.text}')
277
+
278
+ def post_document(self, index_name: str, document: dict) -> requests.Response:
279
+ """
280
+ Posts a document to the specified index. Documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
281
+ :param index_name: The name of the index to which the document should be posted
282
+ :param document: The document to be posted
283
+ :return: The response of the request to elasticsearch
284
+ """
285
+ url = f'{self.elasticsearch_host}/{index_name}/_doc/'
286
+ body = json.dumps(document)
287
+ response = requests.post(url=url, data=body, headers=self.elastic_headers, verify=self.verify)
288
+ return response
289
+
290
+ def get_document(self, index_name: str, document_id: str) -> requests.Response:
291
+ """
292
+ Gets a document from the specified index. Documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html
293
+ :param index_name: The name of the index from which the document should be retrieved
294
+ :param document_id: The id of the document to be retrieved
295
+ :return: The response of the request to elasticsearch
296
+ """
297
+ url = f'{self.elasticsearch_host}/{index_name}/_doc/{document_id}'
298
+ response = requests.get(url=url, headers=self.elastic_headers, verify=self.verify)
299
+ return response
300
+
301
+ def delete_document(self, index_name: str, document_id: str) -> requests.Response:
302
+ """
303
+ Deletes a document from the specified index. Documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html
304
+ :param index_name: The name of the index from which the document should be deleted
305
+ :param document_id: The id of the document to be deleted
306
+ :return: The response of the request to elasticsearch
307
+ """
308
+ url = f'{self.elasticsearch_host}/{index_name}/_doc/{document_id}'
309
+ response = requests.delete(url=url, headers=self.elastic_headers, verify=self.verify)
310
+ return response
311
+
312
+ def task_execution_log(self, information: dict) -> requests.Response:
313
+ """
314
+ Write a document to the elasticsearch database
315
+ :param information: the information to be inserted into the database.
316
+ :return: the response of the post request
317
+ """
318
+ # Add new document
319
+ url = f'{self.elasticsearch_host}/task_execution_log_{self.client_user}/_doc/'
320
+ body = json.dumps(information)
321
+ response = requests.post(url=url, data=body, headers=self.elastic_headers, verify=self.verify)
322
+ return response
323
+
324
+ @staticmethod
325
+ def generate_password(length=20):
326
+ characters = string.ascii_letters + string.digits + string.punctuation
327
+ password = ''.join(random.choice(characters) for _ in range(length))
328
+ return password
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: brynq-sdk-elastic
3
+ Version: 1.0.0
4
+ Summary: elastic wrapper from BrynQ
5
+ Home-page: UNKNOWN
6
+ Author: BrynQ
7
+ Author-email: support@brynq.com
8
+ License: BrynQ License
9
+ Description: elastic wrapper from BrynQ
10
+ Platform: UNKNOWN
@@ -0,0 +1,9 @@
1
+ setup.py
2
+ brynq_sdk/elastic/__init__.py
3
+ brynq_sdk/elastic/elastic.py
4
+ brynq_sdk_elastic.egg-info/PKG-INFO
5
+ brynq_sdk_elastic.egg-info/SOURCES.txt
6
+ brynq_sdk_elastic.egg-info/dependency_links.txt
7
+ brynq_sdk_elastic.egg-info/not-zip-safe
8
+ brynq_sdk_elastic.egg-info/requires.txt
9
+ brynq_sdk_elastic.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests<=3,>=2
2
+ paramiko<=3,>=2
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ from setuptools import setup
2
+
3
+
4
+ setup(
5
+ name='brynq_sdk_elastic',
6
+ version='1.0.0',
7
+ description='elastic wrapper from BrynQ',
8
+ long_description='elastic wrapper from BrynQ',
9
+ author='BrynQ',
10
+ author_email='support@brynq.com',
11
+ packages=["brynq_sdk.elastic"],
12
+ license='BrynQ License',
13
+ install_requires=[
14
+ 'requests>=2,<=3',
15
+ 'paramiko>=2,<=3'
16
+ ],
17
+ zip_safe=False,
18
+ )