-
Notifications
You must be signed in to change notification settings - Fork 1
/
phonism_freeswitch_agent.py
executable file
·325 lines (258 loc) · 11.9 KB
/
phonism_freeswitch_agent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/usr/bin/env python3
# (C) Phonism, LLC. 2018
# All rights reserved
# Licensed under BSD 3-Clause "New" or "Revised" License (see LICENSE)
import sys
import subprocess
import json
import requests
import argparse
import configparser
from pprint import pprint
from collections import defaultdict
class VAction(argparse.Action):
def __call__(self, parser, args, values, option_string=None):
# print('values: {v!r}'.format(v=values))
if values == None:
values = '1'
try:
values = int(values)
except ValueError:
values = values.count('v') + 1
setattr(args, self.dest, values)
## Global Variables
ini_file = '/opt/phonism/phonism_freeswitch_agent.ini'
company_id = None
tenant_id = None
verbose = 0
## Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('-v', nargs='?', action=VAction, dest='verbose', help='Output verbosity')
args = parser.parse_args(sys.argv[1:])
if args.verbose:
verbose = args.verbose
## Functions
def getConfig():
# Get config values from phonism_freeswitch_agent.ini
config = configparser.ConfigParser()
config.read(ini_file)
return (config['phonism']['endpoint'],
config['phonism']['api_key'])
def processRequestsResponse(response, request_url='', request_verb=''):
# Try to parse the json. If not json store that too.
try:
response_content = json.loads(response.content.decode('utf-8'))
except ValueError:
response_content = response.content.decode('utf-8')
# No 200 response, exit script...
if response.status_code != 200:
print('')
print("An Http request to Phonism resulted in an error.")
if request_url:
print("Request url: ", request_url)
if request_verb:
print("Request verb: ", request_verb)
print("Status Code: ", response.status_code)
print("Response Content: ", sep="", end="\n")
pprint(response_content, width=120, depth=10)
print('')
sys.exit(1)
return response_content
def executeShellCmd(cmd):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
output, err = p.communicate()
p_status = p.wait()
if int(p_status) > 0:
print("{0} exit status: {1}".format(cmd, p_status))
print("error: {0}".format(err))
sys.exit(1)
if err:
print("{0} exit status: {1}".format(cmd, p_status))
print("error: {0}".format(err))
sys.exit(1)
return output.decode("utf-8")
if __name__ == '__main__':
## Get the INI values
endpoint, api_key = getConfig()
## Build the headers used in the api requests
headers = {
'Content-Type': 'application/json',
'X-API-KEY': '{0}'.format(api_key)
}
## Contact the Phonism integrations endpoint to see if we have one and it's still active
integration_api_url = endpoint + 'integrations/mine'
if verbose > 2:
print('integration_api_url', integration_api_url)
response = requests.get(integration_api_url, headers=headers)
phonism_integration_data = processRequestsResponse(response=response, request_url=integration_api_url, request_verb='get')
if not phonism_integration_data:
print('Could not parse \"/integrations/mine\" response data.')
try:
tenant_id = int(phonism_integration_data['tenant_id'])
company_id = int(phonism_integration_data['company_id'])
except (TypeError, ValueError, KeyError):
print('There was no tenant_id in the response data.')
print('The response data: ', phonism_integration_data, sep="\n", end="\n\n", flush=True)
sys.exit(1)
if verbose > 1:
print("Integration data: ")
pprint(phonism_integration_data, width=120)
print('')
## Now get the FreeSwitch users, and parse them into a usable format.
cmd = 'fs_cli -x list_users'
fs_user_data = executeShellCmd(cmd=cmd)
fs_user_data = fs_user_data.split('\n')
## Parse fs_user_data into a list of dictionaries
col_names = []
fs_user_list = []
for i, user_data_string in enumerate(fs_user_data):
user_data_string = user_data_string.strip()
if i == 0:
col_names = list(user_data_string.split('|'))
continue
if not col_names:
print('Could not obtain column names from the FreeSwitch data.')
sys.exit(1)
if not user_data_string:
continue
if user_data_string == '+OK':
continue
user_data_list = list(user_data_string.split('|'))
user_dict = {}
for j, col_name in enumerate(col_names):
try:
user_dict[col_name] = user_data_list[j]
except IndexError:
continue
fs_user_list.append(user_dict)
## Now loop through fs_user_list and lookup the user's password and append it to the user_dict
for i, user_dict in enumerate(fs_user_list):
cmd = 'fs_cli -x "user_data {0}@{1} param password"'.format(user_dict['userid'], user_dict['domain'])
user_password = executeShellCmd(cmd=cmd)
if user_password:
user_dict['user_password'] = user_password.strip()
## Group like fs user extension columns together.
processed = []
grouped_fs_user_list = []
for i, user_dict in enumerate(fs_user_list):
# Don't process the users in fs_user_list more than once.
if user_dict['userid'] in processed:
continue
# Find all matching users in fs_cli user list
similar_users = list(filter(lambda fs_user: fs_user['userid'] == user_dict['userid'], fs_user_list))
if len(similar_users) == 1:
# If there is only 1, append it to the grouped_fs_user_list
grouped_fs_user_list.append(similar_users[0])
processed.append(similar_users[0]['userid'])
elif len(similar_users) > 1:
# Else If, there is more than one, group the column values into lists.
grouped_user_dict = defaultdict(list)
for key, value in [(k, v) for udict in similar_users for (k, v) in udict.items()]:
if value not in list(grouped_user_dict[key]) :
grouped_user_dict[key].append(value)
# Loop back through the grouped_user_dict and convert any list
# that only has one element into a string.
for key, value in grouped_user_dict.items():
if len(value) == 1:
grouped_user_dict[key] = str(value[0])
# Append the grouped_user_dict to the grouped_fs_user_list
grouped_fs_user_list.append(grouped_user_dict)
processed.append(grouped_user_dict['userid'])
# Overwrite fs_user_list to be the grouped grouped_fs_user_list
fs_user_list = grouped_fs_user_list
if verbose > 1:
print('FreeSwitch users:')
pprint(fs_user_list, width=120)
print('')
## Change the headers for this section of the script.
headers['Content-Type'] = 'application/x-www-form-urlencoded'
# Limit (Take)
limit = 10
# Offset (Skip)
start_after = 0
# List of Phonism extensions not to create in the for loop after this while loop.
updated_phonism_extensions = []
# Loop counter for fail safe
counter = 0
## Keep requesting Phonism extension data until there is no more...
while True:
## Get Phonism extension data
extensions_api_url = endpoint + 'extensions?tenant_id={0}&limit={1}&start_after={2}'.format(tenant_id, limit, start_after)
if verbose > 2:
print('extensions_api_url: ', extensions_api_url)
print('limit:', limit)
print('start_after:', start_after)
print('updated_phonism_extensions:', updated_phonism_extensions)
print('')
response = requests.get(extensions_api_url, headers=headers)
phonism_extension_data = processRequestsResponse(response=response, request_url=extensions_api_url, request_verb='get')
if verbose > 1:
print('Extension data:', end="\n")
pprint(phonism_extension_data, width=120)
print('')
try:
# Break the while loop if ph_extension_count == 0
ph_extension_count = len(phonism_extension_data)
if ph_extension_count == 0:
break # Break While loop
except (TypeError, ValueError):
print('Could not obtain phonism_extension_data.', phonism_extension_data, sep="\n", end="\n\n", flush=True)
sys.exit(1)
## Loop through phonism_extension_data and do stuff depending on whether you find it in fs_user_list
for pi, ph_ext_dict in enumerate(phonism_extension_data):
fs_user_found = False
found_fs_user_dict = {}
for fi, fs_user_dict in enumerate(fs_user_list):
if str(fs_user_dict['userid']) == str(ph_ext_dict['extension']):
fs_user_found = True
found_fs_user_dict = dict(fs_user_dict)
if fs_user_found: # If the Phonism extension is found in FreeSwitch, update it.
# Add the Phonism extension to the updated list so that it is not created.
updated_phonism_extensions.append(found_fs_user_dict['userid'])
# Create the update url
extensions_api_url = endpoint + 'extensions/{0}'.format(ph_ext_dict['id'])
# Update Data:
put_data = {
'tenant_id': tenant_id,
'extension': found_fs_user_dict['userid'],
'secret': found_fs_user_dict['user_password']
}
# Make Http REST request. Die on response error.
response = requests.put(extensions_api_url, data=put_data, headers=headers)
updated_extension = processRequestsResponse(response=response, request_url=extensions_api_url, request_verb='put')
if verbose > 0:
print("Updated extension \"{0}\" in Phonism.".format(updated_extension['extension']))
else: # Else, delete the Phonism extension.
# Create the delete url
extensions_api_url = endpoint + 'extensions/{0}'.format(ph_ext_dict['id'])
# Make Http REST request. Die on response error.
response = requests.delete(extensions_api_url, headers=headers)
deleted_extension = processRequestsResponse(response=response, request_url=extensions_api_url, request_verb='delete')
if verbose > 0:
print("Deleted extension id #{0} from Phonism.".format(deleted_extension['id']))
# The Phonism extension was deleted. We shouldn't use it in calculating the start_after value.
ph_extension_count -= 1
# Start after the number of users processed
start_after += ph_extension_count
counter += 1
# Fail Safe. 10 * 100000 = 1000000 extensions
if counter > 100000:
break
## Loop through fs_user_list and create the extension if it is not in the updated_phonism_extensions list.
for fi, fs_user_dict in enumerate(fs_user_list):
if fs_user_dict['userid'] not in updated_phonism_extensions:
# Create the insert url.
extensions_api_url = endpoint + 'extensions'
# Insert Data:
post_data = {
'tenant_id': tenant_id,
'extension': fs_user_dict['userid'],
'secret': fs_user_dict['user_password']
}
# Make Http REST request. Die on response error.
response = requests.post(extensions_api_url, data=post_data, headers=headers)
created_extension = processRequestsResponse(response=response, request_url=extensions_api_url, request_verb='post')
if verbose > 0:
print("Created extension \"{0}\" in Phonism.".format(created_extension['extension']))
# Done!
sys.exit(0)