diff --git a/datadog_sync/commands/shared/options.py b/datadog_sync/commands/shared/options.py index 953a2642..05599f82 100644 --- a/datadog_sync/commands/shared/options.py +++ b/datadog_sync/commands/shared/options.py @@ -280,6 +280,13 @@ def click_config_file_provider(ctx: Context, opts: CustomOptionClass, value: Non "Disables progress bar.", cls=CustomOptionClass, ), + option( + "--datadog-host-override", + envvar=constants.DD_DATADOG_HOST_OVERRIDE, + required=False, + help="Optional CNAME override for the Datadog host used in DDR private location replication.", + cls=CustomOptionClass, + ), ] _storage_options = [ diff --git a/datadog_sync/constants.py b/datadog_sync/constants.py index 2ce141ec..e3913a4d 100644 --- a/datadog_sync/constants.py +++ b/datadog_sync/constants.py @@ -28,6 +28,7 @@ DD_VERIFY_SSL_CERTIFICATES = "DD_VERIFY_SSL_CERTIFICATES" DD_ALLOW_PARTIAL_PERMISSIONS_ROLES = "DD_ALLOW_PARTIAL_PERMISSIONS_ROLES" DD_SYNC_JSON = "DD_SYNC_JSON" +DD_DATADOG_HOST_OVERRIDE = "DD_DATADOG_HOST_OVERRIDE" LOCAL_STORAGE_TYPE = "local" S3_STORAGE_TYPE = "s3" diff --git a/datadog_sync/model/synthetics_private_locations.py b/datadog_sync/model/synthetics_private_locations.py index 6001d1bc..599f4b44 100644 --- a/datadog_sync/model/synthetics_private_locations.py +++ b/datadog_sync/model/synthetics_private_locations.py @@ -4,6 +4,7 @@ # Copyright 2019 Datadog, Inc. from __future__ import annotations +import json import re from typing import TYPE_CHECKING, List, Dict, Optional, Tuple @@ -14,7 +15,6 @@ if TYPE_CHECKING: from datadog_sync.utils.custom_client import CustomClient - class SyntheticsPrivateLocations(BaseResource): resource_type = "synthetics_private_locations" resource_config = ResourceConfig( @@ -27,7 +27,9 @@ class SyntheticsPrivateLocations(BaseResource): "createdBy", "secrets", "config", - "result_encryption", + "ddr_metadata", + "pl_id", + "public_key_test", ], tagging_config=TaggingConfig(path="tags"), skip_resource_mapping=True, @@ -48,7 +50,10 @@ async def import_resource(self, _id: Optional[str] = None, resource: Optional[Di if not self.pl_id_regex.match(import_id): raise SkipResource(import_id, self.resource_type, "Managed location.") - pl = await source_client.get(self.resource_config.base_path + f"/{import_id}") + pl = await source_client.get( + self.resource_config.base_path + f"/{import_id}", + params={"include_pl_info": "true"}, + ) self.config.state.set_source(self.resource_type, import_id, pl) return import_id, pl @@ -61,14 +66,23 @@ 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 + source_pl = self.config.state.source[self.resource_type][_id] - resp = await destination_client.post(self.resource_config.base_path, resource) + if resource.get("metadata") is None: + resource.pop("metadata", None) - pl = resp["private_location"] - pl["config"] = resp.get("config") - pl["result_encryption"] = resp.get("result_encryption") + resource["ddr_metadata"] = { + "disaster_recovery": { + "source_pl_id": source_pl["pl_id"], + "source_name": _id, + } + } + resource["test_encryption_public_key"] = json.dumps(source_pl["public_key_test"]) + if self.config.datadog_host_override: + resource["datadog_host_override"] = self.config.datadog_host_override - return _id, pl + resp = await destination_client.post(self.resource_config.base_path, resource) + return _id, resp["private_location"] async def update_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client diff --git a/datadog_sync/utils/configuration.py b/datadog_sync/utils/configuration.py index 05947d6e..472f66bf 100644 --- a/datadog_sync/utils/configuration.py +++ b/datadog_sync/utils/configuration.py @@ -75,6 +75,7 @@ class Configuration(object): backup_before_reset: bool show_progress_bar: bool allow_self_lockout: bool + datadog_host_override: Optional[str] = None emit_json: bool = False command: str = "" allow_partial_permissions_roles: List[str] = field(default_factory=list) @@ -323,6 +324,7 @@ def build_config(cmd: Command, **kwargs: Optional[Any]) -> Configuration: if emit_json: show_progress_bar = False allow_self_lockout = kwargs.get("allow_self_lockout", False) + datadog_host_override = kwargs.get("datadog_host_override") # Parse allow_partial_permissions_roles allow_partial_permissions_roles = [] @@ -564,6 +566,7 @@ def build_config(cmd: Command, **kwargs: Optional[Any]) -> Configuration: backup_before_reset=backup_before_reset, show_progress_bar=show_progress_bar, allow_self_lockout=allow_self_lockout, + datadog_host_override=datadog_host_override, emit_json=emit_json, command=cmd.value, allow_partial_permissions_roles=allow_partial_permissions_roles, diff --git a/datadog_sync/utils/resources_handler.py b/datadog_sync/utils/resources_handler.py index 3c2044b8..e9bab037 100644 --- a/datadog_sync/utils/resources_handler.py +++ b/datadog_sync/utils/resources_handler.py @@ -149,6 +149,9 @@ async def init_async(self) -> None: self.worker: Workers = Workers(self.config) async def reset(self) -> None: + # Save existing destination state — only contains resources managed by sync-cli + managed_destination = self.config.state._data.destination + if self.config.backup_before_reset: await self.import_resources() else: @@ -157,8 +160,9 @@ async def reset(self) -> None: sleep(5) await self.import_resources_without_saving() - # move the import data from source to destination - self.config.state._data.destination = self.config.state._data.source + # Restore the original destination state so only managed resources are deleted, + # not all resources fetched from the destination API during backup. + self.config.state._data.destination = managed_destination for resource_type in self.config.resources_arg: resources = {}