hid_data_transfer_lib 0.3.10__tar.gz → 0.4.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.
Files changed (20) hide show
  1. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/PKG-INFO +3 -3
  2. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/README.md +2 -2
  3. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/conf/hid_dt_configuration.py +74 -70
  4. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/hid_dt_lib.py +116 -68
  5. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/keycloak/keycloak_rest.py +4 -2
  6. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/nifi/nifi_client.py +111 -42
  7. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/nifi/nifi_rest.py +157 -16
  8. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/util/util.py +7 -3
  9. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/pyproject.toml +2 -1
  10. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/__init__.py +0 -0
  11. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/conf/__init__.py +0 -0
  12. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/conf/hid_dt.cfg +0 -0
  13. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/conf/hid_dt_local.cfg +0 -0
  14. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/conf/hid_dt_psnc.cfg +0 -0
  15. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/exceptions/__init__.py +0 -0
  16. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/exceptions/hid_dt_exceptions.py +0 -0
  17. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/keycloak/__init__.py +0 -0
  18. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/nifi/__init__.py +0 -0
  19. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/py.typed +0 -0
  20. {hid_data_transfer_lib-0.3.10 → hid_data_transfer_lib-0.4.0}/hid_data_transfer_lib/util/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hid_data_transfer_lib
3
- Version: 0.3.10
3
+ Version: 0.4.0
4
4
  Summary: HiDALGO Data Transfer library provides methods to transfer data between different data providers and consumers using NIFI pipelines
5
5
  License: APL-2.0
6
6
  Author: Jesús Gorroñogoitia
@@ -51,7 +51,7 @@ The Data Transfer Library sends requests to NIFI through its REST API.
51
51
  To use the Data Transfer library, it is required the following requirements:
52
52
  - **Python3** execution environment
53
53
  - **Poetry** python package management tool (optional)
54
- - **NIFI** instance, with a NIFI server SSH account (for keys transfer)
54
+ - **NIFI** instance, with a NIFI 2.X server SSH account (for keys transfer). Latest NIFI supported version: 2.8
55
55
  - **Keycloak** instance, with a KEYCLOAK user's account
56
56
  - **HDFS** instance, with an user's Kerberos principal account
57
57
  - **CKAN** instance, with an user APIKey
@@ -123,7 +123,7 @@ keycloak_login=<user_name>
123
123
  keycloak_password=<password>
124
124
  ```
125
125
 
126
- Under the Nifi section, you must also specify a user account (username, private_key) that grants to upload/download files to the NIFI server (as requested to upload temporary HPC keys or to support local file transfer). This user's account is provided by Hidalgo2 infrastructure provider and it is user's or service's specific.
126
+ Under the Nifi section, you must also specify a user account (username, private_key) that grants to upload/download files to the NIFI server (as requested to upload temporary HPC keys or to support local file transfer). This user's account is provided by Hidalgo2 infrastructure provider and it is user's or service's specific. Note: this user's account requires to belond to group *users*.
127
127
 
128
128
  Under the Keycloak section, you must specify your Keycloak account (username and password). This account granted with access to the NIFI service.
129
129
 
@@ -32,7 +32,7 @@ The Data Transfer Library sends requests to NIFI through its REST API.
32
32
  To use the Data Transfer library, it is required the following requirements:
33
33
  - **Python3** execution environment
34
34
  - **Poetry** python package management tool (optional)
35
- - **NIFI** instance, with a NIFI server SSH account (for keys transfer)
35
+ - **NIFI** instance, with a NIFI 2.X server SSH account (for keys transfer). Latest NIFI supported version: 2.8
36
36
  - **Keycloak** instance, with a KEYCLOAK user's account
37
37
  - **HDFS** instance, with an user's Kerberos principal account
38
38
  - **CKAN** instance, with an user APIKey
@@ -104,7 +104,7 @@ keycloak_login=<user_name>
104
104
  keycloak_password=<password>
105
105
  ```
106
106
 
107
- Under the Nifi section, you must also specify a user account (username, private_key) that grants to upload/download files to the NIFI server (as requested to upload temporary HPC keys or to support local file transfer). This user's account is provided by Hidalgo2 infrastructure provider and it is user's or service's specific.
107
+ Under the Nifi section, you must also specify a user account (username, private_key) that grants to upload/download files to the NIFI server (as requested to upload temporary HPC keys or to support local file transfer). This user's account is provided by Hidalgo2 infrastructure provider and it is user's or service's specific. Note: this user's account requires to belond to group *users*.
108
108
 
109
109
  Under the Keycloak section, you must specify your Keycloak account (username and password). This account granted with access to the NIFI service.
110
110
 
@@ -24,6 +24,7 @@ from hid_data_transfer_lib.exceptions.hid_dt_exceptions import (
24
24
  HidDataTransferException
25
25
  )
26
26
 
27
+ STATUS_SLEEP_LAPSE = 10 # seconds
27
28
 
28
29
  class HidDataTransferConfiguration:
29
30
  """This class manages the configuration of the Data Transfer library"""
@@ -44,21 +45,22 @@ class HidDataTransferConfiguration:
44
45
  __keycloak_login = None
45
46
  __keycloak_passwd = None
46
47
 
47
- __check_status_sleep_lapse = 10 # seconds
48
+ __check_status_sleep_lapse = STATUS_SLEEP_LAPSE # seconds
48
49
 
49
50
  def __init__(self, logging_level: Optional[int] = logging.INFO):
50
51
  self.__logging_level = logging_level
51
52
 
52
- def check_keycloak_conf(self):
53
+ def check_keycloak_conf(self, use_keycloak_account):
53
54
  """Check if keycloak configuration is valid"""
54
- if self.__keycloak_login is None:
55
- raise HidDataTransferException(
56
- "Keycloak login must be set in the configuration"
57
- )
58
- if self.__keycloak_passwd is None:
59
- raise HidDataTransferException(
60
- "Keycloak passwd must be set in the configuration"
61
- )
55
+ if use_keycloak_account:
56
+ if self.__keycloak_login is None:
57
+ raise HidDataTransferException(
58
+ "Keycloak login must be set in the configuration"
59
+ )
60
+ if self.__keycloak_passwd is None:
61
+ raise HidDataTransferException(
62
+ "Keycloak passwd must be set in the configuration"
63
+ )
62
64
  if self.__keycloak_endpoint is None:
63
65
  raise HidDataTransferException(
64
66
  "Keycloak endpoint must be set in the configuration"
@@ -195,6 +197,14 @@ class HidDataTransferConfiguration:
195
197
  """Returns the NIFI login password"""
196
198
  return self.__keycloak_passwd
197
199
 
200
+ def reset_keycloak_login(self):
201
+ """Reset the NIFI login name"""
202
+ self.__keycloak_login = None
203
+
204
+ def reset_keycloak_passwd(self):
205
+ """Reset the NIFI login password"""
206
+ self.__keycloak_passwd = None
207
+
198
208
  def logger(self, logger_name):
199
209
  """Returns the logging level"""
200
210
  return self.__set_logger(logger_name)
@@ -256,7 +266,8 @@ class HidDataTransferConfiguration:
256
266
  self,
257
267
  user_config_file: Optional[str] = None,
258
268
  user_config_dict: Optional[dict] = None,
259
- logging_level: Optional[int] = None
269
+ logging_level: Optional[int] = None,
270
+ use_keycloak_account: Bool = True,
260
271
  ) -> HidDataTransferConfiguration:
261
272
  """
262
273
  Reads the configuration and sets this class' configuration values
@@ -276,38 +287,33 @@ class HidDataTransferConfiguration:
276
287
  self.load_nifi_configuration(config)
277
288
 
278
289
  # Keycloak section
279
- self.load_keycloak_configuration(config)
280
-
281
- # Check if either NIFI or Keycloak login and password are set
282
- if (self.__nifi_login is None and self.__nifi_passwd is None) and (
283
- self.__keycloak_login is None and
284
- self.__keycloak_passwd is None
285
- ):
286
- raise HidDataTransferException(
287
- "Either (KEYCLOAK_LOGIN and KEYCLOAK_PASSWORD) "
288
- "or (NIFI_LOGIN and NIFI_PASSWORD) "
289
- "environment variables must be set"
290
- )
290
+ self.load_keycloak_configuration(
291
+ config, check_configuration = use_keycloak_account)
292
+
293
+ # # Check if either NIFI or Keycloak login and password are set
294
+ # if not use_keycloak_token and (
295
+ # self.__nifi_login is None and self.__nifi_passwd is None
296
+ # ) and (
297
+ # self.__keycloak_login is None and
298
+ # self.__keycloak_passwd is None
299
+ # ):
300
+ # raise HidDataTransferException(
301
+ # "Either (KEYCLOAK_LOGIN and KEYCLOAK_PASSWORD) "
302
+ # "or (NIFI_LOGIN and NIFI_PASSWORD) "
303
+ # "environment variables must be set"
304
+ # )
291
305
 
292
306
  # Logging section
293
307
  if logging_level is not None:
294
308
  self.__logging_level = logging_level
295
309
  else:
296
- logging_level_str = config.get("Logging", "logging_level")
310
+ logging_level_str = config.get("Logging", "logging_level", fallback="Info")
297
311
  self.__logging_level = getattr(
298
312
  logging, logging_level_str.upper(), logging.INFO)
299
313
 
300
314
  # Network section
301
- self.__check_status_sleep_lapse = config.get(
302
- "Network", "check_status_sleep_lapse")
303
-
304
- # Network section
305
- self.__check_status_sleep_lapse = config.get(
306
- "Network", "check_status_sleep_lapse")
307
-
308
- # Network section
309
- self.__check_status_sleep_lapse = config.get(
310
- "Network", "check_status_sleep_lapse")
315
+ self.__check_status_sleep_lapse = int(config.get(
316
+ "Network", "check_status_sleep_lapse", fallback=STATUS_SLEEP_LAPSE))
311
317
 
312
318
  return self
313
319
 
@@ -316,69 +322,67 @@ class HidDataTransferConfiguration:
316
322
  "Error parsing CLI configuration file"
317
323
  ) from ex
318
324
 
319
- def load_keycloak_configuration(self, config):
325
+ def load_keycloak_configuration(self, config, check_configuration = True):
320
326
  """Load Keycloak configuration from config file"""
321
327
  # Read Keycloak login and password from configuration
322
- self.__keycloak_login = config.get("Keycloak", "keycloak_login")
323
- self.__keycloak_passwd = config.get("Keycloak", "keycloak_password")
324
- if (self.__keycloak_login and self.__keycloak_passwd is None) or (
325
- self.__keycloak_login is None and self.__keycloak_passwd
326
- ):
328
+ self.__keycloak_login = config.get("Keycloak", "keycloak_login", fallback=None)
329
+ self.__keycloak_passwd = config.get("Keycloak", "keycloak_password", fallback=None)
330
+ if check_configuration and (
331
+ self.__keycloak_login is None or self.__keycloak_passwd is None):
327
332
  raise HidDataTransferException(
328
333
  "Both KEYCLOAK_LOGIN and KEYCLOAK_PASSWORD "
329
334
  "environment variables must be set"
330
335
  )
331
336
 
332
337
  # Read other keycloak configuration from config file
333
- if self.__keycloak_login or self.__keycloak_passwd:
334
- self.__keycloak_endpoint = config.get(
335
- "Keycloak", "keycloak_endpoint")
336
- if self.__keycloak_endpoint is None:
337
- raise HidDataTransferException(
338
- "Could not find Keycloak endpoint in config file."
339
- )
338
+ self.__keycloak_endpoint = config.get(
339
+ "Keycloak", "keycloak_endpoint", fallback=None)
340
+ if self.__keycloak_endpoint is None:
341
+ raise HidDataTransferException(
342
+ "Could not find Keycloak endpoint in config file."
343
+ )
340
344
 
341
- self.__keycloak_client_id = config.get(
342
- "Keycloak", "keycloak_client_id")
343
- if self.__keycloak_client_id is None:
344
- raise HidDataTransferException(
345
- "Could not find Keycloak client id "
346
- "for NIFI in config file."
347
- )
345
+ self.__keycloak_client_id = config.get(
346
+ "Keycloak", "keycloak_client_id", fallback=None)
347
+ if self.__keycloak_client_id is None:
348
+ raise HidDataTransferException(
349
+ "Could not find Keycloak client id "
350
+ "for NIFI in config file."
351
+ )
348
352
 
349
- keycloak_client_secret = config.get(
350
- "Keycloak", "keycloak_client_secret")
351
- if keycloak_client_secret is not None:
352
- self.__keycloak_client_secret = keycloak_client_secret
353
- else:
354
- raise HidDataTransferException(
355
- "Could not find Keycloak client secret "
356
- "for NIFI in config file."
357
- )
353
+ keycloak_client_secret = config.get(
354
+ "Keycloak", "keycloak_client_secret", fallback=None)
355
+ if keycloak_client_secret is not None:
356
+ self.__keycloak_client_secret = keycloak_client_secret
357
+ else:
358
+ raise HidDataTransferException(
359
+ "Could not find Keycloak client secret "
360
+ "for NIFI in config file."
361
+ )
358
362
 
359
363
  def load_nifi_configuration(self, config):
360
364
  """Load NIFI configuration from config file"""
361
- self.__nifi_endpoint = config.get("Nifi", "nifi_endpoint")
365
+ self.__nifi_endpoint = config.get("Nifi", "nifi_endpoint", fallback=None)
362
366
  if self.__nifi_endpoint is None:
363
367
  raise HidDataTransferException(
364
368
  "Could not find Nifi endpoint in config file."
365
369
  )
366
370
 
367
- nifi_secure_connection = config.get("Nifi", "nifi_secure_connection")
371
+ nifi_secure_connection = config.get("Nifi", "nifi_secure_connection", fallback=None)
368
372
  if nifi_secure_connection is not None:
369
373
  self.__nifi_secure_connection = \
370
374
  nifi_secure_connection.lower() == "true"
371
375
  else:
372
376
  self.__nifi_secure_connection = False
373
377
 
374
- self.__nifi_upload_folder = config.get("Nifi", "nifi_upload_folder")
378
+ self.__nifi_upload_folder = config.get("Nifi", "nifi_upload_folder", fallback=None)
375
379
  if self.__nifi_upload_folder is None:
376
380
  raise HidDataTransferException(
377
381
  "Could not find Nifi nifi_upload_folder in config file"
378
382
  )
379
383
 
380
384
  self.__nifi_download_folder = config.get(
381
- "Nifi", "nifi_download_folder")
385
+ "Nifi", "nifi_download_folder", fallback=None)
382
386
  if self.__nifi_download_folder is None:
383
387
  raise HidDataTransferException(
384
388
  "Could not find Nifi nifi_download_folder in config file"
@@ -386,9 +390,9 @@ class HidDataTransferConfiguration:
386
390
 
387
391
  # Optional NIFI server account in case of local file transfer
388
392
  self.__nifi_server_user_name = config.get(
389
- "Nifi", "nifi_server_username")
393
+ "Nifi", "nifi_server_username", fallback=None)
390
394
  self.__nifi_server_private_key = config.get(
391
- "Nifi", "nifi_server_private_key")
395
+ "Nifi", "nifi_server_private_key", fallback=None)
392
396
 
393
397
  if (
394
398
  self.__nifi_server_user_name and
@@ -400,7 +404,7 @@ class HidDataTransferConfiguration:
400
404
  "environment variables must be set"
401
405
  )
402
406
 
403
- nifi_concurrent_tasks = config.get("Nifi", "nifi_concurrent_tasks")
407
+ nifi_concurrent_tasks = config.get("Nifi", "nifi_concurrent_tasks", fallback=None)
404
408
  if nifi_concurrent_tasks is not None:
405
409
  self.__nifi_concurrent_tasks = int(nifi_concurrent_tasks)
406
410
  else:
@@ -34,10 +34,11 @@ from hid_data_transfer_lib.nifi.nifi_client import (
34
34
  NIFIClient, ExecutionType, AccountingInfo
35
35
  )
36
36
 
37
- KERBEROS_PRINCIPAL = "kerberos.principal"
38
37
  HDFS_DATA_FOLDER = "hdfs.data_folder"
38
+ HDFS_DATA_FILENAME = "hdfs.data_filename"
39
39
  HDFS_FILENAME = "hdfs.filename"
40
40
  HPC_TARGET_FOLDER = "hpc.target_folder"
41
+ HPC_TARGET_FILENAME = "hpc.target_filename"
41
42
  HPC_DATA_FOLDER = "hpc.data_folder"
42
43
  HPC_FILENAME = "hpc.filename"
43
44
  HPC_FILENAME_REGEX = "hpc.filename.regex"
@@ -69,13 +70,16 @@ class HIDDataTransfer:
69
70
  """
70
71
  self.__conf = conf
71
72
  self.__secure = secure
72
- self.__nifi_client = NIFIClient().configure(conf, secure)
73
+ self.use_keycloak_account = (keycloak_token is None)
74
+ self.__nifi_client = NIFIClient().configure(
75
+ conf, secure, use_keycloak_account=self.use_keycloak_account)
73
76
  self.__logger = self.__conf.logger("hid_data_transfer_lib")
74
77
  self.__keycloak_token = keycloak_token
75
78
 
76
79
  def reset(self):
77
80
  """ Reset NIFIClient configuration"""
78
- self.__nifi_client = NIFIClient().configure(self.__conf, self.__secure)
81
+ self.__nifi_client = NIFIClient().configure(
82
+ self.__conf, self.__secure, use_keycloak_account=self.use_keycloak_account)
79
83
 
80
84
  # Helper methods
81
85
  def process_wildcards(self, name):
@@ -158,9 +162,13 @@ class HIDDataTransfer:
158
162
  hpc_secret_key=hpc_secret_key_path,
159
163
  data_source=data_source,
160
164
  data_target=data_target,
165
+ recursive=recursive,
166
+ )
167
+
168
+ processors_with_kerberos = ["ListHDFS", "FetchHDFS"]
169
+ kerberos_account = SimpleNamespace(
161
170
  kerberos_principal=kerberos_principal,
162
171
  kerberos_password=kerberos_password,
163
- recursive=recursive,
164
172
  )
165
173
 
166
174
  self.__logger.info(
@@ -170,12 +178,26 @@ class HIDDataTransfer:
170
178
  try:
171
179
  arguments: Dict[str, Any] = {}
172
180
  self.__nifi_client.add_default_hpc_parameters(arguments, args)
173
- arguments[HPC_TARGET_FOLDER] = args.data_target
181
+
174
182
  arguments[HDFS_DATA_FOLDER] = \
175
183
  "/".join(args.data_source.split("/")[:-1])
176
184
  arguments[HDFS_FILENAME] = \
177
185
  self.process_wildcards(args.data_source.split("/")[-1])
178
- arguments[KERBEROS_PRINCIPAL] = args.kerberos_principal
186
+
187
+ # If multiple data source files are selected, data_target represents a folder
188
+ # Else If data_target ends with / represents a folder, otherwise a file
189
+ arguments[HPC_TARGET_FILENAME] = ""
190
+ if args.data_source and (
191
+ "*" in args.data_source or
192
+ "?" in args.data_source
193
+ ):
194
+ arguments[HPC_TARGET_FOLDER] = args.data_target
195
+ elif args.data_target.endswith('/'):
196
+ arguments[HPC_TARGET_FOLDER] = args.data_target[:-1]
197
+ else:
198
+ arguments[HPC_TARGET_FOLDER] = "/".join(args.data_target.split("/")[:-1])
199
+ arguments[HPC_TARGET_FILENAME] = "/".join(args.data_target.split("/")[-1:])
200
+
179
201
  arguments[RECURSIVE] = args.recursive
180
202
 
181
203
  execution_type = {
@@ -197,14 +219,6 @@ class HIDDataTransfer:
197
219
  args
198
220
  )
199
221
 
200
- processors_with_hdfs_sensitive_parameters = \
201
- ["ListHDFS", "FetchHDFS"]
202
- sensitive_parameters.update(
203
- self.__nifi_client.set_sensitive_hdfs_parameters(
204
- processors_with_hdfs_sensitive_parameters, args
205
- )
206
- )
207
-
208
222
  if not args.hpc_secret_key and not args.hpc_password:
209
223
  raise HidDataTransferException(HPC_ACCOUNT_MSG)
210
224
 
@@ -227,6 +241,8 @@ class HIDDataTransfer:
227
241
  accounting_target="PutSFTP",
228
242
  concurrent_tasks=concurrent_tasks_dict,
229
243
  ignore_bulletins_in_processors=["PutSFTP"],
244
+ processors_with_kerberos=processors_with_kerberos,
245
+ kerberos_account=kerberos_account,
230
246
  )
231
247
 
232
248
  except Exception as ex:
@@ -387,6 +403,12 @@ class HIDDataTransfer:
387
403
  kerberos_password=kerberos_password,
388
404
  )
389
405
 
406
+ processors_with_kerberos = ["ListHDFS", "FetchHDFS"]
407
+ kerberos_account = SimpleNamespace(
408
+ kerberos_principal=kerberos_principal,
409
+ kerberos_password=kerberos_password,
410
+ )
411
+
390
412
  self.__logger.info(
391
413
  "executing hdfs2hpc_2fa command with args: %s",
392
414
  self.__nifi_client.format_args_to_string(args),
@@ -399,7 +421,6 @@ class HIDDataTransfer:
399
421
  "/".join(args.data_source.split("/")[:-1])
400
422
  arguments[HDFS_FILENAME] = \
401
423
  self.process_wildcards(args.data_source.split("/")[-1])
402
- arguments[KERBEROS_PRINCIPAL] = args.kerberos_principal
403
424
 
404
425
  # set local.data_folder to keep dataset retrieved from HDFS
405
426
  __uuid = uuid.uuid4()
@@ -422,16 +443,6 @@ class HIDDataTransfer:
422
443
  ],
423
444
  }
424
445
 
425
- sensitive_parameters = {}
426
-
427
- processors_with_hdfs_sensitive_parameters = \
428
- ["ListHDFS", "FetchHDFS"]
429
- sensitive_parameters.update(
430
- self.__nifi_client.set_sensitive_hdfs_parameters(
431
- processors_with_hdfs_sensitive_parameters, args
432
- )
433
- )
434
-
435
446
  if not args.hpc_secret_key:
436
447
  raise HidDataTransferException(HPC_ACCOUNT_MSG)
437
448
 
@@ -450,10 +461,12 @@ class HIDDataTransfer:
450
461
  execution_type,
451
462
  arguments,
452
463
  keys_arguments=keys_arguments,
453
- sensitive_parameters=sensitive_parameters,
464
+ sensitive_parameters=None,
454
465
  keycloak_token=self.__keycloak_token,
455
466
  accounting_target="PutFile",
456
467
  concurrent_tasks=concurrent_tasks_dict,
468
+ processors_with_kerberos=processors_with_kerberos,
469
+ kerberos_account=kerberos_account,
457
470
  )
458
471
  process_ids.append(process_id)
459
472
 
@@ -462,7 +475,7 @@ class HIDDataTransfer:
462
475
  execution_type,
463
476
  arguments,
464
477
  keys_arguments=keys_arguments,
465
- sensitive_parameters=sensitive_parameters,
478
+ sensitive_parameters=None,
466
479
  keycloak_token=self.__keycloak_token,
467
480
  processor_with_2fa="SftpTransferToHPCWithVPNCode",
468
481
  callback_2fa=callback_2fa,
@@ -536,6 +549,12 @@ class HIDDataTransfer:
536
549
  recursive=recursive,
537
550
  )
538
551
 
552
+ processors_with_kerberos = ["PutHDFS"]
553
+ kerberos_account = SimpleNamespace(
554
+ kerberos_principal=kerberos_principal,
555
+ kerberos_password=kerberos_password,
556
+ )
557
+
539
558
  self.__logger.info(
540
559
  "executing hpc2hdfs command with args: %s",
541
560
  self.__nifi_client.format_args_to_string(args),
@@ -548,8 +567,21 @@ class HIDDataTransfer:
548
567
  arguments[HPC_DATA_FOLDER] = args.data_source[
549
568
  : args.data_source.rfind("/")
550
569
  ]
551
- arguments[HDFS_DATA_FOLDER] = args.data_target
552
- arguments[KERBEROS_PRINCIPAL] = args.kerberos_principal
570
+
571
+ # If multiple data source files are selected, data_target represents a folder
572
+ # Else If data_target ends with / represents a folder, otherwise a file
573
+ arguments[HDFS_DATA_FILENAME] = ""
574
+ if args.data_source and (
575
+ "*" in args.data_source or
576
+ "?" in args.data_source
577
+ ):
578
+ arguments[HDFS_DATA_FOLDER] = args.data_target
579
+ elif args.data_target.endswith('/'):
580
+ arguments[HDFS_DATA_FOLDER] = args.data_target[:-1]
581
+ else:
582
+ arguments[HDFS_DATA_FOLDER] = "/".join(args.data_target.split("/")[:-1])
583
+ arguments[HDFS_DATA_FILENAME] = "/".join(args.data_target.split("/")[-1:])
584
+
553
585
  arguments[RECURSIVE] = args.recursive
554
586
 
555
587
  execution_type = {
@@ -572,14 +604,6 @@ class HIDDataTransfer:
572
604
  processors_with_hpc_sensitive_parameters, args
573
605
  )
574
606
 
575
- processors_with_hdfs_sensitive_parameters = \
576
- ["PutHDFS"]
577
- sensitive_parameters.update(
578
- self.__nifi_client.set_sensitive_hdfs_parameters(
579
- processors_with_hdfs_sensitive_parameters, args
580
- )
581
- )
582
-
583
607
  if not args.hpc_secret_key and not args.hpc_password:
584
608
  raise HidDataTransferException(HPC_ACCOUNT_MSG)
585
609
 
@@ -602,6 +626,8 @@ class HIDDataTransfer:
602
626
  accounting_target="PutHDFS",
603
627
  concurrent_tasks=concurrent_tasks_dict,
604
628
  ignore_bulletins_in_processors=["FetchSFTP"],
629
+ processors_with_kerberos=processors_with_kerberos,
630
+ kerberos_account=kerberos_account,
605
631
  )
606
632
 
607
633
  except Exception as ex:
@@ -712,6 +738,12 @@ class HIDDataTransfer:
712
738
  kerberos_password=kerberos_password,
713
739
  )
714
740
 
741
+ processors_with_kerberos = ["PutHDFS"]
742
+ kerberos_account = SimpleNamespace(
743
+ kerberos_principal=kerberos_principal,
744
+ kerberos_password=kerberos_password,
745
+ )
746
+
715
747
  self.__logger.info(
716
748
  "executing hpc2hdfs_2fa command with args: %s",
717
749
  self.__nifi_client.format_args_to_string(args),
@@ -728,7 +760,6 @@ class HIDDataTransfer:
728
760
  : args.data_source.rfind("/")
729
761
  ]
730
762
  arguments[HDFS_DATA_FOLDER] = args.data_target
731
- arguments[KERBEROS_PRINCIPAL] = args.kerberos_principal
732
763
 
733
764
  # set local.data_folder to keep dataset retrieved from HPC
734
765
  __uuid = uuid.uuid4()
@@ -758,14 +789,6 @@ class HIDDataTransfer:
758
789
  processors_with_hpc_sensitive_parameters, args
759
790
  )
760
791
 
761
- processors_with_hdfs_sensitive_parameters = \
762
- ["PutHDFS"]
763
- sensitive_parameters.update(
764
- self.__nifi_client.set_sensitive_hdfs_parameters(
765
- processors_with_hdfs_sensitive_parameters, args
766
- )
767
- )
768
-
769
792
  if not args.hpc_secret_key and not args.hpc_password:
770
793
  raise HidDataTransferException(HPC_ACCOUNT_MSG)
771
794
 
@@ -788,7 +811,6 @@ class HIDDataTransfer:
788
811
  keycloak_token=self.__keycloak_token,
789
812
  processor_with_2fa="SftpTransferFromHPCWithVPNCode",
790
813
  callback_2fa=callback_2fa,
791
- accounting_target="PutHDFS",
792
814
  concurrent_tasks=concurrent_tasks_dict,
793
815
  )
794
816
  process_ids.append(process_id)
@@ -802,6 +824,8 @@ class HIDDataTransfer:
802
824
  keycloak_token=self.__keycloak_token,
803
825
  accounting_target="PutHDFS",
804
826
  concurrent_tasks=concurrent_tasks_dict,
827
+ processors_with_kerberos=processors_with_kerberos,
828
+ kerberos_account=kerberos_account,
805
829
  )
806
830
  process_ids.append(process_id)
807
831
 
@@ -913,6 +937,12 @@ class HIDDataTransfer:
913
937
  recursive=recursive,
914
938
  )
915
939
 
940
+ processors_with_kerberos = ["ListHDFS", "FetchHDFS"]
941
+ kerberos_account = SimpleNamespace(
942
+ kerberos_principal=kerberos_principal,
943
+ kerberos_password=kerberos_password,
944
+ )
945
+
916
946
  self.__logger.info(
917
947
  "executing hdfs2ckan command with args: %s",
918
948
  self.__nifi_client.format_args_to_string(args),
@@ -923,7 +953,6 @@ class HIDDataTransfer:
923
953
  "/".join(args.data_source.split("/")[:-1])
924
954
  arguments[HDFS_FILENAME] = \
925
955
  self.process_wildcards(args.data_source.split("/")[-1])
926
- arguments[KERBEROS_PRINCIPAL] = args.kerberos_principal
927
956
  self.__nifi_client.add_default_ckan_parameters(arguments, args)
928
957
  arguments[self.__nifi_client.CKAN_RESOURCE] = \
929
958
  self.generate_ckan_resource_name(args)
@@ -943,14 +972,6 @@ class HIDDataTransfer:
943
972
  processors_with_ckan_sensitive_parameters, args
944
973
  )
945
974
 
946
- processors_with_hdfs_sensitive_parameters = \
947
- ["ListHDFS", "FetchHDFS"]
948
- sensitive_parameters.update(
949
- self.__nifi_client.set_sensitive_hdfs_parameters(
950
- processors_with_hdfs_sensitive_parameters, args
951
- )
952
- )
953
-
954
975
  # Set the number of concurrent tasks
955
976
  # for parallel processors in the pipeline
956
977
  if concurrent_tasks is None:
@@ -982,6 +1003,8 @@ class HIDDataTransfer:
982
1003
  accounting_target="MergeContent",
983
1004
  concurrent_tasks=concurrent_tasks_dict,
984
1005
  synchronization_processor_name="MergeContent",
1006
+ processors_with_kerberos=processors_with_kerberos,
1007
+ kerberos_account=kerberos_account,
985
1008
  )
986
1009
 
987
1010
  except Exception as ex:
@@ -1033,6 +1056,12 @@ class HIDDataTransfer:
1033
1056
  kerberos_password=kerberos_password,
1034
1057
  )
1035
1058
 
1059
+ processors_with_kerberos = ["PutHDFS"]
1060
+ kerberos_account = SimpleNamespace(
1061
+ kerberos_principal=kerberos_principal,
1062
+ kerberos_password=kerberos_password,
1063
+ )
1064
+
1036
1065
  self.__logger.info(
1037
1066
  "executing ckan2hdfs command with args: %s",
1038
1067
  self.__nifi_client.format_args_to_string(args),
@@ -1042,8 +1071,20 @@ class HIDDataTransfer:
1042
1071
  self.__nifi_client.add_default_ckan_parameters(arguments, args)
1043
1072
  arguments[self.__nifi_client.CKAN_RESOURCE] = \
1044
1073
  self.process_wildcards(args.ckan_resource)
1045
- arguments[HDFS_DATA_FOLDER] = args.data_target
1046
- arguments[KERBEROS_PRINCIPAL] = args.kerberos_principal
1074
+
1075
+ # If multiple ckan resource files are selected, data_target represents a folder
1076
+ # Else If data_target ends with / represents a folder, otherwise a file
1077
+ arguments[HDFS_DATA_FILENAME] = ""
1078
+ if args.ckan_resource and (
1079
+ "*" in args.ckan_resource or
1080
+ "?" in args.ckan_resource
1081
+ ):
1082
+ arguments[HDFS_DATA_FOLDER] = args.data_target
1083
+ elif args.data_target.endswith('/'):
1084
+ arguments[HDFS_DATA_FOLDER] = args.data_target[:-1]
1085
+ else:
1086
+ arguments[HDFS_DATA_FOLDER] = "/".join(args.data_target.split("/")[:-1])
1087
+ arguments[HDFS_DATA_FILENAME] = "/".join(args.data_target.split("/")[-1:])
1047
1088
 
1048
1089
  execution_type = {
1049
1090
  "CKANFileDownloader": ExecutionType.ONCE,
@@ -1056,14 +1097,6 @@ class HIDDataTransfer:
1056
1097
  processors_with_ckan_sensitive_parameters, args
1057
1098
  )
1058
1099
 
1059
- processors_with_hdfs_sensitive_parameters = \
1060
- ["PutHDFS"]
1061
- sensitive_parameters.update(
1062
- self.__nifi_client.set_sensitive_hdfs_parameters(
1063
- processors_with_hdfs_sensitive_parameters, args
1064
- )
1065
- )
1066
-
1067
1100
  # Set the number of concurrent tasks
1068
1101
  # for parallel processors in the pipeline
1069
1102
  if concurrent_tasks is None:
@@ -1095,7 +1128,9 @@ class HIDDataTransfer:
1095
1128
  sensitive_parameters,
1096
1129
  keycloak_token=self.__keycloak_token,
1097
1130
  accounting_target="PutHDFS",
1098
- concurrent_tasks=concurrent_tasks_dict
1131
+ concurrent_tasks=concurrent_tasks_dict,
1132
+ processors_with_kerberos=processors_with_kerberos,
1133
+ kerberos_account=kerberos_account,
1099
1134
  )
1100
1135
 
1101
1136
  except Exception as ex:
@@ -1169,11 +1204,24 @@ class HIDDataTransfer:
1169
1204
  try:
1170
1205
  arguments: Dict[str, Any] = {}
1171
1206
  self.__nifi_client.add_default_hpc_parameters(arguments, args)
1172
- arguments[HPC_TARGET_FOLDER] = args.data_target
1173
1207
  self.__nifi_client.add_default_ckan_parameters(arguments, args)
1174
1208
  arguments[self.__nifi_client.CKAN_RESOURCE] = \
1175
1209
  self.process_wildcards(args.ckan_resource)
1176
1210
 
1211
+ # If multiple ckan resource files are selected, data_target represents a folder
1212
+ # Else If data_target ends with / represents a folder, otherwise a file
1213
+ arguments[HPC_TARGET_FILENAME] = ""
1214
+ if args.ckan_resource and (
1215
+ "*" in args.ckan_resource or
1216
+ "?" in args.ckan_resource
1217
+ ):
1218
+ arguments[HPC_TARGET_FOLDER] = args.data_target
1219
+ elif args.data_target.endswith('/'):
1220
+ arguments[HPC_TARGET_FOLDER] = args.data_target[:-1]
1221
+ else:
1222
+ arguments[HPC_TARGET_FOLDER] = "/".join(args.data_target.split("/")[:-1])
1223
+ arguments[HPC_TARGET_FILENAME] = "/".join(args.data_target.split("/")[-1:])
1224
+
1177
1225
  keys_arguments = {
1178
1226
  self.__nifi_client.HPC_SECRET_KEY_PATH: arguments[
1179
1227
  self.__nifi_client.HPC_SECRET_KEY_PATH
@@ -69,12 +69,13 @@ class KeycloakRESTClient:
69
69
 
70
70
  def configure(
71
71
  self, conf: HidDataTransferConfiguration,
72
- secure: bool = False, refresh: bool = False
72
+ secure: bool = False, refresh: bool = False,
73
+ use_keycloak_account = True,
73
74
  ) -> KeycloakRESTClient:
74
75
  """constructs a API client,
75
76
  with the Keycloak endpoint taken from configuration
76
77
  """
77
- conf.check_keycloak_conf()
78
+ conf.check_keycloak_conf(use_keycloak_account)
78
79
  self.__conf = conf
79
80
  self.__refresh = refresh
80
81
  self.__keycloak_login = conf.keycloak_login()
@@ -117,6 +118,7 @@ class KeycloakRESTClient:
117
118
 
118
119
  def set_token(self, user, token, expires_in, refresh_token):
119
120
  """Set the given token and renews it every time it expires"""
121
+ self.__keycloak_login = user
120
122
  self.__token = token
121
123
  self.__refresh_token = refresh_token
122
124
  self.__set_expires_in(expires_in)
@@ -135,14 +135,15 @@ class NIFIClient:
135
135
  self.check_status_sleep_lapse = 1
136
136
 
137
137
  def configure(self, conf: HidDataTransferConfiguration,
138
- secure: bool = False) -> NIFIClient:
138
+ secure: bool = False, use_keycloak_account = True) -> NIFIClient:
139
139
  """constructs a NIFI client,
140
140
  with the NIFI endpoint taken from configuration
141
141
  """
142
142
  self.__conf = conf
143
143
  self.nifi_rest_client = NIFIRESTClient().configure(conf, secure)
144
+ self.use_keycloak_account = use_keycloak_account
144
145
  self.keycloak_rest_client = KeycloakRESTClient().configure(
145
- conf, secure, refresh=True)
146
+ conf, secure, refresh=True, use_keycloak_account=self.use_keycloak_account)
146
147
  self.__logger = self.__conf.logger("nifi.v2.client")
147
148
  self.check_status_sleep_lapse = int(
148
149
  self.__conf.check_status_sleep_lapse())
@@ -152,14 +153,21 @@ class NIFIClient:
152
153
  """gets the NIFI access token,
153
154
  using NIFI account taken from configuration
154
155
  this access token is required for any further request sent to NIFI
156
+ If a taken was provided upon configuration, it gets the latest renewed
155
157
  """
158
+
156
159
  if self.__conf.nifi_login() and self.__conf.nifi_passwd():
157
160
  return self.nifi_rest_client.get_access_token()
158
- elif self.__conf.keycloak_login() and self.__conf.keycloak_passwd():
161
+ # Keycloak token provided, but not an account
162
+ elif not self.use_keycloak_account:
159
163
  return self.keycloak_rest_client.get_token()
160
- else:
164
+ # Keycloak token not provided, check an account is available
165
+ elif self.use_keycloak_account and self.__conf.keycloak_login() and \
166
+ self.__conf.keycloak_passwd():
167
+ return self.keycloak_rest_client.get_token()
168
+ else: # Not valid token provider configuration found
161
169
  raise HidDataTransferException(
162
- "NIFI or Keycloak login and password must be provided"
170
+ "NIFI or Keycloak account (login and password) or Keycloak token must be provided"
163
171
  )
164
172
 
165
173
  def set_access_token(self, keycloak_token):
@@ -192,13 +200,13 @@ class NIFIClient:
192
200
  def get_process_group_processors(self, group_id) -> dict:
193
201
  """gets the processors of a process group identified by id"""
194
202
  return self.nifi_rest_client.process_group_get_request(
195
- group_id, self.token(), "processors"
203
+ group_id, self.token(), entity="processors"
196
204
  )
197
205
 
198
206
  def get_process_group_connections(self, group_id) -> dict:
199
207
  """gets the connetions of a process group identified by id"""
200
208
  return self.nifi_rest_client.process_group_get_request(
201
- group_id, self.token(), "connections"
209
+ group_id, self.token(), entity="connections"
202
210
  )
203
211
 
204
212
  def get_processors_sequence(self, group_id) -> list:
@@ -324,6 +332,22 @@ class NIFIClient:
324
332
  processor["component"]["config"][configuration_name] = \
325
333
  configuration_value
326
334
 
335
+ def update_kpus_kerberos_account(
336
+ self, kpus, kerberos_account,
337
+ ):
338
+ """updates the Kerberos account of a kpus"""
339
+ kpus["component"]["properties"]['Kerberos Principal'] = \
340
+ kerberos_account.kerberos_principal
341
+ kpus["component"]["properties"]['Kerberos Password'] = \
342
+ kerberos_account.kerberos_password
343
+
344
+ def update_processor_kus(
345
+ self, processor, kus_id,
346
+ ):
347
+ """updates the KUs of a processor"""
348
+ processor["component"]['config']["properties"]['Kerberos User Service'] = \
349
+ kus_id
350
+
327
351
  # DELETE requests
328
352
 
329
353
  # MULTI-REQUESTS methods
@@ -890,6 +914,8 @@ class NIFIClient:
890
914
  accounting_target: Optional[str] = None,
891
915
  concurrent_tasks: Optional[dict] = None,
892
916
  ignore_bulletins_in_processors=None,
917
+ processors_with_kerberos=None,
918
+ kerberos_account=None,
893
919
  ) -> tuple[str, AccountingInfo]:
894
920
  """MAIN method to run a processor from a template identify by name
895
921
  The procedure is as follows:
@@ -946,6 +972,11 @@ class NIFIClient:
946
972
  self.__logger.info(
947
973
  "Process group %s created\n", process_group["id"])
948
974
 
975
+ # Set Kerberos configuration
976
+ if kerberos_account and processors_with_kerberos:
977
+ self.set_kerberos_configuration(
978
+ process_group["id"], kerberos_account, processors_with_kerberos)
979
+
949
980
  # Get ordered list of all processors in the process group
950
981
  processors = self.get_ordered_processors_in_group(
951
982
  process_group["id"])
@@ -1102,6 +1133,8 @@ class NIFIClient:
1102
1133
  concurrent_tasks: Optional[dict] = None,
1103
1134
  synchronization_processor_name=None,
1104
1135
  ignore_bulletins_in_processors=None,
1136
+ processors_with_kerberos=None,
1137
+ kerberos_account=None,
1105
1138
  ) -> tuple[str, AccountingInfo]:
1106
1139
  """MAIN method to run a processor from a template identify by name
1107
1140
  The procedure is as follows:
@@ -1127,6 +1160,11 @@ class NIFIClient:
1127
1160
  self.__logger.info(
1128
1161
  "Process group %s created\n", process_group["id"])
1129
1162
 
1163
+ # Set Kerberos configuration
1164
+ if kerberos_account and processors_with_kerberos:
1165
+ self.set_kerberos_configuration(
1166
+ process_group["id"], kerberos_account, processors_with_kerberos)
1167
+
1130
1168
  # Accounting initialization
1131
1169
  pipeline_start_time = time.time()
1132
1170
 
@@ -1156,7 +1194,8 @@ class NIFIClient:
1156
1194
  accounting_target,
1157
1195
  parameter_context,
1158
1196
  synchronization_processor_name=synchronization_processor_name,
1159
- first_processor_expected_outgoing_flowfiles=first_processor_expected_outgoing_flowfiles,
1197
+ first_processor_expected_outgoing_flowfiles=
1198
+ first_processor_expected_outgoing_flowfiles,
1160
1199
  ignore_bulletins_in_processors=ignore_bulletins_in_processors,
1161
1200
  )
1162
1201
 
@@ -1338,21 +1377,21 @@ class NIFIClient:
1338
1377
 
1339
1378
  ok, response = self.start_process_group(process_group)
1340
1379
 
1341
- # Wait for first processor to find source files
1342
- if not self.has_processor_executed_once_and_produce_outgoing_flowfiles(
1343
- processors[0]):
1344
- raise HidDataTransferException("Source files not found")
1345
-
1346
- # manage the synchronization point if required
1347
- if synchronization_processor_name:
1348
- self.synchronize_processor_execution(
1349
- process_group['id'],
1350
- synchronization_processor_name,
1351
- execution_type,
1352
- first_processor_expected_outgoing_flowfiles,
1353
- ignore_bulletins_in_processors)
1354
-
1355
1380
  if ok:
1381
+ # Wait for first processor to find source files
1382
+ if not self.has_processor_executed_once_and_produce_outgoing_flowfiles(
1383
+ processors[0]):
1384
+ raise HidDataTransferException("Source files not found")
1385
+
1386
+ # manage the synchronization point if required
1387
+ if synchronization_processor_name:
1388
+ self.synchronize_processor_execution(
1389
+ process_group['id'],
1390
+ synchronization_processor_name,
1391
+ execution_type,
1392
+ first_processor_expected_outgoing_flowfiles,
1393
+ ignore_bulletins_in_processors)
1394
+
1356
1395
  self.wait_for_process_group_completion(
1357
1396
  process_group['id'], processors,
1358
1397
  ignore_bulletins_in_processors)
@@ -1401,6 +1440,30 @@ class NIFIClient:
1401
1440
  processor, self.token())
1402
1441
  return processor
1403
1442
 
1443
+ def set_kpus_kerberos_account(
1444
+ self, kpus, kerberos_account):
1445
+ """Set the Kerberos account of a kpus instance"""
1446
+
1447
+ self.update_kpus_kerberos_account(
1448
+ kpus,
1449
+ kerberos_account,
1450
+ )
1451
+ kpus = self.nifi_rest_client.update_kpus(
1452
+ kpus, self.token())
1453
+ return kpus
1454
+
1455
+ def set_processor_kus(
1456
+ self, processor, kus_id):
1457
+ """Set the Kerberos User Service of a processor instance"""
1458
+
1459
+ self.update_processor_kus(
1460
+ processor,
1461
+ kus_id,
1462
+ )
1463
+ processor = self.nifi_rest_client.update_processor(
1464
+ processor, self.token())
1465
+ return processor
1466
+
1404
1467
  def set_2fa_token(self, processor, parameter_context,
1405
1468
  processor_with_2fa, callback_2fa):
1406
1469
  '''Set the 2FA token in context parameter
@@ -1707,19 +1770,25 @@ class NIFIClient:
1707
1770
  )
1708
1771
  return sensitive_parameters
1709
1772
 
1710
- def set_sensitive_hdfs_parameters(
1711
- self, processors_with_hdfs_sensitive_parameters, args
1712
- ):
1713
- """Set sensitive parameters for HDFS connection"""
1714
- sensitive_parameters = {}
1715
- for processor in processors_with_hdfs_sensitive_parameters:
1716
- inner_sensitive_parameters = {}
1717
- sensitive_parameters[processor] = inner_sensitive_parameters
1718
- if args.kerberos_password:
1719
- inner_sensitive_parameters[self.KERBEROS_PASSWORD] = (
1720
- args.kerberos_password
1721
- )
1722
- return sensitive_parameters
1773
+ def set_kerberos_configuration(self, process_group_id,
1774
+ kerberos_account, processors_with_kerberos):
1775
+ """Set Kerberos configuration for processors"""
1776
+ # Implementation to configure Kerberos for specified processors
1777
+ processors = self.get_ordered_processors_in_group(process_group_id)
1778
+
1779
+ # Create a Kerberos Password User Service (KPUS) associated to a process group
1780
+ kpus = self.nifi_rest_client.instantiate_kpus(process_group_id, self.token())
1781
+ # Set principal and password in KPUS
1782
+ kpus = self.set_kpus_kerberos_account(kpus, kerberos_account)
1783
+ # Enable KUPS
1784
+ kpus = self.nifi_rest_client.enable_kpus(kpus, self.token())
1785
+
1786
+ for processor_name in processors_with_kerberos:
1787
+ # Get processor by name
1788
+ processor = self.find_processor_by_name(processors, processor_name)
1789
+
1790
+ # Save changes in processor
1791
+ self.set_processor_kus(processor, kpus['id'])
1723
1792
 
1724
1793
  def clean_process_group_up(self, process_group_id, retry=True):
1725
1794
  """Clean up the process group and associated parameter context"""
@@ -1729,13 +1798,17 @@ class NIFIClient:
1729
1798
  try:
1730
1799
  # Remove transferred keys
1731
1800
  self.__logger.debug("Removing uploaded keys\n")
1732
- self.remove_uploaded_keys(process_group["id"])
1801
+ self.remove_uploaded_keys(process_group_id)
1733
1802
 
1734
1803
  # Stop process_group and wait for all processosr to be stopped
1735
- self.stop_process_group(process_group["id"])
1804
+ self.stop_process_group(process_group_id)
1805
+
1806
+ # Disable controller services
1807
+ self.nifi_rest_client.disable_controller_services(
1808
+ process_group_id, self.token())
1736
1809
 
1737
1810
  # Empty process group queues
1738
- self.empty_process_group_queues(process_group["id"])
1811
+ self.empty_process_group_queues(process_group_id)
1739
1812
 
1740
1813
  # Get parameter context
1741
1814
  if "parameterContext" in process_group["component"]:
@@ -1759,10 +1832,6 @@ class NIFIClient:
1759
1832
  except HidDataTransferException:
1760
1833
  # If case of exception try clean up again
1761
1834
  if retry:
1762
- # self.stop_process_group(
1763
- # process_group["id"])
1764
- # self.empty_process_group_queues(
1765
- # process_group["id"])
1766
1835
  self.clean_process_group_up(
1767
1836
  process_group["id"], retry=False)
1768
1837
 
@@ -126,14 +126,16 @@ class NIFIRESTClient:
126
126
  url = self.build_url("nifi-api", "flow", "process-groups", "root")
127
127
  return self._get_entity(url, token)
128
128
 
129
- def process_group_get_request(self, group_id, token, entity=None) -> dict:
129
+ def process_group_get_request(
130
+ self, group_id, token, process_group_flow=False, entity=None) -> dict:
130
131
  """helper method to process a get request for a process group
131
132
  identified by id"""
132
- url = (
133
- self.build_url("nifi-api", "process-groups", group_id, entity)
134
- if entity
135
- else self.build_url("nifi-api", "process-groups", group_id)
136
- )
133
+ if entity:
134
+ url = self.build_url("nifi-api", "process-groups", group_id, entity)
135
+ elif process_group_flow:
136
+ url = self.build_url("nifi-api", "flow", "process-groups", group_id)
137
+ else:
138
+ url = self.build_url("nifi-api", "process-groups", group_id)
137
139
  return self._get_entity(url, token)
138
140
 
139
141
  def get_templates(self, token) -> dict:
@@ -318,8 +320,8 @@ class NIFIRESTClient:
318
320
  "Content-Type": self.__JSON_CONTENT_TYPE,
319
321
  "Authorization": f"Bearer {token}",
320
322
  }
321
- x = random.uniform(0, 3)
322
- y = random.uniform(0, 3)
323
+ x = random.uniform(0, 1000)
324
+ y = random.uniform(0, 1000)
323
325
  payload = {
324
326
  "revision": {"version": 0},
325
327
  "disconnectedNodeAcknowledged": False,
@@ -353,10 +355,11 @@ class NIFIRESTClient:
353
355
  if response.ok:
354
356
  response_json = response.content.decode()
355
357
  process_group = json.loads(response_json)
358
+
356
359
  # Rename process_group as flow_definition_name + process_group_id
357
360
  process_group_name = process_group["component"]["name"]
358
361
  process_group = self.rename_process_group(
359
- process_group, process_group_name, token
362
+ process_group["id"], process_group_name, token
360
363
  )
361
364
  return process_group
362
365
  else:
@@ -365,18 +368,34 @@ class NIFIRESTClient:
365
368
  f"{response.content.decode()}"
366
369
  )
367
370
 
368
- def rename_process_group(self, process_group, name, token) -> str:
369
- """creates a process group from a given template"""
371
+ def rename_process_group(self, process_group_id, name, token) -> str:
372
+ # TODO Send only what is required to modify the name
373
+ """rename a process group name"""
374
+
375
+ # Reload process_group
376
+ process_group = self.process_group_get_request(
377
+ process_group_id, token, process_group_flow=True)
378
+
370
379
  headers = {
371
380
  "Content-Type": self.__JSON_CONTENT_TYPE,
372
381
  "Authorization": f"Bearer {token}",
373
382
  }
374
- process_group_id = process_group["id"]
375
- process_group["component"]["name"] = name + "_" + process_group_id
376
- payload = json.dumps(process_group)
383
+ process_group_id = process_group["processGroupFlow"]["id"]
384
+
385
+ payload = {
386
+ "component": {
387
+ "id": process_group_id,
388
+ "name": name + "_" + process_group_id
389
+ },
390
+ "revision": {"version": process_group["revision"]["version"]},
391
+ }
392
+
393
+ # payload = json.dumps(process_group)
394
+ payload_json = json.dumps(payload)
395
+
377
396
  response = requests.put(
378
397
  url=self.build_url("nifi-api", "process-groups", process_group_id),
379
- data=payload,
398
+ data=payload_json,
380
399
  verify=self.__conf.nifi_secure_connection(),
381
400
  headers=headers,
382
401
  timeout=self.__REQUEST_TIMEOUT,
@@ -472,6 +491,51 @@ class NIFIRESTClient:
472
491
  f"{response.content.decode()}"
473
492
  )
474
493
 
494
+ def instantiate_kpus(
495
+ self, process_group_id, token
496
+ ) -> dict:
497
+ """creates a Kerberos Password User Service"""
498
+ headers = {
499
+ "Content-Type": self.__JSON_CONTENT_TYPE,
500
+ "Authorization": f"Bearer {token}",
501
+ }
502
+
503
+ payload = {
504
+ "revision": {"version": 0},
505
+ "disconnectedNodeAcknowledged": False,
506
+ "component": {
507
+ "bundle": {
508
+ "group": "org.apache.nifi",
509
+ "artifact": "nifi-kerberos-user-service-nar",
510
+ "version": "2.8.0"
511
+ },
512
+ "type": "org.apache.nifi.kerberos.KerberosPasswordUserService"
513
+ }
514
+ } # /nifi-api/process-groups/e2dde1d0-019c-1000-b433-43f8611cef38/controller-services
515
+ payload_json = json.dumps(payload)
516
+ response = requests.post(
517
+ url=self.build_url(
518
+ "nifi-api",
519
+ "process-groups",
520
+ process_group_id,
521
+ "controller-services",
522
+ ),
523
+ data=payload_json,
524
+ verify=self.__conf.nifi_secure_connection(),
525
+ headers=headers,
526
+ timeout=self.__REQUEST_TIMEOUT,
527
+ )
528
+ if response.ok:
529
+ response_json = response.content.decode()
530
+ kpus = json.loads(response_json)
531
+ return kpus
532
+ else:
533
+ raise HidDataTransferException(
534
+ "Instantiate kpus exception: "
535
+ f"{response.content.decode()}"
536
+ )
537
+
538
+
475
539
  # PUT requests
476
540
 
477
541
  def associate_parameter_context_to_group(
@@ -486,7 +550,6 @@ class NIFIRESTClient:
486
550
  payload = {
487
551
  "revision": {
488
552
  "version": process_group["revision"]["version"],
489
- # "clientId": parameter_context['revision']['clientId']
490
553
  },
491
554
  "disconnectedNodeAcknowledged": False,
492
555
  "component": {
@@ -541,6 +604,84 @@ class NIFIRESTClient:
541
604
  f"Processor exception: {response.content.decode()}"
542
605
  )
543
606
 
607
+ def update_kpus(self, kpus, token) -> dict:
608
+ """updates a kpus identified by id"""
609
+ headers = {
610
+ "Content-Type": self.__JSON_CONTENT_TYPE,
611
+ "Authorization": f"Bearer {token}",
612
+ }
613
+ response = requests.put(
614
+ url=self.build_url("nifi-api", "controller-services", kpus["id"]),
615
+ verify=self.__conf.nifi_secure_connection(),
616
+ data=json.dumps(kpus),
617
+ headers=headers,
618
+ timeout=self.__REQUEST_TIMEOUT,
619
+ )
620
+ if response.ok:
621
+ response_json = response.content.decode()
622
+ return json.loads(response_json)
623
+ else:
624
+ raise HidDataTransferException(
625
+ f"Controller service exception: {response.content.decode()}"
626
+ )
627
+
628
+ def enable_kpus(self, kpus, token) -> dict:
629
+ """updates a kpus identified by id"""
630
+ headers = {
631
+ "Content-Type": self.__JSON_CONTENT_TYPE,
632
+ "Authorization": f"Bearer {token}",
633
+ }
634
+
635
+ payload = {
636
+ "revision":{"version":kpus["revision"]["version"]},
637
+ "state":"ENABLED"
638
+ }
639
+ payload_json = json.dumps(payload)
640
+
641
+ response = requests.put(
642
+ url=self.build_url("nifi-api", "controller-services", kpus['id'], "run-status"),
643
+ verify=self.__conf.nifi_secure_connection(),
644
+ data=payload_json,
645
+ headers=headers,
646
+ timeout=self.__REQUEST_TIMEOUT,
647
+ )
648
+ if response.ok:
649
+ response_json = response.content.decode()
650
+ return json.loads(response_json)
651
+ else:
652
+ raise HidDataTransferException(
653
+ f"Controller service exception: {response.content.decode()}"
654
+ )
655
+
656
+ def disable_controller_services(self, process_group_id, token):
657
+ ''' disable the controller services'''
658
+ headers = {
659
+ "Content-Type": self.__JSON_CONTENT_TYPE,
660
+ "Authorization": f"Bearer {token}",
661
+ }
662
+
663
+ payload = {
664
+ "id": process_group_id,
665
+ "state":"DISABLED"
666
+ }
667
+ payload_json = json.dumps(payload)
668
+
669
+ response = requests.put(
670
+ url=self.build_url(
671
+ "nifi-api", "flow", "process-groups", process_group_id, "controller-services"),
672
+ verify=self.__conf.nifi_secure_connection(),
673
+ data=payload_json,
674
+ headers=headers,
675
+ timeout=self.__REQUEST_TIMEOUT,
676
+ )
677
+ if response.ok:
678
+ response_json = response.content.decode()
679
+ return json.loads(response_json)
680
+ else:
681
+ raise HidDataTransferException(
682
+ f"Process group exception: {response.content.decode()}"
683
+ )
684
+
544
685
  # DELETE requests
545
686
  def delete_update_request(self, entity_name,
546
687
  entity_id, request_id, token) -> bool:
@@ -127,10 +127,12 @@ def sftp_put(
127
127
  sftp.put(source, target)
128
128
  if target_permissions:
129
129
  command = f"chmod {target_permissions} {target}"
130
- ssh.exec_command(command)
130
+ _, stdout, _ = ssh.exec_command(command)
131
+ stdout.channel.recv_exit_status() # Block until done, drains buffers
131
132
  if target_ownership:
132
133
  command = f"chown {target_ownership} {target}"
133
- ssh.exec_command(command)
134
+ _, stdout, _ = ssh.exec_command(command)
135
+ stdout.channel.recv_exit_status() # Block until done, drains buffers
134
136
  except Exception as ex:
135
137
  msg = "Exception transferring keys with SFTP"
136
138
  raise HidDataTransferException(msg) from ex
@@ -189,8 +191,9 @@ def __ssh_execute_command(server, username, command, private_key=None):
189
191
  ssh.connect(server, username=username)
190
192
  else:
191
193
  ssh.connect(server, username=username, key_filename=private_key)
192
- _, _, stderr = ssh.exec_command(command)
194
+ _, stdout, stderr = ssh.exec_command(command)
193
195
  error = stderr.read().decode("utf-8")
196
+ stdout.channel.recv_exit_status() # Block until done, drains buffers
194
197
  if error:
195
198
  raise HidDataTransferException(
196
199
  f"Error '{error}' executing command '{command}'"
@@ -217,6 +220,7 @@ def ssh_list(path, server, username, private_key=None, prefix=None) -> list:
217
220
  else:
218
221
  command = f'find {path} -maxdepth 1 -type f -printf "%p\n"'
219
222
  _, stdout, _ = ssh.exec_command(command)
223
+ stdout.channel.recv_exit_status() # Block until done, drains buffers
220
224
 
221
225
  # read lines from stdout in a list
222
226
  files = []
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hid_data_transfer_lib"
3
- version = "0.3.10"
3
+ version = "0.4.0"
4
4
  description = "HiDALGO Data Transfer library provides methods to transfer data between different data providers and consumers using NIFI pipelines"
5
5
  authors = [
6
6
  { name = "Jesús Gorroñogoitia", email = "jesus.gorronogoitia@eviden.com" },
@@ -28,6 +28,7 @@ include = ["py.typed"]
28
28
  [tool.poetry.group.dev.dependencies]
29
29
  flake8 = "^7.2.0"
30
30
  black = "^25.1.0"
31
+ ipykernel = "^7.2.0"
31
32
 
32
33
 
33
34
  [tool.poetry.group.types.dependencies]