Skip to content
Merged
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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,18 +259,18 @@ asyncio.run(main())
ActiveGates - Auto-update jobs | :heavy_check_mark: | `dt.activegates_autoupdate_jobs` |
ActiveGates - Remote configuration | :heavy_check_mark: | `dt.activegates_remote_configuration` |
Audit Logs | :heavy_check_mark: | `dt.audit_logs` |
Events | :warning: | `dt.events_v2` |
Events | :heavy_check_mark: | `dt.events_v2` |
Extensions 2.0 | :heavy_check_mark: | `dt.extensions_v2` |
Logs | :warning: | `dt.logs` |
Logs | :heavy_check_mark: | `dt.logs` |
Metrics | :heavy_check_mark: | `dt.metrics` |
Monitored entities | :warning: | `dt.entities` |
Monitored entities | :heavy_check_mark: | `dt.entities` |
Monitored entities - Custom tags | :heavy_check_mark: | `dt.custom_tags` |
Network zones | :warning: | `dt.network_zones` |
Network zones | :heavy_check_mark: | `dt.network_zones` |
OneAgents - Remote configuration | :heavy_check_mark: | `dt.oneagents_remote_configuration` |
Problems | :heavy_check_mark: | `dt.problems` |
Security problems | :heavy_check_mark: | `dt.security_problems` |
Service-level objectives | :heavy_check_mark: | `dt.slos` |
Settings | :warning: | `dt.settings` |
Settings | :heavy_check_mark: | `dt.settings` |
Settings - Management zones | :heavy_check_mark: | `dt.management_zones_v2` |
Synthetic - Monitors | :heavy_check_mark: | `dt.synthetic_monitors_v2` |
Credential vault | :heavy_check_mark: | `dt.credentials` |
Expand Down
51 changes: 51 additions & 0 deletions dynatrace/environment_v2/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
class EventServiceV2:
ENDPOINT_EVENTS = "/api/v2/events"
ENDPOINT_INGEST = "/api/v2/events/ingest"
ENDPOINT_PROPERTIES = "/api/v2/eventProperties"
ENDPOINT_TYPES = "/api/v2/eventTypes"

def __init__(self, http_client: HttpClient):
Expand Down Expand Up @@ -90,6 +91,38 @@ async def get(self, event_id: str) -> "Event":
)
return Event(raw_element=response.json(), http_client=self.__http_client)

async def list_properties(
self, page_size: int | None = None
) -> "PaginatedList[EventPropertyDetails]":
"""Lists all event properties.

:param page_size: The amount of event properties in a single response payload. The maximal allowed page size is 500. If not set, 100 is used.

:returns PaginatedList[EventPropertyDetails]: the list of event properties
"""
params = {"pageSize": page_size}
return await PaginatedList(
target_class=EventPropertyDetails,
http_client=self.__http_client,
target_url=self.ENDPOINT_PROPERTIES,
list_item="eventProperties",
target_params=params,
).initialize()

async def get_property(self, property_key: str) -> "EventPropertyDetails":
"""Gets the details of an event property.

:param property_key: The event property key you're inquiring.

:returns EventPropertyDetails: the event property requested
"""
response = await self.__http_client.make_request(
path=f"{self.ENDPOINT_PROPERTIES}/{property_key}"
)
return EventPropertyDetails(
raw_element=response.json(), http_client=self.__http_client
)

async def list_types(
self, page_size: int | None = None
) -> "PaginatedList[EventType]":
Expand Down Expand Up @@ -234,6 +267,24 @@ def to_json(self) -> dict[str, Any]:
return {"key": self.key, "value": self.value}


class EventPropertyDetails(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.key: str = raw_element["key"]
self.display_name: str | None = raw_element.get("displayName")
self.filterable: bool | None = raw_element.get("filterable")
self.description: str | None = raw_element.get("description")
self.writable: bool | None = raw_element.get("writable")

def to_json(self) -> dict[str, Any]:
return {
"key": self.key,
"displayName": self.display_name,
"filterable": self.filterable,
"description": self.description,
"writable": self.writable,
}


class EventType(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.type: str = raw_element["type"]
Expand Down
85 changes: 83 additions & 2 deletions dynatrace/environment_v2/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@


class LogService:
# TODO - Add search and aggregate
ENDPOINT = "/api/v2/logs"
ENDPOINT_EXPORT = "/api/v2/logs/export"
ENDPOINT_SEARCH = "/api/v2/logs/search"
ENDPOINT_AGGREGATE = "/api/v2/logs/aggregate"

def __init__(self, http_client: HttpClient):
self.__http_client = http_client
Expand Down Expand Up @@ -60,11 +62,76 @@ async def export(
return await PaginatedList(
LogRecord,
self.__http_client,
"/api/v2/logs/export",
self.ENDPOINT_EXPORT,
params,
list_item="results",
).initialize()

async def search(
self,
query: str | None = None,
time_from: datetime | str | None = None,
time_to: datetime | str | None = None,
sort: str | None = None,
limit: int | None = None,
) -> list["LogRecord"]:
"""Reads log records using the deprecated search endpoint and fetches all slices."""
params = {
"from": timestamp_to_string(time_from),
"to": timestamp_to_string(time_to),
"query": query,
"sort": sort,
"limit": limit,
}
records: list[LogRecord] = []
request_params = {
key: value for key, value in params.items() if value is not None
}

while True:
response = await self.__http_client.make_request(
self.ENDPOINT_SEARCH, params=request_params
)
payload = response.json()
records.extend(
LogRecord(raw_element=record, http_client=self.__http_client)
for record in payload.get("results", [])
)
next_slice_key = payload.get("nextSliceKey")
if not next_slice_key:
break
request_params = {"nextSliceKey": next_slice_key}

return records

async def aggregate(
self,
query: str | None = None,
time_from: datetime | str | None = None,
time_to: datetime | str | None = None,
time_buckets: int | None = None,
max_group_values: int | None = None,
group_by: list[str] | None = None,
) -> "AggregatedLog":
"""Gets aggregated log records from the deprecated aggregate endpoint."""
params = {
"from": timestamp_to_string(time_from),
"to": timestamp_to_string(time_to),
"query": query,
"timeBuckets": time_buckets,
"maxGroupValues": max_group_values,
"groupBy": group_by,
}
request_params = {
key: value for key, value in params.items() if value is not None
}
response = await self.__http_client.make_request(
self.ENDPOINT_AGGREGATE, params=request_params
)
return AggregatedLog(
raw_element=response.json(), http_client=self.__http_client
)

async def ingest(self, payload: dict[str, Any] | list[dict[str, Any]]) -> Response:
"""
Ingests logs into the Dynatrace log store.
Expand All @@ -89,6 +156,20 @@ def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.status: LogRecordStatus = LogRecordStatus(raw_element.get("status"))


class AggregatedLog(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.aggregation_result: dict[str, Any] | None = raw_element.get(
"aggregationResult"
)
self.warnings: str | None = raw_element.get("warnings")

def to_json(self) -> dict[str, Any]:
return {
"aggregationResult": self.aggregation_result,
"warnings": self.warnings,
}


class EventType(Enum):
K8S = "K8S"
LOG = "LOG"
Expand Down
62 changes: 61 additions & 1 deletion dynatrace/environment_v2/monitored_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
class EntityService:
ENDPOINT_ENTITIES = "/api/v2/entities"
ENDPOINT_TYPES = "/api/v2/entityTypes"
ENDPOINT_SECURITY_CONTEXT = "/api/v2/entities/securityContext"

def __init__(self, http_client: HttpClient):
self.__http_client = http_client
Expand Down Expand Up @@ -119,6 +120,50 @@ async def post_custom_device(self, device: "CustomDeviceCreation") -> "Response"
"""
return await device.post()

async def set_security_context(
self,
entity_selector: str,
security_context: builtins.list[str],
time_from: datetime | str | None = None,
time_to: datetime | str | None = None,
) -> "SecurityContextResult":
"""Sets the given security context for all entities matching the entity selector."""
params = {
"entitySelector": entity_selector,
"from": timestamp_to_string(time_from),
"to": timestamp_to_string(time_to),
}
response = await self.__http_client.make_request(
self.ENDPOINT_SECURITY_CONTEXT,
method="POST",
params={"securityContext": security_context},
query_params=params,
)
return SecurityContextResult(
raw_element=response.json(), http_client=self.__http_client
)

async def delete_security_context(
self,
entity_selector: str,
time_from: datetime | str | None = None,
time_to: datetime | str | None = None,
) -> "SecurityContextResult":
"""Deletes the security context for all entities matching the entity selector."""
params = {
"entitySelector": entity_selector,
"from": timestamp_to_string(time_from),
"to": timestamp_to_string(time_to),
}
response = await self.__http_client.make_request(
self.ENDPOINT_SECURITY_CONTEXT,
method="DELETE",
params=params,
)
return SecurityContextResult(
raw_element=response.json(), http_client=self.__http_client
)

def create_custom_device(
self,
custom_device_id: str,
Expand Down Expand Up @@ -312,11 +357,12 @@ def to_json(self):
body["messageType"] = str(self.message_type)
return body

async def post(self) -> "Response":
async def post(self, ui_based: bool | None = None) -> "Response":
return await self._http_client.make_request(
path=f"{EntityService.ENDPOINT_ENTITIES}/custom",
method="POST",
params=self.to_json(),
query_params={"uiBased": ui_based},
)


Expand All @@ -341,6 +387,20 @@ def _create_from_raw_data(self, raw_element: dict):
self.tags: str | None = raw_element.get("tags")


class SecurityContextResult(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.entity_ids: list[str] | None = raw_element.get("entityIds", [])
self.management_zone_ids: list[int] | None = raw_element.get(
"managementZoneIds"
)

def to_json(self) -> dict[str, Any]:
return {
"entityIds": self.entity_ids,
"managementZoneIds": self.management_zone_ids,
}


class EntityTypePropertyDto(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.id: str = raw_element["id"]
Expand Down
48 changes: 46 additions & 2 deletions dynatrace/environment_v2/networkzones.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
class NetworkZoneService:
ENDPOINT = "/api/v2/networkZones"
ENDPOINT_GLOBALSETTINGS = "/api/v2/networkZoneSettings"
ENDPOINT_HOST_CONNECTION_STATISTICS = "/api/v2/networkZones"

def __init__(self, http_client: HttpClient):
self.__http_client = http_client
Expand Down Expand Up @@ -52,6 +53,19 @@ async def get(self, networkzone_id: str):
).json()
return NetworkZone(raw_element=response)

async def get_host_statistics(
self, networkzone_id: str, filter: str | None = None
) -> "NetworkZoneConnectionStatistics":
"""Gets the statistics about hosts using the network zone."""
params = {"filter": filter}
response = (
await self.__http_client.make_request(
f"{self.ENDPOINT}/{networkzone_id}/hostConnectionStatistics",
params=params,
)
).json()
return NetworkZoneConnectionStatistics(raw_element=response)

async def update(
self,
networkzone_id: str,
Expand Down Expand Up @@ -80,7 +94,7 @@ async def delete(self, networkzone_id: str):
path=f"{self.ENDPOINT}/{networkzone_id}", method="DELETE"
)

async def getGlobalConfig(self):
async def get_global_config(self) -> "NetworkZoneSettings":
"""Gets the global configuration of network zones. No params
:return: Network Zone Global Settings object
"""
Expand All @@ -89,7 +103,7 @@ async def getGlobalConfig(self):
).json()
return NetworkZoneSettings(raw_element=response)

async def updateGlobalConfig(self, configuration: bool):
async def update_global_config(self, configuration: bool):
"""Updates the global configuration of network zones.

:param configuration: boolean setting to enable/disable NZs
Expand All @@ -100,6 +114,12 @@ async def updateGlobalConfig(self, configuration: bool):
path=self.ENDPOINT_GLOBALSETTINGS, method="PUT", params=params
)

async def getGlobalConfig(self):
return await self.get_global_config()

async def updateGlobalConfig(self, configuration: bool):
return await self.update_global_config(configuration)


class NetworkZone(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
Expand All @@ -119,3 +139,27 @@ def _create_from_raw_data(self, raw_element: dict[str, Any]):
class NetworkZoneSettings(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, bool]):
self.network_zones_enabled: bool = raw_element.get("networkZonesEnabled")


class NetworkZoneConnectionStatistics(DynatraceObject):
def _create_from_raw_data(self, raw_element: dict[str, Any]):
self.hosts_configured_but_not_connected: list[str] = raw_element.get(
"hostsConfiguredButNotConnected", []
)
self.hosts_connected_as_alternative: list[str] = raw_element.get(
"hostsConnectedAsAlternative", []
)
self.hosts_connected_as_failover: list[str] = raw_element.get(
"hostsConnectedAsFailover", []
)
self.hosts_connected_as_failover_without_active_gates: list[str] = (
raw_element.get("hostsConnectedAsFailoverWithoutActiveGates", [])
)

def to_json(self) -> dict[str, Any]:
return {
"hostsConfiguredButNotConnected": self.hosts_configured_but_not_connected,
"hostsConnectedAsAlternative": self.hosts_connected_as_alternative,
"hostsConnectedAsFailover": self.hosts_connected_as_failover,
"hostsConnectedAsFailoverWithoutActiveGates": self.hosts_connected_as_failover_without_active_gates,
}
Loading
Loading