opensipscli 0.4.0__tar.gz → 0.4.2__tar.gz

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.
Files changed (62) hide show
  1. opensipscli-0.4.2/.github/workflows/docker-push-image.yml +38 -0
  2. {opensipscli-0.4.0 → opensipscli-0.4.2}/.github/workflows/docker-readme.yml +2 -2
  3. {opensipscli-0.4.0 → opensipscli-0.4.2}/.github/workflows/pypi.yml +6 -6
  4. {opensipscli-0.4.0 → opensipscli-0.4.2}/PKG-INFO +3 -3
  5. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/INSTALLATION.md +2 -2
  6. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/database.md +15 -1
  7. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/mi.md +6 -1
  8. opensipscli-0.4.2/docs/modules/tls.md +191 -0
  9. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/cli.py +10 -3
  10. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/db.py +51 -42
  11. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/database.py +70 -1
  12. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/mi.py +29 -3
  13. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/tls.py +337 -0
  14. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/version.py +1 -1
  15. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/changelog +12 -0
  16. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/control +1 -1
  17. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/redhat_fedora/opensips-cli.spec +7 -3
  18. {opensipscli-0.4.0 → opensipscli-0.4.2}/pyproject.toml +1 -1
  19. {opensipscli-0.4.0 → opensipscli-0.4.2}/setup.py +1 -1
  20. {opensipscli-0.4.0 → opensipscli-0.4.2}/test/alltests.py +12 -0
  21. opensipscli-0.4.0/.github/workflows/docker-push-image.yml +0 -26
  22. opensipscli-0.4.0/docs/modules/tls.md +0 -99
  23. {opensipscli-0.4.0 → opensipscli-0.4.2}/.gitignore +0 -0
  24. {opensipscli-0.4.0 → opensipscli-0.4.2}/LICENSE +0 -0
  25. {opensipscli-0.4.0 → opensipscli-0.4.2}/README.md +0 -0
  26. {opensipscli-0.4.0 → opensipscli-0.4.2}/SECURITY.md +0 -0
  27. {opensipscli-0.4.0 → opensipscli-0.4.2}/bin/opensips-cli +0 -0
  28. {opensipscli-0.4.0 → opensipscli-0.4.2}/docker/Dockerfile +0 -0
  29. {opensipscli-0.4.0 → opensipscli-0.4.2}/docker/Makefile +0 -0
  30. {opensipscli-0.4.0 → opensipscli-0.4.2}/docker/docker.md +0 -0
  31. {opensipscli-0.4.0 → opensipscli-0.4.2}/docker/run.sh +0 -0
  32. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/diagnose.md +0 -0
  33. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/instance.md +0 -0
  34. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/trace.md +0 -0
  35. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/trap.md +0 -0
  36. {opensipscli-0.4.0 → opensipscli-0.4.2}/docs/modules/user.md +0 -0
  37. {opensipscli-0.4.0 → opensipscli-0.4.2}/etc/default.cfg +0 -0
  38. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/__init__.py +0 -0
  39. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/args.py +0 -0
  40. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/comm.py +0 -0
  41. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/config.py +0 -0
  42. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/defaults.py +0 -0
  43. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/libs/__init__.py +0 -0
  44. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/libs/sqlalchemy_utils.py +0 -0
  45. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/logger.py +0 -0
  46. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/main.py +0 -0
  47. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/module.py +0 -0
  48. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/__init__.py +0 -0
  49. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/diagnose.py +0 -0
  50. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/instance.py +0 -0
  51. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/trace.py +0 -0
  52. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/trap.py +0 -0
  53. {opensipscli-0.4.0 → opensipscli-0.4.2}/opensipscli/modules/user.py +0 -0
  54. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/.gitignore +0 -0
  55. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/compat +0 -0
  56. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/copyright +0 -0
  57. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/rules +0 -0
  58. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/source/format +0 -0
  59. {opensipscli-0.4.0 → opensipscli-0.4.2}/packaging/debian/watch +0 -0
  60. {opensipscli-0.4.0 → opensipscli-0.4.2}/setup.cfg +0 -0
  61. {opensipscli-0.4.0 → opensipscli-0.4.2}/test/test-database.sh +0 -0
  62. {opensipscli-0.4.0 → opensipscli-0.4.2}/test/test.sh +0 -0
@@ -0,0 +1,38 @@
1
+ name: Push OpenSIPS CLI Images in Docker Hub
2
+
3
+ on:
4
+ push:
5
+ repository_dispatch:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+
15
+ - name: Set up Docker Buildx
16
+ uses: docker/setup-buildx-action@v4
17
+
18
+ - name: Log in to Docker Hub
19
+ uses: docker/login-action@v4
20
+ with:
21
+ username: ${{ secrets.DOCKER_USERNAME }}
22
+ password: ${{ secrets.DOCKER_TOKEN }}
23
+
24
+ - name: Log in to GitHub Container Registry
25
+ uses: docker/login-action@v4
26
+ with:
27
+ registry: ghcr.io
28
+ username: ${{ github.repository_owner }}
29
+ password: ${{ secrets.GITHUB_TOKEN }}
30
+
31
+ - name: Build and push Docker image
32
+ uses: docker/build-push-action@v7
33
+ with:
34
+ context: ./docker/
35
+ push: true
36
+ tags: |
37
+ opensips/opensips-cli:latest
38
+ ghcr.io/opensips/opensips-cli:latest
@@ -12,11 +12,11 @@ jobs:
12
12
  dockerHubDescription:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v4
15
+ - uses: actions/checkout@v6
16
16
 
17
17
 
18
18
  - name: Docker Hub Description
19
- uses: peter-evans/dockerhub-description@v4
19
+ uses: peter-evans/dockerhub-description@v5
20
20
  with:
21
21
  username: ${{ secrets.DOCKER_USERNAME }}
22
22
  password: ${{ secrets.DOCKER_TOKEN }}
@@ -9,9 +9,9 @@ jobs:
9
9
  runs-on: ubuntu-latest
10
10
 
11
11
  steps:
12
- - uses: actions/checkout@v4
12
+ - uses: actions/checkout@v6
13
13
  - name: Set up Python
14
- uses: actions/setup-python@v5
14
+ uses: actions/setup-python@v6
15
15
  with:
16
16
  python-version: "3.x"
17
17
  - name: Install pypa/build
@@ -23,7 +23,7 @@ jobs:
23
23
  - name: Build a binary wheel and a source tarball
24
24
  run: python3 -m build
25
25
  - name: Store the distribution packages
26
- uses: actions/upload-artifact@v4
26
+ uses: actions/upload-artifact@v7
27
27
  with:
28
28
  name: python-package-distributions
29
29
  path: dist/
@@ -43,7 +43,7 @@ jobs:
43
43
 
44
44
  steps:
45
45
  - name: Download all the dists
46
- uses: actions/download-artifact@v4
46
+ uses: actions/download-artifact@v8
47
47
  with:
48
48
  name: python-package-distributions
49
49
  path: dist/
@@ -64,12 +64,12 @@ jobs:
64
64
 
65
65
  steps:
66
66
  - name: Download all the dists
67
- uses: actions/download-artifact@v4
67
+ uses: actions/download-artifact@v8
68
68
  with:
69
69
  name: python-package-distributions
70
70
  path: dist/
71
71
  - name: Sign the dists with Sigstore
72
- uses: sigstore/gh-action-sigstore-python@v2.1.1
72
+ uses: sigstore/gh-action-sigstore-python@v3.5.0
73
73
  with:
74
74
  inputs: >-
75
75
  ./dist/*.tar.gz
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: opensipscli
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: OpenSIPS Command Line Interface
5
5
  Project-URL: Homepage, https://github.com/OpenSIPS/opensips-cli
6
6
  Project-URL: Source, https://github.com/OpenSIPS/opensips-cli
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3
16
16
  Requires-Python: >=3.8
17
17
  Requires-Dist: opensips
18
18
  Requires-Dist: pymysql
19
- Requires-Dist: sqlalchemy>=1.3.16
19
+ Requires-Dist: sqlalchemy>=1.3.2
20
20
  Description-Content-Type: text/markdown
21
21
 
22
22
  # OpenSIPS CLI (Command Line Interface)
@@ -17,8 +17,8 @@ to your system using the instructions provided, then install the
17
17
 
18
18
  Supported Operating Systems (at the time of writing):
19
19
 
20
- * Debian 8-10
21
- * Ubuntu 14.04, 16.04, 18.04, 19.04
20
+ * Debian 10-12
21
+ * Ubuntu 18.04, 20.04, 22.04, 24.04
22
22
 
23
23
  ### RPM packages (.rpm)
24
24
 
@@ -14,7 +14,10 @@ which database to delete.
14
14
  parameter the name of the module, as specified in the OpenSIPS scripts
15
15
  hierarchy.
16
16
  * `migrate` - copy and convert an OpenSIPS database into its next OpenSIPS
17
- release equivalent
17
+ release equivalent
18
+ * `pi` - creates a `pi_http` framework from the framework shipped with
19
+ OpenSIPS, using the configured database URL and modules. An optional output
20
+ path may be provided.
18
21
 
19
22
  ## Configuration
20
23
 
@@ -90,6 +93,17 @@ opensips-cli -x database add rtpproxy
90
93
  ```
91
94
  The command above will create the `rtpproxy_sockets` table.
92
95
 
96
+ To create a provisioning framework for the configured modules, run:
97
+
98
+ ```
99
+ opensips-cli -x database pi /etc/opensips/pi_framework.xml
100
+ ```
101
+
102
+ The command reuses the framework shipped in the OpenSIPS `pi_http` scripts,
103
+ including its module-specific columns, validations, and commands. When
104
+ `database_modules` is not `ALL`, entries for other modules are removed from
105
+ the generated file.
106
+
93
107
  A drop command will prompt the user whether they really want to drop the
94
108
  database or not:
95
109
 
@@ -7,9 +7,14 @@ commands and display the result in the console.
7
7
 
8
8
  This module exports all the commands that are exported by the OpenSIPS
9
9
  instance that `opensips-cli` points to. It fetches the available commands by
10
- running the OpenSIPS MI `which` command. When running a command, it returns
10
+ running the OpenSIPS MI `which` command. It also loads MI aliases using the
11
+ `which_aliases` command. When running a command, it returns
11
12
  the raw, unprocessed data from OpenSIPS.
12
13
 
14
+ Aliases are accepted when executing commands, but are not included in command
15
+ completion suggestions. Ambiguous aliases are ignored and must be invoked by
16
+ their canonical command name.
17
+
13
18
  Commands using this module can be run in two manners:
14
19
  * using *positional* parameters: this is similar to the old way the
15
20
  `opensipsctl` tool was working: the parameters passed to the function are
@@ -0,0 +1,191 @@
1
+ # OpenSIPS CLI - TLS module
2
+
3
+ Using the `tls` module, you can generate TLS certificates and private keys.
4
+
5
+ The module has the following subcommands:
6
+ * `rootCA` - generates a CA (certification authority) self signed certificate
7
+ and private key pair. These are to be used by a TLS server.
8
+ * `userCERT` - generates a certificate signed by a given CA, a private key and
9
+ a CA list (chain of trust) file. These are to be used by TLS clients (users).
10
+ * `add` - adds a new TLS domain to the `tls_mgm` table.
11
+ * `update` - changes the columns of an existing TLS domain.
12
+ * `list` - lists the TLS domains provisioned in the `tls_mgm` table.
13
+ * `show` - prints the columns of a TLS domain.
14
+ * `delete` - removes a TLS domain from the `tls_mgm` table.
15
+
16
+ The `add`, `update`, `list`, `show` and `delete` subcommands provision the
17
+ `tls_mgm` module over the database, where the certificate, private key and CA
18
+ list are stored as BLOB values rather than as paths to files. A TLS domain is
19
+ identified by its name and its type (`server` or `client`), both passed as
20
+ arguments:
21
+ ```
22
+ opensips-cli -x tls delete a.example.org server
23
+ ```
24
+ The domain and its type may also be given by name, in which case they can
25
+ appear anywhere among the other columns:
26
+ ```
27
+ opensips-cli -x tls delete domain=a.example.org type=server
28
+ ```
29
+ Giving one of them both ways at once is an error. The commands addressing a
30
+ single domain ask for whatever is left out; `add` and `show` default the
31
+ type to `server`, while `update` and `delete` have no default and keep
32
+ asking until one is given, so that they cannot change a different domain than
33
+ the intended one.
34
+
35
+ `add` and `update` take the remaining `tls_mgm` columns as `column=value`
36
+ arguments, in any order and after the domain and the type:
37
+ ```
38
+ opensips-cli -x tls add a.example.org server method=TLSv1_2 verify_cert=1
39
+ ```
40
+ The settable columns are `match_ip_address`, `match_sip_domain`, `method`,
41
+ `verify_cert`, `require_cert`, `certificate`, `private_key`, `crl_check_all`,
42
+ `crl_dir`, `ca_list`, `ca_dir`, `cipher_list`, `dh_params` and `ec_curve`. A
43
+ column that is not given is left to its default in the database schema;
44
+ `update` only changes the columns it is given.
45
+
46
+ The `certificate`, `private_key`, `ca_list` and `dh_params` columns hold PEM
47
+ content, so their value is the path of the file holding it, and that file is
48
+ read and stored in the table:
49
+ ```
50
+ opensips-cli -x tls add a.example.org server \
51
+ certificate=/etc/opensips/tls/user/user-cert.pem \
52
+ private_key=/etc/opensips/tls/user/user-privkey.pem
53
+ ```
54
+ Every other column is stored as the value it is given, paths included: for
55
+ example, `ca_list` reads the file it points to, while `ca_dir` and `crl_dir`
56
+ keep the directory as such, which is what `tls_mgm` expects of them.
57
+
58
+ After every change, the `tls_reload` MI command is issued so that a running
59
+ OpenSIPS picks up the new domains. If OpenSIPS cannot be reached, a warning is
60
+ logged and the domains are loaded at the next restart.
61
+
62
+ ## Configuration
63
+
64
+ Certificates and private keys can be customized using the following settings:
65
+
66
+ List of `opensips-cli.cfg` settings for configuring self-signed CA certificates:
67
+
68
+ * tls_ca_dir - output directory where the cert and key will be written to
69
+ * tls_ca_cert_file - output certificate file path, within `tls_ca_dir`
70
+ * tls_ca_key_file - output private key file path, within `tls_ca_dir`
71
+ * tls_ca_overwrite - set this to "y" in order to overwrite existing files
72
+ * tls_ca_common_name - the address of the website (e.g. "opensips.org")
73
+ * tls_ca_country - the initials of the country (e.g. "RO")
74
+ * tls_ca_state - the state (e.g. "Bucharest")
75
+ * tls_ca_locality - the city (e.g. "Bucharest")
76
+ * tls_ca_organisation - the name of the organisation (e.g. "OpenSIPS")
77
+ * tls_ca_organisational_unit - the organisational unit (e.g. "Project")
78
+ * tls_ca_notafter - the validity period, in seconds (e.g. 315360000)
79
+ * tls_ca_key_size - the size of the RSA key, in bits (e.g. 4096)
80
+ * tls_ca_md - the digest algorithm to use for signing (e.g. SHA256)
81
+
82
+ List of `opensips-cli.cfg` settings for configuring user certificates:
83
+
84
+ * tls_user_dir - output directory where the cert and key will be written to
85
+ * tls_user_cert_file - output certificate file path, within `tls_user_dir`
86
+ * tls_user_key_file - output private key file path, within `tls_user_dir`
87
+ * tls_user_calist_file - output CA list file path, within `tls_user_dir`
88
+ * tls_user_overwrite - set this to "y" in order to overwrite existing files
89
+ * tls_user_cacert - path to the input CA certificate
90
+ * tls_user_cakey - path to the input CA private key
91
+ * tls_user_common_name - the address of the website (e.g. "www.opensips.org")
92
+ * tls_user_country - the initials of the country (e.g. "RO")
93
+ * tls_user_state - the state (e.g. "Bucharest")
94
+ * tls_user_locality - the city (e.g. "Bucharest")
95
+ * tls_user_organisation - the name of the organisation (e.g. "OpenSIPS")
96
+ * tls_user_organisational_unit - the organisational unit (e.g. "Project")
97
+ * tls_user_notafter - the validity period, in seconds (e.g. 315360000)
98
+ * tls_user_key_size - the size of the RSA key, in bits (e.g. 4096)
99
+ * tls_user_md - the digest algorithm to use for signing (e.g. SHA256)
100
+
101
+ List of `opensips-cli.cfg` settings for the `tls_mgm` database subcommands:
102
+
103
+ * database_tls_url - URL of the database holding the `tls_mgm` table; falls
104
+ back to `database_url`
105
+ * database_tls_name - name of the database; falls back to `database_name`
106
+
107
+
108
+ ## Examples
109
+
110
+ To create a self-signed certificate and a private key for rootCA, enter this snippet:
111
+ ```
112
+ opensips-cli -x tls rootCA
113
+ ```
114
+ Configuration file example for rootCA:
115
+ ```
116
+ [default]
117
+ tls_ca_dir: /etc/opensips/tls/rootCA
118
+ tls_ca_cert_file: cacert.pem
119
+ tls_ca_key_file: private/cakey.pem
120
+ tls_ca_overwrite: yes
121
+ tls_ca_common_name: opensips.org
122
+ tls_ca_country: RO
123
+ tls_ca_state: Bucharest
124
+ tls_ca_locality: Bucharest
125
+ tls_ca_organisation: OpenSIPS
126
+ tls_ca_organisational_unit: Project
127
+ tls_ca_notafter: 315360000
128
+ tls_ca_key_size: 4096
129
+ tls_ca_md: SHA256
130
+ ```
131
+
132
+ To create a user certificate signed by the above rootCA, along with a private
133
+ key and a CA list (chain of trust) file:
134
+ ```
135
+ opensips-cli -x tls userCERT
136
+ ```
137
+ Configuration file example for userCERT:
138
+ ```
139
+ [default]
140
+ tls_user_dir: /etc/opensips/tls/user
141
+ tls_user_cert_file: user-cert.pem
142
+ tls_user_key_file: user-privkey.pem
143
+ tls_user_calist_file: user-calist.pem
144
+ tls_user_overwrite: yes
145
+ tls_user_cacert: /etc/opensips/tls/rootCA/cacert.pem
146
+ tls_user_cakey: /etc/opensips/tls/rootCA/private/cakey.pem
147
+ tls_user_common_name: www.opensips.org
148
+ tls_user_country: RO
149
+ tls_user_state: Bucharest
150
+ tls_user_locality: Bucharest
151
+ tls_user_organisation: OpenSIPS
152
+ tls_user_organisational_unit: Project
153
+ tls_user_notafter: 315360000
154
+ tls_user_key_size: 4096
155
+ tls_user_md: SHA256
156
+ ```
157
+
158
+ To provision the certificate generated above as a TLS domain in the database:
159
+ ```
160
+ opensips-cli -x tls add a.example.org server \
161
+ certificate=/etc/opensips/tls/user/user-cert.pem \
162
+ private_key=/etc/opensips/tls/user/user-privkey.pem \
163
+ ca_list=/etc/opensips/tls/user/user-calist.pem
164
+ ```
165
+ Certificates issued by a public CA are provisioned the same way:
166
+ ```
167
+ opensips-cli -x tls add a.example.org server \
168
+ certificate=/etc/letsencrypt/live/a.example.org/fullchain.pem \
169
+ private_key=/etc/letsencrypt/live/a.example.org/privkey.pem
170
+ ```
171
+ Configuration file example for the `tls_mgm` database subcommands:
172
+ ```
173
+ [default]
174
+ database_url: mysql://opensips:opensipsrw@localhost
175
+ database_name: opensips
176
+ ```
177
+
178
+ To renew the certificate of a domain, or to change any of its other columns:
179
+ ```
180
+ opensips-cli -x tls update a.example.org server \
181
+ certificate=/etc/letsencrypt/live/a.example.org/fullchain.pem \
182
+ private_key=/etc/letsencrypt/live/a.example.org/privkey.pem
183
+ opensips-cli -x tls update a.example.org server cipher_list=HIGH
184
+ ```
185
+
186
+ To inspect and remove the provisioned domains:
187
+ ```
188
+ opensips-cli -x tls list
189
+ opensips-cli -x tls show a.example.org server
190
+ opensips-cli -x tls delete a.example.org server
191
+ ```
@@ -29,7 +29,6 @@ from opensipscli import comm
29
29
  from opensipscli import defaults
30
30
  from opensipscli.config import cfg
31
31
  from opensipscli.logger import logger
32
- from opensipscli.modules import *
33
32
 
34
33
  class OpenSIPSCLI(cmd.Cmd, object):
35
34
  """
@@ -121,6 +120,10 @@ class OpenSIPSCLI(cmd.Cmd, object):
121
120
  self.current_instance = instance
122
121
  self.update_logger()
123
122
 
123
+ # import the modules only now, so that their import-time messages
124
+ # honor the logging level
125
+ importlib.import_module("opensipscli.modules")
126
+
124
127
  # Update the intro and prompt
125
128
  self.intro = cfg.get('prompt_intro')
126
129
  self.prompt = '(%s): ' % cfg.get('prompt_name')
@@ -175,7 +178,11 @@ class OpenSIPSCLI(cmd.Cmd, object):
175
178
  continue
176
179
  logger.debug("Loaded module '{}'".format(name))
177
180
  imod = mod()
178
- self.modules[name] = (imod, mod.__get_methods__(imod))
181
+ methods = mod.__get_methods__(imod)
182
+ aliases = {}
183
+ if hasattr(imod, '__get_aliases__'):
184
+ aliases = imod.__get_aliases__() or {}
185
+ self.modules[name] = (imod, methods, aliases)
179
186
 
180
187
  def history_write(self):
181
188
  """
@@ -419,7 +426,7 @@ class OpenSIPSCLI(cmd.Cmd, object):
419
426
  logger.error("module '{}' expects the following commands: {}".
420
427
  format(module, ", ".join(mod[1])))
421
428
  return -1
422
- elif cmd and not cmd in mod[1]:
429
+ elif cmd and cmd not in mod[1] and cmd not in mod[2]:
423
430
  logger.error("no command '{}' in module '{}'".
424
431
  format(cmd, module))
425
432
  return -1
@@ -36,17 +36,26 @@ try:
36
36
  #from sqlalchemy.engine.url import make_url
37
37
 
38
38
  sqlalchemy_available = True
39
+ sqlalchemy_import_error = None
39
40
  logger.debug("SQLAlchemy version: %s", sqlalchemy.__version__)
40
41
  # always use the vendored shim — the system sqlalchemy-utils package
41
42
  # varies a lot across distros (0.36.x ships broken database_exists for
42
43
  # PostgreSQL on Bullseye; 0.41.x is required for SA 2.0); the shim is
43
44
  # tested against SA 1.3–2.0 and behaves consistently
44
45
  from opensipscli.libs import sqlalchemy_utils
46
+ except ImportError as e:
47
+ # keep the cause around so callers can report it when the database
48
+ # is actually needed
49
+ logger.info("sqlalchemy not available: %s", e)
50
+ sqlalchemy_available = False
51
+ sqlalchemy_import_error = e
52
+
53
+ try:
45
54
  import pymysql
46
55
  pymysql.install_as_MySQLdb()
47
56
  except ImportError:
48
- logger.info("sqlalchemy not available!")
49
- sqlalchemy_available = False
57
+ # only needed for mysql:// URLs; the other backends work without it
58
+ pass
50
59
 
51
60
  SUPPORTED_BACKENDS = [
52
61
  "mysql",
@@ -210,7 +219,16 @@ class osdb(object):
210
219
 
211
220
  # TODO: do this only for SQLAlchemy
212
221
  try:
213
- self.__engine = sqlalchemy.create_engine(db_url, isolation_level='AUTOCOMMIT')
222
+ if self.dialect == "sqlite":
223
+ # SQLite only got the AUTOCOMMIT isolation level in SA 1.3.16;
224
+ # older versions (EL8 ships 1.3.2) reject it with ArgumentError.
225
+ # Setting the pysqlite isolation_level to None gives the same
226
+ # autocommit behaviour on every supported SA version.
227
+ self.__engine = sqlalchemy.create_engine(
228
+ db_url, connect_args={'isolation_level': None})
229
+ else:
230
+ self.__engine = sqlalchemy.create_engine(
231
+ db_url, isolation_level='AUTOCOMMIT')
214
232
 
215
233
  logger.debug("connecting to %s", db_url)
216
234
  self.__conn = self.__engine.connect()
@@ -477,10 +495,10 @@ class osdb(object):
477
495
  if not self.__conn:
478
496
  raise osdbError("connection not available")
479
497
 
480
- where_str = self.get_where(filter_keys)
498
+ where_str, params = self.get_where(filter_keys)
481
499
  statement = "DELETE FROM {}{}".format(table, where_str)
482
500
  try:
483
- self.__conn.execute(text(statement))
501
+ self.__conn.execute(text(statement), params)
484
502
  except sqlalchemy.exc.SQLAlchemyError as ex:
485
503
  logger.error("cannot execute query: {}".format(statement))
486
504
  logger.error(ex)
@@ -658,13 +676,13 @@ class osdb(object):
658
676
  elif type(fields) != list:
659
677
  fields = [ fields ]
660
678
 
661
- where_str = self.get_where(filter_keys)
679
+ where_str, params = self.get_where(filter_keys)
662
680
  statement = "SELECT {} FROM {}{}".format(
663
681
  ", ".join(fields),
664
682
  table,
665
683
  where_str)
666
684
  try:
667
- result = self.__conn.execute(text(statement))
685
+ result = self.__conn.execute(text(statement), params)
668
686
  except sqlalchemy.exc.SQLAlchemyError as ex:
669
687
  logger.error("cannot execute query: {}".format(statement))
670
688
  logger.error(ex)
@@ -679,22 +697,18 @@ class osdb(object):
679
697
 
680
698
  def get_where(self, filter_keys):
681
699
  """
682
- construct a sql 'where clause' from given filter keys
683
- """
684
- if filter_keys:
685
- where_str = ""
686
- for k, v in filter_keys.items():
687
- where_str += " AND {} = ".format(k)
688
- if type(v) == int:
689
- where_str += str(v)
690
- else:
691
- where_str += "'{}'".format(
692
- v.translate(str.maketrans({'\'': '\\\''})))
693
- if where_str != "":
694
- where_str = " WHERE " + where_str[5:]
695
- else:
696
- where_str = ""
697
- return where_str
700
+ construct a sql 'where clause' from given filter keys, along with
701
+ the parameters to bind to it
702
+ """
703
+ if not filter_keys:
704
+ return "", {}
705
+
706
+ where_str = ""
707
+ params = {}
708
+ for i, (k, v) in enumerate(filter_keys.items()):
709
+ where_str += " AND {} = :w{}".format(k, i)
710
+ params["w{}".format(i)] = v
711
+ return " WHERE " + where_str[5:], params
698
712
 
699
713
  def get_role(self, role_name="opensips"):
700
714
  """
@@ -762,6 +776,8 @@ class osdb(object):
762
776
  check for support of a given database dialect via SQLAlchemy
763
777
  """
764
778
  # TODO: do this only for SQLAlchemy
779
+ if not sqlalchemy_available:
780
+ return False
765
781
  try:
766
782
  sqlalchemy.create_engine('{}://'.format(dialect))
767
783
  except sqlalchemy.exc.NoSuchModuleError:
@@ -781,18 +797,13 @@ class osdb(object):
781
797
  if not self.__conn:
782
798
  raise osdbError("connection not available")
783
799
 
784
- values = ""
785
- for v in keys.values():
786
- values += ", "
787
- if type(v) == int:
788
- values += str(v)
789
- else:
790
- values += "'{}'".format(
791
- v.translate(str.maketrans({'\'': '\\\''})))
800
+ cols = list(keys.keys())
801
+ params = {"v{}".format(i): keys[k] for i, k in enumerate(cols)}
792
802
  statement = "INSERT INTO {} ({}) VALUES ({})".format(
793
- table, ", ".join(keys.keys()), values[2:])
803
+ table, ", ".join(cols),
804
+ ", ".join(":v{}".format(i) for i in range(len(cols))))
794
805
  try:
795
- result = self.__conn.execute(text(statement))
806
+ result = self.__conn.execute(text(statement), params)
796
807
  except sqlalchemy.exc.SQLAlchemyError as ex:
797
808
  logger.error("cannot execute query: {}".format(statement))
798
809
  logger.error(ex)
@@ -874,18 +885,16 @@ class osdb(object):
874
885
  raise osdbError("connection not available")
875
886
 
876
887
  update_str = ""
877
- for k, v in update_keys.items():
878
- update_str += ", {} = ".format(k)
879
- if type(v) == int:
880
- update_str += str(v)
881
- else:
882
- update_str += "'{}'".format(
883
- v.translate(str.maketrans({'\'': '\\\''})))
884
- where_str = self.get_where(filter_keys)
888
+ params = {}
889
+ for i, (k, v) in enumerate(update_keys.items()):
890
+ update_str += ", {} = :s{}".format(k, i)
891
+ params["s{}".format(i)] = v
892
+ where_str, where_params = self.get_where(filter_keys)
893
+ params.update(where_params)
885
894
  statement = "UPDATE {} SET {}{}".format(table,
886
895
  update_str[2:], where_str)
887
896
  try:
888
- result = self.__conn.execute(text(statement))
897
+ result = self.__conn.execute(text(statement), params)
889
898
  except sqlalchemy.exc.SQLAlchemyError as ex:
890
899
  logger.error("cannot execute query: {}".format(statement))
891
900
  logger.error(ex)