oc-cdtapi 3.15.2__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 +383 -27
- oc_cdtapi/PgAPI.py +437 -0
- oc_cdtapi/PgQAPI.py +75 -6
- oc_cdtapi/VaultAPI.py +79 -0
- {oc_cdtapi-3.15.2.dist-info → oc_cdtapi-3.31.2.dist-info}/METADATA +5 -2
- {oc_cdtapi-3.15.2.dist-info → oc_cdtapi-3.31.2.dist-info}/RECORD +10 -8
- {oc_cdtapi-3.15.2.dist-info → oc_cdtapi-3.31.2.dist-info}/WHEEL +1 -1
- {oc_cdtapi-3.15.2.data → oc_cdtapi-3.31.2.data}/scripts/nexus.py +0 -0
- {oc_cdtapi-3.15.2.dist-info → oc_cdtapi-3.31.2.dist-info/licenses}/LICENSE +0 -0
- {oc_cdtapi-3.15.2.dist-info → oc_cdtapi-3.31.2.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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 = {
|
|
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,7 +90,7 @@ 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)
|
|
@@ -95,7 +101,7 @@ class ForemanAPI(HttpAPI):
|
|
|
95
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.
|
|
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.
|
|
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
|
-
|
|
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'),
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
236
|
+
logging.debug('requesting payload from msg_proc_start')
|
|
170
237
|
payload = self.msg_proc_start(msg_id)
|
|
171
|
-
|
|
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.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: oc-cdtapi
|
|
3
|
-
Version: 3.
|
|
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=
|
|
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/
|
|
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.
|
|
14
|
-
oc_cdtapi-3.
|
|
15
|
-
oc_cdtapi-3.
|
|
16
|
-
oc_cdtapi-3.
|
|
17
|
-
oc_cdtapi-3.
|
|
18
|
-
oc_cdtapi-3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|