opensipscli 0.3.1__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.
@@ -0,0 +1,1062 @@
1
+ #!/usr/bin/env python
2
+ ##
3
+ ## This file is part of OpenSIPS CLI
4
+ ## (see https://github.com/OpenSIPS/opensips-cli).
5
+ ##
6
+ ## This program is free software: you can redistribute it and/or modify
7
+ ## it under the terms of the GNU General Public License as published by
8
+ ## the Free Software Foundation, either version 3 of the License, or
9
+ ## (at your option) any later version.
10
+ ##
11
+ ## This program is distributed in the hope that it will be useful,
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ ## GNU General Public License for more details.
15
+ ##
16
+ ## You should have received a copy of the GNU General Public License
17
+ ## along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ ##
19
+
20
+ from opensipscli.module import Module
21
+ from opensipscli.logger import logger
22
+ from opensipscli.config import cfg
23
+ from opensipscli.db import (
24
+ osdb, osdbError, osdbConnectError,
25
+ osdbArgumentError, osdbNoSuchModuleError,
26
+ osdbModuleAlreadyExistsError, osdbAccessDeniedError,
27
+ SUPPORTED_BACKENDS,
28
+ )
29
+
30
+ import os, re
31
+ from getpass import getpass, getuser
32
+ from collections import OrderedDict
33
+
34
+ DEFAULT_DB_TEMPLATE = "template1"
35
+ OPENSIPS_SCHEMA_SRC_PATH = "/usr/local/share/opensips"
36
+
37
+ STANDARD_DB_MODULES = [
38
+ "acc",
39
+ "alias_db",
40
+ "auth_db",
41
+ "avpops",
42
+ "clusterer",
43
+ "dialog",
44
+ "dialplan",
45
+ "dispatcher",
46
+ "domain",
47
+ "drouting",
48
+ "group",
49
+ "load_balancer",
50
+ "msilo",
51
+ "permissions",
52
+ "rtpproxy",
53
+ "rtpengine",
54
+ "speeddial",
55
+ "tls_mgm",
56
+ "usrloc"
57
+ ]
58
+
59
+ EXTRA_DB_MODULES = [
60
+ "b2b",
61
+ "b2b_sca",
62
+ "call_center",
63
+ "carrierroute",
64
+ "closeddial",
65
+ "domainpolicy",
66
+ "emergency",
67
+ "fraud_detection",
68
+ "freeswitch_scripting",
69
+ "imc",
70
+ "presence",
71
+ "registrant",
72
+ "rls",
73
+ "smpp",
74
+ "tracer",
75
+ "userblacklist"
76
+ ]
77
+
78
+ DB_MIGRATIONS = {
79
+ '3.4_to_3.5': [
80
+ 'acc',
81
+ 'active_watchers',
82
+ 'address',
83
+ 'b2b_entities',
84
+ 'b2b_logic',
85
+ 'b2b_sca',
86
+ 'cachedb',
87
+ 'carrierfailureroute',
88
+ 'carrierroute',
89
+ 'cc_agents',
90
+ 'cc_calls',
91
+ 'cc_cdrs',
92
+ 'cc_flows',
93
+ 'closeddial',
94
+ 'clusterer',
95
+ 'cpl',
96
+ 'dbaliases',
97
+ 'dialog',
98
+ 'dialplan',
99
+ 'dispatcher',
100
+ 'domain',
101
+ 'domainpolicy',
102
+ 'dr_carriers',
103
+ 'dr_gateways',
104
+ 'dr_groups',
105
+ 'dr_partitions',
106
+ 'dr_rules',
107
+ 'emergency_report',
108
+ 'emergency_routing',
109
+ 'emergency_service_provider',
110
+ 'fraud_detection',
111
+ 'freeswitch',
112
+ 'globalblacklist',
113
+ 'grp',
114
+ 'imc_members',
115
+ 'imc_rooms',
116
+ 'jwt_profiles',
117
+ 'jwt_secrets',
118
+ 'load_balancer',
119
+ 'location',
120
+ 'missed_calls',
121
+ 'presentity',
122
+ 'pua',
123
+ 'qr_profiles',
124
+ 'rc_clients',
125
+ 'rc_demo_ratesheet',
126
+ 'rc_ratesheets',
127
+ 'rc_vendors',
128
+ 're_grp',
129
+ 'registrant',
130
+ 'rls_presentity',
131
+ 'rls_watchers',
132
+ 'route_tree',
133
+ 'rtpengine',
134
+ 'rtpproxy_sockets',
135
+ 'silo',
136
+ 'sip_trace',
137
+ 'smpp',
138
+ 'speed_dial',
139
+ 'subscriber',
140
+ 'tcp_mgm',
141
+ 'tls_mgm',
142
+ 'uri',
143
+ 'userblacklist',
144
+ 'usr_preferences',
145
+ 'watchers',
146
+ 'xcap',
147
+ ],
148
+
149
+ '3.3_to_3.4': [
150
+ 'dispatcher', # changed in 3.4
151
+ 'cc_agents',
152
+ 'acc',
153
+ 'active_watchers',
154
+ 'address',
155
+ 'b2b_entities',
156
+ 'b2b_logic',
157
+ 'b2b_sca',
158
+ 'cachedb',
159
+ 'carrierfailureroute',
160
+ 'carrierroute',
161
+ 'cc_agents',
162
+ 'cc_calls',
163
+ 'cc_cdrs',
164
+ 'cc_flows',
165
+ 'closeddial',
166
+ 'clusterer',
167
+ 'cpl',
168
+ 'dbaliases',
169
+ 'dialog',
170
+ 'dialplan',
171
+ 'domain',
172
+ 'domainpolicy',
173
+ 'dr_carriers',
174
+ 'dr_gateways',
175
+ 'dr_groups',
176
+ 'dr_partitions',
177
+ 'dr_rules',
178
+ 'emergency_report',
179
+ 'emergency_routing',
180
+ 'emergency_service_provider',
181
+ 'fraud_detection',
182
+ 'freeswitch',
183
+ 'globalblacklist',
184
+ 'grp',
185
+ 'imc_members',
186
+ 'imc_rooms',
187
+ 'jwt_profiles',
188
+ 'jwt_secrets',
189
+ 'load_balancer',
190
+ 'location',
191
+ 'missed_calls',
192
+ 'presentity',
193
+ 'pua',
194
+ 'qr_profiles',
195
+ 'rc_clients',
196
+ 'rc_demo_ratesheet',
197
+ 'rc_ratesheets',
198
+ 'rc_vendors',
199
+ 're_grp',
200
+ 'registrant',
201
+ 'rls_presentity',
202
+ 'rls_watchers',
203
+ 'route_tree',
204
+ 'rtpengine',
205
+ 'rtpproxy_sockets',
206
+ 'silo',
207
+ 'sip_trace',
208
+ 'smpp',
209
+ 'speed_dial',
210
+ 'subscriber',
211
+ 'tcp_mgm',
212
+ 'tls_mgm',
213
+ 'uri',
214
+ 'userblacklist',
215
+ 'usr_preferences',
216
+ 'watchers',
217
+ 'xcap',
218
+ ],
219
+
220
+ '3.2_to_3.3': [
221
+ 'cc_agents', # changed in 3.3
222
+ 'cc_calls' # changed in 3.3
223
+ 'cc_cdrs', # changed in 3.3
224
+ 'tcp_mgm', # new in 3.3
225
+ 'acc',
226
+ 'active_watchers',
227
+ 'address',
228
+ 'b2b_entities',
229
+ 'b2b_logic',
230
+ 'b2b_sca',
231
+ 'cachedb',
232
+ 'carrierfailureroute',
233
+ 'carrierroute',
234
+ 'cc_flows',
235
+ 'closeddial',
236
+ 'clusterer',
237
+ 'cpl',
238
+ 'dbaliases',
239
+ 'dialog',
240
+ 'dialplan',
241
+ 'dispatcher',
242
+ 'domain',
243
+ 'domainpolicy',
244
+ 'dr_carriers',
245
+ 'dr_gateways',
246
+ 'dr_groups',
247
+ 'dr_partitions',
248
+ 'dr_rules',
249
+ 'emergency_report',
250
+ 'emergency_routing',
251
+ 'emergency_service_provider',
252
+ 'fraud_detection',
253
+ 'freeswitch',
254
+ 'globalblacklist',
255
+ 'grp',
256
+ 'imc_members',
257
+ 'imc_rooms',
258
+ 'jwt_profiles',
259
+ 'jwt_secrets',
260
+ 'load_balancer',
261
+ 'location',
262
+ 'missed_calls',
263
+ 'presentity',
264
+ 'pua',
265
+ 'qr_profiles',
266
+ 'rc_clients',
267
+ 'rc_demo_ratesheet',
268
+ 'rc_ratesheets',
269
+ 'rc_vendors',
270
+ 're_grp',
271
+ 'registrant',
272
+ 'rls_presentity',
273
+ 'rls_watchers',
274
+ 'route_tree',
275
+ 'rtpengine',
276
+ 'rtpproxy_sockets',
277
+ 'silo',
278
+ 'sip_trace',
279
+ 'smpp',
280
+ 'speed_dial',
281
+ 'subscriber',
282
+ 'tls_mgm',
283
+ 'uri',
284
+ 'userblacklist',
285
+ 'usr_preferences',
286
+ 'watchers',
287
+ 'xcap',
288
+ ],
289
+
290
+ '3.1_to_3.2': [
291
+ 'b2b_logic', # changed in 3.2
292
+ 'pua', # changed in 3.2
293
+ 'registrant', # changed in 3.2
294
+ 'subscriber', # changed in 3.2
295
+ 'jwt_profiles', # new in 3.1
296
+ 'jwt_secrets', # new in 3.1
297
+ 'qr_profiles', # new in 3.1
298
+ 'rc_clients', # new in 3.1
299
+ 'rc_vendors', # new in 3.1
300
+ 'rc_ratesheets', # new in 3.1
301
+ 'rc_demo_ratesheet', # new in 3.1
302
+ 'acc',
303
+ 'active_watchers',
304
+ 'address',
305
+ 'b2b_entities',
306
+ 'b2b_sca',
307
+ 'cachedb',
308
+ 'carrierfailureroute',
309
+ 'carrierroute',
310
+ 'cc_agents',
311
+ 'cc_calls',
312
+ 'cc_cdrs',
313
+ 'cc_flows',
314
+ 'closeddial',
315
+ 'clusterer',
316
+ 'cpl',
317
+ 'dbaliases',
318
+ 'dialog',
319
+ 'dialplan',
320
+ 'dispatcher',
321
+ 'domain',
322
+ 'domainpolicy',
323
+ 'dr_carriers',
324
+ 'dr_gateways',
325
+ 'dr_groups',
326
+ 'dr_partitions',
327
+ 'dr_rules',
328
+ 'emergency_report',
329
+ 'emergency_routing',
330
+ 'emergency_service_provider',
331
+ 'fraud_detection',
332
+ 'freeswitch',
333
+ 'globalblacklist',
334
+ 'grp',
335
+ 'imc_members',
336
+ 'imc_rooms',
337
+ 'load_balancer',
338
+ 'location',
339
+ 'missed_calls',
340
+ 'presentity',
341
+ 're_grp',
342
+ 'rls_presentity',
343
+ 'rls_watchers',
344
+ 'route_tree',
345
+ 'rtpengine',
346
+ 'rtpproxy_sockets',
347
+ 'silo',
348
+ 'sip_trace',
349
+ 'smpp',
350
+ 'speed_dial',
351
+ 'tls_mgm',
352
+ 'uri',
353
+ 'userblacklist',
354
+ 'usr_preferences',
355
+ 'watchers',
356
+ 'xcap',
357
+ ],
358
+
359
+ '3.0_to_3.1': [
360
+ 'smpp', # new in 3.0
361
+ 'cc_agents', # changed in 3.1
362
+ 'cc_calls', # changed in 3.1
363
+ 'cc_flows', # changed in 3.1
364
+ 'dialog', # changed in 3.1
365
+ 'dr_carriers', # changed in 3.1
366
+ 'dr_rules', # changed in 3.1
367
+ 'load_balancer', # changed in 3.1
368
+ 'acc',
369
+ 'active_watchers',
370
+ 'address',
371
+ 'b2b_entities',
372
+ 'b2b_logic',
373
+ 'b2b_sca',
374
+ 'cachedb',
375
+ 'carrierfailureroute',
376
+ 'carrierroute',
377
+ 'cc_cdrs',
378
+ 'closeddial',
379
+ 'clusterer',
380
+ 'cpl',
381
+ 'dbaliases',
382
+ 'dialplan',
383
+ 'dispatcher',
384
+ 'domain',
385
+ 'domainpolicy',
386
+ 'dr_gateways',
387
+ 'dr_groups',
388
+ 'dr_partitions',
389
+ 'emergency_report',
390
+ 'emergency_routing',
391
+ 'emergency_service_provider',
392
+ 'fraud_detection',
393
+ 'freeswitch',
394
+ 'globalblacklist',
395
+ 'grp',
396
+ 'imc_members',
397
+ 'imc_rooms',
398
+ 'location',
399
+ 'missed_calls',
400
+ 'presentity',
401
+ 'pua',
402
+ 're_grp',
403
+ 'registrant',
404
+ 'rls_presentity',
405
+ 'rls_watchers',
406
+ 'route_tree',
407
+ 'rtpengine',
408
+ 'rtpproxy_sockets',
409
+ 'silo',
410
+ 'sip_trace',
411
+ 'speed_dial',
412
+ 'subscriber',
413
+ 'tls_mgm',
414
+ 'uri',
415
+ 'userblacklist',
416
+ 'usr_preferences',
417
+ 'watchers',
418
+ 'xcap',
419
+ ],
420
+
421
+ '2.4_to_3.0': [
422
+ 'registrant', # changed in 3.0
423
+ 'tls_mgm', # changed in 3.0
424
+ 'acc',
425
+ 'active_watchers',
426
+ 'address',
427
+ 'b2b_entities',
428
+ 'b2b_logic',
429
+ 'b2b_sca',
430
+ 'cachedb',
431
+ 'carrierfailureroute',
432
+ 'carrierroute',
433
+ 'cc_agents',
434
+ 'cc_calls',
435
+ 'cc_cdrs',
436
+ 'cc_flows',
437
+ 'closeddial',
438
+ 'clusterer',
439
+ 'cpl',
440
+ 'dbaliases',
441
+ 'dialog',
442
+ 'dialplan',
443
+ 'dispatcher',
444
+ 'domain',
445
+ 'domainpolicy',
446
+ 'dr_carriers',
447
+ 'dr_gateways',
448
+ 'dr_groups',
449
+ 'dr_partitions',
450
+ 'dr_rules',
451
+ 'emergency_report',
452
+ 'emergency_routing',
453
+ 'emergency_service_provider',
454
+ 'fraud_detection',
455
+ 'freeswitch',
456
+ 'globalblacklist',
457
+ 'grp',
458
+ 'imc_members',
459
+ 'imc_rooms',
460
+ 'load_balancer',
461
+ 'location',
462
+ 'missed_calls',
463
+ 'presentity',
464
+ 'pua',
465
+ 're_grp',
466
+ 'rls_presentity',
467
+ 'rls_watchers',
468
+ 'route_tree',
469
+ 'rtpengine',
470
+ 'rtpproxy_sockets',
471
+ 'silo',
472
+ 'sip_trace',
473
+ 'speed_dial',
474
+ 'subscriber',
475
+ 'uri',
476
+ 'userblacklist',
477
+ 'usr_preferences',
478
+ 'watchers',
479
+ 'xcap',
480
+ ],
481
+ }
482
+
483
+
484
+ class database(Module):
485
+ """
486
+ Class: database modules
487
+ """
488
+ def __init__(self, *args, **kwargs):
489
+ """
490
+ Constructor
491
+ """
492
+ super().__init__(*args, **kwargs)
493
+ self.db_path = None
494
+
495
+ def __complete__(self, command, text, line, begidx, endidx):
496
+ """
497
+ helper for autocompletion in interactive mode
498
+ """
499
+
500
+ if command == 'create':
501
+ db_name = ['opensips', 'opensips_test']
502
+ if not text:
503
+ return db_name
504
+ ret = [t for t in db_name if t.startswith(text)]
505
+ elif command == 'add':
506
+ modules = STANDARD_DB_MODULES + EXTRA_DB_MODULES
507
+ if not text:
508
+ return modules
509
+
510
+ ret = [t for t in modules if t.startswith(text)]
511
+ elif command == 'migrate':
512
+ arg = len(line.split())
513
+ if arg == 2 or (arg == 3 and line[-1] != ' '):
514
+ mig_flavours = [f + ' ' for f in DB_MIGRATIONS]
515
+ if not text:
516
+ return mig_flavours
517
+ ret = [t for t in mig_flavours if t.startswith(text)]
518
+
519
+ elif arg == 3 or (arg == 4 and line[-1] != ' '):
520
+ db_source = ['opensips ']
521
+ if not text:
522
+ return db_source
523
+ ret = [t for t in db_source if t.startswith(text)]
524
+
525
+ elif arg == 4 or (arg == 5 and line[-1] != ' '):
526
+ db_dest = ['opensips_new ']
527
+ if not text:
528
+ return db_dest
529
+ ret = [t for t in db_dest if t.startswith(text)]
530
+
531
+ return ret or ['']
532
+
533
+ def __exclude__(self):
534
+ """
535
+ method exlusion list
536
+ """
537
+ if cfg.exists("database_url"):
538
+ db_url = cfg.get("database_url")
539
+ return (not osdb.has_dialect(osdb.get_dialect(db_url)), None)
540
+ else:
541
+ return (not osdb.has_sqlalchemy(), None)
542
+
543
+ def __get_methods__(self):
544
+ """
545
+ methods available for autocompletion
546
+ """
547
+ return [
548
+ 'create',
549
+ 'drop',
550
+ 'add',
551
+ 'migrate',
552
+ ]
553
+
554
+ def get_db_url(self, db_name=cfg.get('database_name')):
555
+ engine = osdb.get_db_engine()
556
+ if not engine:
557
+ return None
558
+
559
+ # make sure to inherit the 'database_admin_url' engine + host
560
+ db_url = osdb.set_url_driver(cfg.get("database_url"), engine)
561
+ db_url = osdb.set_url_host(db_url, osdb.get_db_host())
562
+
563
+ logger.debug("DB URL: '{}'".format(db_url))
564
+ return db_url
565
+
566
+ def get_admin_db_url(self, db_name):
567
+ engine = osdb.get_db_engine()
568
+ if not engine:
569
+ return None
570
+
571
+ if cfg.exists('database_admin_url'):
572
+ admin_url = cfg.get("database_admin_url")
573
+ if engine == "postgresql":
574
+ admin_url = osdb.set_url_db(admin_url, 'postgres')
575
+ else:
576
+ admin_url = osdb.set_url_db(admin_url, None)
577
+ else:
578
+ if engine == 'postgresql':
579
+ if getuser() != "postgres":
580
+ logger.error("Command must be run as 'postgres' user: "
581
+ "sudo -u postgres opensips-cli ...")
582
+ return None
583
+
584
+ """
585
+ For PG, do the initial setup using 'postgres' as role + DB
586
+ """
587
+ admin_url = "postgresql://postgres@localhost/postgres"
588
+ else:
589
+ admin_url = "{}://root@localhost".format(engine)
590
+
591
+ if osdb.get_url_pswd(admin_url) is None:
592
+ pswd = getpass("Password for admin {} user ({}): ".format(
593
+ osdb.get_url_driver(admin_url, capitalize=True),
594
+ osdb.get_url_user(admin_url)))
595
+ logger.debug("read password: '%s'", pswd)
596
+ admin_url = osdb.set_url_password(admin_url, pswd)
597
+
598
+ logger.debug("admin DB URL: '{}'".format(admin_url))
599
+ return admin_url
600
+
601
+ def do_add(self, params, modifiers=None):
602
+ """
603
+ add a given table to the database (connection via URL)
604
+ """
605
+ if len(params) < 1:
606
+ logger.error("Please specify a module to add (e.g. dialog)")
607
+ return -1
608
+ module = params[0]
609
+
610
+ if len(params) < 2:
611
+ db_name = cfg.read_param("database_name",
612
+ "Please provide the database to add the module to")
613
+ else:
614
+ db_name = params[1]
615
+
616
+ db_url = self.get_db_url(db_name)
617
+ if not db_url:
618
+ logger.error("no DB URL specified: aborting!")
619
+ return -1
620
+
621
+ engine = osdb.get_db_engine()
622
+ if not engine:
623
+ return -1
624
+
625
+ if engine != 'sqlite':
626
+ admin_url = self.get_admin_db_url(db_name)
627
+ if not admin_url:
628
+ return -1
629
+
630
+ db = self.get_db(admin_url if engine != 'sqlite' else db_url, db_name)
631
+ if not db:
632
+ return -1
633
+
634
+ if engine == 'sqlite' and not db.exists(db_name):
635
+ logger.error("database '%s' does not exist!", db_name)
636
+ return -1
637
+
638
+ ret = self.create_tables(db_name, db_url, db, tables=[module],
639
+ create_std=False)
640
+
641
+ db.destroy()
642
+ return ret
643
+
644
+
645
+ def do_create(self, params=None, modifiers=None):
646
+ """
647
+ create database with role-assigment and tables
648
+ """
649
+ if len(params) >= 1:
650
+ db_name = params[0]
651
+ else:
652
+ db_name = cfg.read_param("database_name",
653
+ "Please provide the database to create")
654
+ logger.debug("db_name: '%s'", db_name)
655
+
656
+ engine = osdb.get_db_engine()
657
+ if not engine:
658
+ return -1
659
+
660
+ if engine != 'sqlite':
661
+ admin_url = self.get_admin_db_url(db_name)
662
+ if not admin_url:
663
+ return -1
664
+
665
+ db_url = self.get_db_url(db_name)
666
+ if not db_url:
667
+ return -1
668
+
669
+ db = self.get_db(admin_url if engine != 'sqlite' else db_url, db_name)
670
+ if not db:
671
+ return -1
672
+
673
+ if self.create_db(db_name, \
674
+ admin_url if engine != 'sqlite' else db_url, db) < 0:
675
+ return -1
676
+
677
+ if self.ensure_user(db_url, db_name, db) < 0:
678
+ return -1
679
+
680
+ if self.create_tables(db_name, db_url, db) < 0:
681
+ return -1
682
+
683
+ db.destroy()
684
+ return 0
685
+
686
+ def create_db(self, db_name, admin_url, db=None):
687
+ # 1) create an object store database instance
688
+ # -> use it to create the database itself
689
+ if not db:
690
+ db = self.get_db(admin_url, db_name)
691
+ if not db:
692
+ return -1
693
+ destroy = True
694
+ else:
695
+ destroy = False
696
+
697
+ # check to see if the database has already been created
698
+ if db.exists(db_name):
699
+ logger.warn("database '%s' already exists!", db_name)
700
+ return -2
701
+
702
+ # create the db instance
703
+ if not db.create(db_name):
704
+ return -1
705
+
706
+ if destroy:
707
+ db.destroy()
708
+ return 0
709
+
710
+ def create_tables(self, db_name, db_url, admin_db, tables=[],
711
+ create_std=True):
712
+ """
713
+ create database tables
714
+ """
715
+ if admin_db.dialect != "sqlite":
716
+ db_url = osdb.set_url_db(db_url, db_name)
717
+ else:
718
+ db_url = 'sqlite:///' + db_name
719
+
720
+ # 2) prepare new object store database instance
721
+ # use it to connect to the created database
722
+ db = self.get_db(db_url, db_name)
723
+ if db is None:
724
+ return -1
725
+
726
+ if admin_db.dialect != "sqlite" and not db.exists():
727
+ logger.warning("database '{}' does not exist!".format(db_name))
728
+ return -1
729
+
730
+ schema_path = self.get_schema_path(db.dialect)
731
+ if schema_path is None:
732
+ return -1
733
+
734
+ table_files = OrderedDict()
735
+
736
+ if create_std:
737
+ standard_file_path = os.path.join(schema_path, "standard-create.sql")
738
+ if not os.path.isfile(standard_file_path):
739
+ logger.error("cannot find stardard OpenSIPS DB file: '{}'!".
740
+ format(standard_file_path))
741
+ return -1
742
+ table_files['standard'] = standard_file_path
743
+
744
+ # check to see what tables we shall deploy
745
+ if tables:
746
+ pass
747
+ elif cfg.exists("database_modules"):
748
+ # we know exactly what modules we want to instsall
749
+ tables_line = cfg.get("database_modules").strip().lower()
750
+ if tables_line == "all":
751
+ logger.debug("Creating all tables")
752
+ tables = [ f.replace('-create.sql', '') \
753
+ for f in os.listdir(schema_path) \
754
+ if os.path.isfile(os.path.join(schema_path, f)) and \
755
+ f.endswith('-create.sql') ]
756
+ else:
757
+ logger.debug("Creating custom tables")
758
+ tables = tables_line.split(" ")
759
+ else:
760
+ logger.debug("Creating standard tables")
761
+ tables = STANDARD_DB_MODULES
762
+
763
+ # check for corresponding SQL schemas files in system path
764
+ logger.debug("checking tables: {}".format(" ".join(tables)))
765
+
766
+ for table in tables:
767
+ if table == "standard":
768
+ # already checked for it
769
+ continue
770
+ table_file_path = os.path.join(schema_path,
771
+ "{}-create.sql".format(table))
772
+ if not os.path.isfile(table_file_path):
773
+ logger.warn("cannot find SQL file for module {}: {}".
774
+ format(table, table_file_path))
775
+ else:
776
+ table_files[table] = table_file_path
777
+
778
+ username = osdb.get_url_user(db_url)
779
+ admin_db.connect(db_name)
780
+
781
+ if db.dialect == "postgresql":
782
+ self.pg_grant_schema(username, admin_db)
783
+
784
+ # create tables from SQL schemas
785
+ for module, table_file in table_files.items():
786
+ logger.info("Running {}...".format(os.path.basename(table_file)))
787
+ try:
788
+ db.create_module(table_file)
789
+ if db.dialect == "postgresql":
790
+ self.pg_grant_table_access(table_file, username, admin_db)
791
+ except osdbModuleAlreadyExistsError:
792
+ logger.error("{} table(s) are already created!".format(module))
793
+ except osdbError as ex:
794
+ logger.error("cannot import: {}".format(ex))
795
+
796
+ # terminate active database connection
797
+ db.destroy()
798
+ return 0
799
+
800
+ def ensure_user(self, db_url, db_name, admin_db):
801
+ """
802
+ Ensures that the user/password in @db_url can connect to @db_name.
803
+ It assumes @db_name has been created beforehand. If the user doesn't
804
+ exist or has insufficient permissions, this will be fixed using the
805
+ @admin_db connection.
806
+ """
807
+ if admin_db.dialect != "sqlite":
808
+ db_url = osdb.set_url_db(db_url, db_name)
809
+ else:
810
+ db_url = 'sqlite:///' + db_name
811
+
812
+ try:
813
+ db = self.get_db(db_url, db_name, check_access=True)
814
+ logger.info("connected to DB, '%s' user is already created",
815
+ osdb.get_url_user(db_url))
816
+ except osdbAccessDeniedError:
817
+ logger.info("creating access user for {} ...".format(db_name))
818
+ if not admin_db.ensure_user(db_url):
819
+ logger.error("failed to create user on {} DB".format(db_name))
820
+ return -1
821
+
822
+ db = self.get_db(db_url, db_name, cfg_url_param='database_url')
823
+ if db is None:
824
+ return -1
825
+
826
+ db.destroy()
827
+ return 0
828
+
829
+ def do_drop(self, params=None, modifiers=None):
830
+ """
831
+ drop a given database object (connection via URL)
832
+ For PostgreSQL, perform this operation using 'postgres' as role + DB
833
+ """
834
+ if params and len(params) > 0:
835
+ db_name = params[0]
836
+ else:
837
+ db_name = cfg.read_param("database_name",
838
+ "Please provide the database to drop")
839
+
840
+ engine = osdb.get_db_engine()
841
+ if not engine:
842
+ return -1
843
+
844
+ if engine != 'sqlite':
845
+ db_url = self.get_admin_db_url(db_name)
846
+ if db_url is None:
847
+ return -1
848
+
849
+ if db_url.lower().startswith("postgresql"):
850
+ db_url = osdb.set_url_db(db_url, 'postgres')
851
+ else:
852
+ db_url = self.get_db_url(db_name)
853
+ if not db_url:
854
+ return -1
855
+
856
+ # create an object store database instance
857
+ db = self.get_db(db_url, db_name)
858
+ if db is None:
859
+ return -1
860
+
861
+ # check to see if the database has already been created
862
+ if db.exists():
863
+ if cfg.read_param("database_force_drop",
864
+ "Do you really want to drop the '{}' database".
865
+ format(db_name),
866
+ False, True, isbool=True):
867
+
868
+ if db.drop():
869
+ logger.info("database '%s' dropped!", db_name)
870
+ else:
871
+ logger.info("database '%s' not dropped!", db_name)
872
+ else:
873
+ logger.info("database '{}' not dropped!".format(db_name))
874
+ else:
875
+ logger.warning("database '{}' does not exist!".format(db_name))
876
+ db.destroy()
877
+ return -1
878
+
879
+ db.destroy()
880
+ return 0
881
+
882
+
883
+ def do_migrate(self, params, modifiers=None):
884
+ if len(params) < 3:
885
+ print("Usage: database migrate <flavour> <old-database> <new-database>")
886
+ return 0
887
+
888
+ flavour = params[0].lower()
889
+ old_db = params[1]
890
+ new_db = params[2]
891
+
892
+ if flavour not in DB_MIGRATIONS:
893
+ logger.error("unsupported migration flavour: {}".format(flavour))
894
+ return -1
895
+
896
+ admin_url = self.get_admin_db_url(new_db)
897
+ if not admin_url:
898
+ return -1
899
+
900
+ db = self.get_db(admin_url, new_db)
901
+ if not db:
902
+ return -1
903
+
904
+ if db.dialect != "mysql":
905
+ logger.error("'migrate' is only available for MySQL right now! :(")
906
+ return -1
907
+
908
+ if not db.exists(old_db):
909
+ logger.error("the source database ({}) does not exist!".format(old_db))
910
+ return -2
911
+
912
+ print("Creating database {}...".format(new_db))
913
+ if self.create_db(new_db, admin_url, db) < 0:
914
+ return -1
915
+ if self.create_tables(new_db, admin_url, db) < 0:
916
+ return -1
917
+
918
+ backend = osdb.get_url_driver(admin_url)
919
+
920
+ # obtain the DB schema files for the in-use backend
921
+ schema_path = self.get_schema_path(backend)
922
+ if schema_path is None:
923
+ return -1
924
+
925
+ migrate_scripts = self.get_migrate_scripts_path(backend)
926
+ if migrate_scripts is None:
927
+ logger.debug("migration scripts for %s not found", backend)
928
+ return -1
929
+ else:
930
+ logger.debug("found migration scripts for %s", backend)
931
+
932
+ logger.info("Migrating all matching OpenSIPS tables...")
933
+ db.migrate(flavour.replace('.', '_').upper(),
934
+ migrate_scripts, old_db, new_db, DB_MIGRATIONS[flavour])
935
+
936
+ db.destroy()
937
+ return True
938
+
939
+ def get_db(self, db_url, db_name, cfg_url_param="database_admin_url",
940
+ check_access=False):
941
+ """
942
+ helper function: check database url and its dialect
943
+ """
944
+ try:
945
+ return osdb(db_url, db_name)
946
+ except osdbAccessDeniedError:
947
+ if check_access:
948
+ raise
949
+ logger.error("failed to connect to DB as %s, please provide or " +
950
+ "fix the '%s'", osdb.get_url_user(db_url), cfg_url_param)
951
+ except osdbArgumentError:
952
+ logger.error("Bad URL, it should resemble: {}".format(
953
+ "backend://user:pass@hostname" if not \
954
+ db_url.startswith('sqlite:') else "sqlite:////path/to/db"))
955
+ except osdbConnectError:
956
+ logger.error("Failed to connect to database!")
957
+ except osdbNoSuchModuleError:
958
+ logger.error("This database backend is not supported! " \
959
+ "Supported: {}".format(', '.join(SUPPORTED_BACKENDS)))
960
+
961
+ def get_migrate_scripts_path(self, backend="mysql"):
962
+ """
963
+ helper function: migrate database schema
964
+ """
965
+ if '+' in backend:
966
+ backend = backend[0:backend.index('+')]
967
+
968
+ if self.db_path is not None:
969
+ scripts = [
970
+ os.path.join(self.db_path, backend, 'table-migrate.sql'),
971
+ os.path.join(self.db_path, backend, 'db-migrate.sql'),
972
+ ]
973
+
974
+ for s in scripts:
975
+ if not os.path.isfile(s):
976
+ logger.error("Missing migration script ({}), please pull" \
977
+ " the latest OpenSIPS {} module package!".format(
978
+ s, backend))
979
+ return None
980
+
981
+ return scripts
982
+
983
+ def get_schema_path(self, backend="mysql"):
984
+ """
985
+ helper function: get the path defining the root path holding sql schema template
986
+ """
987
+ if '+' in backend:
988
+ backend = backend[0:backend.index('+')]
989
+
990
+ if self.db_path is not None:
991
+ return os.path.join(self.db_path, backend)
992
+
993
+ db_path = os.path.expanduser(cfg.get("database_schema_path"))
994
+
995
+ if db_path.endswith('/'):
996
+ db_path = db_path[:-1]
997
+ if os.path.basename(db_path) == backend:
998
+ db_path = os.path.dirname(db_path)
999
+
1000
+ if not os.path.exists(db_path):
1001
+ if not os.path.exists(OPENSIPS_SCHEMA_SRC_PATH):
1002
+ logger.error("path '{}' to OpenSIPS DB scripts does not exist!".
1003
+ format(db_path))
1004
+ return None
1005
+
1006
+ logger.info("schema path '{}' not found, using '{}' instead (detected on system)".
1007
+ format(db_path, OPENSIPS_SCHEMA_SRC_PATH))
1008
+ db_path = OPENSIPS_SCHEMA_SRC_PATH
1009
+
1010
+ if not os.path.isdir(db_path):
1011
+ logger.error("path '{}' to OpenSIPS DB scripts is not a directory!".
1012
+ format(db_path))
1013
+ return None
1014
+
1015
+ def build_schema_path(db_path, backend):
1016
+ """
1017
+ Replaces schema path of postgresql to old postgre schema path if exists.
1018
+ Should be deleted after opensips main repo refactors folder name to the new backend name.
1019
+ """
1020
+ if backend == "postgresql":
1021
+ old_postgre_path = os.path.join(db_path, "postgres")
1022
+ if os.path.isdir(old_postgre_path):
1023
+ return old_postgre_path
1024
+ schema_path = os.path.join(db_path, backend)
1025
+ return schema_path
1026
+
1027
+ schema_path = build_schema_path(db_path, backend)
1028
+ if not os.path.isdir(schema_path):
1029
+ logger.error("invalid OpenSIPS DB scripts dir: '{}'!".
1030
+ format(schema_path))
1031
+ return None
1032
+
1033
+ std_tables = os.path.join(schema_path, 'standard-create.sql')
1034
+ if not os.path.isfile(std_tables):
1035
+ logger.error("standard tables file not found ({})".format(std_tables))
1036
+ return None
1037
+
1038
+ self.db_path = db_path
1039
+ return schema_path
1040
+
1041
+ def pg_grant_table_access(self, sql_file, username, admin_db):
1042
+ """
1043
+ Grant access to all tables and sequence IDs of a DB module
1044
+ """
1045
+ with open(sql_file, "r") as f:
1046
+ for line in f.readlines():
1047
+ res = re.search('CREATE TABLE (.*) ', line, re.IGNORECASE)
1048
+ if res:
1049
+ table = res.group(1)
1050
+ admin_db.grant_table_options(username, table)
1051
+
1052
+ res = re.search('ALTER SEQUENCE (.*) MAXVALUE', line,
1053
+ re.IGNORECASE)
1054
+ if res:
1055
+ seq = res.group(1)
1056
+ admin_db.grant_table_options(username, seq)
1057
+
1058
+ def pg_grant_schema(self, username, admin_db):
1059
+ """
1060
+ Grant access to public schema of DB
1061
+ """
1062
+ admin_db.grant_public_schema(username)