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