oc-cdtapi 3.15.1__py3-none-any.whl → 3.31.2__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.
oc_cdtapi/ForemanAPI.py CHANGED
@@ -2,6 +2,8 @@ import json
2
2
  import logging
3
3
  import posixpath
4
4
  import re
5
+ import time
6
+ from time import sleep
5
7
 
6
8
  from oc_cdtapi.API import HttpAPI, HttpAPIError
7
9
  from collections import namedtuple, defaultdict
@@ -9,7 +11,8 @@ from datetime import datetime, timedelta
9
11
  from packaging import version
10
12
 
11
13
  class ForemanAPIError(HttpAPIError):
12
- pass
14
+ def __str__(self):
15
+ return self.text
13
16
 
14
17
 
15
18
  class ForemanAPI(HttpAPI):
@@ -20,7 +23,10 @@ class ForemanAPI(HttpAPI):
20
23
  _error = ForemanAPIError
21
24
  _env_prefix = "FOREMAN"
22
25
 
23
- headers = {"Accept": "version=2,application/json", "Content-Type": "application/json"}
26
+ headers = {
27
+ "Accept": "application/json;version=2",
28
+ "Content-Type": "application/json"
29
+ }
24
30
 
25
31
  def __init__(self, *args, **kwargs):
26
32
  """
@@ -67,11 +73,11 @@ class ForemanAPI(HttpAPI):
67
73
  return self.__foreman_version_major
68
74
 
69
75
  def re(self, req):
70
- if not req.startswith("foreman_puppet"):
76
+ if not req.startswith(("foreman_puppet", "ansible")):
71
77
  return posixpath.join(self.root, "api", req)
72
78
  else:
73
79
  return posixpath.join(self.root, req)
74
-
80
+
75
81
  def get_host_by_owner(self, owner):
76
82
  """
77
83
  wrapper for api v1/v2
@@ -84,18 +90,18 @@ class ForemanAPI(HttpAPI):
84
90
  elif self.apiversion == 2:
85
91
  logging.debug('Passing to get_host_by_owner_v2')
86
92
  return self.get_host_by_owner_v2(owner)
87
-
93
+
88
94
  def get_host_by_owner_v2(self, owner):
89
95
  logging.debug('Reached get_host_by_owner_v2')
90
96
  logging.debug('owner = [%s]' % owner)
91
97
  params = {
92
- 'search': f'owner_login={owner}',
98
+ 'search': f'owner={owner}',
93
99
  'per_page': 'all'
94
100
  }
95
- response = self.get('hosts', params=params)
101
+ response = self.get('hosts', params=params).json()
96
102
  results = response.get('results')
97
103
  return results
98
-
104
+
99
105
  def get_environment(self, env_name):
100
106
  """
101
107
  wrapper for api v1/v2
@@ -243,14 +249,14 @@ class ForemanAPI(HttpAPI):
243
249
  deploy_on, custom_json):
244
250
  """
245
251
  Creates a host using the default parameters or the ones from an external json
246
- note that create_vm in engine actually sends db_task instead of hostname and custom_json,
252
+ note that create_vm in engine actually sends db_task instead of hostname and custom_json,
247
253
  other parms are ignored
248
254
  """
249
255
  logging.debug('Reached create_host_v1')
250
256
  logging.debug('hostname = [%s]' % hostname)
251
257
 
252
258
  if not exp_date:
253
- exp_date = self.defs.exp_date
259
+ exp_date = self._set_expiration()
254
260
  if not location_id:
255
261
  location_id = self.defs.location_id
256
262
  if not hostgroup:
@@ -283,10 +289,10 @@ class ForemanAPI(HttpAPI):
283
289
  default_params.update(custom)
284
290
 
285
291
  if not default_params["is_owned_by"]:
286
- raise ForemanAPIError("The owner id is not specified")
292
+ raise ForemanAPIError(code=400, text="The owner id is not specified")
287
293
 
288
294
  if not default_params["name"]:
289
- raise ForemanAPIError("The hostname is not specified")
295
+ raise ForemanAPIError(code=400, text="The hostname is not specified")
290
296
 
291
297
  logging.debug("ForemanAPI is about to send the following payload:")
292
298
  logging.debug(default_params)
@@ -303,7 +309,7 @@ class ForemanAPI(HttpAPI):
303
309
  disk = 50
304
310
  owner_id = None
305
311
 
306
- exp_date = self.defs.exp_date
312
+ exp_date = self._set_expiration()
307
313
  location_id = self.defs.location_id
308
314
  hostgroup = self.defs.hostgroup
309
315
  deploy_on = self.defs.deploy_on
@@ -341,10 +347,10 @@ class ForemanAPI(HttpAPI):
341
347
  default_params.update(custom)
342
348
 
343
349
  if not default_params["is_owned_by"]:
344
- raise ForemanAPIError("The owner id is not specified")
350
+ raise ForemanAPIError(code=400, text="The owner id is not specified")
345
351
 
346
352
  if not default_params["name"]:
347
- raise ForemanAPIError("The hostname is not specified")
353
+ raise ForemanAPIError(code=400, text="The hostname is not specified")
348
354
 
349
355
  if not default_params.get('hostgroup_id'):
350
356
  hostgroup = self.get_hostgroup_id('stands')
@@ -626,7 +632,7 @@ class ForemanAPI(HttpAPI):
626
632
  """
627
633
  logging.debug('Reached override_smart_class_v1')
628
634
  request = self.post(posixpath.join("smart_class_parameters", str(scid),
629
- "override_values"), headers=self.headers, data=params)
635
+ "override_values"), headers=self.headers, data=params)
630
636
 
631
637
  def override_smart_class_v2(self, scid, params):
632
638
  """
@@ -697,7 +703,8 @@ class ForemanAPI(HttpAPI):
697
703
  """
698
704
  logging.debug('Reached add_puppet_class_to_host_v2')
699
705
  logging.debug('Params to be sent: %s' % params)
700
- response = self.post(posixpath.join('foreman_puppet', 'api', 'hosts', hostname, 'puppetclass_ids'), headers=self.headers, data=params)
706
+ response = self.post(posixpath.join('foreman_puppet', 'api', 'hosts', hostname, 'puppetclass_ids'),
707
+ headers=self.headers, data=params)
701
708
 
702
709
  def get_subnets(self):
703
710
  """
@@ -773,6 +780,14 @@ class ForemanAPI(HttpAPI):
773
780
  logging.debug(response)
774
781
  return response
775
782
 
783
+ def is_host_powered_on(self, hostname):
784
+ """
785
+ Returns true if host is powered on
786
+ """
787
+ logging.debug('Reached is_host_powered_on')
788
+ response = self.get(posixpath.join("hosts", hostname, "power")).json()
789
+ return response['state'] == 'on'
790
+
776
791
  def host_power(self, hostname, action):
777
792
  """
778
793
  wrapper for api v1/v2
@@ -794,7 +809,7 @@ class ForemanAPI(HttpAPI):
794
809
  actions = ["start", "stop"]
795
810
 
796
811
  if action not in actions:
797
- raise ForemanAPIError("500 - Incorrect power action was provided")
812
+ raise ForemanAPIError(code=500, text="Incorrect power action was provided")
798
813
 
799
814
  params = json.dumps({"power_action": action})
800
815
  request = self.put(posixpath.join("hosts", hostname, "power"), headers=self.headers, data=params)
@@ -806,7 +821,7 @@ class ForemanAPI(HttpAPI):
806
821
  logging.debug('Reached host_power_v2')
807
822
  actions = ["on", "off"]
808
823
  if action not in actions:
809
- raise ForemanAPIError("500 - Incorrect power action was provided")
824
+ raise ForemanAPIError(code=500, text="Incorrect power action was provided")
810
825
  params = json.dumps({"power_action": action})
811
826
  request = self.put(posixpath.join('hosts', hostname, "power"), headers=self.headers, data=params)
812
827
  if not request.status_code == 200:
@@ -866,8 +881,8 @@ class ForemanAPI(HttpAPI):
866
881
 
867
882
  for hostgroup in hostgroups:
868
883
  if hostgroup.get("name") == hostgroup_name:
869
- return hostgroup.get ('id')
870
-
884
+ return hostgroup.get('id')
885
+
871
886
  logging.debug("Hostgroup [%s] not found, returning None" % hostgroup_name)
872
887
  return None
873
888
 
@@ -972,7 +987,7 @@ class ForemanAPI(HttpAPI):
972
987
  def set_host_expiry_v1(self, hostname, expiry):
973
988
  """
974
989
  Attempts to set host expiry date
975
- :param hostname: full hostname
990
+ :param hostname: full hostname
976
991
  :param expiry: expiry date in format yyyy-mm-dd
977
992
  """
978
993
  logging.debug('Reached set_host_expiry_v1')
@@ -984,7 +999,7 @@ class ForemanAPI(HttpAPI):
984
999
  def set_host_expiry_v2(self, hostname, expiry):
985
1000
  """
986
1001
  Attempts to set host expiry date
987
- :param hostname: full hostname
1002
+ :param hostname: full hostname
988
1003
  :param expiry: expiry date in format yyyy-mm-dd
989
1004
  """
990
1005
  logging.debug('Reached set_host_expiry_v2')
@@ -1051,7 +1066,7 @@ class ForemanAPI(HttpAPI):
1051
1066
  """
1052
1067
  logging.debug('Reached get_flavor_id_v1')
1053
1068
  flavors_list = self.get(posixpath.join("compute_resources", str(compute_resource_id),
1054
- "available_flavors")).json()["results"]
1069
+ "available_flavors")).json()["results"]
1055
1070
 
1056
1071
  try:
1057
1072
  flavor_id = next(flavor["id"] for flavor in flavors_list if flavor["name"] == flavor_name)
@@ -1169,6 +1184,44 @@ class ForemanAPI(HttpAPI):
1169
1184
  logging.debug('Passing to get_host_uuid_v1')
1170
1185
  return self.get_host_uuid_v1(hostname)
1171
1186
 
1187
+ def get_host_disk_size(self, hostname):
1188
+ """
1189
+ :param hostname: str
1190
+ :return: int
1191
+ """
1192
+ logging.debug('Reached get_host_disk_size')
1193
+ response = self.get(posixpath.join("hosts", hostname, "vm_compute_attributes"))
1194
+ data = response.json()
1195
+ try:
1196
+ volumes = data.get("volumes_attributes", {})
1197
+ volume = volumes.get("0") or volumes.get(0)
1198
+ if volume and "size_gb" in volume:
1199
+ return int(volume["size_gb"])
1200
+ logging.error('Could not find size_gb in vm_compute_attributes for [%s]' % hostname)
1201
+ return None
1202
+ except (KeyError, ValueError, TypeError) as e:
1203
+ logging.error('Error parsing disk size for [%s]: %s' % (hostname, e))
1204
+ return None
1205
+
1206
+ def get_host_memory_mb(self, hostname):
1207
+ """
1208
+ :param hostname: str
1209
+ :return: int
1210
+ """
1211
+ logging.debug('Reached get_host_memory_mb')
1212
+ response = self.get(posixpath.join("hosts", hostname, "vm_compute_attributes"))
1213
+ data = response.json()
1214
+
1215
+ try:
1216
+ memory_mb = data.get("memory_mb")
1217
+ if memory_mb is not None:
1218
+ return int(memory_mb)
1219
+ logging.error('Could not find memory_mb in vm_compute_attributes for [%s]' % hostname)
1220
+ return None
1221
+ except (ValueError, TypeError) as e:
1222
+ logging.error('Error parsing memory_mb for [%s]: %s' % (hostname, e))
1223
+ return None
1224
+
1172
1225
  def get_all_users(self):
1173
1226
  """
1174
1227
  :return: list
@@ -1195,16 +1248,15 @@ class ForemanAPI(HttpAPI):
1195
1248
  logging.debug('Reached set_host_owner')
1196
1249
  owner_id = self.get_owner(owner)
1197
1250
  owner_type = 'User'
1198
-
1251
+
1199
1252
  if not owner_id:
1200
1253
  owner_id = self.get_usergroup_id(owner)
1201
1254
  owner_type = 'Usergroup'
1202
-
1255
+
1203
1256
  if not owner_id:
1204
- raise ForemanAPIError(f"The owner [{owner}] is not found")
1205
-
1206
- self.update_host(hostname, {"host": {"owner_id": owner_id, "owner_type": owner_type}})
1257
+ raise ForemanAPIError(code=404, text=f"The owner [{owner}] is not found")
1207
1258
 
1259
+ self.update_host(hostname, {"host": {"owner_id": owner_id, "owner_type": owner_type}})
1208
1260
 
1209
1261
  def set_host_owner_id(self, hostname, owner_id):
1210
1262
  """
@@ -1217,3 +1269,307 @@ class ForemanAPI(HttpAPI):
1217
1269
  pl['host'] = {}
1218
1270
  pl['host']['owner_id'] = owner_id
1219
1271
  self.update_host(hostname, pl)
1272
+
1273
+ def set_backup_policy(self, hostname, backup_policy):
1274
+ """
1275
+ Change backup policy
1276
+ :param hostname: str
1277
+ :param backup_policy: str
1278
+ """
1279
+ logging.debug('Reached set_backup_policy')
1280
+ payload = {
1281
+ "parameter": {
1282
+ "value": backup_policy
1283
+ }
1284
+ }
1285
+ self.update_host(hostname=hostname, payload=payload)
1286
+
1287
+ def get_job_template_id(self, template_name):
1288
+ """
1289
+ Get template ID by name. Cache results to avoid repeated API calls.
1290
+ :param template_name: str
1291
+ :return template_id: int
1292
+ """
1293
+ logging.debug('Reached get_job_template_id')
1294
+ if not hasattr(self, '_template_cache'):
1295
+ self._template_cache = {}
1296
+
1297
+ if template_name in self._template_cache:
1298
+ return self._template_cache[template_name]
1299
+
1300
+ response = self.get(posixpath.join("job_templates"), params={'per_page': 'all'})
1301
+ templates = response.json()["results"]
1302
+
1303
+ for job in templates:
1304
+ self._template_cache[job["name"]] = job["id"]
1305
+
1306
+ template_id = self._template_cache.get(template_name)
1307
+ if not template_id:
1308
+ raise ForemanAPIError(code=404, text=f"Job template '{template_name}' not found")
1309
+
1310
+ logging.debug(f"Template id for [{template_name}] is [{template_id}]")
1311
+ return template_id
1312
+
1313
+ def send_job_invocation(self, task_name, vm_name, **inputs):
1314
+ """
1315
+ Send a job invocation for a specific task and VM.
1316
+ :param task_name: str
1317
+ :param vm_name: str
1318
+ :param **inputs: kwargs
1319
+ :return job_id
1320
+ """
1321
+ logging.debug('Reached send_job_invocation')
1322
+ task_configs = {
1323
+ "resize_partition": {
1324
+ "template_name": "Run \"cdt-resize-partition\" role CDT",
1325
+ "description": "Partition Resizing"
1326
+ }
1327
+ }
1328
+
1329
+ config = task_configs.get(task_name)
1330
+ if not config:
1331
+ raise ForemanAPIError(code=404, text=f"Unknown task: {task_name}")
1332
+
1333
+ logging.debug(f"config for [{task_name}] is [{config}]")
1334
+
1335
+ template_id = self.get_job_template_id(config["template_name"])
1336
+
1337
+ payload = {
1338
+ "job_invocation": {
1339
+ "job_template_id": template_id,
1340
+ "targeting_type": "static_query",
1341
+ "search_query": f'name = "{vm_name}"',
1342
+ "description_format": config["description"],
1343
+ "inputs": inputs,
1344
+ "ansible": {
1345
+ "tags": "",
1346
+ "tags_flag": "include"
1347
+ }
1348
+ }
1349
+ }
1350
+
1351
+ logging.debug(f"About to send job invocations with following request [{payload}]")
1352
+ response = self.post(
1353
+ posixpath.join("job_invocations"),
1354
+ headers=self.headers,
1355
+ json=payload
1356
+ )
1357
+
1358
+ return response.json()["id"]
1359
+
1360
+ def is_job_invocation_success(self, job_id, timeout=300, poll_interval=5):
1361
+ """
1362
+ Get a job status by given id.
1363
+ :param job_id: str
1364
+ :param timeout: int
1365
+ :param poll_interval: int
1366
+ :return status: bool
1367
+ """
1368
+ start_time = time.time()
1369
+ while True:
1370
+ if time.time() - start_time > timeout:
1371
+ raise ForemanAPIError(code=500, text=f"Job {job_id} timed out after {timeout} seconds")
1372
+ response = self.get(posixpath.join("job_invocations", str(job_id)))
1373
+ is_pending = bool(response.json().get("pending", False))
1374
+ if not is_pending:
1375
+ break
1376
+ logging.debug(f"job still pending, sleeping for {poll_interval} second")
1377
+ sleep(poll_interval)
1378
+
1379
+ return bool(response.json().get("succeeded", False))
1380
+
1381
+ def get_parameter_value(self, hostname, parameter_name):
1382
+ """
1383
+ Get a parameter value by given parameter_name.
1384
+ :param hostname: str
1385
+ :param parameter_name: str or list/tuple - Single parameter name or list/tuple of parameter names
1386
+ :return: dict or None - Dict of {name: value} for found parameters, None if none found
1387
+ """
1388
+ host_info = self.get_host_info(hostname=hostname)
1389
+ host_parameters = host_info.get("parameters")
1390
+ if not host_parameters:
1391
+ return None
1392
+
1393
+ result = {}
1394
+ if isinstance(parameter_name, str):
1395
+ for host_parameter in host_parameters:
1396
+ if host_parameter["name"] == parameter_name:
1397
+ result[host_parameter["name"]] = host_parameter["value"]
1398
+ return result
1399
+ return None
1400
+
1401
+ elif isinstance(parameter_name, (list, tuple)):
1402
+ param_set = set(parameter_name)
1403
+ for host_parameter in host_parameters:
1404
+ if host_parameter["name"] in param_set:
1405
+ result[host_parameter["name"]] = host_parameter["value"]
1406
+ return result if result else None
1407
+
1408
+ return None
1409
+
1410
+ def set_parameter_value(self, hostname, parameter_name, parameter_value, auto_create=False):
1411
+ """
1412
+ Set a parameter value by given parameter_name and parameter_value.
1413
+ Also, auto_create option will automatically create the parameter if not appear.
1414
+ :param hostname: str
1415
+ :param parameter_name: str
1416
+ :param parameter_value: str
1417
+ :param auto_create: bool
1418
+ """
1419
+ logging.debug('Reached set_parameter_value')
1420
+ payload = {
1421
+ "parameter": {
1422
+ "name": parameter_name,
1423
+ "value": parameter_value
1424
+ }
1425
+ }
1426
+
1427
+ if self.get_parameter_value(hostname=hostname, parameter_name=parameter_name) is None:
1428
+ logging.debug(f"parameter {parameter_name} is not created yet")
1429
+ if auto_create:
1430
+ logging.debug(f"creating parameter {parameter_name}")
1431
+ self.post(posixpath.join("hosts", hostname, "parameters"), headers=self.headers, json=payload)
1432
+ return
1433
+
1434
+ logging.debug(f"updating {parameter_name}")
1435
+ self.put(posixpath.join("hosts", hostname, "parameters", parameter_name), headers=self.headers, json=payload)
1436
+
1437
+ def get_host_ansible_roles(self, hostname):
1438
+ """
1439
+ Get ansible role from host.
1440
+ :param hostname: str
1441
+ """
1442
+ logging.debug('Reached get_host_ansible_roles')
1443
+
1444
+ response = self.get(posixpath.join("hosts", hostname, "ansible_roles"), headers=self.headers)
1445
+ roles = response.json()
1446
+
1447
+ return roles
1448
+
1449
+ def get_ansible_role(self, roles=None):
1450
+ """
1451
+ Get ansible role id by its name.
1452
+ :param role_name: str
1453
+ """
1454
+ logging.debug('Reached get_ansible_role')
1455
+
1456
+ if not roles:
1457
+ params = {'per_page': 'all'}
1458
+ response = self.get(posixpath.join("ansible", "api", "ansible_roles"), params=params, headers=self.headers).json()
1459
+
1460
+ logging.debug(f"About to return {response.get('subtotal')} roles")
1461
+ return response.get("results")
1462
+
1463
+ params = {'search': None}
1464
+
1465
+ if isinstance(roles, (str, int)):
1466
+ roles = [roles]
1467
+
1468
+ query = []
1469
+ for role in roles:
1470
+ if isinstance(role, str):
1471
+ query.append(f"name={role}")
1472
+ elif isinstance(role, int):
1473
+ query.append(f"id={role}")
1474
+ else:
1475
+ raise ForemanAPIError(f"Invalid role type: {type(role)}")
1476
+
1477
+ params["search"] = " or ".join(query)
1478
+
1479
+ logging.debug(f"Search param is {params.get('search')}")
1480
+ response = self.get(posixpath.join("ansible", "api", "ansible_roles"), params=params, headers=self.headers).json()
1481
+
1482
+ logging.debug(f"About to return {response.get('subtotal')} roles")
1483
+ return response.get("results")
1484
+
1485
+ def assign_ansible_roles(self, hostname, roles):
1486
+ """
1487
+ Assign an ansible roles and override value by given role_id and kwargs to specific hostname.
1488
+ :param hostname: str
1489
+ :param roles: list/str/int
1490
+ """
1491
+ logging.debug('Reached assign_ansible_roles')
1492
+ logging.debug(f'Hostname: [{hostname}]')
1493
+ logging.debug(f'Roles: [{roles}]')
1494
+
1495
+ role_ids = []
1496
+ role_names = []
1497
+
1498
+ if isinstance(roles, (str, int)):
1499
+ roles = [roles]
1500
+
1501
+ roles = self.get_ansible_role(roles)
1502
+ for role in roles:
1503
+ role_ids.append(role.get("id"))
1504
+ role_names.append(role.get("name"))
1505
+
1506
+ payload = {
1507
+ "ansible_role_ids": role_ids
1508
+ }
1509
+
1510
+ # Assign ansible roles
1511
+ logging.debug(f'About to set roles {role_names}')
1512
+ self.post(posixpath.join("hosts", hostname, "assign_ansible_roles"), headers=self.headers, json=payload)
1513
+
1514
+ def assign_ansible_roles_and_override(self, hostname, roles):
1515
+ """
1516
+ Assign an ansible roles and override value by given role_id and kwargs to specific hostname.
1517
+ :param hostname: str
1518
+ :param roles: dict
1519
+ """
1520
+ logging.debug('Reached assign_ansible_roles_and_override')
1521
+ logging.debug(f'Hostname: [{hostname}]')
1522
+ logging.debug(f'Roles: [{roles}]')
1523
+
1524
+ role_ids = []
1525
+ updated_dict = {}
1526
+
1527
+ if not isinstance(roles, dict):
1528
+ raise ForemanAPIError(code=400, text="Input must be in dict")
1529
+
1530
+ temp_roles = []
1531
+ for role, variable in roles.items():
1532
+ temp_roles.append(role)
1533
+
1534
+ new_roles = self.get_ansible_role(temp_roles)
1535
+ for new_role in new_roles:
1536
+ role_ids.append(new_role.get("id"))
1537
+ if not roles.get(new_role.get("id")):
1538
+ continue
1539
+
1540
+ roles[new_role.get("name")] = roles.pop(new_role.get("id"))
1541
+
1542
+ payload = {
1543
+ "ansible_role_ids": role_ids
1544
+ }
1545
+
1546
+ for key, values in roles.items():
1547
+ params = {"search": f"ansible_role={key}", "per_page": "all"}
1548
+
1549
+ variables = self.get(posixpath.join("ansible", "api", "ansible_variables"), params=params).json()["results"]
1550
+ valid_params = {v["parameter"]: v["id"] for v in variables}
1551
+
1552
+ missing = [p for p in values.keys() if p not in valid_params]
1553
+ if missing:
1554
+ raise ForemanAPIError(code=400, text=f"Role '{key}' missing variables: {', '.join(missing)}")
1555
+
1556
+ for param_name, param_value in values.items():
1557
+ variable_id = valid_params[param_name]
1558
+ updated_dict[f"{variable_id}-{param_name}"] = param_value
1559
+
1560
+ # Assign ansible roles
1561
+ logging.debug(f'About to set roles {roles}')
1562
+ self.post(posixpath.join("hosts", hostname, "assign_ansible_roles"), headers=self.headers, json=payload)
1563
+
1564
+ for key, value in updated_dict.items():
1565
+ payload = {
1566
+ "ansible_variable_id": key,
1567
+ "override_value": {
1568
+ "match": f"fqdn={hostname}",
1569
+ "value": value
1570
+ }
1571
+ }
1572
+ logging.debug(f'About to override ansible variables {key} with value {value}')
1573
+
1574
+ self.post(posixpath.join("ansible", "api", "ansible_override_values"), headers=self.headers, json=payload)
1575
+ sleep(1)
oc_cdtapi/PgAPI.py ADDED
@@ -0,0 +1,437 @@
1
+ import logging
2
+ import os
3
+
4
+ from oc_cdtapi import API
5
+ from oc_cdtapi.API import HttpAPIError
6
+
7
+
8
+ class PostgresAPI(API.HttpAPI):
9
+ _env_prefix = 'PSQL'
10
+ _env_token = 'TOKEN'
11
+ def __init__(self, *args, **kwargs):
12
+ super().__init__(*args, **kwargs)
13
+
14
+ def get_citypedms_by_citype_id(self, citype):
15
+ """
16
+ Retrieve citypedms information for a given CI type ID.
17
+
18
+ This method fetches a list of citypedms entries containing id, ci_type_id,
19
+ dms_id, and gav_template for the specified CI type ID.
20
+
21
+ Args:
22
+ citype (str): The CI type ID to query.
23
+
24
+ Returns:
25
+ dict: A dictionary containing citypedms information with the following keys:
26
+ - id: The citypedms entry ID
27
+ - ci_type_id: The CI type ID
28
+ - dms_id: The DMS ID
29
+ - gav_template: The GAV (Group:Artifact:Version) template
30
+
31
+ Example:
32
+ >>> citypedms = api.get_citypedms_by_citype_id("CI123")
33
+ >>> print(citypedms['id'])
34
+ 'CP456'
35
+ """
36
+ req = f"rest/api/1/citypedms/{citype}"
37
+ res = self.get(req).json()
38
+ logging.debug(f'Using get_citypedms_by_citype_id to get information about {citype}')
39
+
40
+ return res
41
+
42
+ def get_citypedms_by_dms_id(self, dms_id):
43
+ """
44
+ Retrieve citypedms information for a given DMS ID.
45
+
46
+ This method fetches a list of citypedms entries containing id, ci_type_id,
47
+ dms_id, and gav_template for the specified DMS ID (component ID).
48
+
49
+ Args:
50
+ dms_id (str): The DMS ID (component ID) to query.
51
+
52
+ Returns:
53
+ dict: A dictionary containing citypedms information with the following keys:
54
+ - id: The citypedms entry ID
55
+ - ci_type_id: The CI type ID
56
+ - dms_id: The DMS ID
57
+ - gav_template: The GAV (Group:Artifact:Version) template
58
+
59
+ Example:
60
+ >>> citypedms = api.get_citypedms_by_dms_id("DMS789")
61
+ >>> print(citypedms['gav_template'])
62
+ 'com.example:{artifact}:{version}'
63
+ """
64
+ req = f"rest/api/1/citypedms/{dms_id}?bycomponent=True"
65
+ res = self.get(req)
66
+ logging.debug(f'Using get_citypedms_by_dms_id to get information about {dms_id}')
67
+
68
+ return res.json()
69
+
70
+ def get_ci_type_by_code(self, request):
71
+ """
72
+ Retrieve a list of ci type based on the provided ci type code.
73
+
74
+ This function queries the ci database and returns a ci type
75
+ that match the specified code in the request.
76
+
77
+ Args:
78
+ code (string): A code containing search parameters. For example:
79
+ NSDC
80
+
81
+ Returns:
82
+ dict: A dict of citype object matching the search criteria.
83
+
84
+ Example:
85
+ >>> request = 'NSDC'
86
+ >>> citype = get_ci_type_by_code(request)
87
+ >>> print(citype)
88
+ {
89
+ "code": "NSDC",
90
+ "doc_artifactid": null,
91
+ "is_deliverable": false,
92
+ "is_standard": "N",
93
+ "name": "NetServer distribution component",
94
+ "rn_artifactid": null
95
+ }
96
+ """
97
+ req = f"rest/api/1/citype/{request}"
98
+ res = self.get(req)
99
+ logging.debug(f'Using get_ci_type_by_code to get information about citype with code {request}')
100
+
101
+ return res.json()
102
+
103
+ def get_deliveries(self, request):
104
+ """
105
+ Retrieve a list of deliveries based on the provided search criteria.
106
+
107
+ This function queries the delivery database and returns a list of deliveries
108
+ that match the specified parameters in the request.
109
+
110
+ Args:
111
+ request (dict): A dictionary containing search parameters. For example:
112
+ {
113
+ 'gav': 'somegav',
114
+ 'flag_approved': True
115
+ }
116
+
117
+ Returns:
118
+ list: A list of delivery objects matching the search criteria.
119
+
120
+ Example:
121
+ >>> request = {'gav': 'group:artifact:version', 'flag_approved': True}
122
+ >>> deliveries = get_delivery(request)
123
+ >>> print(deliveries)
124
+ [Delivery1, Delivery2, ...]
125
+ """
126
+ req = f"rest/api/1/deliveries"
127
+ res = self.get(req, params=request)
128
+ logging.debug(f'Using get_delivery_by_gav to get information about delivery with {request}')
129
+
130
+ return res.json()
131
+
132
+ def get_historicaldelivery(self, request):
133
+ """
134
+ Retrieve a list of historicaldelivery based on the provided search criteria.
135
+
136
+ This function queries the delivery database and returns a list of deliveries
137
+ that match the specified parameters in the request.
138
+
139
+ Args:
140
+ request (dict): A dictionary containing search parameters. For example:
141
+ {
142
+ 'gav': 'somegav',
143
+ 'flag_approved': True
144
+ }
145
+
146
+ Returns:
147
+ list: A list of delivery objects matching the search criteria.
148
+
149
+ Example:
150
+ >>> request = {'gav': 'group:artifact:version', 'flag_approved': True}
151
+ >>> historicaldeliveries = get_historicaldelivery(request)
152
+ >>> print(historicaldeliveries)
153
+ [HistoricalDelivery1, HistoricalDelivery1, ...]
154
+ """
155
+ req = f"rest/api/1/historicaldeliveries"
156
+ res = self.get(req, params=request)
157
+ logging.debug(f'Using get_historicaldelivery to get information about {request}')
158
+
159
+ return res.json()
160
+
161
+ def update_delivery_by_gav(self, gav, json):
162
+ """
163
+ Update delivery information for a specific GAV (Group:Artifact:Version).
164
+
165
+ This method sends a PUT request to update the delivery information
166
+ associated with the provided GAV.
167
+
168
+ Args:
169
+ gav (str): The GAV (Group:Artifact:Version) identifier of the delivery to update.
170
+ json (dict): A dictionary containing the updated delivery information.
171
+
172
+ Returns:
173
+ requests.Response: The response object from the API call.
174
+
175
+ Example:
176
+ >>> gav = "com.example:artifact:1.0.0"
177
+ >>> update_data = {"status": "approved", "version": "1.0.1"}
178
+ >>> response = api.update_delivery_by_gav(gav, update_data)
179
+ >>> print(response.status_code)
180
+ 200
181
+ """
182
+ payload = {"gav": gav}
183
+ req = f"rest/api/1/deliveries"
184
+ res = self.put(req, json=json, params=payload)
185
+ logging.debug(f'Using put_delivery to update information about {gav}')
186
+
187
+ return res
188
+
189
+ def post_historicaldelivery(self, request):
190
+ """
191
+ Create a new historical delivery entry.
192
+
193
+ This method sends a POST request to create a new historical delivery
194
+ record with the provided information.
195
+
196
+ Args:
197
+ request (dict): A dictionary containing the historical delivery information to be created.
198
+
199
+ Returns:
200
+ dict: The JSON response from the API, typically containing the created historical delivery information.
201
+
202
+ Example:
203
+ >>> new_delivery = {
204
+ ... "gav": "com.example:artifact:1.0.0",
205
+ ... "timestamp": "2023-04-01T12:00:00Z",
206
+ ... "status": "delivered"
207
+ ... }
208
+ >>> result = api.post_historicaldelivery(new_delivery)
209
+ >>> print(result['id'])
210
+ 12345
211
+ """
212
+ req = f"rest/api/1/historicaldeliveries"
213
+ res = self.post(req, json=request)
214
+ logging.debug(f'Using post_historicaldelivery to create information')
215
+
216
+ return res
217
+
218
+ def post_tasks(self, request):
219
+ """
220
+ Create a new tasks entry.
221
+
222
+ This method sends a POST request to create a new tasks
223
+ record with the provided information.
224
+
225
+ Args:
226
+ request (dict): A dictionary containing the tasks information to be inserted.
227
+
228
+ Returns:
229
+ requests.Response: The response object from the API call.
230
+
231
+ Example:
232
+ >>> tasks = {
233
+ ... "status": "Completed",
234
+ ... "action_code": "create",
235
+ ... "commentary": "",
236
+ ... "task_content": {
237
+ ... "hardware": {
238
+ ... "memory_mb": 5120
239
+ ... },
240
+ ... "name": "hostname",
241
+ ... "username": "username"
242
+ ... },
243
+ ... "jira_verification_ticket": "ticket_id",
244
+ ... "status_desc": "status"
245
+ }
246
+ >>> result = api.post_tasks(tasks)
247
+ >>> print(result.status_code)
248
+ 201
249
+ """
250
+ req = f"rest/api/1/tasks"
251
+ res = self.post(req, json=request)
252
+
253
+ return res
254
+
255
+ def update_task(self, task_id, request):
256
+ """
257
+ Create a new tasks entry.
258
+
259
+ This method sends a POST request to create a new tasks
260
+ record with the provided information.
261
+
262
+ Args:
263
+ request (dict): A dictionary containing the tasks information to be inserted.
264
+
265
+ Returns:
266
+ requests.Response: The response object from the API call.
267
+
268
+ Example:
269
+ >>> task_id = 2
270
+ >>> tasks = {
271
+ ... "status": "Completed",
272
+ ... "action_code": "create",
273
+ ... "commentary": "",
274
+ ... "task_content": {
275
+ ... "hardware": {
276
+ ... "memory_mb": 5120
277
+ ... },
278
+ ... "name": "hostname",
279
+ ... "username": "username"
280
+ ... },
281
+ ... "jira_verification_ticket": "ticket_id",
282
+ ... "status_desc": "status"
283
+ }
284
+ >>> result = api.update_task(tasks)
285
+ >>> print(result.status_code)
286
+ 200
287
+ """
288
+ req = f"rest/api/1/tasks/{task_id}"
289
+ res = self.put(req, json=request)
290
+
291
+ return res
292
+
293
+ def get_task_by_id(self, task_id):
294
+ """
295
+ Get a task by id.
296
+
297
+ This method sends a GET request to get a task by desired id.
298
+
299
+ Args:
300
+ task_id (int): An integer of the task id.
301
+
302
+ Returns:
303
+ requests.Response: The response object from the API call.
304
+
305
+ Example:
306
+ >>> task_id = 2
307
+ >>> result = api.get_task_by_id(task_id)
308
+ >>> print(result.status_code)
309
+ 200
310
+ """
311
+ try:
312
+ payload = {"id": task_id}
313
+ req = f"rest/api/1/tasks"
314
+ res = self.get(req, params=payload)
315
+ except HttpAPIError as e:
316
+ if e.code == 404:
317
+ return None
318
+ else:
319
+ raise HttpAPIError(e)
320
+
321
+ return res.json()[0]
322
+
323
+ def get_task_by_id_and_username(self, task_id, username):
324
+ """
325
+ Get a task by id and username.
326
+
327
+ This method sends a GET request to get a task by desired id.
328
+
329
+ Args:
330
+ task_id (int): An integer of the task id.
331
+
332
+ Returns:
333
+ requests.Response: The response object from the API call.
334
+
335
+ Example:
336
+ >>> task_id = 2
337
+ >>> result = api.get_task_by_id(task_id)
338
+ >>> print(result.status_code)
339
+ 200
340
+ """
341
+ try:
342
+ payload = {"id": task_id, "username": username}
343
+ req = f"rest/api/1/tasks"
344
+ res = self.get(req, params=payload)
345
+ except HttpAPIError as e:
346
+ if e.code == 404:
347
+ return None
348
+ else:
349
+ raise HttpAPIError(e)
350
+
351
+ return res.json()[0]
352
+
353
+ def get_task_custom_filter(self, **kwargs):
354
+ """
355
+ Get a task by custom filter.
356
+
357
+ This method sends a GET request to get a task by custom filter.
358
+
359
+ Args:
360
+ **kwargs : Keyword arguments for filtering.
361
+
362
+ Returns:
363
+ requests.Response: The response object from the API call.
364
+
365
+ Example:
366
+ >>> task_id = 2
367
+ >>> param = {"id": 1, "hostname": "test.com"}
368
+ >>> result = api.get_task_custom_filter(**param)
369
+ >>> print(result.status_code)
370
+ 200
371
+ """
372
+ try:
373
+ req = f"rest/api/1/tasks"
374
+ res = self.get(req, params=kwargs)
375
+ except HttpAPIError as e:
376
+ if e.code == 404:
377
+ return []
378
+ else:
379
+ raise HttpAPIError(e)
380
+
381
+ return res.json()
382
+
383
+ def get_client_by_code(self, code):
384
+ """
385
+ Get a client by code.
386
+
387
+ This method sends a GET request to get a client including the ftp upload option by code.
388
+
389
+ Args:
390
+ code : Client code.
391
+
392
+ Returns:
393
+ requests.Response: The response object from the API call.
394
+
395
+ Example:
396
+ >>> code = 2
397
+ >>> result = self.get_client_by_code(code)
398
+ >>> print(result.status_code)
399
+ 200
400
+ """
401
+ req = f"rest/api/1/clients/{code}"
402
+ res = self.get(req)
403
+
404
+ return res.json()
405
+
406
+ def post_new_component(self, payload):
407
+ """
408
+ Post new component to the ci database.
409
+
410
+ This method sends a POST request to add new component into ci database.
411
+
412
+ Args:
413
+ payload : New component payload.
414
+
415
+ Returns:
416
+ requests.Response: The response object from the API call.
417
+
418
+ Example:
419
+ >>> payload = {
420
+ "ci_type_id": "NEWDSTR",
421
+ "ci_type_group_id": "NEW",
422
+ "name": "New component",
423
+ "is_standard": "Y",
424
+ "is_deliverable": True,
425
+ "regexp": "test.regexp.NEWDSTR:dstr:com",
426
+ "loc_type_id": "NXS",
427
+ "dms_id": "NEWDSTR"
428
+ }
429
+ >>> result = self.post_new_component(payload)
430
+ >>> print(result.status_code)
431
+ 200
432
+ """
433
+ req = f"rest/api/1/manage_citype"
434
+ res = self.post(req, json=payload)
435
+ logging.debug(f'Using post_manage_citype to register new component')
436
+
437
+ return res
oc_cdtapi/PgQAPI.py CHANGED
@@ -26,7 +26,7 @@ class PgQAPI (object):
26
26
  else:
27
27
  logging.debug('No connection provided, creating')
28
28
  self.conn = self.pg_connect(url, username, password)
29
- self.message_types = ['dlartifacts', 'dlbuild', 'dlcontents', 'dlupload', 'ns']
29
+ self.message_types = ['dlartifacts', 'dlbuild', 'dlcontents', 'dlupload', 'ns', 'register_file', 'register_checksum']
30
30
 
31
31
  def create_queue(self, queue_code, queue_name):
32
32
  logging.debug('reached create_queue')
@@ -69,6 +69,41 @@ class PgQAPI (object):
69
69
  logging.debug(message)
70
70
  return message
71
71
 
72
+ def compose_dlupload(self, parms):
73
+ logging.debug('reached compose_dlupload')
74
+ logging.debug('received parms: [%s]' % parms)
75
+ msg = ["upload_delivery", [parms], {}]
76
+ logging.debug('composed: [%s]' % msg)
77
+ return msg
78
+
79
+ def compose_register_checksum(self, parms):
80
+ logging.debug('reached compose_register_checksum')
81
+ logging.debug('received parms: [%s]' % parms)
82
+ file_loc = parms.get('file_loc')
83
+ checksum = parms.get('checksum')
84
+ citype = parms.get('citype')
85
+ mime = parms.get('mime')
86
+ if file_loc is None or checksum is None or citype is None:
87
+ logging.error('One of mandatory parms is missing: file_loc, checksum, citype')
88
+ return None
89
+ msg = ["register_checksum", [file_loc, checksum, {"citype":citype}, mime] ]
90
+ return msg
91
+
92
+ def compose_register_file(self, parms):
93
+ logging.debug('reached compose_register_file')
94
+ logging.debug('received parms: [%s]' % parms)
95
+
96
+ location = parms.get("location")
97
+ citype = parms.get("citype")
98
+ depth = parms.get("depth")
99
+ if not location or citype is None or depth is None:
100
+ logging.error('location, citype, and depth must all be specified')
101
+ return None
102
+ message = ["register_file", [[*location], citype, depth], {}]
103
+ logging.debug('composed message')
104
+ logging.debug(message)
105
+ return message
106
+
72
107
  def enqueue_message(self, queue_code=None, msg_text=None, priority=50, pg_connection=None):
73
108
  logging.debug('reached enqueue_message')
74
109
  if pg_connection:
@@ -102,7 +137,7 @@ class PgQAPI (object):
102
137
  def get_msg(self, message_id):
103
138
  logging.debug('reached get_msg')
104
139
  logging.debug('getting status of message [%s]' % message_id)
105
- ds = self.exec_select('select status, payload from queue_message where id = %s', (str(message_id) ) )
140
+ ds = self.exec_select('select status, payload from queue_message where id = %s', (str(message_id), ) )
106
141
  if ds:
107
142
  logging.debug('message found, returning [%s]', ds[0])
108
143
  return ds[0]
@@ -135,11 +170,14 @@ class PgQAPI (object):
135
170
  conn = psycopg2.connect(dsn)
136
171
  if conn:
137
172
  logging.debug('connected. [%s]' % conn)
173
+ logging.debug('setting autocommit to True')
174
+ conn.set_session(autocommit=True)
138
175
  return conn
139
176
  logging.error('failed to connect')
140
- return None
177
+ raise ConnectionError('Failed to connect to postgres db')
141
178
 
142
179
  def msg_proc_start(self, message_id):
180
+ # TODO add consumer ip
143
181
  logging.debug('reached msg_proc_start')
144
182
  logging.debug('starting processing of message [%s]' % message_id)
145
183
  ds = self.get_msg(message_id)
@@ -151,6 +189,35 @@ class PgQAPI (object):
151
189
  logging.error('message [%s] is in bad status [%s]' % (str(message_id), msg_status) )
152
190
  return False
153
191
  self.exec_update('update queue_message set proc_start=now(), status=%s where id = %s', ('A', message_id) )
192
+ logging.debug('returning payload [%s]' % payload)
193
+ return payload
194
+
195
+ def msg_proc_end(self, message_id, error_message=None, comment_text=None):
196
+ logging.debug('reached msg_proc_end')
197
+ logging.debug('ending processing of message [%s]' % message_id)
198
+ ds = self.get_msg(message_id)
199
+ if not ds:
200
+ logging.error('message [%s] does not exist' % message_id)
201
+ return None
202
+ (msg_status, payload) = ds
203
+ if msg_status != 'A':
204
+ logging.error('message [%s] is in bad status [%s]' % (str(message_id), msg_status) )
205
+ return False
206
+ self.exec_update('update queue_message set proc_end=now(), status=%s, error_message=%s, comment_text=%s where id = %s', ('P', error_message, comment_text, message_id) )
207
+ return payload
208
+
209
+ def msg_proc_fail(self, message_id, error_message=None, comment_text=None):
210
+ logging.debug('reached msg_proc_fail')
211
+ logging.debug('failing processing of message [%s]' % message_id)
212
+ ds = self.get_msg(message_id)
213
+ if not ds:
214
+ logging.error('message [%s] does not exist' % message_id)
215
+ return None
216
+ (msg_status, payload) = ds
217
+ if msg_status != 'A':
218
+ logging.error('message [%s] is in bad status [%s]' % (str(message_id), msg_status) )
219
+ return False
220
+ self.exec_update('update queue_message set proc_end=now(), status=%s, error_message=%s, comment_text=%s where id = %s', ('F', error_message, comment_text, message_id) )
154
221
  return payload
155
222
 
156
223
  def new_msg_from_queue(self, queue_code):
@@ -160,13 +227,15 @@ class PgQAPI (object):
160
227
  if not queue_id:
161
228
  logging.error('queue [%s] does not exist' % queue_code)
162
229
  return None
163
- ds = self.exec_select('select min(id), count(id) from queue_message where queue_type__oid = %s and status = %s', (queue_id, 'N') )
230
+ ds = self.exec_select('select min(id), count(id) from queue_message where creation_date < current_timestamp - interval \'1 minute\' and queue_type__oid = %s and status = %s', (queue_id, 'N') )
164
231
  if ds[0][1] == 0:
165
232
  logging.debug('currently no new messages in queue [%s]' % queue_code)
166
233
  return None
167
234
  msg_id = ds[0][0]
168
235
  logging.debug('found new message id [%s], [%s] new messages in queue' % (msg_id, ds[0][1]) )
169
- logging.debug('setting status of message to A')
236
+ logging.debug('requesting payload from msg_proc_start')
170
237
  payload = self.msg_proc_start(msg_id)
171
- return payload
238
+ logging.debug('msg_proc_start returned [%s]' % payload)
239
+ logging.debug('returning payload [%s] with msg_id [%s]' % (payload, msg_id) )
240
+ return payload,msg_id
172
241
 
oc_cdtapi/VaultAPI.py ADDED
@@ -0,0 +1,79 @@
1
+ import logging
2
+ import os
3
+
4
+ import hvac
5
+ from hvac.exceptions import VaultError
6
+
7
+ class VaultAPI:
8
+ def __init__(self,
9
+ vault_enable=False,
10
+ vault_url=None,
11
+ vault_token=None,
12
+ vault_mount_point=None,
13
+ verify_ssl=True):
14
+ self.vault_enable = vault_enable or os.getenv("VAULT_ENABLE")
15
+ self.vault_url = vault_url or os.getenv("VAULT_URL")
16
+ self.vault_token = vault_token or os.getenv("VAULT_TOKEN")
17
+ self.mount_point = vault_mount_point or os.getenv("VAULT_MOUNT_POINT")
18
+ self.use_staging_secrets = os.getenv("USE_STAGING_ENVIRONMENT", "false").lower() == "true" #Check whether we have env USE_STAGING_ENVIRONMENT true or not
19
+ self.verify_ssl = verify_ssl
20
+ self._client = None
21
+
22
+ # Create a logger instance for this class
23
+ self.logger = logging.getLogger(__name__)
24
+
25
+ @property
26
+ def client(self):
27
+ if not self.vault_enable:
28
+ self.logger.warning("VAULT_ENABLE environment set to false, skip using vault")
29
+ return None
30
+
31
+ if self._client is None:
32
+ if not self.vault_url:
33
+ self.logger.warning("VAULT_URL environment variable or vault_url parameter is missing, skip using vault")
34
+ return None
35
+ if not self.vault_token:
36
+ self.logger.warning("VAULT_TOKEN environment variable or vault_token parameter is missing, skip using vault")
37
+ return None
38
+
39
+ self._client = hvac.Client(
40
+ url=self.vault_url,
41
+ token=self.vault_token,
42
+ verify=self.verify_ssl
43
+ )
44
+
45
+ if not self._client.is_authenticated():
46
+ self.logger.warning("Failed to authenticate with Vault - check credentials, skip using vault")
47
+ return None
48
+
49
+ return self._client
50
+
51
+ def parse_secret_name(self, name):
52
+ if 'USER' in name:
53
+ split_name = name.split('_USER')[0]
54
+ return split_name, 'USER'
55
+
56
+ if 'PASSWORD' in name:
57
+ split_name = name.split('_PASSWORD')[0]
58
+ return split_name, 'PASSWORD'
59
+
60
+ return 'OTHER', name
61
+
62
+ def get_secret_from_path(self, name):
63
+ client = self.client
64
+ if client is None:
65
+ return None
66
+
67
+ secret_path, credentials = self.parse_secret_name(name)
68
+ if self.use_staging_secrets:
69
+ secret_path = secret_path + "_TEST"
70
+
71
+ try:
72
+ response = client.secrets.kv.read_secret_version(path=secret_path, mount_point=self.mount_point)
73
+ return response['data']['data'].get(credentials)
74
+ except VaultError as e:
75
+ self.logger.warning(f"Failed getting data from vault for path {secret_path} and credentials {credentials}: {e}")
76
+ return None
77
+
78
+ def load_secret(self, name, default=None):
79
+ return self.get_secret_from_path(name) or os.getenv(name, default)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: oc-cdtapi
3
- Version: 3.15.1
3
+ Version: 3.31.2
4
4
  Summary: Custom Development python API libraries
5
5
  License: Apache2.0
6
6
  Requires-Python: >=3.6
@@ -8,9 +8,12 @@ Description-Content-Type: text/plain
8
8
  License-File: LICENSE
9
9
  Requires-Dist: requests
10
10
  Requires-Dist: packaging
11
+ Requires-Dist: psycopg2-binary
12
+ Requires-Dist: hvac
11
13
  Dynamic: description
12
14
  Dynamic: description-content-type
13
15
  Dynamic: license
16
+ Dynamic: license-file
14
17
  Dynamic: requires-dist
15
18
  Dynamic: requires-python
16
19
  Dynamic: summary
@@ -3,16 +3,18 @@ oc_cdtapi/Dbsm2API.py,sha256=WDBst1dCAmDVU9d9Ogzwp2nkjtKZ4thU2xG4sAPI_Nk,9963
3
3
  oc_cdtapi/DevPIAPI.py,sha256=9WND9ld66eHmC5qoLJq3KDYSoO-pP69UqOQsGZNLZYg,1835
4
4
  oc_cdtapi/DmsAPI.py,sha256=eNFdwQLhCbPvHB5SUtP4QcZZtSdjkgt_Cxn3oQ3iJ5s,15605
5
5
  oc_cdtapi/DmsGetverAPI.py,sha256=ZPU4HlF59fngKu5mSFhtss3rlBuduffDOSm_y3XWrxk,15556
6
- oc_cdtapi/ForemanAPI.py,sha256=4iTez5VrrIIO9e-yO3YRLtj6uf6TNPV0gOXoYoya2zU,43958
6
+ oc_cdtapi/ForemanAPI.py,sha256=0GsC9s9uGoYgMbsgBW5aDEGoGLGeRPJJSTAWhbelhVY,57135
7
7
  oc_cdtapi/JenkinsAPI.py,sha256=lZ8pe3a4eb_6h53JE7QLuzOSlu7Sqatc9PQwWhio9Vg,15748
8
8
  oc_cdtapi/NexusAPI.py,sha256=uU12GtHvKlWorFaPAnFcQ5AGEc94MZ5SdmfM2Pw3F7A,26122
9
- oc_cdtapi/PgQAPI.py,sha256=bAgwo4YVuD60uAEv7hrjTQmmAfcMP86IBlSNhlXJzWc,6882
9
+ oc_cdtapi/PgAPI.py,sha256=URSz7qu-Ir7AOj0jI3ucTXn2PM-nC96nmPZI746OLjA,14356
10
+ oc_cdtapi/PgQAPI.py,sha256=p2nDGrnU5juobumwq9QiVPxcsX6bHAtipUHBBxFrOeE,10312
10
11
  oc_cdtapi/RundeckAPI.py,sha256=O3LmcFaHSz8UqeUyIHTTEMJncDD191Utd-iZaeJay2s,24243
11
12
  oc_cdtapi/TestServer.py,sha256=HV97UWg2IK4gOYAp9yaMdwFUWsw9v66MxyZdI3qQctA,2715
13
+ oc_cdtapi/VaultAPI.py,sha256=P-x_PsWe_S0mGUKTCmR1KhUjdfs7GmyaltjGQcnWj_s,2967
12
14
  oc_cdtapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- oc_cdtapi-3.15.1.data/scripts/nexus.py,sha256=4teqZ_KtCSrwHDJVgA7lkreteod4Xt5XJFZNbwb7E6E,6858
14
- oc_cdtapi-3.15.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- oc_cdtapi-3.15.1.dist-info/METADATA,sha256=DYpKV3GV3Yvv-6I7JEv7UO0u7GEnufp9bwhqBzFksYk,431
16
- oc_cdtapi-3.15.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
17
- oc_cdtapi-3.15.1.dist-info/top_level.txt,sha256=d4-5-D-0CSeSXYuLCP7-nIFCpjkfmJr-Y_muzds8iVU,10
18
- oc_cdtapi-3.15.1.dist-info/RECORD,,
15
+ oc_cdtapi-3.31.2.data/scripts/nexus.py,sha256=4teqZ_KtCSrwHDJVgA7lkreteod4Xt5XJFZNbwb7E6E,6858
16
+ oc_cdtapi-3.31.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ oc_cdtapi-3.31.2.dist-info/METADATA,sha256=rrW8LJY11tNQ9FXtBjY1Wc8bwIcF0rBIlniE2-j8rSM,504
18
+ oc_cdtapi-3.31.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ oc_cdtapi-3.31.2.dist-info/top_level.txt,sha256=d4-5-D-0CSeSXYuLCP7-nIFCpjkfmJr-Y_muzds8iVU,10
20
+ oc_cdtapi-3.31.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5