-
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy path__init__.py
More file actions
234 lines (177 loc) · 7.92 KB
/
__init__.py
File metadata and controls
234 lines (177 loc) · 7.92 KB
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
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Python API for the Kiwi TCMS test case management system.
#
# Copyright (c) 2012 Red Hat, Inc. All rights reserved.
# Author: Petr Splichal <psplicha@redhat.com>
#
# Copyright (c) 2018,2020-2024 Kiwi TCMS project. All rights reserved.
# Author: Alexander Todorov <info@kiwitcms.org>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
This module provides a dictionary based Python interface for the
Kiwi TCMS test management system. It operates via the XML-RPC protocol.
Installation::
pip install tcms-api
If you want to use Kerberos then::
pip install tcms-api[gssapi]
**WARNING:** on Windows you need to install MIT Kerberos and make sure
``C:\\Program Files\\MIT\\Kerberos\\bin`` is included in ``%PATH%`` -
this is usually the case when you install and restart! It must be
a 64bit installation, see
`MIT Kerberos for Windows 4.1 <https://web.mit.edu/kerberos/dist/index.html#kfw-4.1>`_
**WARNING:** on Linux you will need gcc, Python and kerberos devel packages to
build ``gssapi`` because it doesn't provide binary packages via PyPI. Try
``dnf install gcc krb5-devel python3-devel`` (Red Hat/Fedora) or
``apt-get install gcc libkrb5-dev libpython3-dev`` (Debian/Ubuntu).
Minimal config file ``~/.tcms.conf``::
[tcms]
url = https://tcms.server/xml-rpc/
username = your-username
password = your-password
For Kerberos specify the ``use_kerberos = True`` key without username
and password! Also make sure that your ``/etc/krb5.conf`` contains::
[libdefaults]
default_realm = .EXAMPLE.COM
where ``EXAMPLE.COM`` matches the realm in your organization.
.. important::
The filename ``~/.tcms.conf`` is expanded to something like
``/home/tcms-bot/.tcms.conf`` on Linux and
``C:\\Users\\tcms-bot\\.tcms.conf`` on Windows, where ``tcms-bot``
is the username on the local computer.
It's also possible to provide system-wide config in ``/etc/tcms.conf``
on Linux and ``C:\\tcms.conf`` on Windows!
Execute the following Python snippet to find the exact location on your
system::
import os
print(os.path.expanduser('~/.tcms.conf'))
Connect to backend::
from tcms_api import TCMS
rpc = TCMS().exec
for test_case in rpc.TestCase.filter({'pk': 46490}):
print(test_case)
After tcms-api v13.2 you can pass connection configuration directly as
arguments when initializing the TCMS() class::
TCMS("https://kiwitcms.example.com/xml-rpc/", "api-bot", "keep-me-secret").exec
.. important::
For a list of available RPC methods see
https://kiwitcms.readthedocs.io/en/latest/modules/tcms.rpc.api.html
Example(s) and API scripts contributed by the Kiwi TCMS community
can be found at https://github.com/kiwitcms/api-scripts. You are welcome
to open a pull request with your own examples!
"""
import os
from configparser import ConfigParser
from datetime import datetime, timedelta
try:
from distutils.util import strtobool # pylint: disable=deprecated-module
except ModuleNotFoundError:
from setuptools.dist import strtobool
from tcms_api.xmlrpc import TCMSXmlrpc, TCMSKerbXmlrpc
class _ConnectionProxy:
def __init__(self, config):
self.__connected_since = datetime(2024, 1, 1, 0, 0)
self.__connection = None
self.__config = config
@staticmethod
def server_url(config):
"""
Returns the server URL and performs various sanity checks!
"""
# Make sure the server URL is set
try:
config["tcms"]["url"] is not None
except (KeyError, AttributeError) as err:
raise RuntimeError(f"No url found in {config}") from err
return config["tcms"]["url"].replace("json-rpc", "xml-rpc")
def create_connection(self):
# try authentication credentials from Python arguments first
if self.__config["tcms"]["url"]:
config = self.__config
else:
# if not provided then try reading from the filesystem
path = os.path.expanduser("~/.tcms.conf")
# Try system settings when the config does not exist in user directory
if not os.path.exists(path):
path = "/etc/tcms.conf"
if not os.path.exists(path):
path = "c:/tcms.conf"
if not os.path.exists(path):
raise RuntimeError(f"Config file '{path}' not found")
config = ConfigParser()
config.read(path)
rpc_implementor = None
server_url = self.server_url(config)
if strtobool(config["tcms"].get("use_kerberos", "False")):
# use Kerberos
rpc_implementor = TCMSKerbXmlrpc(None, None, server_url)
else:
try:
# use password authentication
rpc_implementor = TCMSXmlrpc(
config["tcms"]["username"],
config["tcms"]["password"],
server_url,
)
except KeyError as err:
raise RuntimeError(f"username/password required in '{path}'") from err
self.__connected_since = datetime.now()
return rpc_implementor.server
def __getattr__(self, name):
"""
refresh the connection every 4 minutes to avoid an
`ssl.SSLEOFError: EOF occurred in violation of protocol` error with Python >= 3.10
In practice I've discovered that 5 minutes works as well, 6 minutes fails so
be more cautious and refresh the connection earlier!
Side note: originally I thought this is related to calling
context.set_alpn_protocols(['http/1.1']) inside http/client.py, introduced in
https://github.com/python/cpython/commit/f97406be4c0a02c1501c7ab8bc8ef3850eddb962
but that doesn't seem to be the case (or is much harder for me to debug)!
"""
# NOTE: Method only called for attributes which don't exist, iow
# XML-RPC methods, see
# https://medium.com/@satishgoda/python-attribute-access-using-getattr-and-getattribute-6401f7425ce6
if datetime.now() - self.__connected_since > timedelta(minutes=4):
self.__connection = self.create_connection()
elif self.__connection is None:
self.__connection = self.create_connection()
return self.__connection.__getattr__(name)
class TCMS: # pylint: disable=too-few-public-methods
"""
Takes care of initiating the connection to the TCMS server and
parses user configuration using a utilities class!
"""
def __init__(self, url=None, username=None, password=None):
self.config = {
"tcms": {
"url": url,
"username": username,
"password": password,
}
}
@property
def exec(self):
"""
Property that returns the underlying XML-RPC connection on which
you can call various server-side functions.
.. important::
Call this property once and assign it to a temporary variable as
shown in the examples above. Then use the ``rpc`` variable to
access the different RPC methods!
Starting with tcms-api v12.9.1 this property is automatically refreshed
every 4 minutes to avoid SSL connection timeout errors!
"""
return _ConnectionProxy(self.config)