From 23b59be5a87438d16b4fa04ee205ec7a8d49bd12 Mon Sep 17 00:00:00 2001 From: Jaydee94 Date: Mon, 2 Dec 2024 16:53:36 +0100 Subject: [PATCH 1/2] Add Metrics endpoint for the api to track encrypted secrets count and latency --- api/kubeseal_webgui_api/app.py | 26 ++++++++++++++++ api/kubeseal_webgui_api/routers/kubeseal.py | 33 ++++++++++++++------- api/poetry.lock | 16 +++++++++- api/pyproject.toml | 1 + 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/api/kubeseal_webgui_api/app.py b/api/kubeseal_webgui_api/app.py index 541fd91..58cd3c4 100644 --- a/api/kubeseal_webgui_api/app.py +++ b/api/kubeseal_webgui_api/app.py @@ -1,14 +1,23 @@ from contextlib import asynccontextmanager import logging +import time import fastapi from fastapi.middleware.cors import CORSMiddleware +from prometheus_client import Counter, Histogram, generate_latest, REGISTRY, CONTENT_TYPE_LATEST +from starlette.responses import Response from .app_config import fetch_sealed_secrets_cert from .routers import config, kubernetes, kubeseal LOGGER = logging.getLogger("kubeseal-webgui") +HTTP_REQUESTS = Counter( + "kubeseal_webgui_http_requests_total", "Total HTTP requests", ["method", "endpoint", "http_status"] +) +HTTP_LATENCY = Histogram( + "kubeseal_webgui_http_request_latency_seconds", "HTTP request latency in seconds", ["method", "endpoint"] +) @asynccontextmanager async def lifespan(fastapi_app: fastapi.FastAPI): # noqa: ANN201 skipcq: PYL-W0613 @@ -32,6 +41,23 @@ async def lifespan(fastapi_app: fastapi.FastAPI): # noqa: ANN201 skipcq: PYL-W0 allow_headers=["*"], ) +@app.middleware("http") +async def prometheus_middleware(request: fastapi.Request, call_next): # noqa: ANN001, ANN201 + start_time = time.time() + response = await call_next(request) + process_time = time.time() - start_time + + HTTP_REQUESTS.labels( + method=request.method, endpoint=request.url.path, http_status=response.status_code + ).inc() + HTTP_LATENCY.labels(method=request.method, endpoint=request.url.path).observe(process_time) + + return response + +@app.get("/metrics") +def metrics() -> Response: + return Response(generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST) + app.include_router( kubernetes.router, ) diff --git a/api/kubeseal_webgui_api/routers/kubeseal.py b/api/kubeseal_webgui_api/routers/kubeseal.py index 403b72d..dcd7d9a 100644 --- a/api/kubeseal_webgui_api/routers/kubeseal.py +++ b/api/kubeseal_webgui_api/routers/kubeseal.py @@ -5,6 +5,7 @@ from typing import List, Optional, Union, overload from fastapi import APIRouter, HTTPException +from prometheus_client import Counter, Histogram from kubeseal_webgui_api.app_config import settings from kubeseal_webgui_api.routers.models import Data, KeyValuePair, Scope, Secret @@ -12,20 +13,30 @@ router = APIRouter() LOGGER = logging.getLogger("kubeseal-webgui") +SECRETS_ENCRYPTED = Counter("kubeseal_webgui_secrets_encrypted_total", "Total encrypted secrets") +ENCRYPTION_FAILURES = Counter("kubeseal_webgui_encryption_failures_total", "Total failed encryption attempts") +ENCRYPTION_LATENCY = Histogram("kubeseal_webgui_encryption_latency_seconds", "Latency for encryption operations") @router.post("/secrets", response_model=List[KeyValuePair]) def encrypt(data: Data) -> list[KeyValuePair]: - try: - return run_kubeseal( - data.secrets, - data.namespace, - data.secret, - data.scope or Scope.STRICT, - ) - except (KeyError, ValueError) as e: - raise HTTPException(400, f"Invalid data for sealing secrets: {e}") - except RuntimeError: - raise HTTPException(500, "Server is dreaming...") + with ENCRYPTION_LATENCY.time(): + try: + result = run_kubeseal( + data.secrets, + data.namespace, + data.secret, + data.scope or Scope.STRICT, + ) + SECRETS_ENCRYPTED.inc() + return result + except (KeyError, ValueError) as e: + ENCRYPTION_FAILURES.inc() # Increment failure counter + LOGGER.error("Encryption failed due to invalid input: %s", e) + raise HTTPException(400, f"Invalid data for sealing secrets: {e}") from e + except RuntimeError as e: + ENCRYPTION_FAILURES.inc() # Increment failure counter + LOGGER.error("Encryption failed due to runtime error: %s", e) + raise HTTPException(500, "Server is dreaming...") from e def is_blank(value: Optional[str]) -> bool: diff --git a/api/poetry.lock b/api/poetry.lock index f78d8a4..362ecb9 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -794,6 +794,20 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "prometheus-client" +version = "0.21.0" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.8" +files = [ + {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, + {file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"}, +] + +[package.extras] +twisted = ["twisted"] + [[package]] name = "pyasn1" version = "0.6.1" @@ -1436,4 +1450,4 @@ test = ["websockets"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "59770345914eec24aee5c8459254ff2a366eb087b21d94866b689633f9f3c67a" +content-hash = "2db7f616a1845363194728272e9ac07900a04f474822a08b71dbef83e127bcd4" diff --git a/api/pyproject.toml b/api/pyproject.toml index 5dfc6ff..0bb28f1 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -19,6 +19,7 @@ pydantic = "^2.0.0" pydantic-settings = "^2.0.3" playwright = "^1.47.0" pytest-playwright = "^0.5.2" +prometheus-client = "^0.21.0" [tool.poetry.group.dev.dependencies] black = "^22.8.0" From a7af85eb95c8c7fb6b58856c6c65bff6244d8ae2 Mon Sep 17 00:00:00 2001 From: Jaydee94 Date: Thu, 5 Dec 2024 05:57:24 +0100 Subject: [PATCH 2/2] Use opentelemetry to generate the metrics --- api/kubeseal_webgui_api/app.py | 34 +-- api/kubeseal_webgui_api/routers/kubeseal.py | 53 +++-- api/poetry.lock | 234 +++++++++++++++++++- api/pyproject.toml | 4 + 4 files changed, 278 insertions(+), 47 deletions(-) diff --git a/api/kubeseal_webgui_api/app.py b/api/kubeseal_webgui_api/app.py index 58cd3c4..18296d6 100644 --- a/api/kubeseal_webgui_api/app.py +++ b/api/kubeseal_webgui_api/app.py @@ -4,7 +4,10 @@ import fastapi from fastapi.middleware.cors import CORSMiddleware -from prometheus_client import Counter, Histogram, generate_latest, REGISTRY, CONTENT_TYPE_LATEST +from opentelemetry import metrics +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.exporter.prometheus import PrometheusMetricReader +from prometheus_client import generate_latest, CONTENT_TYPE_LATEST from starlette.responses import Response from .app_config import fetch_sealed_secrets_cert @@ -12,11 +15,19 @@ LOGGER = logging.getLogger("kubeseal-webgui") -HTTP_REQUESTS = Counter( - "kubeseal_webgui_http_requests_total", "Total HTTP requests", ["method", "endpoint", "http_status"] +exporter = PrometheusMetricReader() +metrics.set_meter_provider(MeterProvider(metric_readers=[exporter])) +meter = metrics.get_meter("kubeseal-webgui") + +http_requests = meter.create_counter( + name="kubeseal_webgui_http_requests_total", + description="Total HTTP requests", + unit="1", ) -HTTP_LATENCY = Histogram( - "kubeseal_webgui_http_request_latency_seconds", "HTTP request latency in seconds", ["method", "endpoint"] +http_latency = meter.create_histogram( + name="kubeseal_webgui_http_request_latency_seconds", + description="HTTP request latency in seconds", + unit="seconds", ) @asynccontextmanager @@ -26,7 +37,6 @@ async def lifespan(fastapi_app: fastapi.FastAPI): # noqa: ANN201 skipcq: PYL-W0 LOGGER.info("Startup tasks complete.") yield - app = fastapi.FastAPI(lifespan=lifespan) origins = [ @@ -47,16 +57,15 @@ async def prometheus_middleware(request: fastapi.Request, call_next): # noqa: A response = await call_next(request) process_time = time.time() - start_time - HTTP_REQUESTS.labels( - method=request.method, endpoint=request.url.path, http_status=response.status_code - ).inc() - HTTP_LATENCY.labels(method=request.method, endpoint=request.url.path).observe(process_time) + # Record metrics + http_requests.add(1, {"method": request.method, "endpoint": request.url.path, "http_status": response.status_code}) + http_latency.record(process_time, {"method": request.method, "endpoint": request.url.path}) return response @app.get("/metrics") -def metrics() -> Response: - return Response(generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST) +def metrics_endpoint() -> Response: + return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST) app.include_router( kubernetes.router, @@ -68,7 +77,6 @@ def metrics() -> Response: kubeseal.router, ) - @app.get("/") def root() -> dict[str, str]: return {"status": "Kubeseal-WebGui API"} diff --git a/api/kubeseal_webgui_api/routers/kubeseal.py b/api/kubeseal_webgui_api/routers/kubeseal.py index dcd7d9a..9764478 100644 --- a/api/kubeseal_webgui_api/routers/kubeseal.py +++ b/api/kubeseal_webgui_api/routers/kubeseal.py @@ -3,40 +3,47 @@ import re import subprocess # noqa: S404 the binary has to be configured by an admin from typing import List, Optional, Union, overload +from opentelemetry import metrics from fastapi import APIRouter, HTTPException -from prometheus_client import Counter, Histogram from kubeseal_webgui_api.app_config import settings from kubeseal_webgui_api.routers.models import Data, KeyValuePair, Scope, Secret router = APIRouter() LOGGER = logging.getLogger("kubeseal-webgui") - -SECRETS_ENCRYPTED = Counter("kubeseal_webgui_secrets_encrypted_total", "Total encrypted secrets") -ENCRYPTION_FAILURES = Counter("kubeseal_webgui_encryption_failures_total", "Total failed encryption attempts") -ENCRYPTION_LATENCY = Histogram("kubeseal_webgui_encryption_latency_seconds", "Latency for encryption operations") +meter = metrics.get_meter("kubeseal-webgui") + +SECRETS_ENCRYPTED = meter.create_counter( + name="kubeseal_webgui_secrets_encrypted_total", + description="Total encrypted secrets", + unit="1", +) +ENCRYPTION_FAILURES = meter.create_counter( + name="kubeseal_webgui_encryption_failures_total", + description="Total failed encryption attempts", + unit="1", +) @router.post("/secrets", response_model=List[KeyValuePair]) def encrypt(data: Data) -> list[KeyValuePair]: - with ENCRYPTION_LATENCY.time(): - try: - result = run_kubeseal( - data.secrets, - data.namespace, - data.secret, - data.scope or Scope.STRICT, - ) - SECRETS_ENCRYPTED.inc() - return result - except (KeyError, ValueError) as e: - ENCRYPTION_FAILURES.inc() # Increment failure counter - LOGGER.error("Encryption failed due to invalid input: %s", e) - raise HTTPException(400, f"Invalid data for sealing secrets: {e}") from e - except RuntimeError as e: - ENCRYPTION_FAILURES.inc() # Increment failure counter - LOGGER.error("Encryption failed due to runtime error: %s", e) - raise HTTPException(500, "Server is dreaming...") from e + try: + result = run_kubeseal( + data.secrets, + data.namespace, + data.secret, + data.scope or Scope.STRICT, + ) + SECRETS_ENCRYPTED.add(1, {"scope": data.scope or Scope.STRICT, "namespace": data.namespace}) + return result + except (KeyError, ValueError) as e: + ENCRYPTION_FAILURES.add(1, {"error": "invalid_input", "namespace": data.namespace}) + LOGGER.error("Encryption failed due to invalid input: %s", e) + raise HTTPException(400, f"Invalid data for sealing secrets: {e}") from e + except RuntimeError as e: + ENCRYPTION_FAILURES.add(1, {"error": "runtime_error", "namespace": data.namespace}) + LOGGER.error("Encryption failed due to runtime error: %s", e) + raise HTTPException(500, "Server is dreaming...") from e def is_blank(value: Optional[str]) -> bool: diff --git a/api/poetry.lock b/api/poetry.lock index 362ecb9..130da59 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -299,6 +299,23 @@ files = [ [package.extras] toml = ["tomli"] +[[package]] +name = "deprecated" +version = "1.2.15" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ + {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, + {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] + [[package]] name = "fastapi" version = "0.109.2" @@ -497,6 +514,29 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -721,6 +761,85 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +[[package]] +name = "opentelemetry-api" +version = "1.28.2" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_api-1.28.2-py3-none-any.whl", hash = "sha256:6fcec89e265beb258fe6b1acaaa3c8c705a934bd977b9f534a2b7c0d2d4275a6"}, + {file = "opentelemetry_api-1.28.2.tar.gz", hash = "sha256:ecdc70c7139f17f9b0cf3742d57d7020e3e8315d6cffcdf1a12a905d45b19cc0"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<=8.5.0" + +[[package]] +name = "opentelemetry-exporter-prometheus" +version = "0.49b2" +description = "Prometheus Metric Exporter for OpenTelemetry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_prometheus-0.49b2-py3-none-any.whl", hash = "sha256:307594007ee20ec3a51c42548a4dbd66e46701f8523a7780d5e12a8f986a7783"}, + {file = "opentelemetry_exporter_prometheus-0.49b2.tar.gz", hash = "sha256:70ca3a462ce1ba0d756e4be8a87c04f7196687825fd2d151a428f6c18ef6fd2d"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-sdk = ">=1.28.2,<1.29.0" +prometheus-client = ">=0.5.0,<1.0.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.49b2" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation-0.49b2-py3-none-any.whl", hash = "sha256:f6d782b0ef9fef4a4c745298651c65f5c532c34cd4c40d230ab5b9f3b3b4d151"}, + {file = "opentelemetry_instrumentation-0.49b2.tar.gz", hash = "sha256:8cf00cc8d9d479e4b72adb9bd267ec544308c602b7188598db5a687e77b298e2"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +opentelemetry-semantic-conventions = "0.49b2" +packaging = ">=18.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.28.2" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_sdk-1.28.2-py3-none-any.whl", hash = "sha256:93336c129556f1e3ccd21442b94d3521759541521861b2214c499571b85cb71b"}, + {file = "opentelemetry_sdk-1.28.2.tar.gz", hash = "sha256:5fed24c5497e10df30282456fe2910f83377797511de07d14cec0d3e0a1a3110"}, +] + +[package.dependencies] +opentelemetry-api = "1.28.2" +opentelemetry-semantic-conventions = "0.49b2" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.49b2" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_semantic_conventions-0.49b2-py3-none-any.whl", hash = "sha256:51e7e1d0daa958782b6c2a8ed05e5f0e7dd0716fc327ac058777b8659649ee54"}, + {file = "opentelemetry_semantic_conventions-0.49b2.tar.gz", hash = "sha256:44e32ce6a5bb8d7c0c617f84b9dc1c8deda1045a07dc16a688cc7cbeab679997"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +opentelemetry-api = "1.28.2" + [[package]] name = "packaging" version = "24.2" @@ -796,13 +915,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prometheus-client" -version = "0.21.0" +version = "0.21.1" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, - {file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"}, + {file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"}, + {file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"}, ] [package.extras] @@ -835,13 +954,13 @@ pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pydantic" -version = "2.10.2" +version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, - {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] [package.dependencies] @@ -1315,13 +1434,13 @@ type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12 [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -1447,7 +1566,100 @@ docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] +[[package]] +name = "wrapt" +version = "1.17.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +files = [ + {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, + {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, + {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, + {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, + {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, + {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, + {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, + {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, + {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, + {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, + {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, + {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, + {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, + {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, + {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, + {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, + {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, + {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, + {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, + {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, + {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, + {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, + {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, +] + +[[package]] +name = "zipp" +version = "3.21.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "2db7f616a1845363194728272e9ac07900a04f474822a08b71dbef83e127bcd4" +content-hash = "dba2abc4e8a568096e6c3dbad1e85b3ede4973b4f2625ba4cc449ed0e02184ff" diff --git a/api/pyproject.toml b/api/pyproject.toml index 0bb28f1..feb1055 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -20,6 +20,10 @@ pydantic-settings = "^2.0.3" playwright = "^1.47.0" pytest-playwright = "^0.5.2" prometheus-client = "^0.21.0" +opentelemetry-api = "^1.28.2" +opentelemetry-sdk = "^1.28.2" +opentelemetry-instrumentation = "^0.49b2" +opentelemetry-exporter-prometheus = "^0.49b2" [tool.poetry.group.dev.dependencies] black = "^22.8.0"