Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

RT-258 Add Prometheus Metrics exports #5

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/

# Exporter Metrics
exporter_metrics
4 changes: 4 additions & 0 deletions src/vulnrelay/core/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class Settings(BaseSettings):
SCANNERS: list[ScannerOption] = ["grype"]
SCAN_HOST: bool = False

# Metric Exporter Settings
METRICS_DIR: str = "exporter_metrics"
METRICS_FILENAME: str = "vulnrelay.prom"

model_config = SettingsConfigDict(
env_file=ROOT_DIR / ".env",
)
Expand Down
4 changes: 4 additions & 0 deletions src/vulnrelay/metrics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .exporter import MetricExporter
from .metric import MetricNames

__all__ = ["MetricExporter", "Metric", "MetricNames"]
joao-vieira-leao-reef marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 37 additions & 0 deletions src/vulnrelay/metrics/exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
import shutil
from pathlib import Path
from typing import Any


class MetricExporter:
joao-vieira-leao-reef marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, *, metrics: dict[str, Any], path: str) -> None:
joao-vieira-leao-reef marked this conversation as resolved.
Show resolved Hide resolved
self.metrics: dict[str, Any] = metrics
self.path = path
self._check_dir()

def _get_export_content(self) -> str:
return "\n".join([f"{name} {metric}" for name, metric in self.metrics.items()])

def _check_dir(self) -> None:
dir_path = Path(self.path).parent
dir_path.mkdir(parents=True, exist_ok=True)

def export_all(self) -> None:
content = self._get_export_content()
path = self.path
temp_file = f"{path}.tmp"
joao-vieira-leao-reef marked this conversation as resolved.
Show resolved Hide resolved

with open(temp_file, "w") as file:
file.write(content)

try:
shutil.move(temp_file, path)
except Exception as e:
os.remove(temp_file)
raise e

def save_and_export(self, *, values: dict[str, Any]) -> None:
for name, value in values.items():
self.metrics[name] = value
self.export_all()
5 changes: 5 additions & 0 deletions src/vulnrelay/metrics/metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from enum import Enum


class MetricNames(Enum):
LAST_SCAN_AND_PUSH = "last_successful_scan_push"
21 changes: 21 additions & 0 deletions src/vulnrelay/services.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import logging
import os
import subprocess
import time
from collections.abc import Callable, Iterable

from vulnrelay.core.conf import settings
from vulnrelay.metrics import MetricExporter, MetricNames
from vulnrelay.scanners import Scanner
from vulnrelay.uploader import DefectDojoUploader, Uploader

Expand Down Expand Up @@ -37,16 +40,27 @@ def get_uploader() -> Uploader:
)


def get_metric_exporter() -> MetricExporter:
return MetricExporter(
metrics={
MetricNames.LAST_SCAN_AND_PUSH.value: None,
},
path=os.path.join(settings.METRICS_DIR, settings.METRICS_FILENAME),
)


def run_workflow(
*,
images: Iterable[str] | None = None,
scanner_names: Iterable[str] | None = None,
get_uploader: Callable[[], Uploader] = get_uploader,
get_metric_exporter: Callable[[], MetricExporter] = get_metric_exporter,
scan_host: bool = True,
) -> None:
images = images or get_running_images()
scanner_names = scanner_names or settings.SCANNERS
uploader = get_uploader()
metric_exporter = get_metric_exporter()

for scanner_name in scanner_names:
scanner = Scanner.get_scanner(scanner_name)()
Expand Down Expand Up @@ -97,3 +111,10 @@ def run_workflow(
continue

logger.info("Upload successful")

try:
metric_exporter.save_and_export(
joao-vieira-leao-reef marked this conversation as resolved.
Show resolved Hide resolved
values={MetricNames.LAST_SCAN_AND_PUSH.value: int(time.time())},
joao-vieira-leao-reef marked this conversation as resolved.
Show resolved Hide resolved
)
except Exception:
logger.exception("Failed to export metrics!")