pyxecm 1.6__py3-none-any.whl → 2.0.1__py3-none-any.whl

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.

Potentially problematic release.


This version of pyxecm might be problematic. Click here for more details.

Files changed (78) hide show
  1. pyxecm/__init__.py +7 -4
  2. pyxecm/avts.py +727 -254
  3. pyxecm/coreshare.py +686 -467
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +163 -0
  9. pyxecm/customizer/api/auth/__init__.py +1 -0
  10. pyxecm/customizer/api/auth/functions.py +92 -0
  11. pyxecm/customizer/api/auth/models.py +13 -0
  12. pyxecm/customizer/api/auth/router.py +78 -0
  13. pyxecm/customizer/api/common/__init__.py +1 -0
  14. pyxecm/customizer/api/common/functions.py +47 -0
  15. pyxecm/customizer/api/common/metrics.py +92 -0
  16. pyxecm/customizer/api/common/models.py +21 -0
  17. pyxecm/customizer/api/common/payload_list.py +870 -0
  18. pyxecm/customizer/api/common/router.py +72 -0
  19. pyxecm/customizer/api/settings.py +128 -0
  20. pyxecm/customizer/api/terminal/__init__.py +1 -0
  21. pyxecm/customizer/api/terminal/router.py +87 -0
  22. pyxecm/customizer/api/v1_csai/__init__.py +1 -0
  23. pyxecm/customizer/api/v1_csai/router.py +87 -0
  24. pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
  25. pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
  26. pyxecm/customizer/api/v1_maintenance/models.py +12 -0
  27. pyxecm/customizer/api/v1_maintenance/router.py +76 -0
  28. pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
  29. pyxecm/customizer/api/v1_otcs/functions.py +61 -0
  30. pyxecm/customizer/api/v1_otcs/router.py +179 -0
  31. pyxecm/customizer/api/v1_payload/__init__.py +1 -0
  32. pyxecm/customizer/api/v1_payload/functions.py +179 -0
  33. pyxecm/customizer/api/v1_payload/models.py +51 -0
  34. pyxecm/customizer/api/v1_payload/router.py +499 -0
  35. pyxecm/customizer/browser_automation.py +721 -286
  36. pyxecm/customizer/customizer.py +1076 -1425
  37. pyxecm/customizer/exceptions.py +35 -0
  38. pyxecm/customizer/guidewire.py +1186 -0
  39. pyxecm/customizer/k8s.py +901 -379
  40. pyxecm/customizer/log.py +107 -0
  41. pyxecm/customizer/m365.py +2967 -920
  42. pyxecm/customizer/nhc.py +1169 -0
  43. pyxecm/customizer/openapi.py +258 -0
  44. pyxecm/customizer/payload.py +18228 -7820
  45. pyxecm/customizer/pht.py +717 -286
  46. pyxecm/customizer/salesforce.py +516 -342
  47. pyxecm/customizer/sap.py +58 -41
  48. pyxecm/customizer/servicenow.py +611 -372
  49. pyxecm/customizer/settings.py +445 -0
  50. pyxecm/customizer/successfactors.py +408 -346
  51. pyxecm/customizer/translate.py +83 -48
  52. pyxecm/helper/__init__.py +5 -2
  53. pyxecm/helper/assoc.py +83 -43
  54. pyxecm/helper/data.py +2406 -870
  55. pyxecm/helper/logadapter.py +27 -0
  56. pyxecm/helper/web.py +229 -101
  57. pyxecm/helper/xml.py +596 -171
  58. pyxecm/maintenance_page/__init__.py +5 -0
  59. pyxecm/maintenance_page/__main__.py +6 -0
  60. pyxecm/maintenance_page/app.py +51 -0
  61. pyxecm/maintenance_page/settings.py +28 -0
  62. pyxecm/maintenance_page/static/favicon.avif +0 -0
  63. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  64. pyxecm/otac.py +235 -141
  65. pyxecm/otawp.py +2668 -1220
  66. pyxecm/otca.py +569 -0
  67. pyxecm/otcs.py +7956 -3237
  68. pyxecm/otds.py +2178 -925
  69. pyxecm/otiv.py +36 -21
  70. pyxecm/otmm.py +1272 -325
  71. pyxecm/otpd.py +231 -127
  72. pyxecm-2.0.1.dist-info/METADATA +122 -0
  73. pyxecm-2.0.1.dist-info/RECORD +76 -0
  74. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
  75. pyxecm-1.6.dist-info/METADATA +0 -53
  76. pyxecm-1.6.dist-info/RECORD +0 -32
  77. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info/licenses}/LICENSE +0 -0
  78. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
pyxecm/avts.py CHANGED
@@ -1,83 +1,101 @@
1
- """
2
- AVTS stands for Aviator Search and is an OpenText offering for LLMM-based search across multiple repositories
3
-
4
- Class: AVTS
5
- Methods:
6
- __init__: class initializer
7
- request_header: Returns the request header used for Application calls.
8
- do_request: Call an Aviator Search REST API in a safe way
9
- parse_request_response: Converts the request response (JSon) to a Python list in a safe way
10
- authenticate: Authenticate at Search Aviator via oAuth authentication
11
- repo_create_extended_ecm: Create a new repository to crawl in Aviator Search
12
- start_crawling: Start crawling of a repository
13
- stop_crawling: Stop the crawling of a repository
14
- get_repo_list: Get a list of all repositories
15
- get_repo_by_name: Get a repository by name
16
- """
1
+ """AVTS stands for Aviator Search and is an OpenText offering for LLMM-based search across multiple repositories."""
17
2
 
18
3
  __author__ = "Dr. Marc Diefenbruch"
19
- __copyright__ = "Copyright 2024, OpenText"
4
+ __copyright__ = "Copyright (C) 2024-2025, OpenText"
20
5
  __credits__ = ["Kai-Philip Gatzweiler"]
21
6
  __maintainer__ = "Dr. Marc Diefenbruch"
22
7
  __email__ = "mdiefenb@opentext.com"
23
8
 
9
+ import base64
24
10
  import json
25
11
  import logging
26
- import time
27
12
  import os
28
- import base64
13
+ import platform
14
+ import sys
15
+ import time
16
+ from importlib.metadata import version
29
17
 
30
18
  import requests
31
19
 
32
- logger = logging.getLogger("pyxecm.customizer.avts")
20
+ APP_NAME = "pyxecm"
21
+ APP_VERSION = version("pyxecm")
22
+ MODULE_NAME = APP_NAME + ".avts"
23
+
24
+ PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
25
+ OS_INFO = f"{platform.system()} {platform.release()}"
26
+ ARCH_INFO = platform.machine()
27
+ REQUESTS_VERSION = requests.__version__
33
28
 
34
- REQUEST_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
29
+ USER_AGENT = (
30
+ f"{APP_NAME}/{APP_VERSION} ({MODULE_NAME}/{APP_VERSION}; "
31
+ f"Python/{PYTHON_VERSION}; {OS_INFO}; {ARCH_INFO}; Requests/{REQUESTS_VERSION})"
32
+ )
33
+
34
+ REQUEST_HEADERS = {"User-Agent": USER_AGENT, "accept": "application/json", "Content-Type": "application/json"}
35
35
 
36
36
  REQUEST_TIMEOUT = 60
37
37
  REQUEST_RETRY_DELAY = 20
38
38
  REQUEST_MAX_RETRIES = 2
39
39
 
40
+ default_logger = logging.getLogger(MODULE_NAME)
41
+
42
+
43
+ class AVTS:
44
+ """Configure and interact with Aviator Search REST API."""
40
45
 
41
- class AVTS(object):
42
- """Used to configure and interact with Aviator Search"""
46
+ logger: logging.Logger = default_logger
43
47
 
44
48
  _config: dict
45
49
  _session = None
46
50
 
47
51
  def __init__(
48
52
  self,
49
- otds_url: str,
50
53
  client_id: str,
51
54
  client_secret: str,
52
55
  base_url: str,
53
56
  username: str,
54
57
  password: str,
55
- ):
56
- """Initialize the AVTS object
58
+ otds_url: str,
59
+ logger: logging.Logger = default_logger,
60
+ ) -> None:
61
+ """Initialize the Aviator Search (AVTS) object.
57
62
 
58
63
  Args:
59
- otds_url (str): URL of the OTDS Server used by Aviator Search
60
- client_id (str): Client ID for the Aviator Search oAuth client
61
- client_secret (str): Client Secret for the Aviator Search oAuth client
62
- base_url (str): Aviator Search base URL
63
- username (str): User with administrative permissions in Aviator Search
64
- password (str): Password of the user with administrative permissions in Aviator Search
64
+ client_id (str):
65
+ The client ID for the Aviator Search oAuth client.
66
+ client_secret (str):
67
+ The client secret for the Aviator Search oAuth client.
68
+ base_url (str):
69
+ The Aviator Search base URL.
70
+ username (str):
71
+ User with administrative permissions in Aviator Search.
72
+ password (str):
73
+ Password of the user with administrative permissions in Aviator Search.
74
+ otds_url (str):
75
+ The URL of the OTDS Server used by Aviator Search.
76
+ logger (logging.Logger, optional):
77
+ The logging object to use for all log messages. Defaults to default_logger.
78
+
65
79
  """
66
80
 
81
+ if logger != default_logger:
82
+ self.logger = logger.getChild("avts")
83
+ for logfilter in logger.filters:
84
+ self.logger.addFilter(logfilter)
85
+
67
86
  avts_config = {}
68
87
 
69
88
  # Store the credentials and parameters in a config dictionary:
70
- avts_config["otdsUrl"] = otds_url
71
89
  avts_config["clientId"] = client_id
72
90
  avts_config["clientSecret"] = client_secret
73
91
  avts_config["baseUrl"] = base_url
74
92
  avts_config["username"] = username
75
93
  avts_config["password"] = password
94
+ avts_config["otdsUrl"] = otds_url
76
95
 
77
96
  avts_config["tokenUrl"] = avts_config["otdsUrl"] + "/otdsws/oauth2/token"
78
- avts_config["repoUrl"] = (
79
- avts_config["baseUrl"] + "/aviator-gateway/avts-api/admin/v1/repo"
80
- )
97
+ avts_config["repoUrl"] = avts_config["baseUrl"] + "/aviator-gateway/avts-api/admin/v1/repo"
98
+ avts_config["questionsUrl"] = avts_config["baseUrl"] + "/aviator-gateway/avts-api/search/v1/questions"
81
99
 
82
100
  self._config = avts_config
83
101
  self._accesstoken = None
@@ -87,23 +105,34 @@ class AVTS(object):
87
105
  # end method definition
88
106
 
89
107
  def config(self) -> dict:
90
- """Returns the configuration dictionary
108
+ """Return the configuration dictionary.
91
109
 
92
110
  Returns:
93
111
  dict: Configuration dictionary
112
+
94
113
  """
114
+
95
115
  return self._config
96
116
 
97
117
  # end method definition
98
118
 
99
119
  def request_header(self, content_type: str = "") -> dict:
100
- """Returns the request header used for Application calls.
101
- Consists of Bearer access token and Content Type
120
+ """Return the request header used for Application calls.
121
+
122
+ Consists of Bearer access token and Content Type
102
123
 
103
124
  Args:
104
- content_type (str, optional): custom content type for the request
105
- Return:
106
- dict: request header values
125
+ content_type (str, optional):
126
+ Custom content type for the request.
127
+ Typical values:
128
+ * application/json - Used for sending JSON-encoded data
129
+ * application/x-www-form-urlencoded - The default for HTML forms.
130
+ Data is sent as key-value pairs in the body of the request, similar to query parameters.
131
+ * multipart/form-data - Used for file uploads or when a form includes non-ASCII characters
132
+
133
+ Returns:
134
+ dict: The request header values.
135
+
107
136
  """
108
137
 
109
138
  request_header = {}
@@ -125,7 +154,7 @@ class AVTS(object):
125
154
  url: str,
126
155
  method: str = "GET",
127
156
  headers: dict | None = None,
128
- data: dict | None = None,
157
+ data: dict | list | None = None,
129
158
  json_data: dict | None = None,
130
159
  files: dict | None = None,
131
160
  timeout: int | None = REQUEST_TIMEOUT,
@@ -135,24 +164,40 @@ class AVTS(object):
135
164
  max_retries: int = REQUEST_MAX_RETRIES,
136
165
  retry_forever: bool = False,
137
166
  ) -> dict | None:
138
- """Call an Aviator Search REST API in a safe way
167
+ """Call an Aviator Search REST API in a safe way.
139
168
 
140
169
  Args:
141
- url (str): URL to send the request to.
142
- method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
143
- headers (dict | None, optional): Request Headers. Defaults to None.
144
- json (dict | None, optional): Request payload. Defaults to None.
145
- files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
146
- file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
147
- timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
148
- show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
149
- If False, then only a warning is logged. Defaults to True.
150
- failure_message (str, optional): Specific error message. Defaults to "".
151
- max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
152
- retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
170
+ url (str):
171
+ URL to send the request to.
172
+ method (str, optional):
173
+ HTTP method (GET, POST, etc.). Defaults to "GET".
174
+ headers (dict | None, optional):
175
+ Request headers. Defaults to None.
176
+ data (dict | None, optional):
177
+ Request payload. Defaults to None.
178
+ json_data (dict | None, optional):
179
+ Request payload for the JSON parameter. Defaults to None.
180
+ files (dict | None, optional):
181
+ Dictionary of {"name": file-tuple} for multipart encoding upload.
182
+ The file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple
183
+ ("filename", fileobj, "content_type").
184
+ timeout (int | None, optional):
185
+ Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
186
+ show_error (bool, optional):
187
+ Whether or not an error should be logged in case of a failed REST call.
188
+ If False, then only a warning is logged. Defaults to True.
189
+ failure_message (str, optional):
190
+ Specific error message. Defaults to "".
191
+ success_message (str, optional):
192
+ Specific success message. Defaults to "".
193
+ max_retries (int, optional):
194
+ Number of retries on connection errors. Defaults to REQUEST_MAX_RETRIES.
195
+ retry_forever (bool, optional):
196
+ Whether to wait forever without timeout. Defaults to False.
153
197
 
154
198
  Returns:
155
199
  dict | None: Response of Aviator Search REST API or None in case of an error.
200
+
156
201
  """
157
202
 
158
203
  retries = 0
@@ -170,30 +215,35 @@ class AVTS(object):
170
215
 
171
216
  if response.ok:
172
217
  if success_message:
173
- logger.debug(success_message)
218
+ self.logger.debug(success_message)
174
219
  return self.parse_request_response(response)
220
+ elif (
221
+ response.status_code == 500
222
+ and "Cannot modify configuration" in response.text
223
+ and "while the Processor is running" in response.text
224
+ ):
225
+ self.logger.warning("Another operation is already running. Waiting 5 seconds to retry...")
226
+ time.sleep(5)
227
+ retries += 1
175
228
  # Check if Session has expired - then re-authenticate and try once more
176
229
  elif response.status_code == 401 and retries == 0:
177
- logger.debug("Session has expired - try to re-authenticate...")
230
+ self.logger.info("Session has expired - try to re-authenticate...")
178
231
  self.authenticate()
179
232
  retries += 1
180
233
  else:
181
234
  # Handle plain HTML responses to not pollute the logs
182
235
  content_type = response.headers.get("content-type", None)
183
- if content_type == "text/html":
184
- response_text = "HTML content (see debug log)"
185
- else:
186
- response_text = response.text
236
+ response_text = "HTML content (see debug log)" if content_type == "text/html" else response.text
187
237
 
188
238
  if show_error:
189
- logger.error(
239
+ self.logger.error(
190
240
  "%s; status -> %s; error -> %s",
191
241
  failure_message,
192
242
  response.status_code,
193
243
  response_text,
194
244
  )
195
245
  else:
196
- logger.warning(
246
+ self.logger.warning(
197
247
  "%s; status -> %s; warning -> %s",
198
248
  failure_message,
199
249
  response.status_code,
@@ -201,7 +251,7 @@ class AVTS(object):
201
251
  )
202
252
 
203
253
  if content_type == "text/html":
204
- logger.debug(
254
+ self.logger.debug(
205
255
  "%s; status -> %s; warning -> %s",
206
256
  failure_message,
207
257
  response.status_code,
@@ -211,39 +261,39 @@ class AVTS(object):
211
261
  return None
212
262
  except requests.exceptions.Timeout:
213
263
  if retries <= max_retries:
214
- logger.warning(
264
+ self.logger.warning(
215
265
  "Request timed out. Retrying in %s seconds...",
216
266
  str(REQUEST_RETRY_DELAY),
217
267
  )
218
268
  retries += 1
219
269
  time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
220
270
  else:
221
- logger.error(
222
- "%s; timeout error",
271
+ self.logger.error(
272
+ "%s; timeout error.",
223
273
  failure_message,
224
274
  )
225
275
  if retry_forever:
226
276
  # If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
227
- logger.warning("Turn timeouts off and wait forever...")
277
+ self.logger.warning("Turn timeouts off and wait forever...")
228
278
  timeout = None
229
279
  else:
230
280
  return None
231
281
  except requests.exceptions.ConnectionError:
232
282
  if retries <= max_retries:
233
- logger.warning(
283
+ self.logger.warning(
234
284
  "Connection error. Retrying in %s seconds...",
235
285
  str(REQUEST_RETRY_DELAY),
236
286
  )
237
287
  retries += 1
238
288
  time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
239
289
  else:
240
- logger.error(
241
- "%s; connection error",
290
+ self.logger.error(
291
+ "%s; connection error.",
242
292
  failure_message,
243
293
  )
244
294
  if retry_forever:
245
295
  # If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
246
- logger.warning("Turn timeouts off and wait forever...")
296
+ self.logger.warning("Turn timeouts off and wait forever...")
247
297
  timeout = None
248
298
  time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
249
299
  else:
@@ -257,43 +307,47 @@ class AVTS(object):
257
307
  additional_error_message: str = "",
258
308
  show_error: bool = True,
259
309
  ) -> list | None:
260
- """Converts the request response (JSon) to a Python list in a safe way
261
- that also handles exceptions. It first tries to load the response.text
262
- via json.loads() that produces a dict output. Only if response.text is
263
- not set or is empty it just converts the response_object to a dict using
264
- the vars() built-in method.
310
+ """Convert the request response (JSon) to a Python list in a safe way that also handles exceptions.
311
+
312
+ It first tries to load the response.text
313
+ via json.loads() that produces a dict output. Only if response.text is
314
+ not set or is empty it just converts the response_object to a dict using
315
+ the vars() built-in method.
265
316
 
266
317
  Args:
267
- response_object (object): this is reponse object delivered by the request call
268
- additional_error_message (str, optional): use a more specific error message
269
- in case of an error
270
- show_error (bool): True: write an error to the log file
271
- False: write a warning to the log file
318
+ response_object (requests.Response):
319
+ This is reponse object delivered by the request call.
320
+ additional_error_message (str, optional):
321
+ Use a more specific error message in case of an error.
322
+ show_error (bool, optional):
323
+ If True, write an error to the log file.
324
+ If False, write a warning to the log file.
325
+
272
326
  Returns:
273
- list: response information or None in case of an error
327
+ list | None:
328
+ The response information or None in case of an error.
329
+
274
330
  """
275
331
 
276
332
  if not response_object:
277
333
  return None
278
334
 
279
335
  try:
280
- if response_object.text:
281
- list_object = json.loads(response_object.text)
282
- else:
283
- list_object = vars(response_object)
336
+ list_object = json.loads(response_object.text) if response_object.text else vars(response_object)
284
337
  except json.JSONDecodeError as exception:
285
338
  if additional_error_message:
286
339
  message = "Cannot decode response as JSON. {}; error -> {}".format(
287
- additional_error_message, exception
340
+ additional_error_message,
341
+ exception,
288
342
  )
289
343
  else:
290
344
  message = "Cannot decode response as JSON; error -> {}".format(
291
- exception
345
+ exception,
292
346
  )
293
347
  if show_error:
294
- logger.error(message)
348
+ self.logger.error(message)
295
349
  else:
296
- logger.warning(message)
350
+ self.logger.warning(message)
297
351
  return None
298
352
  else:
299
353
  return list_object
@@ -301,7 +355,13 @@ class AVTS(object):
301
355
  # end method definition
302
356
 
303
357
  def authenticate(self) -> str | None:
304
- """Authenticate at Search Aviator via oAuth authentication."""
358
+ """Authenticate at Aviator Search via OAuth.
359
+
360
+ Returns:
361
+ str | None:
362
+ The access token or None in case of an error.
363
+
364
+ """
305
365
 
306
366
  if not self._session:
307
367
  self._session = requests.Session()
@@ -315,10 +375,11 @@ class AVTS(object):
315
375
  }
316
376
  request_payload = {
317
377
  "client_id": self.config()["clientId"],
318
- "grant_type": "password",
319
378
  "client_secret": self.config()["clientSecret"],
379
+ "grant_type": "password",
320
380
  "username": self.config()["username"],
321
381
  "password": self.config()["password"],
382
+ "scope": "otds:roles",
322
383
  }
323
384
 
324
385
  response = self.do_request(
@@ -327,7 +388,10 @@ class AVTS(object):
327
388
  headers=request_header,
328
389
  data=request_payload,
329
390
  timeout=None,
330
- failure_message=f"Failed to authenticate to OTDS with username -> {self.config()['username']} and client_id -> {self.config()['clientId']}",
391
+ failure_message="Failed to authenticate to OTDS with username -> '{}' and client_id -> {}".format(
392
+ self.config()["username"],
393
+ self.config()["clientId"],
394
+ ),
331
395
  )
332
396
 
333
397
  if response is not None:
@@ -337,7 +401,7 @@ class AVTS(object):
337
401
 
338
402
  # end method definition
339
403
 
340
- def repo_create_extended_ecm(
404
+ def create_extended_ecm_repo(
341
405
  self,
342
406
  name: str,
343
407
  username: str,
@@ -345,128 +409,221 @@ class AVTS(object):
345
409
  otcs_url: str,
346
410
  otcs_api_url: str,
347
411
  node_id: int,
348
- version: str = "24.3.0",
349
412
  ) -> dict | None:
350
- """Create a new repository to crawl in Aviator Search
413
+ """Create a new Extended ECM repository to crawl with Aviator Search.
351
414
 
352
415
  Args:
353
- id (str): ID of the repository
354
- name (str): socName of the repository
355
- username (str): Username to use for crawling
356
- password (str): Password of the user used for crawling
357
- otcs_url (str): Base URL of Content Server e.g. https://otcs.base-url.tld/cs/cs
358
- node_id (int): Root Node ID for crawling
416
+ name (str):
417
+ The name of the repository.
418
+ username (str):
419
+ Username to use for crawling.
420
+ password (str):
421
+ Password of the user used for crawling.
422
+ otcs_url (str):
423
+ Base URL of Content Server e.g. https://otcs.base-url.tld/cs/cs
424
+ otcs_api_url (str):
425
+ The REST API URL of Content Server.
426
+ node_id (int):
427
+ Root Node ID for crawling
359
428
 
360
429
  Returns:
361
- dict | None: Parsed response object from the API or None in case of an error
430
+ dict | None:
431
+ Parsed response object from the API or None in case of an error
432
+
362
433
  """
363
434
 
364
435
  payload = {
365
- "id": "xECM",
366
- "name": name,
367
- "metadataFields": ["NODE"],
368
- "socName": "xECM",
436
+ "authType": "Basic",
369
437
  "params": [
370
438
  {
371
439
  "id": "OpenTextApiUrl",
372
- "label": "xECM API URL",
440
+ "label": "Service URL",
373
441
  "ctlType": "text",
442
+ "description": "OpenText Content Management API URL",
374
443
  "required": True,
444
+ "defaultValue": "localhost",
445
+ "visible": True,
446
+ "editable": False,
375
447
  "value": otcs_api_url,
376
448
  },
377
449
  {
378
450
  "id": "Username",
379
- "label": "xECM username",
451
+ "label": "Username",
380
452
  "ctlType": "text",
453
+ "description": "OpenText Content Management Username",
381
454
  "required": True,
455
+ "defaultValue": "",
456
+ "visible": True,
457
+ "editable": True,
382
458
  "value": username,
383
459
  },
384
460
  {
385
461
  "id": "Password",
386
- "label": "xECM Password",
462
+ "label": "Password",
387
463
  "ctlType": "password",
464
+ "description": "OpenText Content Management password",
388
465
  "required": True,
466
+ "defaultValue": "",
467
+ "visible": True,
468
+ "editable": True,
389
469
  "value": password,
390
470
  },
391
471
  {
392
- "id": "RootNodeId",
393
- "label": "Root Node ID",
472
+ "id": "sourceLink",
473
+ "label": "Source Link",
394
474
  "ctlType": "text",
475
+ "description": "Example: <OpenText Content Management API URL>/app/nodes/${NODE}/metadata",
476
+ "required": False,
477
+ "defaultValue": otcs_url + "/app/nodes/${NODE}/metadata",
478
+ "visible": True,
479
+ "editable": True,
480
+ },
481
+ {
482
+ "id": "RootNodeIds",
483
+ "label": "Root Node ID's",
484
+ "ctlType": "text",
485
+ "description": "List of nodes to be crawled(comma seperated)",
395
486
  "required": True,
396
- "value": node_id,
487
+ "defaultValue": "",
488
+ "visible": True,
489
+ "editable": False,
490
+ "value": "2000",
397
491
  },
398
492
  {
399
- "id": "sourceLink",
400
- "label": "Source Link( ex:https://<xECM host>/cs/cs/app/nodes/${NODE}/metadata )",
493
+ "id": "proxy",
494
+ "label": "Proxy Service",
495
+ "ctlType": "boolean",
496
+ "description": "",
497
+ "required": False,
498
+ "defaultValue": "false",
499
+ "value": "false",
500
+ "visible": True,
501
+ "editable": True,
502
+ },
503
+ {
504
+ "id": "proxyScheme",
505
+ "label": "Proxy Scheme",
506
+ "ctlType": "select",
507
+ "description": "",
508
+ "required": False,
509
+ "defaultValue": "HTTP",
510
+ "value": "HTTP",
511
+ "visible": True,
512
+ "acceptedValues": ["HTTP", "HTTPS", "SOCKS5"],
513
+ "editable": True,
514
+ },
515
+ {
516
+ "id": "proxyHost",
517
+ "label": "Proxy Host",
401
518
  "ctlType": "text",
519
+ "description": "",
402
520
  "required": False,
403
- "defaultValue": otcs_url + "/app/nodes/${NODE}/metadata",
521
+ "defaultValue": "",
522
+ "value": "",
523
+ "visible": True,
524
+ "editable": True,
525
+ },
526
+ {
527
+ "id": "proxyPort",
528
+ "label": "Proxy Port",
529
+ "ctlType": "text",
530
+ "description": "",
531
+ "required": False,
532
+ "defaultValue": "",
533
+ "value": "",
404
534
  "visible": True,
535
+ "editable": True,
536
+ },
537
+ {
538
+ "id": "ProxyConfigService",
539
+ "label": "Proxy Config Service",
540
+ "ctlType": "text",
541
+ "description": "",
542
+ "required": False,
543
+ "defaultValue": "",
544
+ "value": "",
545
+ "visible": False,
546
+ "editable": True,
405
547
  },
406
548
  ],
407
- "idolConfig": {
408
- "view": {
409
- "name": "ViewOpenText",
410
- "type": "idol.nifi.connector.ViewOpenText",
549
+ "config": {
550
+ "type": "nifi",
551
+ "id": "xECM",
552
+ "crawlConfig": {
553
+ "name": "GetOpenText",
554
+ "type": "idol.nifi.connector.GetOpenText",
411
555
  "group": "idol.nifi.connector",
412
556
  "artifact": "idol-nifi-connector-opentext",
413
- "version": version,
557
+ "version": "25.1.0-nifi1",
414
558
  },
415
- "crawler": {
416
- "name": "GetOpenText",
417
- "type": "idol.nifi.connector.GetOpenText",
559
+ "viewConfig": {
560
+ "name": "ViewOpenText",
561
+ "type": "idol.nifi.connector.ViewOpenText",
418
562
  "group": "idol.nifi.connector",
419
563
  "artifact": "idol-nifi-connector-opentext",
420
- "version": version,
564
+ "version": "25.1.0-nifi1",
421
565
  },
422
- "omniGroup": {
566
+ "omniConfig": {
423
567
  "name": "GetOpenTextGroups",
424
568
  "type": "idol.nifi.connector.GetOpenTextGroups",
425
569
  "group": "idol.nifi.connector",
426
570
  "artifact": "idol-nifi-connector-opentext",
427
- "version": version,
571
+ "version": "25.1.0-nifi1",
572
+ "repoName": "ECM",
428
573
  },
429
- },
430
- "idolProperties": {
431
- "view": {
574
+ "crawlProps": {
432
575
  "Password": "${Password}",
433
576
  "Username": "${UserName}",
577
+ "META:SOURCE": "OPENTEXT",
578
+ "RootNodeIds": "${RootNodeIds}",
579
+ "MappedSecurity": "true",
434
580
  "OpenTextApiUrl": "${OpenTextApiUrl}",
581
+ "ProxyConfigService": "${ProxyConfigService}",
435
582
  },
436
- "crawler": {
583
+ "viewProps": {
437
584
  "Password": "${Password}",
438
585
  "Username": "${UserName}",
439
- "RootNodeId": "${RootNodeId}",
440
- "META:SOURCE": "OPENTEXT",
441
- "MappedSecurity": "true",
442
586
  "OpenTextApiUrl": "${OpenTextApiUrl}",
587
+ "ProxyConfigService": "${ProxyConfigService}",
443
588
  },
444
- "omniGroup": {
589
+ "omniProps": {
445
590
  "Password": "${Password}",
446
591
  "Username": "${UserName}",
447
592
  "OpenTextApiUrl": "${OpenTextApiUrl}",
593
+ "ProxyConfigService": "${ProxyConfigService}",
448
594
  "OpenTextApiPageSize": "10",
449
595
  },
596
+ "metadataFields": ["NODE"],
450
597
  },
598
+ "name": name,
599
+ "id": "xECM",
600
+ "sourceId": "xECM",
451
601
  }
452
602
 
453
603
  request_header = self.request_header()
454
604
  request_url = self.config()["repoUrl"]
455
605
 
456
- return self.do_request(
606
+ response = self.do_request(
457
607
  url=request_url,
458
608
  method="POST",
459
609
  json_data=payload,
460
610
  headers=request_header,
461
611
  timeout=None,
462
612
  failure_message="Failed to create repository -> '{}' ({})".format(
463
- name, node_id
613
+ name,
614
+ node_id,
464
615
  ),
616
+ show_error=False,
465
617
  )
466
618
 
619
+ if response is None:
620
+ self.logger.error("Failed to create repository -> %s (%s)!", name, node_id)
621
+
622
+ return response
623
+
467
624
  # end method definition
468
625
 
469
- def repo_create_msteams(
626
+ def create_msteams_repo(
470
627
  self,
471
628
  name: str,
472
629
  client_id: str,
@@ -477,36 +634,41 @@ class AVTS(object):
477
634
  index_call_recordings: bool = True,
478
635
  index_message_replies: bool = True,
479
636
  index_user_chats: bool = True,
480
- oauth2_site_name: str = "AVTS",
481
- oauth2_sites_file: str = "",
482
- version: str = "24.3.0",
483
637
  ) -> dict | None:
484
- """Create a new repository to crawl in Aviator Search
638
+ """Create a new Microsoft Teams repository to crawl with Aviator Search.
485
639
 
486
640
  Args:
487
- id (str): ID of the repository
488
- name (str): socName of the repository
489
- #todo: add more params
641
+ name (str):
642
+ The name of the repository.
643
+ client_id (str):
644
+ The M365 client ID.
645
+ tenant_id (str):
646
+ The M365 tenant ID.
647
+ certificate_file (str):
648
+ The path to the certificate file.
649
+ certificate_password (str):
650
+ The password for the certificate.
651
+ index_attachments (bool, optional):
652
+ Whether or not to index / crawl attachments.
653
+ index_call_recordings (bool, optional):
654
+ Whether or not to index / crawl meeting recordings.
655
+ index_message_replies (bool, optional):
656
+ Whether or not to index / crawl message replies.
657
+ index_user_chats (bool, optional):
658
+ Whether or not to index / crawl user chats.
490
659
 
491
660
  Returns:
492
- dict | None: Parsed response object from the API or None in case of an error
661
+ dict | None:
662
+ Parsed response object from the API or None in case of an error
663
+
493
664
  """
494
665
 
495
- if os.path.isfile(certificate_file):
496
- # Open the file in binary mode
497
- with open(certificate_file, "rb") as file:
498
- # Read the content of the file
499
- certificate_file_content = file.read()
500
- # Convert the bytes to a base64 string
501
- certificate_file_content_base64 = base64.b64encode(
502
- certificate_file_content
503
- ).decode("utf-8")
666
+ certificate_file_content_base64 = self.get_certificate_file_content_base64(
667
+ certificate_file,
668
+ )
504
669
 
505
670
  payload = {
506
- "id": "MSTeams",
507
- "socName": "Microsoft Teams",
508
671
  "authType": "OAUTH",
509
- "name": name,
510
672
  "params": [
511
673
  {
512
674
  "id": "OAuth2SiteName",
@@ -516,6 +678,7 @@ class AVTS(object):
516
678
  "defaultValue": "AVTS",
517
679
  "value": "AVTS",
518
680
  "visible": False,
681
+ "editable": True,
519
682
  },
520
683
  {
521
684
  "id": "OAuth2SitesFile",
@@ -525,6 +688,7 @@ class AVTS(object):
525
688
  "defaultValue": "",
526
689
  "value": "",
527
690
  "visible": False,
691
+ "editable": True,
528
692
  },
529
693
  {
530
694
  "id": "sourceLink",
@@ -533,6 +697,7 @@ class AVTS(object):
533
697
  "required": False,
534
698
  "defaultValue": "",
535
699
  "visible": True,
700
+ "editable": True,
536
701
  },
537
702
  {
538
703
  "id": "clientID",
@@ -543,6 +708,7 @@ class AVTS(object):
543
708
  "defaultValue": "",
544
709
  "value": client_id,
545
710
  "visible": True,
711
+ "editable": True,
546
712
  },
547
713
  {
548
714
  "id": "tenant",
@@ -553,6 +719,7 @@ class AVTS(object):
553
719
  "defaultValue": "",
554
720
  "value": tenant_id,
555
721
  "visible": True,
722
+ "editable": False,
556
723
  },
557
724
  {
558
725
  "id": "IndexAttachments",
@@ -561,8 +728,9 @@ class AVTS(object):
561
728
  "description": "Specifies whether to index attachments",
562
729
  "required": False,
563
730
  "defaultValue": "true",
564
- "value": str(index_attachments).lower(),
565
- "visible": True,
731
+ "value": "true",
732
+ "visible": str(index_attachments).lower(),
733
+ "editable": True,
566
734
  },
567
735
  {
568
736
  "id": "IndexCallRecordings",
@@ -573,6 +741,7 @@ class AVTS(object):
573
741
  "defaultValue": "true",
574
742
  "value": str(index_call_recordings).lower(),
575
743
  "visible": True,
744
+ "editable": True,
576
745
  },
577
746
  {
578
747
  "id": "IndexMessageReplies",
@@ -583,6 +752,7 @@ class AVTS(object):
583
752
  "defaultValue": "true",
584
753
  "value": str(index_message_replies).lower(),
585
754
  "visible": True,
755
+ "editable": True,
586
756
  },
587
757
  {
588
758
  "id": "IndexUserChats",
@@ -593,6 +763,7 @@ class AVTS(object):
593
763
  "defaultValue": "true",
594
764
  "value": str(index_user_chats).lower(),
595
765
  "visible": True,
766
+ "editable": True,
596
767
  },
597
768
  {
598
769
  "id": "certificateFile",
@@ -601,8 +772,9 @@ class AVTS(object):
601
772
  "description": 'Please upload a valid "*.pfx" certificate file',
602
773
  "required": True,
603
774
  "defaultValue": "",
604
- "value": "C:\\fakepath\\certificate.pfx",
775
+ "value": "C:\\fakepath\\certificate 1 3 (1).pfx",
605
776
  "visible": True,
777
+ "editable": True,
606
778
  "fileDatabase64": f"data:application/x-pkcs12;base64,{certificate_file_content_base64}",
607
779
  },
608
780
  {
@@ -613,42 +785,126 @@ class AVTS(object):
613
785
  "defaultValue": "",
614
786
  "value": certificate_password,
615
787
  "visible": True,
788
+ "editable": True,
789
+ },
790
+ {
791
+ "id": "proxy",
792
+ "label": "Proxy Service",
793
+ "ctlType": "boolean",
794
+ "description": "",
795
+ "required": False,
796
+ "defaultValue": "false",
797
+ "value": "false",
798
+ "visible": True,
799
+ "editable": True,
800
+ },
801
+ {
802
+ "id": "proxyScheme",
803
+ "label": "Proxy Scheme",
804
+ "ctlType": "select",
805
+ "description": "",
806
+ "required": False,
807
+ "defaultValue": "HTTP",
808
+ "value": "HTTP",
809
+ "visible": True,
810
+ "acceptedValues": [
811
+ "HTTP",
812
+ "HTTPS",
813
+ "SOCKS5",
814
+ ],
815
+ "editable": True,
816
+ },
817
+ {
818
+ "id": "proxyHost",
819
+ "label": "Proxy Host",
820
+ "ctlType": "text",
821
+ "description": "",
822
+ "required": False,
823
+ "defaultValue": "",
824
+ "value": "",
825
+ "visible": True,
826
+ "editable": True,
827
+ },
828
+ {
829
+ "id": "proxyPort",
830
+ "label": "Proxy Port",
831
+ "ctlType": "text",
832
+ "description": "",
833
+ "required": False,
834
+ "defaultValue": "",
835
+ "value": "",
836
+ "visible": True,
837
+ "editable": True,
838
+ },
839
+ {
840
+ "id": "ProxyConfigService",
841
+ "label": "Proxy Config Service",
842
+ "ctlType": "text",
843
+ "description": "",
844
+ "required": False,
845
+ "defaultValue": "",
846
+ "value": "",
847
+ "visible": False,
848
+ "editable": True,
616
849
  },
617
850
  ],
618
- "idolConfig": {
619
- "view": {
851
+ "config": {
852
+ "type": "nifi",
853
+ "id": "MSTeams",
854
+ "crawlConfig": {
855
+ "name": "GetMicrosoftTeams",
856
+ "type": "idol.nifi.connector.GetMicrosoftTeams",
857
+ "group": "idol.nifi.connector",
858
+ "artifact": "idol-nifi-connector-officeteams",
859
+ "version": "25.1.0-nifi1",
860
+ },
861
+ "viewConfig": {
620
862
  "name": "ViewMicrosoftTeams",
621
863
  "type": "idol.nifi.connector.ViewMicrosoftTeams",
622
864
  "group": "idol.nifi.connector",
623
865
  "artifact": "idol-nifi-connector-officeteams",
624
- "version": version,
866
+ "version": "25.1.0-nifi1",
625
867
  },
626
- "crawler": {
627
- "name": "GetMicrosoftTeams",
628
- "type": "idol.nifi.connector.GetMicrosoftTeams",
868
+ "omniConfig": {
869
+ "name": "GetMicrosoftTeamsGroups",
870
+ "type": "idol.nifi.connector.GetMicrosoftTeamsGroups",
629
871
  "group": "idol.nifi.connector",
630
872
  "artifact": "idol-nifi-connector-officeteams",
631
- "version": version,
873
+ "version": "25.1.0-nifi1",
874
+ "repoName": "OneDrive",
632
875
  },
633
- },
634
- "idolProperties": {
635
- "view": {
636
- "Oauth2SiteName": "${OAuth2SiteName}",
637
- "Oauth2SitesFile": "${OAuth2SitesFile}",
638
- "IndexCallRecordings": "true",
639
- },
640
- "crawler": {
641
- "META:SOURCE": "MSTeams",
876
+ "crawlProps": {
877
+ "META:SOURCE": "Microsoft Teams",
642
878
  "IndexUserChats": "${IndexUserChats}",
879
+ "MappedSecurity": "true",
643
880
  "Oauth2SiteName": "${OAuth2SiteName}",
644
881
  "Oauth2SitesFile": "${OAuth2SitesFile}",
645
882
  "IndexAttachments": "${IndexAttachments}",
883
+ "ProxyConfigService": "${ProxyConfigService}",
646
884
  "IndexCallRecordings": "${IndexCallRecordings}",
647
885
  "IndexMessageReplies": "${IndexMessageReplies}",
886
+ "ChatMessageGroupingSection": "chat",
887
+ "ChannelMessageGroupingSection": "channel",
888
+ "[chat]MessageGroupingInterval": "24 hour",
889
+ "[chat]MessageGroupingStrategy": "Interval",
890
+ "[channel]MessageGroupingInterval": "24 hour",
891
+ "[channel]MessageGroupingStrategy": "Interval",
648
892
  },
893
+ "viewProps": {
894
+ "Oauth2SiteName": "${OAuth2SiteName}",
895
+ "Oauth2SitesFile": "${OAuth2SitesFile}",
896
+ "ProxyConfigService": "${ProxyConfigService}",
897
+ },
898
+ "omniProps": {
899
+ "Oauth2SiteName": "${OAuth2SiteName}",
900
+ "Oauth2SitesFile": "${OAuth2SitesFile}",
901
+ "ProxyConfigService": "${ProxyConfigService}",
902
+ },
903
+ "metadataFields": [],
649
904
  },
650
- "authRedirect": "",
651
- "metadataFields": [],
905
+ "name": name,
906
+ "id": "MSTeams",
907
+ "sourceId": "MSTeams",
652
908
  }
653
909
 
654
910
  request_header = self.request_header()
@@ -661,9 +917,11 @@ class AVTS(object):
661
917
  headers=request_header,
662
918
  timeout=None,
663
919
  failure_message="Failed to create repository -> '{}'".format(name),
920
+ show_error=False,
664
921
  )
665
922
 
666
923
  if response is None:
924
+ self.logger.error("Failed to create repository -> '%s'", name)
667
925
  return None
668
926
 
669
927
  self.repo_admin_consent(response["id"])
@@ -672,7 +930,7 @@ class AVTS(object):
672
930
 
673
931
  # end method definition
674
932
 
675
- def repo_create_sharepoint(
933
+ def create_sharepoint_repo(
676
934
  self,
677
935
  name: str,
678
936
  client_id: str,
@@ -686,34 +944,47 @@ class AVTS(object):
686
944
  index_user_profiles: bool = True,
687
945
  oauth2_site_name: str = "AVTS",
688
946
  oauth2_sites_file: str = "",
689
- version: str = "24.3.0",
690
947
  ) -> dict | None:
691
- """Create a new repository to crawl in Aviator Search
948
+ """Create a new Microsoft SharePoint repository to crawl with Aviator Search.
692
949
 
693
950
  Args:
694
- id (str): ID of the repository
695
- name (str): socName of the repository
696
- #todo: add more params
951
+ name (str):
952
+ The name of the repository.
953
+ client_id (str):
954
+ The M365 client ID.
955
+ tenant_id (str):
956
+ The M365 tenant ID.
957
+ certificate_file (str):
958
+ TODO: _description_
959
+ certificate_password (int):
960
+ TODO: _description_
961
+ sharepoint_url (str):
962
+ The SharePoint URL.
963
+ sharepoint_url_type (str):
964
+ The SharePoint URL type.
965
+ sharepoint_mysite_url (str):
966
+ The SharePoint MySite URL.
967
+ sharepoint_admin_url (str):
968
+ The SharePoint administration URL.
969
+ index_user_profiles (bool, optional):
970
+ TODO: _description_. Defaults to True.
971
+ oauth2_site_name (str, optional):
972
+ TODO: _description_. Defaults to "AVTS".
973
+ oauth2_sites_file (str, optional):
974
+ TODO: _description_. Defaults to "".
697
975
 
698
976
  Returns:
699
- dict | None: Parsed response object from the API or None in case of an error
977
+ dict | None:
978
+ Parsed response object from the API or None in case of an error
979
+
700
980
  """
701
981
 
702
- if os.path.isfile(certificate_file):
703
- # Open the file in binary mode
704
- with open(certificate_file, "rb") as file:
705
- # Read the content of the file
706
- certificate_file_content = file.read()
707
- # Convert the bytes to a base64 string
708
- certificate_file_content_base64 = base64.b64encode(
709
- certificate_file_content
710
- ).decode("utf-8")
982
+ certificate_file_content_base64 = self.get_certificate_file_content_base64(
983
+ certificate_file,
984
+ )
711
985
 
712
986
  payload = {
713
- "id": "SharePoint",
714
- "socName": "SharePoint Online",
715
987
  "authType": "OAUTH",
716
- "name": name,
717
988
  "params": [
718
989
  {
719
990
  "id": "OAuth2SiteName",
@@ -723,6 +994,7 @@ class AVTS(object):
723
994
  "defaultValue": "AVTS",
724
995
  "value": oauth2_site_name,
725
996
  "visible": False,
997
+ "editable": True,
726
998
  },
727
999
  {
728
1000
  "id": "OAuth2SitesFile",
@@ -732,6 +1004,7 @@ class AVTS(object):
732
1004
  "defaultValue": "",
733
1005
  "value": oauth2_sites_file,
734
1006
  "visible": False,
1007
+ "editable": True,
735
1008
  },
736
1009
  {
737
1010
  "id": "sourceLink",
@@ -741,8 +1014,8 @@ class AVTS(object):
741
1014
  "required": False,
742
1015
  "defaultValue": "",
743
1016
  "visible": True,
744
- "value": sharepoint_url
745
- + "${FILEDIRREF}/Forms/AllItems.aspx?id=${FILEREF}&parent=${FILEDIRREF}",
1017
+ "editable": True,
1018
+ "value": sharepoint_url + "${FILEDIRREF}/Forms/AllItems.aspx?id=${FILEREF}&parent=${FILEDIRREF}",
746
1019
  },
747
1020
  {
748
1021
  "id": "clientID",
@@ -753,6 +1026,7 @@ class AVTS(object):
753
1026
  "defaultValue": "",
754
1027
  "value": client_id,
755
1028
  "visible": True,
1029
+ "editable": True,
756
1030
  },
757
1031
  {
758
1032
  "id": "tenant",
@@ -763,6 +1037,7 @@ class AVTS(object):
763
1037
  "defaultValue": "",
764
1038
  "value": tenant_id,
765
1039
  "visible": True,
1040
+ "editable": True,
766
1041
  },
767
1042
  {
768
1043
  "id": "sharePointUrl",
@@ -771,8 +1046,19 @@ class AVTS(object):
771
1046
  "description": 'The URL to start synchronizing from. Specify a URL that matches "SharePoint URL type"',
772
1047
  "required": True,
773
1048
  "defaultValue": "",
774
- "value": sharepoint_url + "/",
1049
+ "value": sharepoint_mysite_url,
775
1050
  "visible": True,
1051
+ "editable": False,
1052
+ },
1053
+ {
1054
+ "id": "MappedWebApplicationPolicies",
1055
+ "label": "Mapped Web Application Policies",
1056
+ "ctlType": "boolean",
1057
+ "required": False,
1058
+ "defaultValue": "false",
1059
+ "value": "false",
1060
+ "visible": True,
1061
+ "editable": False,
776
1062
  },
777
1063
  {
778
1064
  "id": "sharePointAdminUrl",
@@ -783,6 +1069,7 @@ class AVTS(object):
783
1069
  "defaultValue": "",
784
1070
  "value": sharepoint_admin_url,
785
1071
  "visible": True,
1072
+ "editable": False,
786
1073
  },
787
1074
  {
788
1075
  "id": "sharePointMySiteUrl",
@@ -793,6 +1080,7 @@ class AVTS(object):
793
1080
  "defaultValue": "",
794
1081
  "value": sharepoint_mysite_url,
795
1082
  "visible": True,
1083
+ "editable": False,
796
1084
  },
797
1085
  {
798
1086
  "id": "sharePointOnline",
@@ -803,15 +1091,7 @@ class AVTS(object):
803
1091
  "defaultValue": "true",
804
1092
  "value": "true",
805
1093
  "visible": False,
806
- },
807
- {
808
- "id": "MappedWebApplicationPolicies",
809
- "label": "Mapped Web Application Policies",
810
- "ctlType": "text",
811
- "required": False,
812
- "defaultValue": "false",
813
- "value": "false",
814
- "visible": False,
1094
+ "editable": False,
815
1095
  },
816
1096
  {
817
1097
  "id": "TenantAdminSitesIncludeTypes",
@@ -822,6 +1102,7 @@ class AVTS(object):
822
1102
  "defaultValue": "all",
823
1103
  "value": "all",
824
1104
  "visible": False,
1105
+ "editable": False,
825
1106
  },
826
1107
  {
827
1108
  "id": "URLType",
@@ -830,7 +1111,7 @@ class AVTS(object):
830
1111
  "description": 'The type of URL specified by "Sharepoint URL"',
831
1112
  "required": True,
832
1113
  "defaultValue": "",
833
- "value": "SiteCollection",
1114
+ "value": sharepoint_url_type,
834
1115
  "visible": True,
835
1116
  "acceptedValues": [
836
1117
  "WebApplication",
@@ -838,6 +1119,7 @@ class AVTS(object):
838
1119
  "PersonalSiteCollection",
839
1120
  "TenantAdmin",
840
1121
  ],
1122
+ "editable": False,
841
1123
  },
842
1124
  {
843
1125
  "id": "IndexUserProfiles",
@@ -848,6 +1130,7 @@ class AVTS(object):
848
1130
  "defaultValue": "false",
849
1131
  "value": str(index_user_profiles).lower(),
850
1132
  "visible": True,
1133
+ "editable": True,
851
1134
  },
852
1135
  {
853
1136
  "id": "certificateFile",
@@ -856,8 +1139,9 @@ class AVTS(object):
856
1139
  "description": 'Please upload a valid "*.pfx" certificate file',
857
1140
  "required": True,
858
1141
  "defaultValue": "",
859
- "value": "C:\\fakepath\\certificate.pfx",
1142
+ "value": "C:\\fakepath\\certificate 1 3 (1).pfx",
860
1143
  "visible": True,
1144
+ "editable": True,
861
1145
  "fileDatabase64": f"data:application/x-pkcs12;base64,{certificate_file_content_base64}",
862
1146
  },
863
1147
  {
@@ -868,51 +1152,141 @@ class AVTS(object):
868
1152
  "defaultValue": "",
869
1153
  "value": certificate_password,
870
1154
  "visible": True,
1155
+ "editable": True,
1156
+ },
1157
+ {
1158
+ "id": "proxy",
1159
+ "label": "Proxy Service",
1160
+ "ctlType": "boolean",
1161
+ "description": "",
1162
+ "required": False,
1163
+ "defaultValue": "false",
1164
+ "value": "false",
1165
+ "visible": True,
1166
+ "editable": True,
1167
+ },
1168
+ {
1169
+ "id": "proxyScheme",
1170
+ "label": "Proxy Scheme",
1171
+ "ctlType": "select",
1172
+ "description": "",
1173
+ "required": False,
1174
+ "defaultValue": "HTTP",
1175
+ "value": "HTTP",
1176
+ "visible": True,
1177
+ "acceptedValues": [
1178
+ "HTTP",
1179
+ "HTTPS",
1180
+ "SOCKS5",
1181
+ ],
1182
+ "editable": True,
1183
+ },
1184
+ {
1185
+ "id": "proxyHost",
1186
+ "label": "Proxy Host",
1187
+ "ctlType": "text",
1188
+ "description": "",
1189
+ "required": False,
1190
+ "defaultValue": "",
1191
+ "value": "",
1192
+ "visible": True,
1193
+ "editable": True,
1194
+ },
1195
+ {
1196
+ "id": "proxyPort",
1197
+ "label": "Proxy Port",
1198
+ "ctlType": "text",
1199
+ "description": "",
1200
+ "required": False,
1201
+ "defaultValue": "",
1202
+ "value": "",
1203
+ "visible": True,
1204
+ "editable": True,
1205
+ },
1206
+ {
1207
+ "id": "ProxyConfigService",
1208
+ "label": "Proxy Config Service",
1209
+ "ctlType": "text",
1210
+ "description": "",
1211
+ "required": False,
1212
+ "defaultValue": "",
1213
+ "value": "",
1214
+ "visible": False,
1215
+ "editable": True,
871
1216
  },
872
1217
  ],
873
- "idolConfig": {
874
- "view": {
1218
+ "config": {
1219
+ "type": "nifi",
1220
+ "id": "SharePoint",
1221
+ "crawlConfig": {
1222
+ "name": "GetSharePointOData",
1223
+ "type": "idol.nifi.connector.GetSharePointOData",
1224
+ "group": "idol.nifi.connector",
1225
+ "artifact": "idol-nifi-connector-sharepointodata",
1226
+ "version": "25.1.0-nifi1",
1227
+ },
1228
+ "viewConfig": {
875
1229
  "name": "ViewSharePointOData",
876
1230
  "type": "idol.nifi.connector.ViewSharePointOData",
877
1231
  "group": "idol.nifi.connector",
878
1232
  "artifact": "idol-nifi-connector-sharepointodata",
879
- "version": version,
1233
+ "version": "25.1.0-nifi1",
880
1234
  },
881
- "crawler": {
882
- "name": "GetSharePointOData",
883
- "type": "idol.nifi.connector.GetSharePointOData",
1235
+ "omniConfig": {
1236
+ "name": "GetSharePointGroupsOData",
1237
+ "type": "idol.nifi.connector.GetSharePointGroupsOData",
884
1238
  "group": "idol.nifi.connector",
885
1239
  "artifact": "idol-nifi-connector-sharepointodata",
886
- "version": version,
1240
+ "version": "25.1.0-nifi1",
1241
+ "repoName": "SharePoint",
887
1242
  },
888
- },
889
- "idolProperties": {
890
- "view": {
1243
+ "crawlProps": {
1244
+ "META:SOURCE": "SharePoint Online",
891
1245
  "SharepointUrl": "${sharePointUrl}",
1246
+ "MappedSecurity": "true",
892
1247
  "Oauth2SiteName": "${OAuth2SiteName}",
893
1248
  "Oauth2SitesFile": "${OAuth2SitesFile}",
894
1249
  "SharepointOnline": "${sharePointOnline}",
1250
+ "IndexUserProfiles": "${IndexUserProfiles}",
895
1251
  "SharepointUrlType": "${URLType}",
1252
+ "ProxyConfigService": "${ProxyConfigService}",
896
1253
  "SharepointAdminUrl": "${sharePointAdminUrl}",
897
1254
  "SharepointMySiteUrl": "${sharePointMySiteUrl}",
1255
+ "RetrieveUserDetailsAs": "Title",
898
1256
  "MappedWebApplicationPolicies": "${MappedWebApplicationPolicies}",
1257
+ "TenantAdminSitesIncludeTypes": "${TenantAdminSitesIncludeTypes}",
899
1258
  },
900
- "crawler": {
901
- "META:SOURCE": "SharePoint",
1259
+ "viewProps": {
902
1260
  "SharepointUrl": "${sharePointUrl}",
903
1261
  "Oauth2SiteName": "${OAuth2SiteName}",
904
1262
  "Oauth2SitesFile": "${OAuth2SitesFile}",
905
1263
  "SharepointOnline": "${sharePointOnline}",
906
- "IndexUserProfiles": "${IndexUserProfiles}",
907
1264
  "SharepointUrlType": "${URLType}",
1265
+ "ProxyConfigService": "${ProxyConfigService}",
908
1266
  "SharepointAdminUrl": "${sharePointAdminUrl}",
909
1267
  "SharepointMySiteUrl": "${sharePointMySiteUrl}",
910
1268
  "MappedWebApplicationPolicies": "${MappedWebApplicationPolicies}",
1269
+ },
1270
+ "omniProps": {
1271
+ "SharepointUrl": "${sharePointUrl}",
1272
+ "Oauth2SiteName": "${OAuth2SiteName}",
1273
+ "Oauth2SitesFile": "${OAuth2SitesFile}",
1274
+ "SharepointOnline": "true",
1275
+ "SharepointUrlType": "${URLType}",
1276
+ "ProxyConfigService": "${ProxyConfigService}",
1277
+ "SharepointAdminUrl": "${sharePointAdminUrl}",
1278
+ "SharepointMySiteUrl": "${sharePointMySiteUrl}",
1279
+ "MappedWebApplicationPolicies": "false",
911
1280
  "TenantAdminSitesIncludeTypes": "${TenantAdminSitesIncludeTypes}",
912
1281
  },
1282
+ "metadataFields": [
1283
+ "FILEREF",
1284
+ "FILEDIRREF",
1285
+ ],
913
1286
  },
914
- "authRedirect": "",
915
- "metadataFields": ["FILEREF", "FILEDIRREF"],
1287
+ "name": name,
1288
+ "id": "SharePoint",
1289
+ "sourceId": "SharePoint",
916
1290
  }
917
1291
 
918
1292
  request_header = self.request_header()
@@ -925,9 +1299,11 @@ class AVTS(object):
925
1299
  headers=request_header,
926
1300
  timeout=None,
927
1301
  failure_message="Failed to create repository -> '{}'".format(name),
1302
+ show_error=False,
928
1303
  )
929
1304
 
930
1305
  if response is None:
1306
+ self.logger.error("Failed to create repository -> '%s'!", name)
931
1307
  return None
932
1308
 
933
1309
  self.repo_admin_consent(response["id"])
@@ -937,21 +1313,22 @@ class AVTS(object):
937
1313
  # end method definition
938
1314
 
939
1315
  def repo_admin_consent(self, repo_id: str) -> dict | None:
940
- """Send admin consent information for a repository
1316
+ """Send admin consent information for a repository.
941
1317
 
942
1318
  Args:
943
- repo_id (str): id of the repository
1319
+ repo_id (str):
1320
+ The ID of the repository.
944
1321
 
945
1322
  Returns:
946
- dict | None: Parsed response object from the API or None in case of an error
1323
+ dict | None:
1324
+ Parsed response object from the API or None in case of an error
1325
+
947
1326
  """
948
1327
 
949
1328
  request_header = self.request_header()
950
1329
  request_url = self.config()["repoUrl"]
951
1330
 
952
- request_url = (
953
- self.config()["repoUrl"] + "/" + repo_id + "/authorize?admin_consent=true"
954
- )
1331
+ request_url = self.config()["repoUrl"] + "/" + repo_id + "/authorize?admin_consent=true"
955
1332
 
956
1333
  return self.do_request(
957
1334
  url=request_url,
@@ -959,22 +1336,26 @@ class AVTS(object):
959
1336
  headers=request_header,
960
1337
  timeout=None,
961
1338
  failure_message="Failed to set admin_consent for repository -> '{}'".format(
962
- repo_id
1339
+ repo_id,
963
1340
  ),
964
1341
  )
965
1342
 
966
1343
  # end method definition
967
1344
 
968
1345
  def start_crawling(self, repo_name: str) -> list | None:
969
- """Start crawling of a repository
1346
+ """Start crawling of a repository.
970
1347
 
971
1348
  Args:
972
- repo_name (str): name of the repository
1349
+ repo_name (str):
1350
+ The name of the repository.
1351
+
973
1352
  Returns:
974
- list | None: Parsed response object from the API or None in case of an error
1353
+ list | None:
1354
+ Parsed response object from the API or None in case of an error
1355
+
975
1356
  """
976
1357
 
977
- logger.info("Start crawling repository -> %s", repo_name)
1358
+ self.logger.info("Start crawling repository -> '%s'...", repo_name)
978
1359
 
979
1360
  repo = self.get_repo_by_name(name=repo_name)
980
1361
  if repo is None:
@@ -988,20 +1369,24 @@ class AVTS(object):
988
1369
  method="POST",
989
1370
  headers=request_header,
990
1371
  timeout=None,
991
- failure_message="Failed to start crawling repository -> '{}'".format(
992
- repo_name
1372
+ failure_message="Failed to start crawling repository -> '{}'!".format(
1373
+ repo_name,
993
1374
  ),
994
1375
  )
995
1376
 
996
1377
  # end method definition
997
1378
 
998
- def stop_crawling(self, repo_name: str) -> list | None:
999
- """Stop the crawling of a repository
1379
+ def stop_crawling(self, repo_name: str) -> dict | None:
1380
+ """Stop the crawling of a repository.
1000
1381
 
1001
1382
  Args:
1002
- repo_name (str): name of the repository
1383
+ repo_name (str):
1384
+ The name of the repository.
1385
+
1003
1386
  Returns:
1004
- list | None: Parsed response object from the API or None in case of an error
1387
+ dict | None:
1388
+ Parsed response object from the API or None in case of an error
1389
+
1005
1390
  """
1006
1391
 
1007
1392
  repo = self.get_repo_by_name(name=repo_name)
@@ -1016,18 +1401,20 @@ class AVTS(object):
1016
1401
  method="POST",
1017
1402
  headers=request_header,
1018
1403
  timeout=None,
1019
- failure_message="Failed to stop crawling repository -> '{}'".format(
1020
- repo_name
1404
+ failure_message="Failed to stop crawling repository -> '{}'!".format(
1405
+ repo_name,
1021
1406
  ),
1022
1407
  )
1023
1408
 
1024
1409
  # end method definition
1025
1410
 
1026
1411
  def get_repo_list(self) -> list | None:
1027
- """Get a list of all repositories
1412
+ """Get a list of all repositories.
1028
1413
 
1029
1414
  Returns:
1030
- list | None: Parsed response object from the API listing all repositories or None in case of an error
1415
+ list | None:
1416
+ Parsed response object from the API listing all repositories or None in case of an error.
1417
+
1031
1418
  """
1032
1419
 
1033
1420
  request_header = self.request_header()
@@ -1038,18 +1425,22 @@ class AVTS(object):
1038
1425
  method="GET",
1039
1426
  headers=request_header,
1040
1427
  timeout=None,
1041
- failure_message="Failed to get list of repositories to crawl.",
1428
+ failure_message="Failed to get list of repositories to crawl",
1042
1429
  )
1043
1430
 
1044
1431
  # end method definition
1045
1432
 
1046
1433
  def get_repo_by_name(self, name: str) -> dict | None:
1047
- """Get a repository by name
1434
+ """Get a repository by name.
1048
1435
 
1049
1436
  Args:
1050
- name (str): name of the repository
1437
+ name (str):
1438
+ The name of the repository.
1439
+
1051
1440
  Returns:
1052
- dict | None: ID of a repostiory by name or None in case of an error
1441
+ dict | None:
1442
+ ID of a repostiory by name or None in case of an error
1443
+
1053
1444
  """
1054
1445
 
1055
1446
  repo_list = self.get_repo_list()
@@ -1063,3 +1454,85 @@ class AVTS(object):
1063
1454
  )
1064
1455
 
1065
1456
  # end method definition
1457
+
1458
+ def get_certificate_file_content_base64(self, filepath: str) -> str | None:
1459
+ """Return the certificate as a base64 string.
1460
+
1461
+ In Kubernetes deploymnets the certificate is already mounted base64 encoded.
1462
+
1463
+ Args:
1464
+ filepath (str):
1465
+ The path to the certificate file.
1466
+
1467
+ Returns:
1468
+ str | None:
1469
+ Base64 encoded certificate file content.
1470
+
1471
+ """
1472
+
1473
+ if not os.path.isfile(filepath):
1474
+ return None
1475
+
1476
+ file_ext = os.path.splitext(filepath)[1].lower()
1477
+
1478
+ if self.running_in_kubernetes_pod() and file_ext == ".pfx":
1479
+ # Return file directly as already base64 encoded
1480
+ self.logger.warning(
1481
+ "Detected a binary pfx file in Kubernetes environment, expecting it to be already base64 encoded",
1482
+ )
1483
+ with open(filepath, encoding="UTF-8") as file:
1484
+ return file.read().strip()
1485
+
1486
+ else:
1487
+ # Return file as base64 encoded
1488
+ with open(filepath, "rb") as file:
1489
+ # Read the content of the file
1490
+ file_content = file.read()
1491
+ # Convert the bytes to a base64 string
1492
+ return base64.b64encode(file_content).decode("utf-8")
1493
+
1494
+ # end method definition
1495
+
1496
+ def running_in_kubernetes_pod(self) -> bool:
1497
+ """Check if the application is running inside a Kubernetes pod.
1498
+
1499
+ This function determines whether the process is running in a Kubernetes
1500
+ environment by checking for the presence of the `KUBERNETES_SERVICE_HOST`
1501
+ and `KUBERNETES_SERVICE_PORT` environment variables.
1502
+
1503
+ Returns:
1504
+ bool:
1505
+ True if running inside a Kubernetes pod, False otherwise.
1506
+
1507
+ """
1508
+
1509
+ return bool(os.getenv("KUBERNETES_SERVICE_HOST") and os.getenv("KUBERNETES_SERVICE_PORT"))
1510
+
1511
+ # end method definition
1512
+
1513
+ def set_questions(self, questions: list) -> list | None:
1514
+ """Get a list of all repositories.
1515
+
1516
+ Args:
1517
+ questions (list):
1518
+ List of proposed questions.
1519
+
1520
+ Returns:
1521
+ list | None:
1522
+ Parsed response object from the API listing all repositories or None in case of an error.
1523
+
1524
+ """
1525
+
1526
+ request_header = self.request_header()
1527
+ request_url = self.config()["questionsUrl"]
1528
+
1529
+ return self.do_request(
1530
+ url=request_url,
1531
+ method="PUT",
1532
+ headers=request_header,
1533
+ data=json.dumps(questions),
1534
+ timeout=None,
1535
+ failure_message="Failed to set list of questions to ask",
1536
+ )
1537
+
1538
+ # end method definition