· 4 years ago · May 20, 2021, 05:06 AM
1#requests
2import requests
3import json
4
5#google stuff
6import datetime
7
8
9#errors
10import logging
11from logging.handlers import RotatingFileHandler
12import sys
13from datetime import timedelta, datetime
14import time
15from datetime import timezone as tz
16import base64
17
18tenant_id = "reciprocity_ppd"
19connector_id = "freshservice_v2_api_external_generic"
20reciprocity_api_key = "fQ3Sj-GjT12xLGAscEET2I7TiW-cT34wKdnYAInTnHimJ4jGfYOREzBqpcUSxbCF"
21reciprocity_api_jobs_uri = "https://reciprocity.saasyan.com.au/api/v1.0/connector/jobs"
22
23logger = logging.getLogger(connector_id)
24hdlr = RotatingFileHandler('./logs/' + connector_id + '.log', maxBytes=10485760, backupCount=10)
25formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
26hdlr.setFormatter(formatter)
27logger.addHandler(hdlr)
28
29
30def error_handling():
31 return 'Error: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno)
32
33
34# manifest = {
35# "tenant_id": "reciprocity_ppd",
36# "log_level": "info",
37# "recordtype": "Tickets",
38# "job_type": "write",
39# "core_config": {
40# "core_api_url": "https://reciprocity.saasyan.com.au/api/v1.0/connector/records"
41# },
42# "token": "e5ba6a6e-e287-4d59-94d0-5751a2186dfb",
43# "job_id": 3811,
44# "records": [
45# {"data":
46# {
47# "emails": [
48# "sam@saasyan.com"
49# ],
50# "desc": "<div>Hi Team,<br/> <br/>I have been unable to send any emails since this morning. What\u2019s going on?<br/><br/>Regards,<br/> Andrea </div>",
51# "priority": 1,
52# "source": 2,
53# "status": 2,
54# "type": "Incident",
55# "email": "marshall@saasyan.com.au",
56# "subject": "big mode"
57
58# },
59# "action": "create", "id": ""}],
60# "end_of_list": 1,
61# "job_status": "success",
62# "connector_config": {
63# "api_key": {
64# "type": "string",
65# "value": "EXuymRE4WLVC2FUCc7ji",
66# "required": "true",
67# "description": "Fresh service api key"
68# },
69# "domain": {
70# "type": "string",
71# "value": "saasyan",
72# "required": "true",
73# "description": "fresh service domain prefix"
74# }
75# },
76# "job_parameters": {
77# "field_mapping": { ##GM this should never be empty
78# "cc_emails": "emails",
79# "description": "desc",
80# "priority": "priority",
81# "source": "source",
82# "status": "status",
83# "type": "type",
84# "email": "email",
85# "subject": "subject"
86# },
87# "retrieve_ids": False,
88# "active_requesters": False,
89# },
90# "connector_id": "freshservice_v2_api_external_generic"
91# }
92
93
94
95
96def perform_api_call(method, query, api_key, domain, payload):
97
98 try:
99 url = 'https://' + domain + '.freshservice.com/api/v2/' + query
100
101 api_key = api_key + ":X"
102 key = base64.b64encode(api_key.encode('ascii'))
103 headers = {
104 "Content-Type": "application/json",
105 "Authorization": "Basic " + str(key, 'ascii')
106 }
107
108
109 data = []
110
111 if method == 'GET':
112 response = requests.request(method, url, headers=headers)
113 data.append(response)
114
115 while "Link" in response.headers.keys():
116 response = requests.request(method, response.headers['Link'][1:].split('>')[0], headers=headers) ###GM are we not checking the http status here?
117 data.append(response)
118
119 elif method == 'POST':
120 response = requests.request(method, url, headers=headers, json=payload)
121 data = response.content.decode()
122
123 else:
124 data = response = requests.request(method, url, headers=headers, json=payload)
125
126
127 return data ##GM do we expect the data to be in different formats? Is it not always JSON?
128
129 except:
130 logger.error("exception: " + str(error_handling()))
131 return {"error": {"exception: ": str(error_handling())}}
132
133
134
135def execute(manifest):
136
137
138 try:
139
140 if not manifest: #if manifest is set to None, fetch the manifest from the internal api
141 uri=reciprocity_api_jobs_uri
142 response = requests.request(method="GET", url=uri,
143 headers={
144 "Content-Type": "application/json",
145 "X-Secret": reciprocity_api_key ,
146 "connector_id": connector_id,
147 "tenant_id": tenant_id }
148 )
149
150 response = response.content.decode()
151 response = json.loads(response)
152
153 manifest = response
154
155 print("job manifest: " + json.dumps(response,indent=2))
156
157 if "error" not in manifest:
158 if manifest=={}:
159 print("No jobs in queue, exiting")
160 exit()
161 else:
162 pass
163 else:
164 logger.error('error: ' + 'connector: ' + str(response))
165 return {"error": {"connector": str(response)}}
166
167
168
169
170 #logging setup
171 log_level = manifest["log_level"]
172 if log_level == "info" or log_level == "Info" or log_level == "INFO":
173 logger.setLevel(logging.INFO)
174 elif log_level == "warning" or log_level == "Warning" or log_level == "WARNING" or log_level == "WARN" \
175 or log_level == "warn" or log_level == "Warn":
176 logger.setLevel(logging.WARNING)
177 else:
178 logger.setLevel(logging.ERROR)
179
180 config = manifest['connector_config']
181 api_key = config['api_key']['value']
182 domain = config['domain']['value']
183
184
185 job_status = None
186
187 if manifest['recordtype'] == "Requesters":
188
189
190 if manifest['job_type'] == 'read':
191
192 try:
193
194
195 error = None
196
197 response = perform_api_call('GET', '/requesters', api_key, domain, payload=None)
198
199 records = []
200
201
202 for res in response:
203 page = json.loads(res.content.decode())['requesters']
204 for data in page:
205
206 requester_id = None
207 error = None
208
209 record = {}
210
211 fieldmapping = manifest['job_parameters']['field_mapping']
212
213 for field in fieldmapping.keys():
214 if field in data:
215 record[fieldmapping[field]] = data[field]
216 else:
217 error = "Could not find field: " + field + " In record retrieved."
218
219
220 if(manifest['job_parameters']['retrieve_ids']):
221 requester_id = data['id']
222 if(manifest['job_parameters']['active_requesters']):
223
224 if(data['active']):
225 records.append({"data": record, "id": requester_id, "error": error})
226
227 else:
228 records.append({"data": record, "id": requester_id, "error": error})
229
230
231 manifest['records'] = records
232
233 job_status = 'success'
234
235 except Exception:
236 logger.error('catchall error processing read operation ')
237 logger.error('Info: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno))
238 logger.error('')
239 job_status = 'error'
240
241 logger.info("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
242 print("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
243
244 # Send response back to core.
245 response = requests.request("POST", manifest["core_config"]["core_api_url"],
246 headers={
247 "Content-Type": "application/json",
248 "X-Secret": manifest["token"],
249 "job_id": str(manifest["job_id"]),
250 "tenant_id": manifest["tenant_id"]
251 },
252 data=json.dumps({
253 "job_status": job_status,
254 "end_of_list": 1,
255 "log_level": manifest["log_level"],
256 "job_type": manifest["job_type"],
257 "connector_id": manifest["connector_id"],
258 "recordtype": manifest["recordtype"],
259 "records": []
260 })
261 )
262 logger.info("data dump " + json.dumps({
263 "job_status": job_status,
264 "end_of_list": 1,
265 "log_level": manifest["log_level"],
266 "job_type": manifest["job_type"],
267 "connector_id": manifest["connector_id"],
268 "recordtype": manifest["recordtype"],
269 "records": []
270 }))
271
272 #either a success or error depending on the status code recieved from core
273 if response.status_code < 300:
274 logger.info("core_api_status_code: " + str(response.status_code))
275 logger.info(str(response.content.decode()))
276 return {"success": {"core_api_status_code": str(response.status_code)}}
277 else:
278 logger.error("core_api_status_code: " + str(response.status_code))
279 logger.info(str(response.content.decode()))
280 return {"error": {"core_api_status_code": str(response.status_code)}}
281
282 elif manifest['job_type'] == 'write':
283
284 updated_records = []
285 try:
286
287
288 for record in manifest['records']:
289
290 if record['action'] == 'create':
291
292
293 error = None
294 requester_id = None
295
296 body = {}
297
298 fieldmapping = manifest['job_parameters']['field_mapping']
299 for field in fieldmapping:
300 body[field] = record['data'][fieldmapping[field]]
301
302
303 requester = {}
304
305 requester = json.loads(perform_api_call('POST', '/requesters', api_key, domain, body))
306
307 if 'requester' not in requester.keys():
308 error = requester
309
310 else:
311 requester_id = None
312 if(manifest['job_parameters']['retrieve_ids']):
313 requester_id = requester['id']
314
315
316 updated_records.append({"data": record['data'], "id": requester_id, "error": error})
317
318
319
320 elif record['action'] == 'delete':
321
322 error = None
323
324 if record['id'] is not "":
325 requester_id = record['id']
326
327 response = perform_api_call('DELETE', '/requesters/' + requester_id + '/forget', api_key, domain, payload=None)
328
329 if(response.status_code == 204):
330 alt_record = record
331 alt_record['error'] = error
332 updated_records.append(alt_record)
333
334 else:
335 error = response.content.decode()
336 alt_record = record
337 alt_record['error'] = error
338 updated_records.append(alt_record)
339 else:
340 error = "Cannot perform delete operation without ID"
341 record['error'] = error
342 updated_records.append(record)
343
344 job_status = 'success'
345
346 except Exception:
347 logger.error('catchall error processing write job')
348 logger.error('Info: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno))
349 logger.error('')
350 job_status = 'error'
351
352
353 manifest['records'] = updated_records
354
355 logger.info("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
356 print("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
357
358
359 # Send response back to core.
360 response = requests.request("POST", manifest["core_config"]["core_api_url"],
361 headers={
362 "Content-Type": "application/json",
363 "X-Secret": manifest["token"],
364 "job_id": str(manifest["job_id"]),
365 "tenant_id": manifest["tenant_id"]
366 },
367 data=json.dumps({
368 "job_status": job_status,
369 "end_of_list": 1,
370 "log_level": manifest["log_level"],
371 "job_type": manifest["job_type"],
372 "connector_id": manifest["connector_id"],
373 "recordtype": manifest["recordtype"],
374 "records": []
375 })
376 )
377
378
379 print('hello')
380 logger.info("data dump " + json.dumps({
381 "job_status": job_status,
382 "end_of_list": 1,
383 "log_level": manifest["log_level"],
384 "job_type": manifest["job_type"],
385 "connector_id": manifest["connector_id"],
386 "recordtype": manifest["recordtype"],
387 "records": []
388 }))
389
390 #either a success or error depending on the status code recieved from core
391 if response.status_code < 300:
392 logger.info("core_api_status_code: " + str(response.status_code))
393 logger.info(str(response.content.decode()))
394 return {"success": {"core_api_status_code": str(response.status_code)}}
395 else:
396 logger.error("core_api_status_code: " + str(response.status_code))
397 logger.info(str(response.content.decode()))
398 return {"error": {"core_api_status_code": str(response.status_code)}}
399
400 elif manifest['recordtype'] == "Tickets":
401
402
403 if manifest['job_type'] == 'read':
404
405 records = []
406
407 try:
408
409 response = perform_api_call('GET', 'tickets', api_key, domain, payload=None)
410
411
412 for res in response:
413
414 page = json.loads(res.content.decode())['tickets']
415 for data in page:
416
417 error = None
418 record = {}
419 fieldmapping = manifest['job_parameters']['field_mapping']
420 for field in fieldmapping:
421 if field in data:
422 record[fieldmapping[field]] = data[field]
423 else:
424 error = "Could not find field: " + field + " In record retrieved."
425
426 ticket_id = None
427 if(manifest['job_parameters']['retrieve_ids']):
428 ticket_id = data['id']
429
430 records.append({"data": record, "id": ticket_id, "error": error})
431
432
433 job_status = 'success'
434 except Exception:
435 logger.error('catchall error processing read job')
436 logger.error('Info: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno))
437 logger.error('')
438 job_status = 'error'
439
440 manifest['records'] = records
441
442 logger.info("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
443 print("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
444
445 # Send response back to core.
446 response = requests.request("POST", manifest["core_config"]["core_api_url"],
447 headers={
448 "Content-Type": "application/json",
449 "X-Secret": manifest["token"],
450 "job_id": str(manifest["job_id"]),
451 "tenant_id": manifest["tenant_id"]
452 },
453 data=json.dumps({
454 "job_status": job_status,
455 "end_of_list": 1,
456 "log_level": manifest["log_level"],
457 "job_type": manifest["job_type"],
458 "connector_id": manifest["connector_id"],
459 "recordtype": manifest["recordtype"],
460 "records": []
461 })
462 )
463 logger.info("data dump " + json.dumps({
464 "job_status": job_status,
465 "end_of_list": 1,
466 "log_level": manifest["log_level"],
467 "job_type": manifest["job_type"],
468 "connector_id": manifest["connector_id"],
469 "recordtype": manifest["recordtype"],
470 "records": []
471 }))
472
473 #either a success or error depending on the status code recieved from core
474 if response.status_code < 300:
475 logger.info("core_api_status_code: " + str(response.status_code))
476 logger.info(str(response.content.decode()))
477 return {"success": {"core_api_status_code": str(response.status_code)}}
478 else:
479 logger.error("core_api_status_code: " + str(response.status_code))
480 logger.info(str(response.content.decode()))
481 return {"error": {"core_api_status_code": str(response.status_code)}}
482
483 elif manifest['job_type'] == 'write':
484
485 updated_records = []
486
487
488 try:
489 for record in manifest['records']:
490
491 if record['action'] == 'create':
492
493
494 error = None
495 ticket_id = None
496
497
498 body = {}
499 fieldmapping = manifest['job_parameters']['field_mapping']
500 for field in fieldmapping:
501 body[field] = record['data'][fieldmapping[field]]
502
503
504
505 ticket = {}
506
507 ticket = json.loads(perform_api_call('POST', '/tickets', api_key, domain, body))
508
509
510 if 'ticket' not in ticket.keys():
511 error = ticket
512
513 else:
514 ticket = ticket['ticket']
515 if(manifest['job_parameters']['retrieve_ids']):
516 ticket_id = ticket['id']
517
518
519 updated_records.append({"data": record['data'], "id": ticket_id, "error": error, "action": "create"})
520
521 if record['action'] == 'delete':
522
523 error = None
524
525
526 if record['id'] is not "":
527 ticket_id = record['id']
528
529 response = perform_api_call('DELETE', '/tickets/' + ticket_id, api_key, domain, payload=None)
530
531 if(response.status_code == 204):
532 alt_record = record
533 alt_record['error'] = error
534 updated_records.append(alt_record)
535
536 else:
537 error = response.content.decode()
538 alt_record = record
539 alt_record['error'] = error
540 updated_records.append(alt_record)
541
542 else:
543 error = "Cannot perform delete operation without ID"
544 record['error'] = error
545 updated_records.append(record)
546
547
548 job_status = 'success'
549 except Exception:
550 logger.error('catchall error processing write job')
551 logger.error('Info: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno))
552 logger.error('')
553 job_status = 'error'
554
555 manifest['records'] = updated_records
556
557 logger.info("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
558 print("manifest sent to core: " + json.dumps(manifest, indent=4, sort_keys=True))
559
560
561 # Send response back to core.
562 response = requests.request("POST", manifest["core_config"]["core_api_url"],
563 headers={
564 "Content-Type": "application/json",
565 "X-Secret": manifest["token"],
566 "job_id": str(manifest["job_id"]),
567 "tenant_id": manifest["tenant_id"]
568 },
569 data=json.dumps({
570 "job_status": job_status,
571 "end_of_list": 1,
572 "log_level": manifest["log_level"],
573 "job_type": manifest["job_type"],
574 "connector_id": manifest["connector_id"],
575 "recordtype": manifest["recordtype"],
576 "records": []
577 })
578 )
579 logger.info("data dump " + json.dumps({
580 "job_status": job_status,
581 "end_of_list": 1,
582 "log_level": manifest["log_level"],
583 "job_type": manifest["job_type"],
584 "connector_id": manifest["connector_id"],
585 "recordtype": manifest["recordtype"],
586 "records": []
587 }))
588
589 #either a success or error depending on the status code recieved from core
590 if response.status_code < 300:
591 logger.info("core_api_status_code: " + str(response.status_code))
592 logger.info(str(response.content.decode()))
593 return {"success": {"core_api_status_code": str(response.status_code)}}
594 else:
595 logger.error("core_api_status_code: " + str(response.status_code))
596 logger.info(str(response.content.decode()))
597 return {"error": {"core_api_status_code": str(response.status_code)}}
598
599
600 else:
601 logger.error("Error: Unsupported record type: "+manifest['recordtype'])
602 return {"error": "Unsupported record type: "+ manifest['recordtype']}
603
604 except:
605 logger.error("exception: " + str(error_handling()))
606 return {"error": {"exception: ": str(error_handling())}}
607
608
609
610
611
612