mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
159 lines
5.6 KiB
Python
159 lines
5.6 KiB
Python
"""Pydantic models for simplified provider responses."""
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
from prowler_mcp_server.prowler_app.models.base import MinimalSerializerMixin
|
|
|
|
|
|
class SimplifiedProvider(MinimalSerializerMixin, BaseModel):
|
|
"""Simplified provider for list/search operations."""
|
|
|
|
id: str
|
|
uid: str
|
|
alias: str | None = None
|
|
provider: str
|
|
connected: bool | None = None
|
|
secret_type: Literal["role", "service_account", "static"] | None = None
|
|
|
|
def _should_exclude(self, key: str, value: Any) -> bool:
|
|
"""Override to always include connected and secret_type fields even when None."""
|
|
# Always include these fields regardless of value (None has semantic meaning)
|
|
if key == "connected" or key == "secret_type":
|
|
return False
|
|
# Use parent class logic for other fields
|
|
return super()._should_exclude(key, value)
|
|
|
|
@classmethod
|
|
def from_api_response(cls, data: dict[str, Any]) -> "SimplifiedProvider":
|
|
"""Transform JSON:API provider response to simplified format."""
|
|
attributes = data["attributes"]
|
|
connection_data = attributes.get("connection", {})
|
|
|
|
return cls(
|
|
id=data["id"],
|
|
uid=attributes["uid"],
|
|
alias=attributes.get("alias"),
|
|
provider=attributes["provider"],
|
|
connected=connection_data.get("connected"),
|
|
secret_type=None, # Will be populated separately via secret endpoint
|
|
)
|
|
|
|
|
|
class DetailedProvider(SimplifiedProvider):
|
|
"""Detailed provider with complete information for deep analysis.
|
|
|
|
Extends SimplifiedProvider with temporal metadata and relationships.
|
|
Use this when you need complete context about a specific provider.
|
|
"""
|
|
|
|
inserted_at: str | None = None
|
|
updated_at: str | None = None
|
|
last_checked_at: str | None = None
|
|
provider_group_ids: list[str] | None = None
|
|
|
|
@classmethod
|
|
def from_api_response(cls, data: dict[str, Any]) -> "DetailedProvider":
|
|
"""Transform JSON:API provider response to detailed format."""
|
|
attributes = data["attributes"]
|
|
connection_data = attributes.get("connection", {})
|
|
relationships = data.get("relationships", {})
|
|
|
|
# Extract provider groups relationship
|
|
provider_group_ids = None
|
|
groups_data = relationships.get("provider_groups", {}).get("data", [])
|
|
if groups_data:
|
|
provider_group_ids = [group["id"] for group in groups_data]
|
|
|
|
return cls(
|
|
id=data["id"],
|
|
uid=attributes["uid"],
|
|
alias=attributes.get("alias"),
|
|
provider=attributes["provider"],
|
|
connected=connection_data.get("connected"),
|
|
inserted_at=attributes.get("inserted_at"),
|
|
updated_at=attributes.get("updated_at"),
|
|
last_checked_at=connection_data.get("last_checked_at"),
|
|
provider_group_ids=provider_group_ids,
|
|
)
|
|
|
|
|
|
class ProvidersListResponse(BaseModel):
|
|
"""Simplified response for providers list queries."""
|
|
|
|
providers: list[SimplifiedProvider]
|
|
total_num_providers: int
|
|
total_num_pages: int
|
|
current_page: int
|
|
|
|
@classmethod
|
|
def from_api_response(cls, response: dict[str, Any]) -> "ProvidersListResponse":
|
|
"""Transform JSON:API response to simplified format."""
|
|
data = response["data"]
|
|
meta = response["meta"]
|
|
pagination = meta["pagination"]
|
|
|
|
providers = [SimplifiedProvider.from_api_response(item) for item in data]
|
|
|
|
return cls(
|
|
providers=providers,
|
|
total_num_providers=pagination["count"],
|
|
total_num_pages=pagination["pages"],
|
|
current_page=pagination["page"],
|
|
)
|
|
|
|
|
|
class ProviderDeletionResult(MinimalSerializerMixin, BaseModel):
|
|
"""Outcome of a provider deletion.
|
|
|
|
Prowler deletes a provider in a background task, so the answer is not always
|
|
a finished deletion. A deletion that never started is raised as an error
|
|
instead of being reported here: this model only describes a deletion Prowler
|
|
accepted and began.
|
|
"""
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
status: Literal["deleted", "in_progress"] = Field(
|
|
description="Outcome of the deletion: 'deleted' when Prowler finished removing the provider, 'in_progress' when the background task was accepted and is still running, which is normal for a provider with many scans and findings"
|
|
)
|
|
task_id: str | None = Field(
|
|
default=None,
|
|
description="UUIDv4 of the background deletion task, present when the deletion did not finish within the polling window so its state can be checked later",
|
|
)
|
|
message: str = Field(
|
|
description="Human-readable description of what happened and what to do next"
|
|
)
|
|
|
|
|
|
class ProviderConnectionStatus(MinimalSerializerMixin, BaseModel):
|
|
"""Result of provider connection operation."""
|
|
|
|
provider: DetailedProvider
|
|
connected: Literal["connected", "failed", "not_tested"]
|
|
error: str | None = None
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
provider_data: dict[str, Any],
|
|
connection_status: dict[str, Any],
|
|
) -> "ProviderConnectionStatus":
|
|
"""Create connection status from provider data and connection test result."""
|
|
|
|
connected: str | None = connection_status.get("connected", None)
|
|
|
|
if connected is None:
|
|
connected = "not_tested"
|
|
elif connected:
|
|
connected = "connected"
|
|
else:
|
|
connected = "failed"
|
|
|
|
return cls(
|
|
provider=DetailedProvider.from_api_response(provider_data),
|
|
connected=connected,
|
|
error=connection_status.get("error", None),
|
|
)
|