Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

add markdown formatter #376

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ setuptools==70.3.0 ; python_version >= "3.9" and python_full_version < "3.13"
shellingham==1.5.4 ; python_version >= "3.9" and python_full_version < "3.13"
six==1.16.0 ; python_version >= "3.9" and python_full_version < "3.13"
slack-sdk==3.27.1 ; python_version >= "3.9" and python_full_version < "3.13"
tabulate==0.9.0 ; python_version >= "3.9" and python_full_version < "3.13"
typer[all]==0.7.0 ; python_version >= "3.9" and python_full_version < "3.13"
typing-extensions==4.6.0 ; python_version >= "3.9" and python_full_version < "3.13"
tzdata==2024.1 ; python_version >= "3.9" and python_full_version < "3.13"
Expand Down
10 changes: 10 additions & 0 deletions robusta_krr/core/models/severity.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ def color(self) -> str:
self.CRITICAL: "red",
}[self]

@property
def emoji(self) -> str:
return {
self.UNKNOWN: "❔",
self.GOOD: "✅",
self.OK: "🟩",
self.WARNING: "🟨",
self.CRITICAL: "🟥",
}[self]

@classmethod
def calculate(
cls, current: RecommendationValue, recommended: RecommendationValue, resource_type: ResourceType
Expand Down
4 changes: 2 additions & 2 deletions robusta_krr/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ def _process_result(self, result: Result) -> None:
file_name = settings.slack_output

with open(file_name, "w") as target_file:
# don't use rich when writing a csv or html to avoid line wrapping etc
if settings.format == "csv" or settings.format == "html":
# don't use rich when writing a csv, html or markdown to avoid line wrapping etc
if settings.format == "csv" or settings.format == "html" or settings.format == "markdown":
target_file.write(formatted)
else:
console = Console(file=target_file, width=settings.width)
Expand Down
1 change: 1 addition & 0 deletions robusta_krr/formatters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .yaml import yaml
from .csv import csv
from .html import html
from .markdown import markdown
111 changes: 111 additions & 0 deletions robusta_krr/formatters/markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import itertools
from tabulate import tabulate
from typing import Any

from robusta_krr.core.abstract import formatters
from robusta_krr.core.models.allocations import RecommendationValue, format_recommendation_value, format_diff, NONE_LITERAL, NAN_LITERAL
from robusta_krr.core.models.result import ResourceScan, ResourceType, Result
from robusta_krr.core.models.config import settings
from robusta_krr.utils import resource_units


def _format_request_str(item: ResourceScan, resource: ResourceType, selector: str) -> str:
allocated = getattr(item.object.allocations, selector)[resource]
info = item.recommended.info.get(resource)
recommended = getattr(item.recommended, selector)[resource]
severity = recommended.severity

if allocated is None and recommended.value is None:
return f"{NONE_LITERAL}"

diff = format_diff(allocated, recommended, selector)
if diff != "":
diff = f"({diff})"

if info is None:
info_formatted = ""
else:
info_formatted = f"*({info})*"

return (
f"{severity.emoji} "
+ diff
+ " "
+ format_recommendation_value(allocated)
+ " -> "
+ format_recommendation_value(recommended.value)
+ " "
+ info_formatted
)


def _format_total_diff(item: ResourceScan, resource: ResourceType, pods_current: int) -> str:
selector = "requests"
allocated = getattr(item.object.allocations, selector)[resource]
recommended = getattr(item.recommended, selector)[resource]

# if we have more than one pod, say so (this explains to the user why the total is different than the recommendation)
if pods_current == 1:
pods_info = ""
else:
pods_info = f"*({pods_current} pods)*"

return f"{format_diff(allocated, recommended, selector, pods_current)} {pods_info}"


@formatters.register()
def markdown(result: Result) -> str:
"""Format the result as markdown.

:param result: The result to format.
:type result: :class:`core.result.Result`
:returns: The formatted results.
:rtype: str
"""

cluster_count = len(set(item.object.cluster for item in result.scans))

headers = []
headers.append("Number")
if cluster_count > 1 or settings.show_cluster_name:
headers.append("Cluster")
headers.append("Namespace")
headers.append("Name")
headers.append("Pods")
headers.append("Old Pods")
headers.append("Type")
headers.append("Container")
for resource in ResourceType:
headers.append(f"{resource.name} Diff")
headers.append(f"{resource.name} Requests")
headers.append(f"{resource.name} Limits")

table = []
for _, group in itertools.groupby(
enumerate(result.scans), key=lambda x: (x[1].object.cluster, x[1].object.namespace, x[1].object.name)
):
group_items = list(group)

for j, (i, item) in enumerate(group_items):
last_row = j == len(group_items) - 1
full_info_row = j == 0

cells: list[Any] = [f"{i + 1}."]
if cluster_count > 1 or settings.show_cluster_name:
cells.append(item.object.cluster if full_info_row else "")
cells += [
item.object.namespace if full_info_row else "",
item.object.name if full_info_row else "",
f"{item.object.current_pods_count}" if full_info_row else "",
f"{item.object.deleted_pods_count}" if full_info_row else "",
item.object.kind if full_info_row else "",
item.object.container
]

for resource in ResourceType:
cells.append(_format_total_diff(item, resource, item.object.current_pods_count))
cells += [_format_request_str(item, resource, selector) for selector in ["requests", "limits"]]

table.append(cells)

return tabulate(table, headers, tablefmt="pipe")