From 3237699e80a77e0d83861df2ebb8543b59399156 Mon Sep 17 00:00:00 2001 From: Blaise von Ohlen Date: Mon, 5 Jan 2026 12:58:31 -0500 Subject: [PATCH 1/2] fix sync cli for metric SLOs --- datadog_sync/model/service_level_objectives.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/datadog_sync/model/service_level_objectives.py b/datadog_sync/model/service_level_objectives.py index 0664ea44..0f26dc5d 100644 --- a/datadog_sync/model/service_level_objectives.py +++ b/datadog_sync/model/service_level_objectives.py @@ -41,12 +41,18 @@ async def pre_apply_hook(self) -> None: async def create_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client + # The GET endpoint returns both sli_specification and query for metric SLOs, + # but CREATE only accepts one or the other. Prefer sli_specification (newer format). + self._remove_conflicting_slo_fields(resource) resp = await destination_client.post(self.resource_config.base_path, resource) return _id, resp["data"][0] async def update_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client + # The GET endpoint returns both sli_specification and query for metric SLOs, + # but UPDATE only accepts one or the other. Prefer sli_specification (newer format). + self._remove_conflicting_slo_fields(resource) resp = await destination_client.put( self.resource_config.base_path + f"/{self.config.state.destination[self.resource_type][_id]['id']}", resource, @@ -54,6 +60,17 @@ async def update_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: return _id, resp["data"][0] + @staticmethod + def _remove_conflicting_slo_fields(resource: Dict) -> None: + """Remove conflicting SLO fields that cannot be sent together to the API. + + For metric SLOs, the GET endpoint returns both 'sli_specification' and 'query', + but the CREATE/UPDATE endpoints only accept one or the other. + We prefer 'sli_specification' as it is the newer format. + """ + if resource.get("sli_specification") and resource.get("query"): + resource.pop("query", None) + async def delete_resource(self, _id: str) -> None: destination_client = self.config.destination_client await destination_client.delete( From 0223778204889aab8f0034debe9d1fef7517466e Mon Sep 17 00:00:00 2001 From: Blaise von Ohlen Date: Tue, 6 Jan 2026 12:52:42 -0500 Subject: [PATCH 2/2] Exclude query attribute --- .../model/service_level_objectives.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/datadog_sync/model/service_level_objectives.py b/datadog_sync/model/service_level_objectives.py index 0f26dc5d..ef44c635 100644 --- a/datadog_sync/model/service_level_objectives.py +++ b/datadog_sync/model/service_level_objectives.py @@ -16,7 +16,7 @@ class ServiceLevelObjectives(BaseResource): resource_config = ResourceConfig( resource_connections={"monitors": ["monitor_ids"], "synthetics_tests": []}, base_path="/api/v1/slo", - excluded_attributes=["creator", "id", "created_at", "modified_at"], + excluded_attributes=["creator", "id", "created_at", "modified_at", "query"], tagging_config=TaggingConfig(path="tags"), ) # Additional ServiceLevelObjectives specific attributes @@ -41,18 +41,12 @@ async def pre_apply_hook(self) -> None: async def create_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client - # The GET endpoint returns both sli_specification and query for metric SLOs, - # but CREATE only accepts one or the other. Prefer sli_specification (newer format). - self._remove_conflicting_slo_fields(resource) resp = await destination_client.post(self.resource_config.base_path, resource) return _id, resp["data"][0] async def update_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client - # The GET endpoint returns both sli_specification and query for metric SLOs, - # but UPDATE only accepts one or the other. Prefer sli_specification (newer format). - self._remove_conflicting_slo_fields(resource) resp = await destination_client.put( self.resource_config.base_path + f"/{self.config.state.destination[self.resource_type][_id]['id']}", resource, @@ -60,17 +54,6 @@ async def update_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: return _id, resp["data"][0] - @staticmethod - def _remove_conflicting_slo_fields(resource: Dict) -> None: - """Remove conflicting SLO fields that cannot be sent together to the API. - - For metric SLOs, the GET endpoint returns both 'sli_specification' and 'query', - but the CREATE/UPDATE endpoints only accept one or the other. - We prefer 'sli_specification' as it is the newer format. - """ - if resource.get("sli_specification") and resource.get("query"): - resource.pop("query", None) - async def delete_resource(self, _id: str) -> None: destination_client = self.config.destination_client await destination_client.delete(