From 43618112e45953528cde593275f755881f852d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB=20=D0=96=D1=83=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2?= <33721692+LeaveMyYard@users.noreply.github.com> Date: Mon, 10 Apr 2023 17:21:26 +0300 Subject: Lower the required python version to 3.9, improve verbose logs --- pyproject.toml | 2 +- robusta_krr/__init__.py | 3 +-- robusta_krr/core/abstract/formatters.py | 7 +++++-- robusta_krr/core/abstract/strategies.py | 12 +++++++----- robusta_krr/core/integrations/kubernetes.py | 19 +++++++++++++++---- robusta_krr/core/integrations/prometheus.py | 6 +++--- robusta_krr/core/models/allocations.py | 8 +++++--- robusta_krr/core/models/config.py | 12 ++++++------ robusta_krr/core/models/objects.py | 4 +++- robusta_krr/core/models/result.py | 6 +++--- robusta_krr/core/runner.py | 5 +++-- robusta_krr/formatters/table.py | 3 ++- robusta_krr/utils/configurable.py | 10 +++++++++- robusta_krr/utils/resource_units.py | 3 ++- robusta_krr/utils/service_discovery.py | 5 +++-- 15 files changed, 68 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3fa7ed..1d0e1fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ include_trailing_comma = true krr = "robusta_krr.main:run" [tool.poetry.dependencies] -python = ">=3.11,<3.12" +python = ">=3.9" typer = {extras = ["all"], version = "^0.7.0"} pydantic = "1.10.7" kubernetes = "^26.1.0" diff --git a/robusta_krr/__init__.py b/robusta_krr/__init__.py index cd50bf6..2564844 100644 --- a/robusta_krr/__init__.py +++ b/robusta_krr/__init__.py @@ -1,5 +1,4 @@ -from . import api from .main import run __version__ = "0.1.0" -__all__ = ["run", "api", "__version__"] +__all__ = ["run", "__version__"] diff --git a/robusta_krr/core/abstract/formatters.py b/robusta_krr/core/abstract/formatters.py index 3d0102e..359edab 100644 --- a/robusta_krr/core/abstract/formatters.py +++ b/robusta_krr/core/abstract/formatters.py @@ -2,7 +2,7 @@ from __future__ import annotations import abc import os -from typing import TYPE_CHECKING, Any, Self +from typing import TYPE_CHECKING, Any, TypeVar if TYPE_CHECKING: from robusta_krr.core.models.result import Result @@ -11,6 +11,9 @@ if TYPE_CHECKING: DEFAULT_FORMATTERS_PATH = os.path.join(os.path.dirname(__file__), "formatters") +Self = TypeVar("Self", bound="BaseFormatter") + + class BaseFormatter(abc.ABC): """Base class for result formatters.""" @@ -31,7 +34,7 @@ class BaseFormatter(abc.ABC): """ @classmethod - def get_all(cls) -> dict[str, type[Self]]: + def get_all(cls: type[Self]) -> dict[str, type[Self]]: """Get all available formatters.""" # NOTE: Load default formatters diff --git a/robusta_krr/core/abstract/strategies.py b/robusta_krr/core/abstract/strategies.py index 9a73177..476779a 100644 --- a/robusta_krr/core/abstract/strategies.py +++ b/robusta_krr/core/abstract/strategies.py @@ -3,7 +3,7 @@ from __future__ import annotations import abc import datetime from decimal import Decimal -from typing import Generic, Self, TypeVar, get_args +from typing import Generic, Optional, TypeVar, get_args import pydantic as pd @@ -11,8 +11,8 @@ from robusta_krr.core.models.result import K8sObjectData, ResourceType class ResourceRecommendation(pd.BaseModel): - request: Decimal | None - limit: Decimal | None + request: Optional[Decimal] + limit: Optional[Decimal] class StrategySettings(pd.BaseModel): @@ -35,6 +35,8 @@ ResourceHistoryData = dict[str, list[Decimal]] HistoryData = dict[ResourceType, ResourceHistoryData] RunResult = dict[ResourceType, ResourceRecommendation] +Self = TypeVar("Self", bound="BaseStrategy") + class BaseStrategy(abc.ABC, Generic[_StrategySettings]): __display_name__: str @@ -51,7 +53,7 @@ class BaseStrategy(abc.ABC, Generic[_StrategySettings]): """Run the strategy to calculate the recommendation""" @classmethod - def find(cls, name: str) -> type[Self]: + def find(cls: type[Self], name: str) -> type[Self]: """Get a strategy from its name.""" strategies = cls.get_all() @@ -61,7 +63,7 @@ class BaseStrategy(abc.ABC, Generic[_StrategySettings]): raise ValueError(f"Unknown strategy name: {name}. Available strategies: {', '.join(strategies)}") @classmethod - def get_all(cls) -> dict[str, type[Self]]: + def get_all(cls: type[Self]) -> dict[str, type[Self]]: # NOTE: Load default formatters from robusta_krr import strategies as _ # noqa: F401 diff --git a/robusta_krr/core/integrations/kubernetes.py b/robusta_krr/core/integrations/kubernetes.py index 607e298..3affe7e 100644 --- a/robusta_krr/core/integrations/kubernetes.py +++ b/robusta_krr/core/integrations/kubernetes.py @@ -1,7 +1,8 @@ import asyncio import itertools +from typing import Union -from kubernetes import client, config +from kubernetes import client, config # type: ignore from kubernetes.client.models import ( V1Container, V1DaemonSet, @@ -69,7 +70,7 @@ class ClusterLoader(Configurable): return f"{expression.key} {expression.operator} ({values})" @staticmethod - def _build_selector_query(selector: V1LabelSelector) -> str | None: + def _build_selector_query(selector: V1LabelSelector) -> Union[str, None]: label_filters = [f"{label[0]}={label[1]}" for label in selector.match_labels.items()] if selector.match_expressions is not None: @@ -79,7 +80,7 @@ class ClusterLoader(Configurable): return ",".join(label_filters) - async def __list_pods(self, resource: V1Deployment | V1DaemonSet | V1StatefulSet) -> list[str]: + async def __list_pods(self, resource: Union[V1Deployment, V1DaemonSet, V1StatefulSet]) -> list[str]: selector = self._build_selector_query(resource.spec.selector) if selector is None: return [] @@ -90,7 +91,7 @@ class ClusterLoader(Configurable): return [pod.metadata.name for pod in ret.items] async def __build_obj( - self, item: V1Deployment | V1DaemonSet | V1StatefulSet, container: V1Container + self, item: Union[V1Deployment, V1DaemonSet, V1StatefulSet], container: V1Container ) -> K8sObjectData: return K8sObjectData( cluster=self.cluster, @@ -103,7 +104,9 @@ class ClusterLoader(Configurable): ) async def _list_deployments(self) -> list[K8sObjectData]: + self.debug(f"Listing deployments in {self.cluster}") ret: V1DeploymentList = await asyncio.to_thread(self.apps.list_deployment_for_all_namespaces, watch=False) + self.debug(f"Found {len(ret.items)} deployments in {self.cluster}") return await asyncio.gather( *[ @@ -114,7 +117,9 @@ class ClusterLoader(Configurable): ) async def _list_all_statefulsets(self) -> list[K8sObjectData]: + self.debug(f"Listing statefulsets in {self.cluster}") ret: V1StatefulSetList = await asyncio.to_thread(self.apps.list_stateful_set_for_all_namespaces, watch=False) + self.debug(f"Found {len(ret.items)} statefulsets in {self.cluster}") return await asyncio.gather( *[ @@ -125,7 +130,9 @@ class ClusterLoader(Configurable): ) async def _list_all_daemon_set(self) -> list[K8sObjectData]: + self.debug(f"Listing daemonsets in {self.cluster}") ret: V1DaemonSetList = await asyncio.to_thread(self.apps.list_daemon_set_for_all_namespaces, watch=False) + self.debug(f"Found {len(ret.items)} daemonsets in {self.cluster}") return await asyncio.gather( *[ @@ -136,7 +143,9 @@ class ClusterLoader(Configurable): ) async def _list_all_jobs(self) -> list[K8sObjectData]: + self.debug(f"Listing jobs in {self.cluster}") ret: V1JobList = await asyncio.to_thread(self.batch.list_job_for_all_namespaces, watch=False) + self.debug(f"Found {len(ret.items)} jobs in {self.cluster}") return await asyncio.gather( *[ @@ -149,7 +158,9 @@ class ClusterLoader(Configurable): async def _list_pods(self) -> list[K8sObjectData]: """For future use, not supported yet.""" + self.debug(f"Listing pods in {self.cluster}") ret: V1PodList = await asyncio.to_thread(self.apps.list_pod_for_all_namespaces, watch=False) + self.debug(f"Found {len(ret.items)} pods in {self.cluster}") return await asyncio.gather( *[self.__build_obj(item, container) for item in ret.items for container in item.spec.containers] diff --git a/robusta_krr/core/integrations/prometheus.py b/robusta_krr/core/integrations/prometheus.py index 16aeaa2..a52dde7 100644 --- a/robusta_krr/core/integrations/prometheus.py +++ b/robusta_krr/core/integrations/prometheus.py @@ -1,7 +1,7 @@ import asyncio import datetime from decimal import Decimal -from typing import no_type_check +from typing import Optional, no_type_check import requests from kubernetes import config as k8s_config @@ -19,7 +19,7 @@ from robusta_krr.utils.service_discovery import ServiceDiscovery class PrometheusDiscovery(ServiceDiscovery): - def find_prometheus_url(self, *, api_client: ApiClient | None = None) -> str | None: + def find_prometheus_url(self, *, api_client: Optional[ApiClient] = None) -> Optional[str]: return super().find_url( selectors=[ "app=kube-prometheus-stack-prometheus", @@ -58,7 +58,7 @@ class PrometheusLoader(Configurable): self, config: Config, *, - cluster: str | None = None, + cluster: Optional[str] = None, ) -> None: super().__init__(config=config) diff --git a/robusta_krr/core/models/allocations.py b/robusta_krr/core/models/allocations.py index 88c1f06..dd7c94f 100644 --- a/robusta_krr/core/models/allocations.py +++ b/robusta_krr/core/models/allocations.py @@ -2,7 +2,7 @@ from __future__ import annotations import enum from decimal import Decimal -from typing import Literal, Self +from typing import Literal, TypeVar, Union import pydantic as pd from kubernetes.client.models import V1Container @@ -20,7 +20,9 @@ class ResourceType(str, enum.Enum): Memory = "memory" -RecommendationValue = Decimal | Literal["?"] | None +RecommendationValue = Union[Decimal, Literal["?"], None] + +Self = TypeVar("Self", bound="ResourceAllocations") class ResourceAllocations(pd.BaseModel): @@ -49,7 +51,7 @@ class ResourceAllocations(pd.BaseModel): } @classmethod - def from_container(cls, container: V1Container) -> Self: + def from_container(cls: type[Self], container: V1Container) -> Self: """Get the resource allocations from a Kubernetes container. Args: diff --git a/robusta_krr/core/models/config.py b/robusta_krr/core/models/config.py index 83b62af..153f9d3 100644 --- a/robusta_krr/core/models/config.py +++ b/robusta_krr/core/models/config.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Literal, Optional, Union import pydantic as pd @@ -10,8 +10,8 @@ class Config(pd.BaseSettings): quiet: bool = pd.Field(False) verbose: bool = pd.Field(False) - clusters: list[str] | Literal["*"] | None = None - namespaces: list[str] | Literal["*"] = pd.Field("*") + clusters: Union[list[str], Literal["*"], None] = None + namespaces: Union[list[str], Literal["*"]] = pd.Field("*") # Make this True if you are running KRR inside the cluster inside_cluster: bool = pd.Field(False) @@ -21,8 +21,8 @@ class Config(pd.BaseSettings): memory_min_value: int = pd.Field(10, ge=0) # in megabytes # Prometheus Settings - prometheus_url: str | None = pd.Field(None) - prometheus_auth_header: str | None = pd.Field(None) + prometheus_url: Optional[str] = pd.Field(None) + prometheus_auth_header: Optional[str] = pd.Field(None) prometheus_ssl_enabled: bool = pd.Field(False) # Logging Settings @@ -32,7 +32,7 @@ class Config(pd.BaseSettings): other_args: list[str] = pd.Field([]) @pd.validator("namespaces") - def validate_namespaces(cls, v: list[str] | Literal["*"]) -> list[str] | Literal["*"]: + def validate_namespaces(cls, v: Union[list[str], Literal["*"]]) -> Union[list[str], Literal["*"]]: if v == []: return "*" diff --git a/robusta_krr/core/models/objects.py b/robusta_krr/core/models/objects.py index 328e9c8..74f07ac 100644 --- a/robusta_krr/core/models/objects.py +++ b/robusta_krr/core/models/objects.py @@ -1,3 +1,5 @@ +from typing import Optional + import pydantic as pd from robusta_krr.core.models.allocations import ResourceAllocations @@ -9,7 +11,7 @@ class K8sObjectData(pd.BaseModel): container: str pods: list[str] namespace: str - kind: str | None + kind: Optional[str] allocations: ResourceAllocations def __str__(self) -> str: diff --git a/robusta_krr/core/models/result.py b/robusta_krr/core/models/result.py index a768a52..eeaa565 100644 --- a/robusta_krr/core/models/result.py +++ b/robusta_krr/core/models/result.py @@ -2,7 +2,7 @@ from __future__ import annotations import enum import itertools -from typing import Any +from typing import Any, Union import pydantic as pd @@ -11,7 +11,7 @@ from robusta_krr.core.models.allocations import RecommendationValue, ResourceAll from robusta_krr.core.models.objects import K8sObjectData -class Severity(enum.StrEnum): +class Severity(str, enum.Enum): """The severity of the scan.""" UNKNOWN = "UNKNOWN" @@ -81,7 +81,7 @@ class Result(pd.BaseModel): super().__init__(*args, **kwargs) self.score = self.__calculate_score() - def format(self, formatter: type[BaseFormatter] | str, **kwargs: Any) -> Any: + def format(self, formatter: Union[type[BaseFormatter], str], **kwargs: Any) -> Any: """Format the result. Args: diff --git a/robusta_krr/core/runner.py b/robusta_krr/core/runner.py index 49354fa..7aae3bd 100644 --- a/robusta_krr/core/runner.py +++ b/robusta_krr/core/runner.py @@ -1,6 +1,7 @@ import asyncio import math from decimal import Decimal +from typing import Optional, Union from robusta_krr.core.abstract.strategies import ResourceRecommendation, RunResult from robusta_krr.core.integrations.kubernetes import KubernetesLoader @@ -17,7 +18,7 @@ class Runner(Configurable): def __init__(self, config: Config) -> None: super().__init__(config) self._k8s_loader = KubernetesLoader(self.config) - self._prometheus_loaders: dict[str, PrometheusLoader | Exception] = {} + self._prometheus_loaders: dict[str, Union[PrometheusLoader, Exception]] = {} self._strategy = self.config.create_strategy() def _get_prometheus_loader(self, cluster: str) -> PrometheusLoader: @@ -53,7 +54,7 @@ class Runner(Configurable): else: return Decimal(0) - def _round_value(self, value: Decimal | None, resource: ResourceType) -> Decimal | None: + def _round_value(self, value: Optional[Decimal], resource: ResourceType) -> Optional[Decimal]: if value is None: return value diff --git a/robusta_krr/formatters/table.py b/robusta_krr/formatters/table.py index fdc9508..8ea2003 100644 --- a/robusta_krr/formatters/table.py +++ b/robusta_krr/formatters/table.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +from typing import Optional from rich.table import Table @@ -20,7 +21,7 @@ class TableFormatter(BaseFormatter): __display_name__ = "table" - def _format_united_decimal(self, value: RecommendationValue, prescision: int | None = None) -> str: + def _format_united_decimal(self, value: RecommendationValue, prescision: Optional[int] = None) -> str: if value is None: return NONE_LITERAL elif isinstance(value, str): diff --git a/robusta_krr/utils/configurable.py b/robusta_krr/utils/configurable.py index d2f1442..3957e6e 100644 --- a/robusta_krr/utils/configurable.py +++ b/robusta_krr/utils/configurable.py @@ -1,4 +1,5 @@ import abc +from inspect import getframeinfo, stack from typing import Literal from rich.console import Console @@ -51,7 +52,14 @@ class Configurable(abc.ABC): """ if self.debug_active: - self.console.print(self.__add_prefix(message, "[bold green][DEBUG][/bold green]", no_prefix=False)) + caller = getframeinfo(stack()[1][0]) + self.console.print( + self.__add_prefix( + message + f"\t\t({caller.filename}:{caller.lineno})", + "[bold green][DEBUG][/bold green]", + no_prefix=False, + ) + ) def debug_exception(self) -> None: """ diff --git a/robusta_krr/utils/resource_units.py b/robusta_krr/utils/resource_units.py index 37a3646..d80ac05 100644 --- a/robusta_krr/utils/resource_units.py +++ b/robusta_krr/utils/resource_units.py @@ -1,4 +1,5 @@ from decimal import Decimal +from typing import Optional UNITS = { "m": Decimal("1e-3"), @@ -25,7 +26,7 @@ def parse(x: str) -> Decimal: return Decimal(x) -def format(x: Decimal, prescision: int | None = None) -> str: +def format(x: Decimal, prescision: Optional[int] = None) -> str: """Converts an integer to a string with respect of units.""" if prescision is not None: diff --git a/robusta_krr/utils/service_discovery.py b/robusta_krr/utils/service_discovery.py index 8c498fe..f341225 100644 --- a/robusta_krr/utils/service_discovery.py +++ b/robusta_krr/utils/service_discovery.py @@ -1,4 +1,5 @@ import logging +from typing import Optional from cachetools import TTLCache from kubernetes import client @@ -13,7 +14,7 @@ class ServiceDiscovery(Configurable): SERVICE_CACHE_TTL_SEC = 900 cache: TTLCache = TTLCache(maxsize=1, ttl=SERVICE_CACHE_TTL_SEC) - def find_service_url(self, label_selector: str, *, api_client: ApiClient | None = None) -> str | None: + def find_service_url(self, label_selector: str, *, api_client: Optional[ApiClient] = None) -> Optional[str]: """ Get the url of an in-cluster service with a specific label """ @@ -36,7 +37,7 @@ class ServiceDiscovery(Configurable): return None - def find_url(self, selectors: list[str], *, api_client: ApiClient | None = None) -> str | None: + def find_url(self, selectors: list[str], *, api_client: Optional[ApiClient] = None) -> Optional[str]: """ Try to autodiscover the url of an in-cluster service """ -- cgit v1.2.3