pyrelukko 0.1.0__tar.gz → 0.2.0__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.

Potentially problematic release.


This version of pyrelukko might be problematic. Click here for more details.

@@ -0,0 +1,9 @@
1
+ [tool.pylint.main]
2
+ # Files or directories to be skipped. They should be base names, not paths.
3
+ ignore = ["LICENSE", "pyproject.toml", "version.py",
4
+ "README.md", "demo.py", "conftest.py"]
5
+
6
+ # Files or directories matching the regular expression patterns are skipped. The
7
+ # regex matches against base names, not paths. The default value ignores Emacs
8
+ # file locks
9
+ ignore-patterns = ["^\\.#", "^test_"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyrelukko
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Relukko client.
5
5
  Author-email: Reto Zingg <g.d0b3rm4n@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -29,3 +29,11 @@ Issues = "https://gitlab.com/relukko/pyrelukko/-/issues"
29
29
  [build-system]
30
30
  requires = ["flit_core >=3.2,<4", "semantic-version >= 2.10"]
31
31
  build-backend = "flit_core.buildapi"
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = [
35
+ "tests",
36
+ ]
37
+ pythonpath = [
38
+ "src",
39
+ ]
@@ -2,22 +2,20 @@
2
2
  TBD
3
3
  """
4
4
  import asyncio
5
+ import json
5
6
  import logging
6
7
  import os
7
- import json
8
- import threading
9
8
  import ssl
9
+ import threading
10
10
  import time
11
-
12
11
  from datetime import datetime
13
- from typing import Dict, List, Union
14
12
  from pathlib import Path
15
- from urllib3.util import Url, parse_url
16
- from urllib3.util.retry import Retry
13
+ from typing import Dict, List, Union
17
14
 
18
15
  import requests
16
+ from urllib3.util import Url, parse_url
17
+ from urllib3.util.retry import Retry
19
18
  from websockets import ConnectionClosed as WsConnectionClosed
20
-
21
19
  from websockets.asyncio.client import connect as ws_connect
22
20
 
23
21
  from .retry import retry
@@ -30,6 +28,14 @@ SSL_KWARGS = [
30
28
  'options',
31
29
  ]
32
30
 
31
+ RETRY_KWARGS = [
32
+ 'tries',
33
+ 'delay',
34
+ 'backoff',
35
+ 'max_delay',
36
+ 'exceptions'
37
+ ]
38
+
33
39
  logger = logging.getLogger(__name__)
34
40
 
35
41
 
@@ -50,12 +56,21 @@ class RelukkoClient:
50
56
  """
51
57
  self.session = requests.Session()
52
58
  self.api_key = api_key
59
+ self.tries=4
60
+ self.delay=5
61
+ self.backoff=2.0
62
+ self.max_delay=None
63
+ self.exceptions = (
64
+ requests.ConnectionError,
65
+ RelukkoDoRetry,
66
+ )
53
67
  self._setup_session(api_key, **kwargs)
54
68
  self._setup_http_adapters_retry(**kwargs)
69
+ self._setup_pyrelukko_retry(**kwargs)
55
70
 
56
71
  self.base_url = self._setup_base_url(base_url)
57
72
  self.ws_url = self._setup_ws_url(str(self.base_url))
58
- self.ssl_ctx = None
73
+ self.ssl_ctx: ssl.SSLContext = None
59
74
  self._setup_ssl_ctx(**kwargs)
60
75
 
61
76
  # event for websocket thread to signal it got a message
@@ -75,6 +90,15 @@ class RelukkoClient:
75
90
  self.base_url = self._setup_base_url(base_url or self.base_url)
76
91
  self.ws_url = self._setup_ws_url(str(self.base_url))
77
92
  self._setup_ssl_ctx(**kwargs)
93
+ self._setup_pyrelukko_retry(**kwargs)
94
+
95
+ def _setup_pyrelukko_retry(self, **kwargs):
96
+ for kwarg in RETRY_KWARGS:
97
+ setattr(
98
+ self,
99
+ kwarg,
100
+ kwargs.get(kwarg, getattr(self, kwarg))
101
+ )
78
102
 
79
103
  def _setup_http_adapters_retry(self, **kwargs):
80
104
  for _, http_adapter in self.session.adapters.items():
@@ -85,7 +109,7 @@ class RelukkoClient:
85
109
  http_adapter.max_retries = http_retry
86
110
 
87
111
  def _setup_session(self, api_key: str, **kwargs):
88
- self.session.headers = {'X-api-Key': api_key}
112
+ self.session.headers['X-api-Key'] = api_key
89
113
  for key, value in kwargs.items():
90
114
  if hasattr(self.session, key):
91
115
  setattr(self.session, key, value)
@@ -116,7 +140,7 @@ class RelukkoClient:
116
140
 
117
141
  # values from kwargs take precedence env vars
118
142
  ca_file = kwargs.get('cafile', ca_bundle_file)
119
- ca_path = kwargs.get('capath', ca_bundle_path)
143
+ ca_path = kwargs.get('capath', f"{ca_bundle_path}/")
120
144
  ca_data = kwargs.get('cadata')
121
145
 
122
146
  if ca_file or ca_path or ca_data:
@@ -196,7 +220,7 @@ class RelukkoClient:
196
220
 
197
221
  def _check_response(self, response: requests.Response):
198
222
  match response.status_code:
199
- case 200 | 201 | 404:
223
+ case 200 | 201 | 404 | 422:
200
224
  return response.json()
201
225
  case 400 | 403:
202
226
  err = response.json()
@@ -207,10 +231,12 @@ class RelukkoClient:
207
231
  logger.info(err.get('status'), err.get('message'))
208
232
  return None
209
233
  case 500 | 502 | 503 | 504:
210
- logger.warning(response.status_code, response.text)
234
+ logger.warning("[%d](%s) %s",
235
+ response.status_code, response.reason, response.text)
211
236
  raise RelukkoDoRetry()
212
237
  case _:
213
- logger.warning(response.status_code, response.text)
238
+ logger.warning("[%d](%s) %s",
239
+ response.status_code, response.reason, response.text)
214
240
  raise RuntimeError()
215
241
 
216
242
  def _make_request(
@@ -219,12 +245,10 @@ class RelukkoClient:
219
245
  method: str,
220
246
  payload: Dict=None) -> requests.Response:
221
247
 
222
- excpetions = (
223
- requests.ConnectionError,
224
- RelukkoDoRetry,
225
- )
226
248
 
227
- @retry(logger, exceptions=excpetions, delay=10)
249
+ @retry(logger, exceptions=self.exceptions, tries=self.tries,
250
+ delay=self.delay, backoff=self.backoff,
251
+ max_delay=self.max_delay)
228
252
  def _do_request():
229
253
  response = self.session.request(
230
254
  method=method,
@@ -274,13 +298,14 @@ class RelukkoClient:
274
298
  url = f"{self.base_url}/v1/locks/"
275
299
  return self._make_request(url, "GET")
276
300
 
277
- def update_relukko(self, lock_id: str, creator: str, expires_at: datetime):
301
+ def update_relukko(
302
+ self, lock_id: str, creator: str=None, expires_at: datetime=None):
278
303
  """
279
304
  TBD
280
305
  """
281
306
  if isinstance(expires_at, datetime):
282
307
  expires_at = expires_at.isoformat()
283
- else:
308
+ elif expires_at is not None:
284
309
  raise ValueError("has to be datetime!")
285
310
 
286
311
  payload = {
@@ -288,7 +313,7 @@ class RelukkoClient:
288
313
  "expires_at": expires_at,
289
314
  }
290
315
  url = f"{self.base_url}/v1/locks/{lock_id}"
291
- return self._make_request(url, "POST", payload)
316
+ return self._make_request(url, "PUT", payload)
292
317
 
293
318
  def delete_relukko(self, lock_id: str):
294
319
  """
@@ -1,2 +1,2 @@
1
1
  # pylint: disable=all
2
- __version__ = "0.1.0"
2
+ __version__ = "0.2.0"
@@ -0,0 +1 @@
1
+ rootCA.crt
@@ -0,0 +1,8 @@
1
+ # How To
2
+ ```
3
+ openssl req -x509 -sha256 -days 7300 -noenc -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt
4
+ ln -s rootCA.crt "$(openssl x509 -hash -noout -in rootCA.crt)"
5
+ openssl req -newkey rsa:2048 -noenc -keyout relukko.key -out relukko.csr
6
+ openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in relukko.csr -out relukko.crt -days 7299 -CAcreateserial
7
+ openssl x509 -inform PEM -in rootCA.crt -outform DER -out rootCA.der
8
+ ```
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDeDCCAmCgAwIBAgIUIG2mO1IxfM3wqiLLFCO+6ubGdHswDQYJKoZIhvcNAQEL
3
+ BQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
4
+ CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHUmVsdWtrbzAeFw0yNDEx
5
+ MTMxNTQxNTdaFw00NDExMDcxNTQxNTdaMFQxCzAJBgNVBAYTAlhYMRUwEwYDVQQH
6
+ DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAO
7
+ BgNVBAMMB3JlbHVra28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr
8
+ 89++zNQs+MG5opoCTWq/QxAJsr9Uul598XoQFXn1N/d/JOeSosy8BNvb1RXsr5sp
9
+ sNJbJEun6OPFtUoVczUtivS99XNchWPjEQ18w22aiLcwl/uOrdfxWj04yEfKmejN
10
+ ZXFrShhAWRGfc3fi2hD0Y5mtbS7T5iqaYzm4U5UDVFizVPnSgPFXMAiat/VQGvBF
11
+ cv5IpBjXOJGkLZTzmFlW61T6m+MJGXF59CLC6vL6QJCqQpSUWG9UM00R8L2pQ1b8
12
+ HLKYUuJ+vp4WVYwYyz769BNG4OG3IkdqIHKDFWBXFVmKaUkX9aAul4BOitKy4XFc
13
+ 8eu653lbkVnRHb01RaP7AgMBAAGjQjBAMB0GA1UdDgQWBBSEFybZBM4O7khSofZl
14
+ 0oAT+CSXEjAfBgNVHSMEGDAWgBRAo64Da82emF0q53wCdj4wbFTmMDANBgkqhkiG
15
+ 9w0BAQsFAAOCAQEAs+zaKt8VqLoVT0OT24Oxyzc+/A0h4BYq3kYD11BaUJ6oKqQu
16
+ 57FRKi0ZWxNSNCbzrwi6W+qNgIXtvSHWk4+BKxzqDVG6LCIuNn4bbvlQMGuTWctY
17
+ ndArPxdKvN9AXeCvam+5XKcA5OJY/naoho7fSoGh2Zgkq9HS7DlKxfBW2GEY0mp8
18
+ jeuxvBvh4xw/YuESVFbKedglaoNFf+aP44zL47Wn9QJcLqByGpTV472j238ObUFi
19
+ k47o+v4Qt7nkJDOcx7ic//uLTcb/1rv1SppYpKr3YJ5fLspzMMPpTQbkt2H8hLiZ
20
+ +1jj5GIoDgLi+/uxBgYQs78jEOIKkWO6EjwGUw==
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,16 @@
1
+ -----BEGIN CERTIFICATE REQUEST-----
2
+ MIICmTCCAYECAQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0
3
+ eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHcmVsdWtr
4
+ bzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKvz377M1Cz4wbmimgJN
5
+ ar9DEAmyv1S6Xn3xehAVefU3938k55KizLwE29vVFeyvmymw0lskS6fo48W1ShVz
6
+ NS2K9L31c1yFY+MRDXzDbZqItzCX+46t1/FaPTjIR8qZ6M1lcWtKGEBZEZ9zd+La
7
+ EPRjma1tLtPmKppjObhTlQNUWLNU+dKA8VcwCJq39VAa8EVy/kikGNc4kaQtlPOY
8
+ WVbrVPqb4wkZcXn0IsLq8vpAkKpClJRYb1QzTRHwvalDVvwcsphS4n6+nhZVjBjL
9
+ Pvr0E0bg4bciR2ogcoMVYFcVWYppSRf1oC6XgE6K0rLhcVzx67rneVuRWdEdvTVF
10
+ o/sCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCHkmw8bBBI9pOrlCrJ2u3sGdlx
11
+ 3ZbW/+kGdsQ8Odb/pIHkLLKpu5yNLEey/U4EZyWSNewYDUKfS2FIyMN2c38QAex6
12
+ 1Y9qPK74RzUB68ki7XKtp+GAGqVb0axxRe0IuBwc9d5jl6zjltcpdutAIIgXcJgg
13
+ 8QPnfpz7Skhjjm/duTVp0rurWsPztQdDpkrcY3c49vuTLA5uKwfa//cPAOmVGwqq
14
+ TMICj+0M2S+bgg9Ksph1IsDxxuIm1aa2EFnF5XRbJn9aOSdyfBkKoZz9ttgu1hP4
15
+ +hPe8S3ekUyJiZAglSI1qJOkpyw14EDIhwJvE9FG5eN/mU8wS1/GU+vhZWKN
16
+ -----END CERTIFICATE REQUEST-----
Binary file
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCr89++zNQs+MG5
3
+ opoCTWq/QxAJsr9Uul598XoQFXn1N/d/JOeSosy8BNvb1RXsr5spsNJbJEun6OPF
4
+ tUoVczUtivS99XNchWPjEQ18w22aiLcwl/uOrdfxWj04yEfKmejNZXFrShhAWRGf
5
+ c3fi2hD0Y5mtbS7T5iqaYzm4U5UDVFizVPnSgPFXMAiat/VQGvBFcv5IpBjXOJGk
6
+ LZTzmFlW61T6m+MJGXF59CLC6vL6QJCqQpSUWG9UM00R8L2pQ1b8HLKYUuJ+vp4W
7
+ VYwYyz769BNG4OG3IkdqIHKDFWBXFVmKaUkX9aAul4BOitKy4XFc8eu653lbkVnR
8
+ Hb01RaP7AgMBAAECggEAF22KShjmkwavf7HfnDmyYMMWhSO//XTJBA1/DSrylg5d
9
+ NQsGVsxslZ9vLyP4sT9PJjPWbUmO2fPuo1TVy6y0jZ3K8Qi8rNnrEphpBzB/Qe38
10
+ +5dxfu6WUebIGZucFca42SdITTm9VSMYNhU0O98XLp0AEV4jYlvlPmoPhrAzSM50
11
+ lEIuriNhqHuqZhXN0jPYI1jZDS5JVi2PlbgyO1125BBMsCMLfhW+wcme/Y/VAySc
12
+ pNkvK3aHeWJUXEeVKuNfn5878PqeD6ZPjl9Hyq3lj6yOauIO/PCdIwm+2qjzrYsy
13
+ IBhjFFEoMGofgz+KV7olZu9by38ZzhW7nZGGe+zbgQKBgQDmFyzzxz3/BF7FKTXL
14
+ v6uVNkGpMGH8EQE1H+YJXTiSaqf/3ZrsXyBsbA6YBanKBXcY/F0PhQ7e/rjtqaGU
15
+ OtekNTHSgosdbJgVX88EjfIa4/rbZPUsX6qRJBuDVOCcCZC1PyFBT2ehKYlIPHS0
16
+ klbXo57pYzgDnzrYPr6UWbCZewKBgQC/UMGNvqJXszYZe3hjd9Ek9oSJMThfpfXn
17
+ opNhCNHKZ8+xqry602TqIwyStYc7elB57R547ZR4kttT8iM1E7ZOoLRPP0r/FjOS
18
+ scpCAk5ouVG9RZaUvTIbvzdEifEOt1Vf0RgIG3Y4DBqtotNoymqyT5wzuxyJQm1E
19
+ 683UVIbXgQKBgQCLPJ0UTI7kwtVCxIRsbum7WuDzLHcvHW84obwIEKSKXgaaHJWC
20
+ 0rIBSoauUkcEHLiMozMBkEiGg2iPUaaY197k3NfwhtT+kleaH6dcHzXSNgH5QCfp
21
+ mV7ThCEuIW/mnRc3xyMtrYqNiWAtGYCaQTBSQA6LN2KPNo1ajOWxSnFG/wKBgQC2
22
+ 9rBksq/nV4ihjidwWSI3S1stKVlUgA9QW3a/EgQwol9K9pJPyeN019gqZljSVQOp
23
+ 10+RLwUS2r/O5H8vP47WW3KVZ1593emsnUNlJXd/R9wYOvjrfpTxXEmqzpEvFb4c
24
+ SIfHGRxSNaE99b5hNVQc+23TO1rrGhAOHcVXDw92AQKBgQDCMEcIcwHx+ahVZbEb
25
+ bAyWLwYxQFRStmYKbYBtEMFW39atRmc0wdAG0my/0cUyHUIfWbHvP5oTfyAthV9G
26
+ 8G0qBk5fPhh6hfNRFMb2j1AqnZ3CadoBZq2HelTTUqt4UgU9Y4iDco+4ew34Uw76
27
+ 2ki1bKXiJKkJYlk14AMrzckDUQ==
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDiTCCAnGgAwIBAgIUeLTh5JgTZlG0dbwtCICijkg1MWwwDQYJKoZIhvcNAQEL
3
+ BQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
4
+ CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHUmVsdWtrbzAeFw0yNDEx
5
+ MTMxNTM3MDBaFw00NDExMDgxNTM3MDBaMFQxCzAJBgNVBAYTAlhYMRUwEwYDVQQH
6
+ DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAO
7
+ BgNVBAMMB1JlbHVra28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4
8
+ PWQPjCCXOvZYUDpYhZHR4Nw0ErkZEZWtPMyoBnyGkYwqK1SrHCcIYQL8C22dN1rn
9
+ VdFvCVH13fQHz1SJ/v3jkS+9IiOUDenXpZP/G0pprTbLGh33aaQU53EqvO1T2CAA
10
+ zIAVdme0cihOB+KL2A5bDzP9fu+c0ddn+N++ZgcqBHMYmZKIGJv5QZ+i+/1xrGqQ
11
+ /znwV7RuBGms8fxA3x4Y4ELUzIHMpbXbQn95qC9u7ee4odQ5KjR1M0oocPKOYBGa
12
+ /Dqnhd3Wx0lHR3No8HgC4TBfxruhs+NZtRyInOTFr99TWbBx+zrDvxecM/5j/AnH
13
+ WHka8xMQSxJUeoXtlFT/AgMBAAGjUzBRMB0GA1UdDgQWBBRAo64Da82emF0q53wC
14
+ dj4wbFTmMDAfBgNVHSMEGDAWgBRAo64Da82emF0q53wCdj4wbFTmMDAPBgNVHRMB
15
+ Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQACdVeJ/CnXpY4t568bQRxIr+t3
16
+ IqeIqJVg1nt6OaB/EJPJil/lvJD8bjO5W5uXAnbkv26i0Q89KFZH3xKFHRmL/zR+
17
+ 0YPcbi3LmQ+NY6LtH1GtBlRiobROfSp8/GxTrGxqMGHHxBdY+v8lPiK1LHTE99ik
18
+ cl8qF+DQMGeliPQcg9rLgXagymEc9oS3OgxjL7Al4/P8bLkdC/hxk+cEUFCXUX4v
19
+ z/PAFnHwEeJPiiNYG+hpQQRloVncynEwhBr/pGGMdLGaFBje41kn9Ss2anc+YaiW
20
+ DyoDLuQkCcRrUvc2dCGMZs0KaCv8i61QWGNy8suGq6nrXW9A57JhS7ak8xg9
21
+ -----END CERTIFICATE-----
Binary file
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4PWQPjCCXOvZY
3
+ UDpYhZHR4Nw0ErkZEZWtPMyoBnyGkYwqK1SrHCcIYQL8C22dN1rnVdFvCVH13fQH
4
+ z1SJ/v3jkS+9IiOUDenXpZP/G0pprTbLGh33aaQU53EqvO1T2CAAzIAVdme0cihO
5
+ B+KL2A5bDzP9fu+c0ddn+N++ZgcqBHMYmZKIGJv5QZ+i+/1xrGqQ/znwV7RuBGms
6
+ 8fxA3x4Y4ELUzIHMpbXbQn95qC9u7ee4odQ5KjR1M0oocPKOYBGa/Dqnhd3Wx0lH
7
+ R3No8HgC4TBfxruhs+NZtRyInOTFr99TWbBx+zrDvxecM/5j/AnHWHka8xMQSxJU
8
+ eoXtlFT/AgMBAAECggEAeQhprIDsdP/dE4MOZ5G8XsHNa6BDWlsuCLivqi77cRsu
9
+ 5XKaBhGkYFv4eg5SZAiUzF0Vz5cSPJf6vwISHHRUsqtbxNIL2ciM3sVO6t+SAQNg
10
+ x1cVDEgYJc8QaL+T0UD7nZgXzR8dgif0ydLjXL67hR9dAHhu+J2BcqKv6KPTU2LB
11
+ KWbCcLkY4PdCGwSNuvyPokc3Fo8U40Lm8tcWQ6sFV/+bv6+ShWhsb5Dx7sfVoijf
12
+ YikzFc7sEHb//nH2+C/FoCqneDPYl8lVH9zKYis4xWcA68bNwyfWpaQLXo32DKYq
13
+ rZxnwnawtU5b+t+TVp1DcCjOM3vaodeKI3Gqq5SjBQKBgQD/Sy33WOK+qelkvJxb
14
+ IcyP3U/ijNpWP3UidYGmmpTBP5UdaXyObX1G4nmfQjDad080jPpP/r350Tr0Xi2s
15
+ 6wZBw0HoTZrZw5MQQKRQyh0Hg5VHHoHbD97UuLGGZ+Yc/hSWWfuDQHeV5Ami+VXD
16
+ TCYZGhtsjY9lUfUWYcsdcOA1fQKBgQD47TcVSC+3/8zKd2AAJeYrpx6wL/5X/4Zn
17
+ U0VeBxfF6MjQlu3avnwi6MxjkuB4KJAgWVHvJspJaS8ilIJN5qBsivphtfD0RV+d
18
+ UW9DCzoDUlU/AmNMD+stylgziSWoG9rzcY7P71izRE1tGGsgOXvZS5xUHzqWBwg6
19
+ MtYB4EQNKwKBgQCTUBVpKmBE9xTXbUKoD5vT1Df5mZ+PrzRvOvEiawa1cHQiMbGP
20
+ Gjz0/1CBBpfcKIaK42K5cFy9X++t/P5MTp4gqoRIgSd+yyz8buCiQc54fIRSMpdq
21
+ CgFiLGU8Eo4lYrQMgkXw2e1nj9vDsC698B331CnI/PKm26EaVjn3dh/anQKBgGxB
22
+ FWRu+TmmyBQA6EIIOVogmqr6pDz2xienQhKLOR57huGX0acAkhHIdiKTnIUE9vDq
23
+ h0Re9TgJw1LhjO1976RkqFDYBArnJJbQ9HcOqdMJ+kKlsjNA9QD773GyIitCueyH
24
+ JRluuH91o8pfBS+FcEPmqvy2fA8EzeIpe4JjWpTzAoGAQHoipuzDbhb02XMqioc+
25
+ UCsa6erj3kGrBUiFcvNutTG+oVq3qXOebOc9bCZcGHwsm0fgX5zk1XgY+IRfCWJY
26
+ X14DQfLMBi/vObDXH9BaoVvc50EPSBW0V5k2aXavJ6XelbBXSI33bzGb+5wAltI/
27
+ CFKf5ePEAi3jE69yYnTwbdc=
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1 @@
1
+ 206DA63B52317CCDF0AA22CB1423BEEAE6C6747B
@@ -0,0 +1,129 @@
1
+ import socket
2
+ import ssl
3
+ import threading
4
+ from pathlib import Path
5
+ from typing import List
6
+
7
+ import pytest
8
+ from testcontainers.core.network import Network
9
+ from testcontainers.core.waiting_utils import wait_container_is_ready
10
+ from testcontainers.generic import ServerContainer
11
+ from testcontainers.postgres import PostgresContainer
12
+
13
+ from pyrelukko import RelukkoClient
14
+
15
+ SCRIPT_DIR = Path(__file__).parent.absolute()
16
+ INITDB_DIR = SCRIPT_DIR / "initdb.d"
17
+
18
+
19
+ class RelukkoContainer(ServerContainer):
20
+ def __init__(self, net: Network,
21
+ image="registry.gitlab.com/relukko/relukko:0.9.0", db_url=None):
22
+ self.db_url = db_url
23
+ self.net = net
24
+ super(RelukkoContainer, self).__init__(image=image, port=3000)
25
+
26
+ def _configure(self):
27
+ self.with_env("DATABASE_URL", self.db_url)
28
+ self.with_env("RELUKKO_API_KEY", "somekey")
29
+ self.with_env("RELUKKO_USER", "relukko")
30
+ self.with_env("RELUKKO_PASSWORD", "relukko")
31
+ self.with_env("RELUKKO_BIND_ADDR", "0.0.0.0")
32
+ self.with_network(self.net)
33
+
34
+ def get_api_url(self) -> str:
35
+ return f"http://localhost:{self.get_exposed_port(3000)}"
36
+
37
+ def _create_connection_url(self) -> str:
38
+ return f"{self.get_api_url()}/healthchecker"
39
+
40
+
41
+ class RelukkoDbContainer(PostgresContainer):
42
+ def __init__(self, net: Network, image: str = "postgres:latest", port: int = 5432, username: str | None = None, password: str | None = None, dbname: str | None = None, driver: str | None = "psycopg2", **kwargs) -> None:
43
+ self.net = net
44
+ super().__init__(image, port, username, password, dbname, driver, **kwargs)
45
+
46
+ def _configure(self) -> None:
47
+ self.with_volume_mapping(INITDB_DIR, "/docker-entrypoint-initdb.d", "Z")
48
+ self.with_env("POSTGRES_USER", "relukko")
49
+ self.with_env("POSTGRES_PASSWORD", "relukko")
50
+ self.with_env("POSTGRES_DB", "relukko")
51
+ self.with_network(self.net)
52
+
53
+ @wait_container_is_ready()
54
+ def _connect(self) -> None:
55
+ packet = bytes([
56
+ 0x00, 0x00, 0x00, 0x52, 0x00, 0x03, 0x00, 0x00,
57
+ 0x75, 0x73, 0x65, 0x72, 0x00, 0x72, 0x65, 0x6c,
58
+ 0x75, 0x6b, 0x6b, 0x6f, 0x00, 0x64, 0x61, 0x74,
59
+ 0x61, 0x62, 0x61, 0x73, 0x65, 0x00, 0x72, 0x65,
60
+ 0x6c, 0x75, 0x6b, 0x6b, 0x6f, 0x00, 0x61, 0x70,
61
+ 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
62
+ 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x70,
63
+ 0x73, 0x71, 0x6c, 0x00, 0x63, 0x6c, 0x69, 0x65,
64
+ 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64,
65
+ 0x69, 0x6e, 0x67, 0x00, 0x55, 0x54, 0x46, 0x38,
66
+ 0x00, 0x00
67
+ ])
68
+ port = self.get_exposed_port(self.port)
69
+ with socket.create_connection(("localhost", port)) as sock:
70
+ sock.send(packet)
71
+ buf = sock.recv(40)
72
+ if len(buf) == 0 and "SCRAM-SHA" not in buf:
73
+ raise ConnectionError
74
+
75
+
76
+ @pytest.fixture(scope="session")
77
+ def relukko_backend():
78
+ with Network() as rl_net:
79
+ with RelukkoDbContainer(net=rl_net,
80
+ image="postgres:16", hostname="relukkodb") as _db:
81
+ db_url = "postgresql://relukko:relukko@relukkodb/relukko"
82
+ with RelukkoContainer(rl_net, db_url=db_url) as backend:
83
+ relukko = RelukkoClient(
84
+ base_url=backend.get_api_url(), api_key="somekey")
85
+ yield relukko, backend
86
+
87
+ @pytest.fixture(scope="function")
88
+ def tls_listener():
89
+
90
+ certfile = SCRIPT_DIR / "cert" / "relukko.crt"
91
+ keyfile = SCRIPT_DIR / "cert" / "relukko.key"
92
+
93
+ def run_server(port_info: List, keep_running: threading.Event):
94
+ # Create a TCP socket
95
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
96
+ sock.bind(("127.0.0.1", 0))
97
+ sock.listen(5) # Listen for incoming connections
98
+
99
+ _, assigned_port = sock.getsockname()
100
+ port_info.append(assigned_port)
101
+
102
+ # Wrap the socket with TLS
103
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
104
+ context.load_cert_chain(certfile=certfile, keyfile=keyfile)
105
+ with context.wrap_socket(sock, server_side=True) as ssock:
106
+
107
+ ssock.settimeout(1)
108
+
109
+ while keep_running.is_set():
110
+ try:
111
+ client_socket, _ = ssock.accept()
112
+ client_socket.sendall(b"Hello, TLS Client!")
113
+ client_socket.close()
114
+ except Exception as _:
115
+ continue
116
+
117
+ port_info = []
118
+ keep_running = threading.Event()
119
+ keep_running.set()
120
+ thread = threading.Thread(target=run_server, args=(port_info, keep_running))
121
+ thread.start()
122
+
123
+ while not port_info:
124
+ pass
125
+
126
+ yield thread, port_info[0]
127
+
128
+ keep_running.clear()
129
+ thread.join()
@@ -0,0 +1,19 @@
1
+ -- Add up migration script here
2
+ CREATE EXTENSION moddatetime;
3
+
4
+ CREATE TABLE locks (
5
+ id uuid DEFAULT gen_random_uuid(),
6
+ lock_name VARCHAR NOT NULL,
7
+ creator VARCHAR,
8
+ ip INET,
9
+ expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + (10 ||' minutes')::interval ,
10
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
12
+ PRIMARY KEY (id),
13
+ UNIQUE(lock_name)
14
+ );
15
+
16
+ CREATE TRIGGER locks_moddatetime
17
+ BEFORE UPDATE ON locks
18
+ FOR EACH ROW
19
+ EXECUTE PROCEDURE moddatetime (updated_at);
@@ -0,0 +1,470 @@
1
+ import logging
2
+ import time
3
+ import ssl
4
+ import socket
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ from typing import Dict
8
+
9
+ import pytest
10
+ import requests
11
+ from requests.adapters import HTTPAdapter
12
+ from requests.cookies import cookiejar_from_dict
13
+ from urllib3.util import parse_url
14
+
15
+ from pyrelukko import RelukkoClient
16
+ from pyrelukko.pyrelukko import RelukkoDoRetry
17
+ from pyrelukko.retry import retry
18
+
19
+
20
+ SCRIPT_DIR = Path(__file__).parent.absolute()
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ @retry(None, ConnectionRefusedError)
26
+ def _check_tls(ssl_ctx: ssl.SSLContext, port):
27
+ with socket.create_connection(("127.0.0.1", port)) as sock:
28
+ with ssl_ctx.wrap_socket(sock, server_hostname="relukko") as ssock:
29
+ response = ssock.recv(20)
30
+ assert response == b"Hello, TLS Client!"
31
+
32
+
33
+ def _check_has_serial_no(ssl_ctx: ssl.SSLContext):
34
+ serial_numbers = [x["serialNumber"] for x in ssl_ctx.get_ca_certs()]
35
+ assert "78B4E1E498136651B475BC2D0880A28E4835316C" in serial_numbers
36
+
37
+
38
+ def test_init_relukko_client():
39
+ relukko = RelukkoClient(base_url="", api_key="")
40
+
41
+ assert isinstance(relukko, RelukkoClient)
42
+ assert relukko.session.trust_env == True
43
+ assert relukko.session.cookies._cookies == {}
44
+ assert relukko.ssl_ctx is None
45
+ http_adapter: HTTPAdapter = relukko.session.adapters.get("http://")
46
+ assert http_adapter.max_retries.total == 0
47
+ assert http_adapter.max_retries.connect is None
48
+ assert http_adapter.max_retries.read == False
49
+ assert http_adapter.max_retries.redirect is None
50
+ assert http_adapter.max_retries.status is None
51
+ assert http_adapter.max_retries.other is None
52
+ assert http_adapter.max_retries.backoff_factor == 0
53
+ assert http_adapter.max_retries.backoff_max == 120
54
+ assert http_adapter.max_retries.backoff_jitter == 0.0
55
+ assert http_adapter.max_retries.raise_on_redirect == True
56
+ assert http_adapter.max_retries.raise_on_status == True
57
+ assert relukko.tries == 4
58
+ assert relukko.delay == 5
59
+ assert relukko.backoff == 2.0
60
+ assert relukko.max_delay is None
61
+ assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
62
+
63
+ with pytest.raises(ValueError):
64
+ RelukkoClient(base_url=1000, api_key="")
65
+
66
+ def test_init_relukko_client_with_env_var(tls_listener):
67
+ _, port = tls_listener
68
+
69
+ capath = SCRIPT_DIR / "cert"
70
+ cafile = capath / "rootCA.crt"
71
+
72
+ pytest.MonkeyPatch().setenv("REQUESTS_CA_BUNDLE", str(cafile.absolute()))
73
+ relukko = RelukkoClient(base_url="https://relukko", api_key="")
74
+ pytest.MonkeyPatch().delenv("REQUESTS_CA_BUNDLE")
75
+ _check_tls(relukko.ssl_ctx, port)
76
+ _check_has_serial_no(relukko.ssl_ctx)
77
+
78
+ pytest.MonkeyPatch().setenv("CURL_CA_BUNDLE", str(cafile.absolute()))
79
+ relukko2 = RelukkoClient(base_url="https://relukko", api_key="")
80
+ pytest.MonkeyPatch().delenv("CURL_CA_BUNDLE")
81
+ _check_tls(relukko2.ssl_ctx, port)
82
+ _check_has_serial_no(relukko2.ssl_ctx)
83
+
84
+ pytest.MonkeyPatch().setenv("REQUESTS_CA_BUNDLE", str(capath.absolute()))
85
+ relukko3 = RelukkoClient(base_url="https://relukko", api_key="")
86
+ pytest.MonkeyPatch().delenv("REQUESTS_CA_BUNDLE")
87
+ _check_tls(relukko3.ssl_ctx, port)
88
+ _check_has_serial_no(relukko3.ssl_ctx)
89
+
90
+ pytest.MonkeyPatch().setenv("CURL_CA_BUNDLE", str(capath.absolute()))
91
+ relukko4 = RelukkoClient(base_url="https://relukko", api_key="")
92
+ pytest.MonkeyPatch().delenv("CURL_CA_BUNDLE")
93
+ _check_tls(relukko4.ssl_ctx, port)
94
+ _check_has_serial_no(relukko4.ssl_ctx)
95
+
96
+
97
+ def test_init_relukko_client_with_cafile(tls_listener):
98
+ _, port = tls_listener
99
+
100
+ cafile = SCRIPT_DIR / "cert" / "rootCA.crt"
101
+
102
+ relukko = RelukkoClient(
103
+ base_url="https://relukko", api_key="", cafile=cafile)
104
+ _check_tls(relukko.ssl_ctx, port)
105
+ _check_has_serial_no(relukko.ssl_ctx)
106
+
107
+
108
+ def test_init_relukko_client_with_capath(tls_listener):
109
+ _, port = tls_listener
110
+
111
+ capath = SCRIPT_DIR / "cert"
112
+
113
+ relukko = RelukkoClient(
114
+ base_url="https://relukko", api_key="", capath=capath)
115
+ _check_tls(relukko.ssl_ctx, port)
116
+ _check_has_serial_no(relukko.ssl_ctx)
117
+
118
+
119
+ def test_init_relukko_client_with_cadata(tls_listener):
120
+ _, port = tls_listener
121
+
122
+ cafile = SCRIPT_DIR / "cert" / "rootCA.crt"
123
+ cadata = cafile.read_text()
124
+
125
+ relukko = RelukkoClient(
126
+ base_url="https://relukko", api_key="", cadata=cadata)
127
+ _check_tls(relukko.ssl_ctx, port)
128
+ _check_has_serial_no(relukko.ssl_ctx)
129
+
130
+ cader = SCRIPT_DIR / "cert" / "rootCA.der"
131
+ cadata = cader.read_bytes()
132
+
133
+ relukko2 = RelukkoClient(
134
+ base_url="https://relukko", api_key="", cadata=cadata)
135
+ _check_tls(relukko2.ssl_ctx, port)
136
+ _check_has_serial_no(relukko2.ssl_ctx)
137
+
138
+
139
+ def test_check_tls_throws_error(tls_listener):
140
+ _, port = tls_listener
141
+
142
+ relukko = RelukkoClient(
143
+ base_url="https://relukko", api_key="")
144
+ with pytest.raises(ssl.SSLCertVerificationError):
145
+ _check_tls(relukko.ssl_ctx, port)
146
+
147
+
148
+ def test_reconfigure_relukko_client():
149
+ relukko = RelukkoClient(base_url="", api_key="")
150
+
151
+ relukko.reconfigure_relukko(
152
+ "https://relukko", api_key="secret-key")
153
+
154
+ assert relukko.base_url == parse_url("https://relukko")
155
+ assert relukko.ws_url == parse_url("wss://relukko/deletions")
156
+ assert relukko.session.headers['X-api-Key'] == "secret-key"
157
+
158
+ default_ctx = ssl.create_default_context()
159
+ assert relukko.ssl_ctx.check_hostname == default_ctx.check_hostname
160
+ assert relukko.ssl_ctx.hostname_checks_common_name == \
161
+ default_ctx.hostname_checks_common_name
162
+ assert relukko.ssl_ctx.keylog_filename == default_ctx.keylog_filename
163
+ assert relukko.ssl_ctx.sni_callback == default_ctx.sni_callback
164
+ assert relukko.ssl_ctx.verify_flags == default_ctx.verify_flags
165
+ assert relukko.ssl_ctx.verify_mode == default_ctx.verify_mode
166
+ assert relukko.ssl_ctx.options == default_ctx.options
167
+ assert relukko.tries == 4
168
+ assert relukko.delay == 5
169
+ assert relukko.backoff == 2.0
170
+ assert relukko.max_delay is None
171
+ assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
172
+
173
+
174
+ def test_reconfigure_relukko_client_extended():
175
+ relukko = RelukkoClient(base_url="", api_key="")
176
+
177
+ cookies = cookiejar_from_dict({"cookie1": "value1"})
178
+ verify_mode = ssl.VerifyMode.CERT_NONE
179
+ verify_flags = ssl.VerifyFlags.VERIFY_DEFAULT
180
+ options = ssl.Options.OP_ALL
181
+
182
+ relukko.reconfigure_relukko(
183
+ base_url="https://relukko", api_key="my-API-key", trust_env=False,
184
+ cookies=cookies, total=100, connect=99, read=98, redirect=97,
185
+ status=96, other=95, backoff_factor=94, backoff_max=1000,
186
+ backoff_jitter=93, raise_on_redirect=False, raise_on_status=False,
187
+ check_hostname=False, hostname_checks_common_name=False,
188
+ verify_mode=verify_mode, verify_flags=verify_flags, options=options
189
+ )
190
+
191
+ assert relukko.session.trust_env == False
192
+ assert relukko.session.cookies == cookies
193
+ assert relukko.base_url == parse_url("https://relukko")
194
+ assert relukko.ws_url == parse_url("wss://relukko/deletions")
195
+ assert relukko.session.headers['X-api-Key'] == "my-API-key"
196
+ http_adapter: HTTPAdapter = relukko.session.adapters.get("http://")
197
+ assert http_adapter.max_retries.total == 100
198
+ assert http_adapter.max_retries.connect == 99
199
+ assert http_adapter.max_retries.read == 98
200
+ assert http_adapter.max_retries.redirect == 97
201
+ assert http_adapter.max_retries.status == 96
202
+ assert http_adapter.max_retries.other == 95
203
+ assert http_adapter.max_retries.backoff_factor == 94.0
204
+ assert http_adapter.max_retries.backoff_max == 1000.0
205
+ assert http_adapter.max_retries.backoff_jitter == 93.0
206
+ assert http_adapter.max_retries.raise_on_redirect == False
207
+ assert http_adapter.max_retries.raise_on_status == False
208
+ assert isinstance(relukko.ssl_ctx, ssl.SSLContext)
209
+ assert relukko.ssl_ctx.check_hostname == False
210
+ assert relukko.ssl_ctx.hostname_checks_common_name == False
211
+ assert relukko.ssl_ctx.keylog_filename is None
212
+ assert relukko.ssl_ctx.sni_callback is None
213
+ assert relukko.ssl_ctx.verify_flags == verify_flags
214
+ assert relukko.ssl_ctx.verify_mode == verify_mode
215
+ assert relukko.ssl_ctx.options == options
216
+ assert relukko.tries == 4
217
+ assert relukko.delay == 5
218
+ assert relukko.backoff == 2.0
219
+ assert relukko.max_delay is None
220
+ assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
221
+
222
+
223
+ def test_init_relukko_client_extented():
224
+ cookies = cookiejar_from_dict({"cookie1": "value1"})
225
+
226
+ relukko = RelukkoClient(
227
+ base_url="http://relukko", api_key="my-API-key", trust_env=False,
228
+ cookies=cookies, total=100, connect=99, read=98, redirect=97,
229
+ status=96, other=95, backoff_factor=94, backoff_max=1000,
230
+ backoff_jitter=93, raise_on_redirect=False, raise_on_status=False,
231
+ )
232
+
233
+ assert relukko.session.trust_env == False
234
+ assert relukko.session.cookies == cookies
235
+ assert relukko.base_url == parse_url("http://relukko")
236
+ assert relukko.ws_url == parse_url("ws://relukko/deletions")
237
+ assert relukko.session.headers['X-api-Key'] == "my-API-key"
238
+ assert relukko.ssl_ctx is None
239
+ http_adapter: HTTPAdapter = relukko.session.adapters.get("http://")
240
+ assert http_adapter.max_retries.total == 100
241
+ assert http_adapter.max_retries.connect == 99
242
+ assert http_adapter.max_retries.read == 98
243
+ assert http_adapter.max_retries.redirect == 97
244
+ assert http_adapter.max_retries.status == 96
245
+ assert http_adapter.max_retries.other == 95
246
+ assert http_adapter.max_retries.backoff_factor == 94.0
247
+ assert http_adapter.max_retries.backoff_max == 1000.0
248
+ assert http_adapter.max_retries.backoff_jitter == 93.0
249
+ assert http_adapter.max_retries.raise_on_redirect == False
250
+ assert http_adapter.max_retries.raise_on_status == False
251
+ assert relukko.tries == 4
252
+ assert relukko.delay == 5
253
+ assert relukko.backoff == 2.0
254
+ assert relukko.max_delay is None
255
+ assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
256
+
257
+
258
+ def test_init_relukko_client_ssl_ctx():
259
+ relukko = RelukkoClient(
260
+ base_url="https://relukko", api_key="my-API-key",
261
+ )
262
+
263
+ default_ctx = ssl.create_default_context()
264
+
265
+ assert isinstance(relukko.ssl_ctx, ssl.SSLContext)
266
+ assert relukko.ssl_ctx.check_hostname == default_ctx.check_hostname
267
+ assert relukko.ssl_ctx.hostname_checks_common_name == \
268
+ default_ctx.hostname_checks_common_name
269
+ assert relukko.ssl_ctx.keylog_filename == default_ctx.keylog_filename
270
+ assert relukko.ssl_ctx.sni_callback == default_ctx.sni_callback
271
+ assert relukko.ssl_ctx.verify_flags == default_ctx.verify_flags
272
+ assert relukko.ssl_ctx.verify_mode == default_ctx.verify_mode
273
+ assert relukko.ssl_ctx.options == default_ctx.options
274
+ assert relukko.tries == 4
275
+ assert relukko.delay == 5
276
+ assert relukko.backoff == 2.0
277
+ assert relukko.max_delay is None
278
+ assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
279
+
280
+
281
+ def test_init_relukko_client_ssl_ctx_extended():
282
+ verify_mode = ssl.VerifyMode.CERT_NONE
283
+ verify_flags = ssl.VerifyFlags.VERIFY_DEFAULT
284
+ options = ssl.Options.OP_ALL
285
+
286
+ relukko = RelukkoClient(
287
+ base_url="https://relukko", api_key="my-API-key", check_hostname=False,
288
+ hostname_checks_common_name=False, verify_mode=verify_mode,
289
+ verify_flags=verify_flags, options=options
290
+ )
291
+ assert isinstance(relukko.ssl_ctx, ssl.SSLContext)
292
+ assert relukko.ssl_ctx.check_hostname == False
293
+ assert relukko.ssl_ctx.hostname_checks_common_name == False
294
+ assert relukko.ssl_ctx.keylog_filename is None
295
+ assert relukko.ssl_ctx.sni_callback is None
296
+ assert relukko.ssl_ctx.verify_flags == verify_flags
297
+ assert relukko.ssl_ctx.verify_mode == verify_mode
298
+ assert relukko.ssl_ctx.options == options
299
+ assert relukko.tries == 4
300
+ assert relukko.delay == 5
301
+ assert relukko.backoff == 2.0
302
+ assert relukko.max_delay is None
303
+ assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
304
+
305
+
306
+ def test_init_relukko_client_retry():
307
+
308
+ exceptions = (
309
+ ValueError,
310
+ ConnectionRefusedError,
311
+ ConnectionResetError,
312
+ )
313
+
314
+ relukko = RelukkoClient(
315
+ base_url="", api_key="", tries=100, delay=99, backoff=98.7,
316
+ max_delay=97, exceptions=exceptions)
317
+
318
+ assert relukko.tries == 100
319
+ assert relukko.delay == 99
320
+ assert relukko.backoff == 98.7
321
+ assert relukko.max_delay == 97
322
+ assert relukko.exceptions == exceptions
323
+
324
+
325
+ def test_reconfigre_relukko_client_retry():
326
+ relukko = RelukkoClient(base_url="", api_key="")
327
+
328
+ exceptions = (
329
+ ValueError,
330
+ ConnectionRefusedError,
331
+ ConnectionResetError,
332
+ )
333
+
334
+ relukko.reconfigure_relukko(
335
+ tries=100, delay=99, backoff=98.7,
336
+ max_delay=97, exceptions=exceptions)
337
+
338
+ assert relukko.tries == 100
339
+ assert relukko.delay == 99
340
+ assert relukko.backoff == 98.7
341
+ assert relukko.max_delay == 97
342
+ assert relukko.exceptions == exceptions
343
+
344
+
345
+ def test_acquire_relukko(relukko_backend):
346
+ relukko, _ = relukko_backend
347
+
348
+ lock = relukko.acquire_relukko("pylock", "pytest", 10)
349
+ assert isinstance(lock, Dict)
350
+ start_time = time.time()
351
+ lock = relukko.acquire_relukko("pylock", "pytest", 30)
352
+ end_time = time.time()
353
+ assert lock is None
354
+ assert 29 < end_time - start_time < 34
355
+
356
+
357
+ def test_delete_relukko(relukko_backend):
358
+ relukko, _ = relukko_backend
359
+
360
+ lock = relukko.acquire_relukko("del_lock", "pytest", 10)
361
+ del_lock = relukko.delete_relukko(lock['id'])
362
+ assert lock == del_lock
363
+
364
+ get_lock = relukko.get_lock(lock['id'])
365
+ assert get_lock['status'] == 422
366
+
367
+ locks = relukko.get_locks()
368
+ assert lock not in locks
369
+
370
+
371
+ def test_get_relukko(relukko_backend):
372
+ relukko, _ = relukko_backend
373
+
374
+ lock = relukko.acquire_relukko("get_lock", "pytest", 10)
375
+ assert isinstance(lock, Dict)
376
+
377
+ get_lock = relukko.get_lock(lock['id'])
378
+ assert get_lock == lock
379
+
380
+ locks = relukko.get_locks()
381
+ assert lock in locks
382
+
383
+
384
+ def test_update_relukko_creator(relukko_backend):
385
+ relukko, _ = relukko_backend
386
+
387
+ lock = relukko.acquire_relukko("update_lock", "pytest", 10)
388
+ assert lock['creator'] == "pytest"
389
+
390
+ upd_lock = relukko.update_relukko(lock['id'], creator="tsetyp")
391
+ assert upd_lock['creator'] == "tsetyp"
392
+
393
+ get_lock = relukko.get_lock(lock['id'])
394
+ assert get_lock['creator'] == "tsetyp"
395
+
396
+
397
+ def test_update_relukko_expires_at(relukko_backend):
398
+ relukko, _ = relukko_backend
399
+
400
+ lock = relukko.acquire_relukko("update_lock_exp", "pytest", 10)
401
+ id = lock['id']
402
+
403
+ expires_at = datetime.fromisoformat("2099-12-31T12:34:56.789Z")
404
+ upd_lock = relukko.update_relukko(id, expires_at=expires_at)
405
+ assert upd_lock['expires_at'] == "2099-12-31T12:34:56.789Z"
406
+
407
+ with pytest.raises(ValueError):
408
+ relukko.update_relukko(id, expires_at="2099-12-01T23:12Z")
409
+
410
+
411
+ def test_keep_relukko_alive(relukko_backend):
412
+ relukko, _ = relukko_backend
413
+
414
+ lock = relukko.acquire_relukko("keep_me", "pytest", 10)
415
+ id = lock['id']
416
+
417
+ start_time = datetime.now(timezone.utc)
418
+ keep_lock = relukko.keep_relukko_alive(id)
419
+ assert lock['expires_at'] is not keep_lock['expires_at']
420
+
421
+ expires_at = datetime.fromisoformat(keep_lock['expires_at'])
422
+
423
+ expires_diff = expires_at - start_time
424
+ assert 295 < expires_diff.seconds < 305
425
+
426
+
427
+ def test_keep_relukko_alive_put(relukko_backend):
428
+ relukko, _ = relukko_backend
429
+
430
+ lock = relukko.acquire_relukko("keep_me_more", "pytest", 10)
431
+ id = lock['id']
432
+
433
+ start_time = datetime.now(timezone.utc)
434
+ keep_lock = relukko.keep_relukko_alive_put(id, 3600)
435
+ assert lock['expires_at'] is not keep_lock['expires_at']
436
+
437
+ expires_at = datetime.fromisoformat(keep_lock['expires_at'])
438
+
439
+ expires_diff = expires_at - start_time
440
+ assert 3595 < expires_diff.seconds < 3605
441
+
442
+
443
+ def test_add_to_expires_at_time(relukko_backend):
444
+ relukko, _ = relukko_backend
445
+ lock = relukko.acquire_relukko("add_me_up", "pytest", 10)
446
+ id = lock['id']
447
+ start_expires_at = datetime.fromisoformat(lock['expires_at'])
448
+
449
+ add_lock = relukko.add_to_expires_at_time(id)
450
+ assert lock['expires_at'] is not add_lock['expires_at']
451
+
452
+ add_expires_at = datetime.fromisoformat(add_lock['expires_at'])
453
+
454
+ expires_diff = add_expires_at - start_expires_at
455
+ assert expires_diff.seconds == 300
456
+
457
+
458
+ def test_add_to_expires_at_time_put(relukko_backend):
459
+ relukko, _ = relukko_backend
460
+ lock = relukko.acquire_relukko("add_me_up_more", "pytest", 10)
461
+ id = lock['id']
462
+ start_expires_at = datetime.fromisoformat(lock['expires_at'])
463
+
464
+ add_lock = relukko.add_to_expires_at_time_put(id, 3600)
465
+ assert lock['expires_at'] is not add_lock['expires_at']
466
+
467
+ add_expires_at = datetime.fromisoformat(add_lock['expires_at'])
468
+
469
+ expires_diff = add_expires_at - start_expires_at
470
+ assert expires_diff.seconds == 3600
@@ -1,4 +0,0 @@
1
- [tool.pylint.main]
2
- # Files or directories to be skipped. They should be base names, not paths.
3
- ignore = ["LICENSE", "pyproject.toml", "version.py",
4
- "README.md", "demo.py",]
File without changes
File without changes