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: 10 additions & 0 deletions datadog_sync/utils/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,20 +154,30 @@ def build_config(cmd: Command, **kwargs: Optional[Any]) -> Configuration:
# JWT takes precedence over API keys
if jwt := kwargs.get("source_jwt"):
source_auth["jwtAuth"] = jwt
logger.info(f"Source authentication: Using JWT (length: {len(jwt)})")
elif k := kwargs.get("source_api_key"):
source_auth["apiKeyAuth"] = k
if k := kwargs.get("source_app_key"):
source_auth["appKeyAuth"] = k
logger.info("Source authentication: Using API Key + App Key")
else:
logger.warning("Source authentication: No credentials provided")

source_client = CustomClient(source_api_url, source_auth, retry_timeout, timeout, send_metrics, verify_ssl)

destination_auth = {}
# JWT takes precedence over API keys
if jwt := kwargs.get("destination_jwt"):
destination_auth["jwtAuth"] = jwt
logger.info(f"Destination authentication: Using JWT (length: {len(jwt)})")
elif k := kwargs.get("destination_api_key"):
destination_auth["apiKeyAuth"] = k
if k := kwargs.get("destination_app_key"):
destination_auth["appKeyAuth"] = k
logger.info("Destination authentication: Using API Key + App Key")
else:
logger.warning("Destination authentication: No credentials provided")

destination_client = CustomClient(
destination_api_url,
destination_auth,
Expand Down
46 changes: 45 additions & 1 deletion datadog_sync/utils/custom_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ def __init__(
self.send_metrics = send_metrics
self.verify_ssl = verify_ssl

# Metrics only work with API keys, not JWT
# If JWT is present, metrics are not available
self.metrics_available = bool(
self.send_metrics
and not auth.get("jwtAuth") # JWT means no metrics
and auth.get("apiKeyAuth")
and auth.get("appKeyAuth")
)

async def _init_session(self):
if self.verify_ssl:
ssl_context = ssl.create_default_context(cafile=certifi.where())
Expand All @@ -101,7 +110,24 @@ async def _init_session(self):
"This is insecure and should only be used in trusted environments."
)
self.session = aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False))
self.session.headers.update(build_default_headers(self.auth))

headers = build_default_headers(self.auth)
self.session.headers.update(headers)

# Log authentication configuration
auth_method = "JWT" if "dd-auth-jwt" in headers else "API Keys"
log.info(f"Initialized HTTP session with {auth_method} authentication for {self.url_object._default}")
log.info(f"Session headers configured: {', '.join(headers.keys())}")

# Log metrics availability
if self.send_metrics:
if self.metrics_available:
log.info("Metrics enabled: Using API keys for /api/v2/series endpoint")
else:
log.warning(
"Metrics disabled: /api/v2/series endpoint requires DD-API-KEY and DD-APPLICATION-KEY headers. "
"Provide --source-api-key/--source-app-key to enable metrics."
)

async def _end_session(self):
try:
Expand Down Expand Up @@ -248,6 +274,15 @@ async def wrapper(*args, **kwargs):
async def send_metric(self, metric: str, tags: List[str] = None) -> None:
if not self.send_metrics:
return None

# Skip if using JWT (metrics endpoint doesn't support JWT)
if not self.metrics_available:
log.debug(
f"Skipping metric '{metric}': /api/v2/series endpoint requires API key authentication. "
"Currently using JWT authentication which is not supported by this endpoint."
)
return None

path = "/api/v2/series"
timestamp = int(datetime.now().timestamp())
full_metric = f"{Metrics.PREFIX.value}.{metric}"
Expand All @@ -266,6 +301,7 @@ async def send_metric(self, metric: str, tags: List[str] = None) -> None:
}
]
}
# Send metric using API key headers from session
await self.post(path, body)

async def get_ddr_status(self) -> Dict:
Expand Down Expand Up @@ -298,9 +334,17 @@ def build_default_headers(auth_obj: Dict[str, str]) -> Dict[str, str]:
# JWT takes precedence over API keys
if jwt := auth_obj.get("jwtAuth"):
headers["dd-auth-jwt"] = jwt
log.info(f"JWT authentication configured - JWT present: {bool(jwt)}, JWT length: {len(jwt) if jwt else 0}")
# Log first and last 3 chars for debugging without exposing the full token
if jwt and len(jwt) > 30:
log.info(f"JWT preview: {jwt[:3]}...{jwt[-3:]}")
log.info(f"Headers being set: {list(headers.keys())}")
else:
headers["DD-API-KEY"] = auth_obj.get("apiKeyAuth", "")
headers["DD-APPLICATION-KEY"] = auth_obj.get("appKeyAuth", "")
api_key_present = bool(auth_obj.get("apiKeyAuth"))
app_key_present = bool(auth_obj.get("appKeyAuth"))
log.info(f"API Key auth configured - API Key present: {api_key_present}, App Key present: {app_key_present}")

return headers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# Copyright 2019 Datadog, Inc.

import pytest
from time import sleep

from datadog_sync.models import MetricTagConfigurations
from tests.integration.helpers import BaseResourcesTestClass
Expand All @@ -22,6 +21,10 @@ def test_resource_update_sync(self):
def test_resource_update_sync_per_file(self):
pass

@pytest.mark.skip(reason="This test is flakey")
def test_resource_sync(self, runner, caplog):
pass

@pytest.mark.skip(reason="This test is flakey")
def test_resource_sync_per_file(self, runner, caplog):
sleep(10)
super(TestMetricConfigurationResources, self).test_resource_sync_per_file(runner, caplog)
pass