Skip to content
Draft
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
7 changes: 7 additions & 0 deletions datadog_sync/commands/shared/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,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 = [
Expand Down
1 change: 1 addition & 0 deletions datadog_sync/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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"
Expand Down
65 changes: 59 additions & 6 deletions datadog_sync/model/synthetics_private_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# Copyright 2019 Datadog, Inc.

from __future__ import annotations
import json
import os
import re

from typing import TYPE_CHECKING, List, Dict, Optional, Tuple
Expand All @@ -14,7 +16,6 @@
if TYPE_CHECKING:
from datadog_sync.utils.custom_client import CustomClient


class SyntheticsPrivateLocations(BaseResource):
resource_type = "synthetics_private_locations"
resource_config = ResourceConfig(
Expand All @@ -28,6 +29,7 @@ class SyntheticsPrivateLocations(BaseResource):
"secrets",
"config",
"result_encryption",
"ddr_metadata",
],
tagging_config=TaggingConfig(path="tags"),
)
Expand All @@ -47,10 +49,13 @@ 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}")
self.config.state.source[self.resource_type][import_id] = pl
resp = await source_client.get(
self.resource_config.base_path + f"/{import_id}",
)

self.config.state.source[self.resource_type][import_id] = resp

return import_id, pl
return import_id, resp

async def pre_resource_action_hook(self, _id, resource: Dict) -> None:
pass
Expand All @@ -60,12 +65,49 @@ 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_client = self.config.source_client

# Fetch pl_info from source API for DDR metadata
pl_info = await source_client.get(
self.resource_config.base_path + f"/{_id}",
params={"include_pl_info": "true"},
)

# Strip null metadata — DDR endpoint requires it to be an object
if resource.get("metadata") is None:
resource.pop("metadata", None)

resource["ddr_metadata"] = {
"disaster_recovery": {
"source_pl_id": pl_info["pl_id"],
"source_name": _id,
"source_dc": pl_info["datacenter"],
"source_org_id": pl_info["org_id"],
}
}
# test_encryption_public_key expects the JSON-stringified public_key_test object
resource["test_encryption_public_key"] = json.dumps(pl_info["public_key_test"])
# result_encryption_public_key expects {"pem": ..., "fingerprint": ...}
pub_key_result = pl_info["public_key_result"]
resource["result_encryption_public_key"] = {
"pem": pub_key_result["key"],
"fingerprint": pub_key_result["id"],
}
if self.config.datadog_host_override:
resource["datadog_host_override"] = self.config.datadog_host_override

resp = await destination_client.post(self.resource_config.base_path, resource)

# DDR response: {"private_location": {...}, "publicKeysByMainDC": {...}}
pl = resp["private_location"]
pl["config"] = resp.get("config")
pl["result_encryption"] = resp.get("result_encryption")

# Save PL config to file for later use running the PL
pl_config = {
"publicKeysByMainDC": resp.get("publicKeysByMainDC"),
}
if self.config.datadog_host_override:
pl_config["datadogHostOverride"] = self.config.datadog_host_override
self._save_pl_config(pl.get("name", _id), pl_config)

return _id, pl

Expand All @@ -88,3 +130,14 @@ async def delete_resource(self, _id: str) -> None:

def connect_id(self, key: str, r_obj: Dict, resource_to_connect: str) -> Optional[List[str]]:
return super(SyntheticsPrivateLocations, self).connect_id(key, r_obj, resource_to_connect)

def _save_pl_config(self, pl_name: str, config: Dict) -> None:
destination_path = self.config.state._storage.destination_resources_path
config_dir = os.path.join(destination_path, "synthetics_private_locations_config")
os.makedirs(config_dir, exist_ok=True)

sanitized_name = re.sub(r"[^\w\-]", "_", pl_name)
config_file = os.path.join(config_dir, f"{sanitized_name}.json")

with open(config_file, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
3 changes: 3 additions & 0 deletions datadog_sync/utils/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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)
Expand Down Expand Up @@ -208,6 +209,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 = []
Expand Down Expand Up @@ -334,6 +336,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,
Expand Down
8 changes: 6 additions & 2 deletions datadog_sync/utils/resources_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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:
Expand All @@ -89,8 +92,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 = {}
Expand Down
Loading