pyrelukko 0.1.0__tar.gz → 0.3.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.
- pyrelukko-0.3.0/.gitlab-ci.yml +137 -0
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/PKG-INFO +2 -1
- pyrelukko-0.3.0/cicd/build-py-rf-image.sh +48 -0
- pyrelukko-0.3.0/cicd/run_docs_pages.sh +17 -0
- pyrelukko-0.3.0/cicd/run_pylint.sh +24 -0
- pyrelukko-0.3.0/cicd/run_pytest.sh +14 -0
- pyrelukko-0.3.0/cicd/run_shellcheck.sh +14 -0
- pyrelukko-0.3.0/container/Containerfile +5 -0
- pyrelukko-0.3.0/container/requirements.txt +3 -0
- pyrelukko-0.3.0/pyproject.toml +61 -0
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/src/pyrelukko/pyrelukko.py +47 -22
- pyrelukko-0.3.0/src/pyrelukko/testcontainers.py +90 -0
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/src/pyrelukko/version.py +1 -1
- pyrelukko-0.3.0/tests/cert/5d868fca.0 +1 -0
- pyrelukko-0.3.0/tests/cert/README.md +11 -0
- pyrelukko-0.3.0/tests/cert/relukko.crt +18 -0
- pyrelukko-0.3.0/tests/cert/relukko.csr +16 -0
- pyrelukko-0.3.0/tests/cert/relukko.key +28 -0
- pyrelukko-0.3.0/tests/cert/rootCA.crt +19 -0
- pyrelukko-0.3.0/tests/cert/rootCA.der +0 -0
- pyrelukko-0.3.0/tests/cert/rootCA.key +28 -0
- pyrelukko-0.3.0/tests/cert/rootCA.srl +1 -0
- pyrelukko-0.3.0/tests/conftest.py +70 -0
- pyrelukko-0.3.0/tests/initdb.d/20240930160154_init.up.sql +19 -0
- pyrelukko-0.3.0/tests/test_relukko.py +470 -0
- pyrelukko-0.1.0/.pylintrc.toml +0 -4
- pyrelukko-0.1.0/pyproject.toml +0 -31
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/LICENSE +0 -0
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/README.md +0 -0
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/src/pyrelukko/__init__.py +0 -0
- {pyrelukko-0.1.0 → pyrelukko-0.3.0}/src/pyrelukko/retry.py +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
variables:
|
|
2
|
+
FF_TIMESTAMPS: true
|
|
3
|
+
BUILD_IMAGE:
|
|
4
|
+
description: Used in rules, if "true" and web triggered it builds the image!
|
|
5
|
+
value: "false"
|
|
6
|
+
options:
|
|
7
|
+
- "false"
|
|
8
|
+
- "true"
|
|
9
|
+
IMAGE_NAME: py-rf-image
|
|
10
|
+
IMAGE_TAG:
|
|
11
|
+
description: >-
|
|
12
|
+
The "tag" added to the image name for the test / lint image.
|
|
13
|
+
Remove this when you trigger by hand an image build and want it to use
|
|
14
|
+
the new built image with Git short sha tag (X icon on the right)!
|
|
15
|
+
value: "latest"
|
|
16
|
+
# RUN_DOCS_PUBLISH:
|
|
17
|
+
# description: >-
|
|
18
|
+
# Used in rules, if "true" and web triggered it generates the docs and
|
|
19
|
+
# publishes them!
|
|
20
|
+
# value: "false"
|
|
21
|
+
# options:
|
|
22
|
+
# - "false"
|
|
23
|
+
# - "true"
|
|
24
|
+
|
|
25
|
+
build-py-rf-image:
|
|
26
|
+
stage: build
|
|
27
|
+
image: docker:25
|
|
28
|
+
services:
|
|
29
|
+
- name: docker:25-dind
|
|
30
|
+
alias: docker
|
|
31
|
+
before_script:
|
|
32
|
+
- docker info
|
|
33
|
+
before_script:
|
|
34
|
+
- echo "Login to Gitlab container registry"
|
|
35
|
+
- echo "$CI_REGISTRY_PASSWORD" | docker login --username $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
36
|
+
script:
|
|
37
|
+
- echo "Build container image..."
|
|
38
|
+
- ./cicd/build-py-rf-image.sh
|
|
39
|
+
- echo "Export IMAGE_TAG var to next stage!"
|
|
40
|
+
- echo "IMAGE_TAG=${CI_COMMIT_SHORT_SHA}" > image_tag.env
|
|
41
|
+
- echo "Build complete."
|
|
42
|
+
artifacts:
|
|
43
|
+
reports:
|
|
44
|
+
dotenv: image_tag.env
|
|
45
|
+
rules:
|
|
46
|
+
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
47
|
+
changes:
|
|
48
|
+
- .gitlab-ci.yml
|
|
49
|
+
- cicd/build-py-rf-image.sh
|
|
50
|
+
- container/**/*
|
|
51
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
|
52
|
+
changes:
|
|
53
|
+
- .gitlab-ci.yml
|
|
54
|
+
- cicd/build-py-rf-image.sh
|
|
55
|
+
- container/**/*
|
|
56
|
+
- if: $CI_PIPELINE_SOURCE == "web" && $BUILD_IMAGE == "true"
|
|
57
|
+
when: always
|
|
58
|
+
|
|
59
|
+
run-pylint:
|
|
60
|
+
stage: test
|
|
61
|
+
image: "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:${IMAGE_TAG}"
|
|
62
|
+
rules:
|
|
63
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
|
64
|
+
changes:
|
|
65
|
+
- src/**/*.py
|
|
66
|
+
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
67
|
+
changes:
|
|
68
|
+
- src/**/*.py
|
|
69
|
+
before_script:
|
|
70
|
+
- echo "Running PyLint."
|
|
71
|
+
script:
|
|
72
|
+
- ./cicd/run_pylint.sh
|
|
73
|
+
artifacts:
|
|
74
|
+
when: always
|
|
75
|
+
expire_in: 2 week
|
|
76
|
+
paths:
|
|
77
|
+
- pylint.out.*
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
run-pytest:
|
|
81
|
+
stage: test
|
|
82
|
+
image: "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:${IMAGE_TAG}"
|
|
83
|
+
rules:
|
|
84
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
|
85
|
+
changes:
|
|
86
|
+
- src/**/*.py
|
|
87
|
+
- tests/test_*.py
|
|
88
|
+
- tests/conftest.py
|
|
89
|
+
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
90
|
+
changes:
|
|
91
|
+
- src/**/*.py
|
|
92
|
+
- tests/test_*.py
|
|
93
|
+
- tests/conftest.py
|
|
94
|
+
before_script:
|
|
95
|
+
- echo "Running PyTest."
|
|
96
|
+
script:
|
|
97
|
+
- ./cicd/run_pytest.sh
|
|
98
|
+
artifacts:
|
|
99
|
+
when: always
|
|
100
|
+
expire_in: 2 week
|
|
101
|
+
paths:
|
|
102
|
+
- pytest-junit.xml
|
|
103
|
+
reports:
|
|
104
|
+
junit: pytest-junit.xml
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
run-shellcheck:
|
|
108
|
+
stage: test
|
|
109
|
+
image: "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:${IMAGE_TAG}"
|
|
110
|
+
rules:
|
|
111
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
|
112
|
+
changes:
|
|
113
|
+
- cicd/*.sh
|
|
114
|
+
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
115
|
+
changes:
|
|
116
|
+
- cicd/*.sh
|
|
117
|
+
before_script:
|
|
118
|
+
- echo "Running ShellCheck."
|
|
119
|
+
script:
|
|
120
|
+
- ./cicd/run_shellcheck.sh
|
|
121
|
+
|
|
122
|
+
# pages:
|
|
123
|
+
# stage: deploy
|
|
124
|
+
# image: "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:${IMAGE_TAG}"
|
|
125
|
+
# rules:
|
|
126
|
+
# - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
127
|
+
# changes:
|
|
128
|
+
# - src/**/*.py
|
|
129
|
+
# - if: $CI_PIPELINE_SOURCE == "web" && $RUN_DOCS_PUBLISH == "true"
|
|
130
|
+
# when: always
|
|
131
|
+
# before_script:
|
|
132
|
+
# - echo "Running Docs generation and publishing..."
|
|
133
|
+
# script:
|
|
134
|
+
# - ./cicd/run_docs_pages.sh
|
|
135
|
+
# artifacts:
|
|
136
|
+
# paths:
|
|
137
|
+
# - public
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyrelukko
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Relukko client.
|
|
5
5
|
Author-email: Reto Zingg <g.d0b3rm4n@gmail.com>
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -11,6 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
12
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
13
|
Requires-Dist: requests >=2.32.3
|
|
14
|
+
Requires-Dist: websockets >= 13.1
|
|
14
15
|
Project-URL: Homepage, https://gitlab.com/relukko/pyrelukko
|
|
15
16
|
Project-URL: Issues, https://gitlab.com/relukko/pyrelukko/-/issues
|
|
16
17
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
|
6
|
+
REPO_DIR="${SCRIPT_DIR}/.."
|
|
7
|
+
CONTAINER_DIR="${REPO_DIR}/container"
|
|
8
|
+
CONTAINER_CMD="docker"
|
|
9
|
+
|
|
10
|
+
# IMAGE_NAME should normally come from .gitlab-ci.yml file,
|
|
11
|
+
# set default for local usage.
|
|
12
|
+
if [ -z "${IMAGE_NAME+x}" ]; then
|
|
13
|
+
IMAGE_NAME=py-rf-image-local
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
cd "${CONTAINER_DIR}" || exit
|
|
17
|
+
|
|
18
|
+
${CONTAINER_CMD} build -t "${IMAGE_NAME}:latest" --file Containerfile .
|
|
19
|
+
|
|
20
|
+
${CONTAINER_CMD} images
|
|
21
|
+
|
|
22
|
+
if [ -n "${CI_REGISTRY_IMAGE}" ]; then
|
|
23
|
+
# In Gitlab
|
|
24
|
+
|
|
25
|
+
# Tag with Git commit short sha and push
|
|
26
|
+
IMAGE_WITH_GIT_SHA="${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
|
|
27
|
+
echo "IMAGE_WITH_GIT_SHA: ${IMAGE_WITH_GIT_SHA}"
|
|
28
|
+
|
|
29
|
+
${CONTAINER_CMD} tag "${IMAGE_NAME}:latest" "${IMAGE_WITH_GIT_SHA}"
|
|
30
|
+
${CONTAINER_CMD} push "${IMAGE_WITH_GIT_SHA}"
|
|
31
|
+
|
|
32
|
+
if [ "${CI_COMMIT_BRANCH}" = "${CI_DEFAULT_BRANCH}" ]; then
|
|
33
|
+
# We run in default branch, also tag with latest
|
|
34
|
+
# Tag with "latest" (overwrites last 'latest' in registry) and push
|
|
35
|
+
IMAGE_WITH_LATEST="${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:latest"
|
|
36
|
+
|
|
37
|
+
echo "Tag image with as 'latest': ${IMAGE_WITH_LATEST}"
|
|
38
|
+
|
|
39
|
+
${CONTAINER_CMD} tag "${IMAGE_NAME}:latest" "${IMAGE_WITH_LATEST}"
|
|
40
|
+
${CONTAINER_CMD} push "${IMAGE_WITH_LATEST}"
|
|
41
|
+
fi
|
|
42
|
+
else
|
|
43
|
+
echo "Runs locally, no pushing!"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
cd -
|
|
47
|
+
|
|
48
|
+
# vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
|
4
|
+
REPO_DIR="${SCRIPT_DIR}/.."
|
|
5
|
+
PUBLIC_DIR="${REPO_DIR}/public"
|
|
6
|
+
|
|
7
|
+
# Main script execution
|
|
8
|
+
main() {
|
|
9
|
+
mkdir -p "${PUBLIC_DIR}"
|
|
10
|
+
# libdoc --pythonpath ./src Relukko "${PUBLIC_DIR}/index.html"
|
|
11
|
+
echo "Not Implemented yet"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Execute the main function
|
|
15
|
+
main
|
|
16
|
+
|
|
17
|
+
# vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
|
4
|
+
REPO_DIR="${SCRIPT_DIR}/.."
|
|
5
|
+
|
|
6
|
+
# Main script execution
|
|
7
|
+
main() {
|
|
8
|
+
pylint --verbose \
|
|
9
|
+
--rcfile "${REPO_DIR}/pyproject.toml" \
|
|
10
|
+
--output-format=json:pylint.out.json,parseable:pylint.out.txt,text \
|
|
11
|
+
"${REPO_DIR}"
|
|
12
|
+
pylint_exit_code=$?
|
|
13
|
+
|
|
14
|
+
pylint-json2html \
|
|
15
|
+
-o "${REPO_DIR}/pylint.out.html" \
|
|
16
|
+
"${REPO_DIR}/pylint.out.json"
|
|
17
|
+
|
|
18
|
+
return ${pylint_exit_code}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Execute the main function
|
|
22
|
+
main
|
|
23
|
+
|
|
24
|
+
# vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
|
4
|
+
REPO_DIR="${SCRIPT_DIR}/.."
|
|
5
|
+
|
|
6
|
+
# Main script execution
|
|
7
|
+
main() {
|
|
8
|
+
pytest --junit-xml "${REPO_DIR}/pytest-junit.xml"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Execute the main function
|
|
12
|
+
main
|
|
13
|
+
|
|
14
|
+
# vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
|
4
|
+
REPO_DIR="${SCRIPT_DIR}/.."
|
|
5
|
+
|
|
6
|
+
# Main script execution
|
|
7
|
+
main() {
|
|
8
|
+
shellcheck "${REPO_DIR}"/cicd/*.sh
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Execute the main function
|
|
12
|
+
main
|
|
13
|
+
|
|
14
|
+
# vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyrelukko"
|
|
3
|
+
authors = [
|
|
4
|
+
{name = "Reto Zingg", email = "g.d0b3rm4n@gmail.com"},
|
|
5
|
+
]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
# https://pypi.org/pypi?%3Aaction=list_classifiers
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
14
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.12"
|
|
17
|
+
dynamic = ["version", "description"]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"requests >=2.32.3",
|
|
20
|
+
"websockets >= 13.1",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.flit.sdist]
|
|
24
|
+
exclude = [".gitignore", "demo.py"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://gitlab.com/relukko/pyrelukko"
|
|
28
|
+
Issues = "https://gitlab.com/relukko/pyrelukko/-/issues"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["flit_core >=3.2,<4", "semantic-version >= 2.10"]
|
|
32
|
+
build-backend = "flit_core.buildapi"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = [
|
|
36
|
+
"tests",
|
|
37
|
+
]
|
|
38
|
+
pythonpath = [
|
|
39
|
+
"src",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.tox]
|
|
43
|
+
requires = ["tox>=4.23"]
|
|
44
|
+
env_list = ["3.13", "3.12"]
|
|
45
|
+
|
|
46
|
+
[tool.tox.env_run_base]
|
|
47
|
+
description = "Run test under {base_python}"
|
|
48
|
+
commands = [["pytest"]]
|
|
49
|
+
deps = ["httpx", "pytest", "testcontainers"]
|
|
50
|
+
set_env = { VIRTUALENV_DISCOVERY = "pyenv" }
|
|
51
|
+
pass_env = [ "TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "DOCKER_HOST" ]
|
|
52
|
+
|
|
53
|
+
[tool.pylint.main]
|
|
54
|
+
# Files or directories to be skipped. They should be base names, not paths.
|
|
55
|
+
ignore = ["LICENSE", "pyproject.toml", "version.py",
|
|
56
|
+
"README.md", "demo.py", "conftest.py", ".tox"]
|
|
57
|
+
|
|
58
|
+
# Files or directories matching the regular expression patterns are skipped. The
|
|
59
|
+
# regex matches against base names, not paths. The default value ignores Emacs
|
|
60
|
+
# file locks
|
|
61
|
+
ignore-patterns = ["^\\.#", "^test_"]
|
|
@@ -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
|
|
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
|
|
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:
|
|
@@ -127,7 +151,7 @@ class RelukkoClient:
|
|
|
127
151
|
|
|
128
152
|
def _setup_ws_url(self, ws_url: str) -> Url:
|
|
129
153
|
url = ws_url.replace("http", "ws", 1)
|
|
130
|
-
return parse_url(f"{url}/
|
|
154
|
+
return parse_url(f"{url}/ws/broadcast")
|
|
131
155
|
|
|
132
156
|
def _setup_base_url(self, base_url: Union[Url, str]) -> Url:
|
|
133
157
|
if isinstance(base_url, str):
|
|
@@ -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(
|
|
234
|
+
logger.warning("[%d](%s) %s",
|
|
235
|
+
response.status_code, response.reason, response.text)
|
|
211
236
|
raise RelukkoDoRetry()
|
|
212
237
|
case _:
|
|
213
|
-
logger.warning(
|
|
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=
|
|
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(
|
|
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
|
-
|
|
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, "
|
|
316
|
+
return self._make_request(url, "PUT", payload)
|
|
292
317
|
|
|
293
318
|
def delete_relukko(self, lock_id: str):
|
|
294
319
|
"""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# pylint: skip-file
|
|
2
|
+
"""
|
|
3
|
+
Testcontainers to be used in PyTests, for a fixture see tests/conftest.py
|
|
4
|
+
(relukko_backend):
|
|
5
|
+
|
|
6
|
+
@pytest.fixture(scope="session")
|
|
7
|
+
def relukko_backend():
|
|
8
|
+
with Network() as rl_net:
|
|
9
|
+
with RelukkoDbContainer(net=rl_net, initdb_dir=INITDB_DIR,
|
|
10
|
+
image="postgres:16", hostname="relukkodb") as _db:
|
|
11
|
+
db_url = "postgresql://relukko:relukko@relukkodb/relukko"
|
|
12
|
+
with RelukkoContainer(rl_net, db_url=db_url) as backend:
|
|
13
|
+
relukko = RelukkoClient(
|
|
14
|
+
base_url=backend.get_api_url(), api_key="somekey")
|
|
15
|
+
yield relukko, backend
|
|
16
|
+
"""
|
|
17
|
+
import socket
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from testcontainers.core.network import Network
|
|
21
|
+
from testcontainers.core.waiting_utils import wait_container_is_ready
|
|
22
|
+
from testcontainers.generic import ServerContainer
|
|
23
|
+
from testcontainers.postgres import PostgresContainer
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RelukkoContainer(ServerContainer):
|
|
27
|
+
def __init__(self, net: Network,
|
|
28
|
+
image="registry.gitlab.com/relukko/relukko:0.10.0", db_url=None):
|
|
29
|
+
self.db_url = db_url
|
|
30
|
+
self.net = net
|
|
31
|
+
super(RelukkoContainer, self).__init__(image=image, port=3000)
|
|
32
|
+
|
|
33
|
+
def _configure(self):
|
|
34
|
+
self.with_env("DATABASE_URL", self.db_url)
|
|
35
|
+
self.with_env("RELUKKO_API_KEY", "somekey")
|
|
36
|
+
self.with_env("RELUKKO_USER", "relukko")
|
|
37
|
+
self.with_env("RELUKKO_PASSWORD", "relukko")
|
|
38
|
+
self.with_env("RELUKKO_BIND_ADDR", "0.0.0.0")
|
|
39
|
+
self.with_network(self.net)
|
|
40
|
+
|
|
41
|
+
def get_api_url(self) -> str:
|
|
42
|
+
return f"http://localhost:{self.get_exposed_port(3000)}"
|
|
43
|
+
|
|
44
|
+
def _create_connection_url(self) -> str:
|
|
45
|
+
return f"{self.get_api_url()}/healthchecker"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RelukkoDbContainer(PostgresContainer):
|
|
49
|
+
def __init__(
|
|
50
|
+
self, net: Network,
|
|
51
|
+
initdb_dir: Path,
|
|
52
|
+
image: str = "postgres:latest",
|
|
53
|
+
port: int = 5432,
|
|
54
|
+
username: str | None = None,
|
|
55
|
+
password: str | None = None,
|
|
56
|
+
dbname: str | None = None,
|
|
57
|
+
driver: str | None = "psycopg2",
|
|
58
|
+
**kwargs) -> None:
|
|
59
|
+
self.net = net
|
|
60
|
+
self.initdb_dir = initdb_dir
|
|
61
|
+
super().__init__(image, port, username, password, dbname, driver, **kwargs)
|
|
62
|
+
|
|
63
|
+
def _configure(self) -> None:
|
|
64
|
+
self.with_volume_mapping(self.initdb_dir, "/docker-entrypoint-initdb.d", "Z")
|
|
65
|
+
self.with_env("POSTGRES_USER", "relukko")
|
|
66
|
+
self.with_env("POSTGRES_PASSWORD", "relukko")
|
|
67
|
+
self.with_env("POSTGRES_DB", "relukko")
|
|
68
|
+
self.with_network(self.net)
|
|
69
|
+
|
|
70
|
+
@wait_container_is_ready()
|
|
71
|
+
def _connect(self) -> None:
|
|
72
|
+
packet = bytes([
|
|
73
|
+
0x00, 0x00, 0x00, 0x52, 0x00, 0x03, 0x00, 0x00,
|
|
74
|
+
0x75, 0x73, 0x65, 0x72, 0x00, 0x72, 0x65, 0x6c,
|
|
75
|
+
0x75, 0x6b, 0x6b, 0x6f, 0x00, 0x64, 0x61, 0x74,
|
|
76
|
+
0x61, 0x62, 0x61, 0x73, 0x65, 0x00, 0x72, 0x65,
|
|
77
|
+
0x6c, 0x75, 0x6b, 0x6b, 0x6f, 0x00, 0x61, 0x70,
|
|
78
|
+
0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
|
79
|
+
0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x70,
|
|
80
|
+
0x73, 0x71, 0x6c, 0x00, 0x63, 0x6c, 0x69, 0x65,
|
|
81
|
+
0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64,
|
|
82
|
+
0x69, 0x6e, 0x67, 0x00, 0x55, 0x54, 0x46, 0x38,
|
|
83
|
+
0x00, 0x00
|
|
84
|
+
])
|
|
85
|
+
port = self.get_exposed_port(self.port)
|
|
86
|
+
with socket.create_connection(("localhost", port)) as sock:
|
|
87
|
+
sock.send(packet)
|
|
88
|
+
buf = sock.recv(40)
|
|
89
|
+
if len(buf) == 0 and "SCRAM-SHA" not in buf:
|
|
90
|
+
raise ConnectionError
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# pylint: disable=all
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rootCA.crt
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# How To
|
|
2
|
+
```
|
|
3
|
+
openssl req -subj '/CN=Relukko CA' -x509 -sha256 -days 7300 -noenc -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt -addext keyUsage=critical,cRLSign,digitalSignature,keyCertSign
|
|
4
|
+
|
|
5
|
+
ln -s rootCA.crt "$(openssl x509 -hash -noout -in rootCA.crt).0"
|
|
6
|
+
|
|
7
|
+
openssl req -subj '/CN=relukko' -newkey rsa:2048 -noenc -keyout relukko.key -out relukko.csr -addext keyUsage=critical,digitalSignature,keyEncipherment,keyAgreement -addext extendedKeyUsage=critical,serverAuth
|
|
8
|
+
|
|
9
|
+
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in relukko.csr -out relukko.crt -days 7299 -CAcreateserial
|
|
10
|
+
openssl x509 -inform PEM -in rootCA.crt -outform DER -out rootCA.der
|
|
11
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIC9zCCAd+gAwIBAgIUXFQN+AOO7KFdiVNVFman8BLkRfkwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwFTETMBEGA1UEAwwKUmVsdWtrbyBDQTAeFw0yNDExMTYyMTM5MTNaFw00NDEx
|
|
4
|
+
MTAyMTM5MTNaMBIxEDAOBgNVBAMMB3JlbHVra28wggEiMA0GCSqGSIb3DQEBAQUA
|
|
5
|
+
A4IBDwAwggEKAoIBAQCljry3blOWFJRdinzcH4XzRnvKlhhofBhtRsPH+pvjmrHO
|
|
6
|
+
v52rXj1XeHqXaEBevhNiN7lP5eg2UgOrs0KAVXYR2Na9Mxrh8ACX+IqeEJF9BKgO
|
|
7
|
+
4t2RvItDU3SWxV3dIYMasBhe/iDXDKKZd/jVOiluL9zuxo9ltDagZxc72jWMnvrO
|
|
8
|
+
lvlnZVEtH4n9i+hnx1daWRZG87DjO6d8I8F8nX6NTqJgBlrRm8jlcq2N/G8eHTFq
|
|
9
|
+
OrLY4xk4CF+aVExpJrEGprfhNkbE025tJIbGNM9Xv+W0sI/nhEwJdv4ifEP/b+44
|
|
10
|
+
n+KHrAurOboFYmRcoZ9Vn4C1KuYBvDOpgkwOs1wtAgMBAAGjQjBAMB0GA1UdDgQW
|
|
11
|
+
BBSy6DZz/N1PW1mkQY7HUj3Fz6yZOjAfBgNVHSMEGDAWgBT+sHazfmyczKb0BV6r
|
|
12
|
+
Z1JQ8Sfx9zANBgkqhkiG9w0BAQsFAAOCAQEAL1c1l3Azna1mh86haErurYk3EsiH
|
|
13
|
+
KDGprRubdB5qfpa2GdAkhitp9juKYdKGdw877eOl5z7AMeMWAA8WXQ3z7jkQkUhU
|
|
14
|
+
9wsZxNyHQajq6Lo9KsRxZiRKZX/fxagcnDem+4xTvzSlJoAT5p/CO5E31odwWkZN
|
|
15
|
+
bg79I36nk87opISXzNAbMBcbQEV5R8l4PzNGs/DcrLzTC66FKxHHzyih4mlBF5mS
|
|
16
|
+
/sMY5l9QNaJHKPK7fKZ5yxohkda7tDwtb5p0bbEUQlMeZIQ8r/go1b7/ppXXM5ei
|
|
17
|
+
mTDcwKgeOBgVujqhvgGXOKtcHVfkrqcID+qgDMxYd7XXE/icGF3zmnHwDQ==
|
|
18
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
|
2
|
+
MIICkDCCAXgCAQAwEjEQMA4GA1UEAwwHcmVsdWtrbzCCASIwDQYJKoZIhvcNAQEB
|
|
3
|
+
BQADggEPADCCAQoCggEBAKWOvLduU5YUlF2KfNwfhfNGe8qWGGh8GG1Gw8f6m+Oa
|
|
4
|
+
sc6/natePVd4epdoQF6+E2I3uU/l6DZSA6uzQoBVdhHY1r0zGuHwAJf4ip4QkX0E
|
|
5
|
+
qA7i3ZG8i0NTdJbFXd0hgxqwGF7+INcMopl3+NU6KW4v3O7Gj2W0NqBnFzvaNYye
|
|
6
|
+
+s6W+WdlUS0fif2L6GfHV1pZFkbzsOM7p3wjwXydfo1OomAGWtGbyOVyrY38bx4d
|
|
7
|
+
MWo6stjjGTgIX5pUTGkmsQamt+E2RsTTbm0khsY0z1e/5bSwj+eETAl2/iJ8Q/9v
|
|
8
|
+
7jif4oesC6s5ugViZFyhn1WfgLUq5gG8M6mCTA6zXC0CAwEAAaA5MDcGCSqGSIb3
|
|
9
|
+
DQEJDjEqMCgwDgYDVR0PAQH/BAQDAgOoMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMB
|
|
10
|
+
MA0GCSqGSIb3DQEBCwUAA4IBAQBfEyjmGQtITqSsmoAQXEb11Cm/olsL5w6kEzgr
|
|
11
|
+
/0K8ASvDvW+3yJ3U9OBngvgh6iQCJjYaixb8NtQS7yVWYEqJoaA0Hp1q0QDmRS/l
|
|
12
|
+
MDLe2cILgbLQUKOf1jLtYsLG93Lb74HSGztjIIEoi3Wgrm5LLGc/cd28q8yw5c/j
|
|
13
|
+
s1EntUHeNWCYAUIRvwmt+1UPcc8EBHH2DugGz2verzARdvLWAajL5r9ZIJkkU0kY
|
|
14
|
+
fh5gnmVb6I6RtIXhWcXMUL9nBwzPCyJMd4cC/fueC+HxzLNhGldvVxj3cg7W57vR
|
|
15
|
+
QnbG/qcJYN5WGhoHrWIiI90GufG4jifVvyQeBC/vQcymRlAL
|
|
16
|
+
-----END CERTIFICATE REQUEST-----
|