diff --git a/datadog_sync/model/dashboard_lists.py b/datadog_sync/model/dashboard_lists.py index 147a177a..a9479c7e 100644 --- a/datadog_sync/model/dashboard_lists.py +++ b/datadog_sync/model/dashboard_lists.py @@ -45,7 +45,7 @@ def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[_id] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/dashboards.py b/datadog_sync/model/dashboards.py index 955488fb..072b3066 100644 --- a/datadog_sync/model/dashboards.py +++ b/datadog_sync/model/dashboards.py @@ -33,7 +33,7 @@ def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = dashboard - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/downtimes.py b/datadog_sync/model/downtimes.py index 2a34b3c3..e6a4ee28 100644 --- a/datadog_sync/model/downtimes.py +++ b/datadog_sync/model/downtimes.py @@ -2,13 +2,22 @@ # under the 3-clause BSD style license (see LICENSE). # This product includes software developed at Datadog (https://www.datadoghq.com/). # Copyright 2019 Datadog, Inc. - +import math from typing import Optional, List, Dict +from datetime import datetime from datadog_sync.utils.base_resource import BaseResource, ResourceConfig from datadog_sync.utils.custom_client import CustomClient +RECURRING_TIMES = { + "days": 86400, + "weeks": 604800, + "months": 2592000, + "years": 31536000, +} + + class Downtimes(BaseResource): resource_type = "downtimes" resource_config = ResourceConfig( @@ -25,18 +34,39 @@ def get_resources(self, client: CustomClient) -> List[Dict]: return resp def import_resource(self, resource: Dict) -> None: + if resource["canceled"]: + return + # Dispose the recurring child downtimes and only retain the parent + if resource["recurrence"] and resource["parent_id"]: + return + self.resource_config.source_resources[str(resource["id"])] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: - pass + def pre_resource_action_hook(self, _id, resource: Dict) -> None: + if _id not in self.resource_config.destination_resources: + current_time = round(datetime.now().timestamp()) + if resource["recurrence"] is None: + # If the downtime start time is in the past, convert it to now + 1min + if resource["start"] and resource["start"] <= current_time: + resource["start"] = current_time + 60 + else: + # Calculate the next recurrence `start` time by counting the number of recurrences + # that has occurred since `start` and round it up + r_time = RECURRING_TIMES[resource["recurrence"]["type"]] + r_period = resource["recurrence"]["period"] + r_interval = r_time * r_period + if resource["start"] and resource["start"] <= current_time: + num_of_recurrences_since_start = math.ceil((current_time - resource["start"]) / r_interval) + resource["start"] += r_interval * num_of_recurrences_since_start + resource["end"] += r_interval * num_of_recurrences_since_start def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: pass def create_resource(self, _id: str, resource: Dict) -> None: destination_client = self.config.destination_client - resp = destination_client.post(self.resource_config.base_path, resource).json() + resp = destination_client.post(self.resource_config.base_path, resource).json() self.resource_config.destination_resources[_id] = resp def update_resource(self, _id: str, resource: Dict) -> None: diff --git a/datadog_sync/model/integrations_aws.py b/datadog_sync/model/integrations_aws.py index a76f948d..9521690c 100644 --- a/datadog_sync/model/integrations_aws.py +++ b/datadog_sync/model/integrations_aws.py @@ -26,7 +26,7 @@ def get_resources(self, client: CustomClient) -> List[Dict]: def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["account_id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/logs_custom_pipelines.py b/datadog_sync/model/logs_custom_pipelines.py index dc3a796b..bb57b3f3 100644 --- a/datadog_sync/model/logs_custom_pipelines.py +++ b/datadog_sync/model/logs_custom_pipelines.py @@ -28,7 +28,7 @@ def import_resource(self, resource: Dict) -> None: return self.resource_config.source_resources[resource["id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/monitors.py b/datadog_sync/model/monitors.py index a302b1a8..2a95deaa 100644 --- a/datadog_sync/model/monitors.py +++ b/datadog_sync/model/monitors.py @@ -14,7 +14,7 @@ class Monitors(BaseResource): resource_type = "monitors" resource_config = ResourceConfig( - resource_connections={"monitors": ["query"], "roles": ["restricted_roles"]}, + resource_connections={"monitors": ["query"], "roles": ["restricted_roles"], "synthetics_tests": []}, base_path="/api/v1/monitor", excluded_attributes=[ "id", @@ -37,12 +37,12 @@ def get_resources(self, client: CustomClient) -> List[Dict]: return resp def import_resource(self, resource: Dict) -> None: - if resource["type"] == "synthetics alert": + if resource["type"] in ("synthetics alert", "slo alert"): return self.resource_config.source_resources[str(resource["id"])] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: @@ -71,13 +71,23 @@ def update_resource(self, _id: str, resource: Dict) -> None: self.resource_config.destination_resources[_id] = resp def connect_id(self, key: str, r_obj: Dict, resource_to_connect: str) -> None: - resources = self.config.resources[resource_to_connect].resource_config.destination_resources + monitors = self.config.resources[resource_to_connect].resource_config.destination_resources + synthetics_tests = self.config.resources["synthetics_tests"].resource_config.destination_resources + if r_obj.get("type") == "composite" and key == "query": ids = re.findall("[0-9]+", r_obj[key]) for _id in ids: - if _id in resources: - new_id = f"{resources[_id]['id']}" + found = False + if _id in monitors: + found = True + new_id = f"{monitors[_id]['id']}" r_obj[key] = re.sub(_id + r"([^#]|$)", new_id + "# ", r_obj[key]) else: + # Check if it is a synthetics monitor + for k, v in synthetics_tests.items(): + if k.endswith(_id): + found = True + r_obj[key] = re.sub(_id + r"([^#]|$)", str(v["monitor_id"]) + "# ", r_obj[key]) + if not found: raise ResourceConnectionError(resource_to_connect, _id=_id) r_obj[key] = (r_obj[key].replace("#", "")).strip() diff --git a/datadog_sync/model/notebooks.py b/datadog_sync/model/notebooks.py index 17a6ea34..dea33fde 100644 --- a/datadog_sync/model/notebooks.py +++ b/datadog_sync/model/notebooks.py @@ -33,7 +33,7 @@ def get_resources(self, client: CustomClient) -> List[Dict]: def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/roles.py b/datadog_sync/model/roles.py index 094243af..d6637c6c 100644 --- a/datadog_sync/model/roles.py +++ b/datadog_sync/model/roles.py @@ -37,7 +37,7 @@ def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: self.destination_roles_mapping = self.get_destination_roles_mapping() return None - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: self.remap_permissions(resource) def create_resource(self, _id, resource): diff --git a/datadog_sync/model/service_level_objectives.py b/datadog_sync/model/service_level_objectives.py index 1569eb53..8258d766 100644 --- a/datadog_sync/model/service_level_objectives.py +++ b/datadog_sync/model/service_level_objectives.py @@ -26,7 +26,7 @@ def get_resources(self, client: CustomClient) -> List[Dict]: def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/slo_corrections.py b/datadog_sync/model/slo_corrections.py index d9d0e2b7..2e6a79fb 100644 --- a/datadog_sync/model/slo_corrections.py +++ b/datadog_sync/model/slo_corrections.py @@ -27,7 +27,7 @@ def get_resources(self, client: CustomClient) -> List[Dict]: def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/synthetics_global_variables.py b/datadog_sync/model/synthetics_global_variables.py index e49c1d36..6e233e34 100644 --- a/datadog_sync/model/synthetics_global_variables.py +++ b/datadog_sync/model/synthetics_global_variables.py @@ -39,7 +39,7 @@ def get_resources(self, client: CustomClient) -> List[Dict]: def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/synthetics_private_locations.py b/datadog_sync/model/synthetics_private_locations.py index 261166ff..e9e128ce 100644 --- a/datadog_sync/model/synthetics_private_locations.py +++ b/datadog_sync/model/synthetics_private_locations.py @@ -33,7 +33,7 @@ def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = pl - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/synthetics_tests.py b/datadog_sync/model/synthetics_tests.py index a30317dc..0c9b1367 100644 --- a/datadog_sync/model/synthetics_tests.py +++ b/datadog_sync/model/synthetics_tests.py @@ -35,7 +35,7 @@ def get_resources(self, client: CustomClient) -> List[Dict]: def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[f"{resource['public_id']}#{resource['monitor_id']}"] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/model/users.py b/datadog_sync/model/users.py index 68763b8a..9cc17583 100644 --- a/datadog_sync/model/users.py +++ b/datadog_sync/model/users.py @@ -46,7 +46,7 @@ def import_resource(self, resource: Dict) -> None: self.resource_config.source_resources[resource["id"]] = resource - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass def pre_apply_hook(self, resources: Dict[str, Dict]) -> Optional[list]: diff --git a/datadog_sync/utils/base_resource.py b/datadog_sync/utils/base_resource.py index 4fcf641f..dfbf51d7 100644 --- a/datadog_sync/utils/base_resource.py +++ b/datadog_sync/utils/base_resource.py @@ -62,7 +62,7 @@ def import_resource(self, resource: Dict) -> None: pass @abc.abstractmethod - def pre_resource_action_hook(self, resource: Dict) -> None: + def pre_resource_action_hook(self, _id, resource: Dict) -> None: pass @abc.abstractmethod @@ -80,13 +80,23 @@ def update_resource(self, _id: str, resource: Dict) -> None: @abc.abstractmethod def connect_id(self, key: str, r_obj: Dict, resource_to_connect: str) -> None: resources = self.config.resources[resource_to_connect].resource_config.destination_resources - _id = str(r_obj[key]) - if _id in resources: - # Cast resource id to str on int based on source type - type_attr = type(r_obj[key]) - r_obj[key] = type_attr(resources[_id]["id"]) + if isinstance(r_obj[key], list): + for i, v in enumerate(r_obj[key]): + _id = str(v) + if _id in resources: + # Cast resource id to str or int based on source type + type_attr = type(v) + r_obj[key][i] = type_attr(resources[_id]["id"]) + else: + raise ResourceConnectionError(resource_to_connect, _id=_id) else: - raise ResourceConnectionError(resource_to_connect, _id=_id) + _id = str(r_obj[key]) + if _id in resources: + # Cast resource id to str on int based on source type + type_attr = type(r_obj[key]) + r_obj[key] = type_attr(resources[_id]["id"]) + else: + raise ResourceConnectionError(resource_to_connect, _id=_id) def import_resources(self) -> None: # reset source resources obj @@ -150,7 +160,7 @@ def check_diffs(self): if not self.filter(resource): continue - self.pre_resource_action_hook(resource) + self.pre_resource_action_hook(_id, resource) try: self.connect_resources(_id, resource) @@ -160,12 +170,12 @@ def check_diffs(self): if _id in self.resource_config.destination_resources: diff = check_diff(self.resource_config, self.resource_config.destination_resources[_id], resource) if diff: - print("{} resource ID {} diff: \n {}".format(self.resource_type, _id, pformat(diff))) + print("{} resource source ID {} diff: \n {}".format(self.resource_type, _id, pformat(diff))) else: - print("Resource to be added {}: \n {}".format(self.resource_type, pformat(resource))) + print("Resource to be added {} source ID {}: \n {}".format(self.resource_type, _id, pformat(resource))) def apply_resource(self, _id: str, resource: Dict) -> None: - self.pre_resource_action_hook(resource) + self.pre_resource_action_hook(_id, resource) self.connect_resources(_id, resource) if _id in self.resource_config.destination_resources: @@ -175,13 +185,17 @@ def apply_resource(self, _id: str, resource: Dict) -> None: try: self.update_resource(_id, resource) except Exception as e: - self.config.logger.error(f"error while updating resource {self.resource_type}. Error: {str(e)}") + self.config.logger.error( + f"error while updating resource {self.resource_type}. source ID: {_id} - Error: {str(e)}" + ) else: prep_resource(self.resource_config, resource) try: self.create_resource(_id, resource) except Exception as e: - self.config.logger.error(f"error while creating resource {self.resource_type}. Error: {str(e)}") + self.config.logger.error( + f"error while creating resource {self.resource_type}. source ID: {_id} - Error: {str(e)}" + ) def connect_resources(self, _id: str, resource: Dict) -> None: if not self.resource_config.resource_connections: