1
|
|
|
#!/usr/bin/env python |
2
|
|
|
# |
3
|
|
|
# iutwilio.py |
4
|
|
|
# |
5
|
|
|
# Copyright (C) 2015-2016, Takazumi Shirayanagi |
6
|
|
|
# This software is released under the new BSD License, |
7
|
|
|
# see LICENSE |
8
|
|
|
# |
9
|
|
|
|
10
|
|
|
import os |
11
|
|
|
import sys |
12
|
|
|
import xml.etree.ElementTree as ET |
13
|
|
|
|
14
|
|
|
from argparse import ArgumentParser |
15
|
|
|
from twilio.rest import TwilioRestClient |
16
|
|
|
|
17
|
|
|
try: |
18
|
|
|
import configparser |
19
|
|
|
except ImportError: |
20
|
|
|
import ConfigParser as configparser |
21
|
|
|
|
22
|
|
|
account_sid = '' |
23
|
|
|
auth_token = '' |
24
|
|
|
my_number = '' |
25
|
|
|
sms_number = '' |
26
|
|
|
|
27
|
|
|
|
28
|
|
|
# command line option |
29
|
|
|
def parse_command_line(): |
30
|
|
|
parser = ArgumentParser() |
31
|
|
|
parser.add_argument( |
32
|
|
|
'-v', |
33
|
|
|
'--version', |
34
|
|
|
action='version', |
35
|
|
|
version=u'%(prog)s version 0.4' |
36
|
|
|
) |
37
|
|
|
parser.add_argument( |
38
|
|
|
'--ini', |
39
|
|
|
default='config.ini', |
40
|
|
|
help='ini file path. (inifile section [Twilio] account_sid,auth_token,number,sms_number)' |
41
|
|
|
) |
42
|
|
|
parser.add_argument( |
43
|
|
|
'--call', |
44
|
|
|
metavar='NUMBER', |
45
|
|
|
help='call to number.' |
46
|
|
|
) |
47
|
|
|
parser.add_argument( |
48
|
|
|
'--sms', |
49
|
|
|
metavar='NUMBER', |
50
|
|
|
help='send message number.' |
51
|
|
|
) |
52
|
|
|
parser.add_argument( |
53
|
|
|
'--account_sid', |
54
|
|
|
metavar='SID', |
55
|
|
|
help='account sid.' |
56
|
|
|
) |
57
|
|
|
parser.add_argument( |
58
|
|
|
'--auth_token', |
59
|
|
|
metavar='TOKEN', |
60
|
|
|
help='auth token.' |
61
|
|
|
) |
62
|
|
|
parser.add_argument( |
63
|
|
|
'--number', |
64
|
|
|
help='twilio phone number.' |
65
|
|
|
) |
66
|
|
|
parser.add_argument( |
67
|
|
|
'--dump', |
68
|
|
|
action='store_true', |
69
|
|
|
help='dump setting.' |
70
|
|
|
) |
71
|
|
|
parser.add_argument( |
72
|
|
|
'--dryrun', |
73
|
|
|
action='store_true', |
74
|
|
|
help='dryrun.' |
75
|
|
|
) |
76
|
|
|
parser.add_argument( |
77
|
|
|
'--url', |
78
|
|
|
default='http://twimlbin.com/2e6ad348', |
79
|
|
|
help='TwiML url.' |
80
|
|
|
) |
81
|
|
|
parser.add_argument( |
82
|
|
|
'xml', |
83
|
|
|
metavar='XML', |
84
|
|
|
nargs='?', |
85
|
|
|
help='test result xml file' |
86
|
|
|
) |
87
|
|
|
options = parser.parse_args() |
88
|
|
|
|
89
|
|
|
if not options.dump: |
90
|
|
|
if not options.xml: |
91
|
|
|
if not options.call: |
92
|
|
|
parser.print_help() |
93
|
|
|
|
94
|
|
|
return options |
95
|
|
|
|
96
|
|
|
|
97
|
|
|
# setup |
98
|
|
|
def setup(options): |
99
|
|
|
global account_sid |
100
|
|
|
global auth_token |
101
|
|
|
global my_number |
102
|
|
|
global sms_number |
103
|
|
|
if options.account_sid: |
104
|
|
|
account_sid = options.account_sid |
105
|
|
|
if options.auth_token: |
106
|
|
|
auth_token = options.auth_token |
107
|
|
|
if options.number: |
108
|
|
|
my_number = options.number |
109
|
|
|
sms_number = options.number |
110
|
|
|
|
111
|
|
|
|
112
|
|
|
def get_ini(ini, s, n): |
113
|
|
|
try: |
114
|
|
|
return ini.get(s, n) |
115
|
|
|
except Exception as e: |
116
|
|
|
print(e) |
117
|
|
|
return None |
118
|
|
|
|
119
|
|
|
|
120
|
|
|
def parse_ini(path): |
121
|
|
|
global account_sid |
122
|
|
|
global auth_token |
123
|
|
|
global my_number |
124
|
|
|
global sms_number |
125
|
|
|
if account_sid and auth_token and my_number: |
126
|
|
|
return |
127
|
|
|
ini = configparser.SafeConfigParser() |
128
|
|
|
if not os.path.exists(path): |
129
|
|
|
sys.stderr.write('%s not found...' % path) |
130
|
|
|
sys.exit(2) |
131
|
|
|
ini.read(path) |
132
|
|
|
|
133
|
|
|
account_sid = get_ini(ini, 'Twilio', 'account_sid') |
134
|
|
|
auth_token = get_ini(ini, 'Twilio', 'auth_token') |
135
|
|
|
my_number = get_ini(ini, 'Twilio', 'number') |
136
|
|
|
sms_number = get_ini(ini, 'Twilio', 'sms_number') |
137
|
|
|
|
138
|
|
|
# for section in ini.sections(): |
139
|
|
|
# print('[%s]' % (section)) |
140
|
|
|
# for key in ini.options(section): |
141
|
|
|
# print('%s.%s =%s' % (section, key, ini.get(section, key))) |
142
|
|
|
|
143
|
|
|
|
144
|
|
|
def parse_xml(path): |
145
|
|
|
tree = ET.parse(path) |
146
|
|
|
root = tree.getroot() |
147
|
|
|
testsuites = root.find('[@failures]') |
148
|
|
|
return testsuites.attrib['failures'] |
149
|
|
|
|
150
|
|
|
|
151
|
|
|
# make twilio client |
152
|
|
|
def make_twilio(): |
153
|
|
|
client = TwilioRestClient(account_sid, auth_token) |
154
|
|
|
return client |
155
|
|
|
|
156
|
|
|
|
157
|
|
|
# call twilio |
158
|
|
|
def call(client, options): |
159
|
|
|
if options.dryrun: |
160
|
|
|
print('twilio call to ' + options.call) |
161
|
|
|
else: |
162
|
|
|
call = client.calls.create( |
163
|
|
|
url=options.url, |
164
|
|
|
to=options.call, |
165
|
|
|
from_=my_number) |
166
|
|
|
print(call.sid) |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
# make_message |
170
|
|
|
def make_message(options): |
171
|
|
|
body = "" |
172
|
|
|
filepath = options.xml |
173
|
|
|
if filepath and os.path.exists(filepath): |
174
|
|
|
tree = ET.parse(filepath) |
175
|
|
|
root = tree.getroot() |
176
|
|
|
tests = int(root.get('tests')) |
177
|
|
|
testcases = len(root.findall('testsuite')) |
178
|
|
|
failures = int(root.get('failures')) |
179
|
|
|
disabled = int(root.get('disabled')) |
180
|
|
|
skipped = int(root.get('skip')) |
181
|
|
|
passed = tests - failures - skipped |
182
|
|
|
time = root.get('time') |
183
|
|
|
timestamp = root.get('timestamp') |
184
|
|
|
|
185
|
|
|
if timestamp: |
186
|
|
|
body += "%s\n" % (timestamp) |
187
|
|
|
first_failure = None |
188
|
|
|
|
189
|
|
|
body += "%d tests from %s testcase ran. (%sms total)\n" % (tests, testcases, time) |
190
|
|
|
if passed: |
191
|
|
|
body += "PASSED : %d\n" % (passed) |
192
|
|
|
if disabled: |
193
|
|
|
body += "DISABLED: %d\n" % (disabled) |
194
|
|
|
if skipped: |
195
|
|
|
body += "SKIPPED : %d\n" % (skipped) |
196
|
|
|
if failures: |
197
|
|
|
body += "FAILED : %d\n" % (failures) |
198
|
|
|
failure_testsuites = root.findall('testsuite') |
199
|
|
|
for suite in failure_testsuites: |
200
|
|
|
if int(suite.get('failures')): |
201
|
|
|
for test in suite: |
202
|
|
|
failure_node = test.find('.//failure') |
203
|
|
|
if failure_node: |
204
|
|
|
if not first_failure: |
205
|
|
|
first_failure = failure_node |
206
|
|
|
name = "%s.%s\n" % (suite.get('name'), test.get('name')) |
207
|
|
|
if len(body) + len(name) < 155: |
208
|
|
|
body += name |
209
|
|
|
else: |
210
|
|
|
body += "..." |
211
|
|
|
return body |
212
|
|
|
if first_failure: |
213
|
|
|
for s in first_failure.text.split('\n'): |
214
|
|
|
if len(body) + len(s) < 155: |
215
|
|
|
body += "%s\n" % s |
216
|
|
|
else: |
217
|
|
|
body += "..." |
218
|
|
|
return body |
219
|
|
|
return body |
220
|
|
|
|
221
|
|
|
|
222
|
|
|
# message |
223
|
|
|
def message(client, options): |
224
|
|
|
m = make_message(options) |
225
|
|
|
if options.dryrun: |
226
|
|
|
print('twilio call to ' + options.sms) |
227
|
|
|
print(m) |
228
|
|
|
else: |
229
|
|
|
call = client.messages.create( |
230
|
|
|
body=m, |
231
|
|
|
to=options.sms, |
232
|
|
|
from_=sms_number) |
233
|
|
|
print(call.sid) |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
# run |
237
|
|
|
def run(options): |
238
|
|
|
filepath = options.xml |
239
|
|
|
if filepath: |
240
|
|
|
if not os.path.exists(filepath): |
241
|
|
|
sys.exit(1) |
242
|
|
|
else: |
243
|
|
|
if int(parse_xml(filepath)) > 0: |
244
|
|
|
if options.sms: |
245
|
|
|
message(make_twilio(), options) |
246
|
|
|
elif options.call: |
247
|
|
|
call(make_twilio(), options) |
248
|
|
|
else: |
249
|
|
|
print('was failed ' + filepath) |
250
|
|
|
else: |
251
|
|
|
if options.call: |
252
|
|
|
call(make_twilio(), options) |
253
|
|
|
|
254
|
|
|
sys.exit(0) |
255
|
|
|
|
256
|
|
|
|
257
|
|
|
# dump |
258
|
|
|
def dump(options): |
259
|
|
|
print('account_sid: %s' % (account_sid)) |
260
|
|
|
print('auth_token : %s' % (auth_token)) |
261
|
|
|
print('my_number : %s' % (my_number)) |
262
|
|
|
if options.call: |
263
|
|
|
print('call : %s' % (options.call)) |
264
|
|
|
if options.sms: |
265
|
|
|
print('sms : %s' % (options.sms)) |
266
|
|
|
|
267
|
|
|
|
268
|
|
|
def main(): |
269
|
|
|
options = parse_command_line() |
270
|
|
|
setup(options) |
271
|
|
|
parse_ini(options.ini) |
272
|
|
|
if options.dump: |
273
|
|
|
dump(options) |
274
|
|
|
else: |
275
|
|
|
run(options) |
276
|
|
|
|
277
|
|
|
|
278
|
|
|
if __name__ == '__main__': |
279
|
|
|
main() |
280
|
|
|
|