diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index 42bafb3ff5e..d91b4c0a74b 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -49,7 +49,8 @@ jobs: env: CIBW_SKIP: ${{ inputs.cibw_skip }} CIBW_PRERELEASE_PYTHONS: ${{ inputs.cibw_prerelease_pythons }} - CIBW_BEFORE_ALL_WINDOWS: "rustup target add aarch64-pc-windows-msvc" + # rust-ruf has too long paths + CIBW_BEFORE_ALL_WINDOWS: "git config --global core.longpaths true && rustup target add aarch64-pc-windows-msvc" # cibuildwheel repair will copy anything's under /output directory from the # build container to the host machine. This is a bit hacky way, but seems # to be the only way getting debug symbols out from the container while diff --git a/.gitlab/scripts/windows-docker-build.ps1 b/.gitlab/scripts/windows-docker-build.ps1 index 01efe7a416b..e93f7674789 100644 --- a/.gitlab/scripts/windows-docker-build.ps1 +++ b/.gitlab/scripts/windows-docker-build.ps1 @@ -34,6 +34,10 @@ foreach ($line in $cmdOut) { $env:DISTUTILS_USE_SDK = '1' $env:MSSdk = '1' +# rust-ruf has too long paths +Write-Host "=== Enabling git long paths ===" +git config --global core.longpaths true + Write-Host "=== Building wheel ===" & uv build --wheel --out-dir C:\workspace\dist C:\workspace if ($LASTEXITCODE -ne 0) { diff --git a/ddtrace/debugging/_uploader.py b/ddtrace/debugging/_uploader.py index fa5e16e3be9..61d272d1aac 100644 --- a/ddtrace/debugging/_uploader.py +++ b/ddtrace/debugging/_uploader.py @@ -33,7 +33,7 @@ def build_debugger_sender() -> DebuggerSender: """Build a sender for the logs, snapshots and diagnostics tracks.""" timeout_ms = int(di_config.upload_timeout * 1000) - if di_config._agentless: + if ddconfig._agentless_enabled: return DebuggerSender( get_native_runtime(), site=ddconfig._dd_site, diff --git a/ddtrace/internal/native/_native.pyi b/ddtrace/internal/native/_native.pyi index 70dd0f0ed18..c375f14c6cb 100644 --- a/ddtrace/internal/native/_native.pyi +++ b/ddtrace/internal/native/_native.pyi @@ -1363,6 +1363,9 @@ class RemoteConfigClient: """Native single-target remote config client (origin process). Children consume published configs via :class:`RemoteConfigReader` instead. + + Passing ``api_key`` (together with ``site`` and ``hostname``) selects agentless + mode: configs are fetched from ``config.`` rather than from the agent. """ def __new__( @@ -1381,6 +1384,9 @@ class RemoteConfigClient: process_tags: Optional[list[tuple[str, str]]] = None, timeout_ms: int = 5000, test_session_token: Optional[str] = None, + site: Optional[str] = None, + api_key: Optional[str] = None, + hostname: Optional[str] = None, ) -> "RemoteConfigClient": ... def add_capabilities(self, capabilities: list[RemoteConfigCapabilities]) -> None: """Add capabilities the client advertises to the agent.""" @@ -1401,6 +1407,13 @@ class RemoteConfigClient: def get_client_id(self) -> str: """The remote config client id (a UUID); stable for the process lifetime.""" ... + def get_refresh_interval(self) -> float: + """Seconds to wait before the next poll. + + Agentless mode follows the interval the backend recommends, refreshed on + every successful fetch; against the agent this is a fixed default. + """ + ... def enable_shared_memory(self) -> None: """Enable cross-process broadcast. Call on the origin before forking.""" ... diff --git a/ddtrace/internal/remoteconfig/client.py b/ddtrace/internal/remoteconfig/client.py index 93604bb71f9..c11408bfab0 100644 --- a/ddtrace/internal/remoteconfig/client.py +++ b/ddtrace/internal/remoteconfig/client.py @@ -85,6 +85,7 @@ class RemoteConfigClient: def __init__(self) -> None: self.id = str(uuid.uuid4()) + self.agentless = ddtrace.config._agentless_enabled self.agent_url = agent_config.trace_agent_url # Product callbacks for single subscriber architecture @@ -104,6 +105,17 @@ def ensure_native(self) -> Any: from ddtrace.internal.native_runtime import get_native_runtime tracer_version = _pep440_to_semver() + # In agentless mode the API key selects the direct-to-backend fetcher; + # site and hostname identify the intake and this client to it. + agentless_kwargs = ( + { + "site": ddtrace.config._dd_site, + "api_key": ddtrace.config._dd_api_key, + "hostname": get_hostname(), + } + if self.agentless + else {} + ) self._native = _NativeClient( get_native_runtime(), agent_url=str(self.agent_url), @@ -117,6 +129,7 @@ def ensure_native(self) -> Any: process_tags=_build_process_tags(), timeout_ms=int(agent_config.trace_agent_timeout_seconds * 1000), test_session_token=get_test_session_token(), + **agentless_kwargs, ) if self._capability_values: self._native.add_capabilities(self._capability_values) @@ -125,6 +138,16 @@ def ensure_native(self) -> Any: def renew_id(self) -> None: self.id = str(uuid.uuid4()) + def refresh_interval(self) -> Optional[float]: + """Seconds the backend wants us to wait before polling again. + + Only agentless fetches carry a server-recommended interval; None means + "keep whatever interval the poller was configured with". + """ + if self._native is None or not self.agentless: + return None + return self._native.get_refresh_interval() + def register_callback(self, product_name: "RemoteConfigProduct", callback: RCCallback) -> None: self._product_callbacks[product_name] = callback log.debug("[%s][P: %s] Registered callback for product %s", os.getpid(), os.getppid(), product_name) diff --git a/ddtrace/internal/remoteconfig/worker.py b/ddtrace/internal/remoteconfig/worker.py index 85164f66a8c..81d7df70d0f 100644 --- a/ddtrace/internal/remoteconfig/worker.py +++ b/ddtrace/internal/remoteconfig/worker.py @@ -40,7 +40,9 @@ def __init__(self) -> None: interval=ddconfig._remote_config_poll_interval, no_wait_at_start=True, autorestart=False ) self._client = RemoteConfigClient() - self._state = self._agent_check + # Agentless fetches go straight to the Remote Config backend, so there is + # no agent to negotiate the v0.7/config endpoint with. + self._state = self._online if self._client.agentless else self._agent_check self._parent_id = os.getpid() self._capabilities_map: "dict[RemoteConfigCapabilities, RemoteConfigProduct]" = dict() self._consecutive_failures = 0 @@ -74,21 +76,31 @@ def _agent_check(self) -> None: def _online(self) -> None: with StopWatch() as sw: - if not self._client.request(): - self._consecutive_failures += 1 - if self._consecutive_failures >= self._MAX_CONSECUTIVE_FAILURES: - self._state = self._agent_check - self._consecutive_failures = 0 - return + succeeded = self._client.request() + + # The interval is the backend's to decide in agentless mode: it recommends + # a cadence on every successful fetch and a backoff after a failed one. + interval = self._client.refresh_interval() + if interval is not None and interval != self.interval: + log.debug("Remote Config poll interval set to %.3fs by the backend", interval) + self.interval = interval + + if not succeeded: + self._consecutive_failures += 1 + # Without an agent there is nothing to fall back to, so keep retrying + # on the backoff the native client asked for. + if self._consecutive_failures >= self._MAX_CONSECUTIVE_FAILURES and not self._client.agentless: + self._state = self._agent_check + self._consecutive_failures = 0 + return self._consecutive_failures = 0 - elapsed = sw.elapsed() log.debug( "[%d][P: %d] Datadog Remote Config Poller sent request to %s in %.5fs", os.getpid(), os.getppid(), - self._client.agent_url, - elapsed, + "the Remote Config intake" if self._client.agentless else self._client.agent_url, + sw.elapsed(), ) def periodic(self) -> None: diff --git a/ddtrace/internal/settings/_agentless.py b/ddtrace/internal/settings/_agentless.py new file mode 100644 index 00000000000..9c08c918622 --- /dev/null +++ b/ddtrace/internal/settings/_agentless.py @@ -0,0 +1,59 @@ +import typing as t + +from ddtrace.internal.settings._core import DDConfig + + +class AgentlessConfig(DDConfig): + # No __prefix__: _DD_APM_TRACING_AGENTLESS_ENABLED start with _. + + api_key = DDConfig.v(t.Optional[str], "dd.api_key", default=None) + site = DDConfig.v(str, "dd.site", default="datadoghq.com") + + #: The global switch. Every product setting below defaults to it. + enabled = DDConfig.v(bool, "dd.agentless.enabled", default=False) + + # Raw per-product overrides; None means "follow the global switch". Read the + # resolved values below instead of these. + _apm_tracing = DDConfig.v(t.Optional[bool], "_dd.apm.tracing.agentless.enabled", default=None) + _ci_visibility = DDConfig.v(t.Optional[bool], "dd.civisibility.agentless.enabled", default=None) + _llmobs = DDConfig.v(t.Optional[bool], "dd.llmobs.agentless.enabled", default=None) + + apm_tracing = DDConfig.d(bool, lambda c: c.enabled if c._apm_tracing is None else c._apm_tracing) + ci_visibility = DDConfig.d(bool, lambda c: c.enabled if c._ci_visibility is None else c._ci_visibility) + # LLM Observability keeps a third state: left unset (and with no global switch) it + # probes the agent at startup and decides then, so it must not collapse to False. + llmobs = DDConfig.d(t.Optional[bool], lambda c: True if c.enabled and c._llmobs is None else c._llmobs) + + #: Whether anything at all submits agentlessly. Products without a transport + #: setting of their own (instrumentation telemetry) follow this. + any_enabled = DDConfig.d(bool, lambda c: bool(c.enabled or c.apm_tracing or c.ci_visibility or c.llmobs)) + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + + if self.enabled and not self.api_key: + msg = ( + "DD_AGENTLESS_ENABLED is set but DD_API_KEY is not. Agentless mode submits data " + "straight to the Datadog intake, which is not possible without an API key. " + "Set DD_API_KEY, or unset DD_AGENTLESS_ENABLED to submit through the agent." + ) + raise ValueError(msg) + + def reported_configuration(self) -> "list[tuple[str, t.Any, str]]": + """The (environment variable, effective value, origin) triples to report as telemetry. + + Agentless config is resolved early, and we thus must explicitly report our config. + """ + return [ + (env_name, value, self.value_source(env_name)) + for env_name, value in ( + ("DD_AGENTLESS_ENABLED", self.enabled), + ("DD_SITE", self.site), + ("_DD_APM_TRACING_AGENTLESS_ENABLED", self.apm_tracing), + ("DD_CIVISIBILITY_AGENTLESS_ENABLED", self.ci_visibility), + ("DD_LLMOBS_AGENTLESS_ENABLED", self.llmobs), + ) + ] + + +config = AgentlessConfig() diff --git a/ddtrace/internal/settings/_config.py b/ddtrace/internal/settings/_config.py index 946dc45f62e..cfaa495c4e1 100644 --- a/ddtrace/internal/settings/_config.py +++ b/ddtrace/internal/settings/_config.py @@ -32,6 +32,7 @@ from ddtrace.internal.serverless import in_azure_function from ddtrace.internal.serverless import in_gcp_function from ddtrace.internal.settings import env +from ddtrace.internal.settings._agentless import AgentlessConfig from ddtrace.internal.telemetry import get_config as _get_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry import validate_and_report_otel_metrics_exporter_enabled @@ -468,6 +469,15 @@ def __init__(self) -> None: self._debug_mode = _get_config("DD_TRACE_DEBUG", False, asbool, "OTEL_LOG_LEVEL") self._startup_logs_enabled = _get_config("DD_TRACE_STARTUP_LOGS", False, asbool) + agentless = AgentlessConfig() + self._dd_api_key = agentless.api_key + self._dd_site = agentless.site + self._agentless_enabled = agentless.enabled + for _name, _value, _origin in agentless.reported_configuration(): + telemetry_writer.add_configuration(_name, _value, _origin) + + self._dd_app_key = _get_config("DD_APP_KEY", report_telemetry=False) + self._trace_rate_limit: int = _get_config("DD_TRACE_RATE_LIMIT", DEFAULT_SAMPLING_RATE_LIMIT, int) if self._trace_rate_limit != DEFAULT_SAMPLING_RATE_LIMIT and self._trace_sampling_rules in ("", "[]"): log.warning( @@ -678,7 +688,7 @@ def __init__(self) -> None: log.warning("Invalid obfuscation pattern, disabling query string tracing", exc_info=True) self._http_tag_query_string = False # Disable query string tagging if malformed obfuscation pattern - self._ci_visibility_agentless_enabled = _get_config("DD_CIVISIBILITY_AGENTLESS_ENABLED", False, asbool) + self._ci_visibility_agentless_enabled = agentless.ci_visibility self._ci_visibility_agentless_url = _get_config("DD_CIVISIBILITY_AGENTLESS_URL", "") self._ci_visibility_intelligent_testrunner_enabled = _get_config("DD_CIVISIBILITY_ITR_ENABLED", True, asbool) self._ci_visibility_log_level = _get_config("DD_CIVISIBILITY_LOG_LEVEL", "info") @@ -701,11 +711,7 @@ def __init__(self) -> None: self._trace_methods = _get_config("DD_TRACE_METHODS") - self._dd_api_key = _get_config("DD_API_KEY", report_telemetry=False) - self._dd_app_key = _get_config("DD_APP_KEY", report_telemetry=False) - self._dd_site = _get_config("DD_SITE", "datadoghq.com") - - self._llmobs_agentless_enabled = _get_config("DD_LLMOBS_AGENTLESS_ENABLED", None, asbool) + self._llmobs_agentless_enabled = agentless.llmobs self._llmobs_instrumented_proxy_urls = _get_config( "DD_LLMOBS_INSTRUMENTED_PROXY_URLS", None, lambda x: set(x.strip().split(",")) ) @@ -761,7 +767,7 @@ def __init__(self) -> None: "DD_TRACE_EXPERIMENTAL_LONG_RUNNING_INITIAL_FLUSH_INTERVAL", default=10.0, modifier=float ) # When True, traces are sent via the JSON span intake (agentless EvP), e.g. browser-intake-*. - self._trace_agentless_enabled = _get_config("_DD_APM_TRACING_AGENTLESS_ENABLED", False, asbool) + self._trace_agentless_enabled = agentless.apm_tracing if self._trace_agentless_enabled: log.debug( "APM Agentless enabled: health metrics and client-side stats are disabled. " diff --git a/ddtrace/internal/settings/_supported_configurations.py b/ddtrace/internal/settings/_supported_configurations.py index ca99d5be239..96dbe60d5d5 100644 --- a/ddtrace/internal/settings/_supported_configurations.py +++ b/ddtrace/internal/settings/_supported_configurations.py @@ -9,6 +9,7 @@ "DATADOG_TAGS", "DATADOG_TRACE_AGENT_HOSTNAME", "DD_ACTION_EXECUTION_ID", + "DD_AGENTLESS_ENABLED", "DD_AGENTLESS_LOG_SUBMISSION_ENABLED", "DD_AGENT_HOST", "DD_AGENT_PORT", diff --git a/ddtrace/internal/settings/_telemetry.py b/ddtrace/internal/settings/_telemetry.py index 24ed031510b..7bc662c80b2 100644 --- a/ddtrace/internal/settings/_telemetry.py +++ b/ddtrace/internal/settings/_telemetry.py @@ -8,12 +8,9 @@ class TelemetryConfig(DDConfig): __prefix__ = "dd" - API_KEY = DDConfig.v(t.Optional[str], "api_key", default=None) - SITE = DDConfig.v(str, "site", default="datadoghq.com") ENV = DDConfig.v(str, "env", default="") SERVICE = DDConfig.v(str, "service", default=detect_service(sys.argv) or "unnamed-python-service") VERSION = DDConfig.v(str, "version", default="") - AGENTLESS_MODE = DDConfig.v(bool, "civisibility.agentless.enabled", default=False) DEBUG = DDConfig.v(bool, "internal.telemetry.debug.enabled", default=False) HEARTBEAT_INTERVAL = DDConfig.v(float, "telemetry.heartbeat_interval", default=60.0) TELEMETRY_ENABLED = DDConfig.v(bool, "instrumentation_telemetry.enabled", default=True) diff --git a/ddtrace/internal/settings/dynamic_instrumentation.py b/ddtrace/internal/settings/dynamic_instrumentation.py index ce70ef4430c..3a910aee9e3 100644 --- a/ddtrace/internal/settings/dynamic_instrumentation.py +++ b/ddtrace/internal/settings/dynamic_instrumentation.py @@ -30,18 +30,6 @@ def _derive_tags(c: DDConfig) -> str: return ",".join([":".join((k, v)) for (k, v) in _tags.items() if v is not None]) -def _resolve_agentless(c: DDConfig) -> bool: - """Whether the APM trace writer should run in agentless mode. - - Falls back when agentless is requested but ``DD_API_KEY`` is unset. - """ - if not ddconfig._trace_agentless_enabled: - return False - if not ddconfig._dd_api_key: - return False - return True - - def normalize_ident(ident: str) -> str: return ident.strip().lower().replace("_", "") @@ -77,8 +65,6 @@ class DynamicInstrumentationConfig(DDConfig): help="Enable Dynamic Instrumentation", ) - _agentless = DDConfig.d(bool, _resolve_agentless) - metrics = DDConfig.v( bool, "metrics.enabled", diff --git a/ddtrace/internal/symbol_db/symbols.py b/ddtrace/internal/symbol_db/symbols.py index b8b9daa35e6..9899cb71a41 100644 --- a/ddtrace/internal/symbol_db/symbols.py +++ b/ddtrace/internal/symbol_db/symbols.py @@ -65,7 +65,7 @@ def build_symdb_sender() -> SymDBSender: """Build a sender for symbol uploads.""" timeout_ms = int(UPLOAD_TIMEOUT * 1000) - if di_config._agentless: + if config._agentless_enabled: return SymDBSender( get_native_runtime(), site=config._dd_site, diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 2a4ecced7b7..ae01d0f7690 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -14,6 +14,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.packages import is_user_code from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._agentless import config as agentless_config from ddtrace.internal.settings._telemetry import config from ...internal import atexit @@ -165,9 +166,11 @@ def __init__(self, agentless: Optional[bool] = None) -> None: self._enabled = config.TELEMETRY_ENABLED if agentless is None: - agentless = config.AGENTLESS_MODE or config.API_KEY not in (None, "") + # An API key on its own says nothing about how data should be submitted; + # only an explicit agentless setting does. + agentless = agentless_config.any_enabled - if agentless and not config.API_KEY: + if agentless and not agentless_config.api_key: log.debug("Disabling telemetry: no Datadog API key found in agentless mode") self._enabled = False @@ -239,8 +242,8 @@ def _build_worker(self) -> "TelemetryWorker": endpoint_url = "file://" + os.path.join(self._payload_file_dir, "") api_key = None elif self._agentless: - endpoint_url = _agentless_endpoint_url(config.SITE) - api_key = config.API_KEY + endpoint_url = _agentless_endpoint_url(agentless_config.site) + api_key = agentless_config.api_key else: endpoint_url = agent_config.trace_agent_url api_key = None @@ -412,7 +415,7 @@ def enable_agentless_client(self, enabled: bool = True) -> None: self._agentless = enabled - if enabled and not config.API_KEY: + if enabled and not agentless_config.api_key: log.debug("Cannot switch telemetry to agentless mode: no Datadog API key found") return diff --git a/docs/configuration.rst b/docs/configuration.rst index d3c4afa1b55..69b5e2e5953 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1005,6 +1005,28 @@ Agent v0.17.0: v1.7.0: + DD_AGENTLESS_ENABLED: + type: Boolean + default: False + + description: | + Submit data directly to the Datadog intake instead of through a Datadog Agent. This + covers instrumentation telemetry, traces, Remote Configuration, Dynamic Instrumentation + and LLM Observability. + + ``DD_API_KEY`` must be set; enabling agentless submission without one raises an error at + startup. ``DD_SITE`` selects the intake to submit to. + + The per-product settings ``_DD_APM_TRACING_AGENTLESS_ENABLED``, + ``DD_CIVISIBILITY_AGENTLESS_ENABLED`` and ``DD_LLMOBS_AGENTLESS_ENABLED`` default to this + value and can each be set explicitly to override it for that product. + + Client-side statistics computation and health metrics are disabled in agentless mode, + since both rely on the Agent. + + version_added: + v4.13.0: + DD_DOGSTATSD_URL: type: URL diff --git a/releasenotes/notes/agentless-enabled-global-setting-3f1a9c2e7b4d8065.yaml b/releasenotes/notes/agentless-enabled-global-setting-3f1a9c2e7b4d8065.yaml new file mode 100644 index 00000000000..2aa9f0d6550 --- /dev/null +++ b/releasenotes/notes/agentless-enabled-global-setting-3f1a9c2e7b4d8065.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + tracing: Adds ``DD_AGENTLESS_ENABLED`` (default ``false``), a single switch that submits all + telemetry, traces, Remote Configuration, Dynamic Instrumentation and LLM Observability data + directly to the Datadog intake instead of through a local Datadog Agent. It becomes the + default for the per-product agentless settings, which can still be set individually to + override it. ``DD_API_KEY`` is required when it is enabled. diff --git a/src/native/Cargo.lock b/src/native/Cargo.lock index 880c8b7b20d..4f73a12c6de 100644 --- a/src/native/Cargo.lock +++ b/src/native/Cargo.lock @@ -197,7 +197,7 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "build_common" version = "40.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "cbindgen", "serde", @@ -287,8 +287,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] @@ -496,7 +498,7 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "datadog-ipc" version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "bincode", @@ -524,7 +526,7 @@ dependencies = [ [[package]] name = "datadog-ipc-macros" version = "0.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "heck", "proc-macro2", @@ -535,13 +537,13 @@ dependencies = [ [[package]] name = "datadog-live-debugger" version = "0.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "bytes", "constcat", "futures", - "http", + "http 1.4.0", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", @@ -651,6 +653,15 @@ dependencies = [ "syn", ] +[[package]] +name = "derp" +version = "0.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b84cfd9b6fa437e498215e5625e9e3ae3bf9bb54d623028a181c40820db169" +dependencies = [ + "untrusted 0.7.1", +] + [[package]] name = "digest" version = "0.10.7" @@ -792,6 +803,12 @@ dependencies = [ "serde", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -1071,6 +1088,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.18", +] + [[package]] name = "http" version = "1.4.0" @@ -1078,7 +1106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "itoa", + "itoa 1.0.18", ] [[package]] @@ -1088,7 +1116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1099,7 +1127,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "pin-project-lite", ] @@ -1120,10 +1148,10 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", + "http 1.4.0", "http-body", "httparse", - "itoa", + "itoa 1.0.18", "pin-project-lite", "smallvec", "tokio", @@ -1136,7 +1164,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http", + "http 1.4.0", "hyper", "hyper-util", "rustls", @@ -1155,7 +1183,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.0", "http-body", "hyper", "ipnet", @@ -1385,6 +1413,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.18" @@ -1478,7 +1512,7 @@ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libdd-alloc" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "allocator-api2", "libc", @@ -1488,24 +1522,24 @@ dependencies = [ [[package]] name = "libdd-capabilities" version = "2.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.0", "thiserror 1.0.69", ] [[package]] name = "libdd-capabilities-impl" version = "3.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "bytes", - "http", + "http 1.4.0", "http-body-util", "libdd-capabilities", "libdd-common", @@ -1515,7 +1549,7 @@ dependencies = [ [[package]] name = "libdd-common" version = "5.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "bytes", @@ -1525,7 +1559,7 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http", + "http 1.4.0", "http-body", "http-body-util", "httparse", @@ -1555,7 +1589,7 @@ dependencies = [ [[package]] name = "libdd-common-ffi" version = "40.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "build_common", @@ -1569,14 +1603,14 @@ dependencies = [ [[package]] name = "libdd-crashtracker" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "blazesym", "cc", "chrono", "errno", - "http", + "http 1.4.0", "libc", "libdd-capabilities", "libdd-capabilities-impl", @@ -1604,7 +1638,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" version = "7.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "arc-swap", @@ -1613,7 +1647,7 @@ dependencies = [ "either", "futures", "getrandom 0.2.17", - "http", + "http 1.4.0", "http-body-util", "libdd-capabilities", "libdd-capabilities-impl", @@ -1642,7 +1676,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline-ffi" version = "40.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "build_common", "libdd-capabilities-impl", @@ -1659,7 +1693,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" version = "1.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "prost", ] @@ -1667,12 +1701,12 @@ dependencies = [ [[package]] name = "libdd-dogstatsd-client" version = "4.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "async-trait", "cadence", - "http", + "http 1.4.0", "libdd-common", "libdd-shared-runtime", "serde", @@ -1683,7 +1717,7 @@ dependencies = [ [[package]] name = "libdd-ffe" version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "chrono", "derive_more", @@ -1705,7 +1739,7 @@ dependencies = [ [[package]] name = "libdd-http-client" version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "bytes", "fastrand", @@ -1718,7 +1752,7 @@ dependencies = [ [[package]] name = "libdd-library-config" version = "3.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "libc", @@ -1735,7 +1769,7 @@ dependencies = [ [[package]] name = "libdd-library-config-ffi" version = "0.0.2" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "build_common", @@ -1759,7 +1793,7 @@ dependencies = [ [[package]] name = "libdd-log" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "chrono", "tracing", @@ -1770,12 +1804,12 @@ dependencies = [ [[package]] name = "libdd-otel-thread-ctx" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" [[package]] name = "libdd-otel-thread-ctx-ffi" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "build_common", "libdd-common-ffi", @@ -1785,7 +1819,7 @@ dependencies = [ [[package]] name = "libdd-profiling" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "allocator-api2", "anyhow", @@ -1797,7 +1831,7 @@ dependencies = [ "crossbeam-utils", "futures", "hashbrown 0.16.1", - "http", + "http 1.4.0", "http-body-util", "httparse", "indexmap 2.14.0", @@ -1824,7 +1858,7 @@ dependencies = [ [[package]] name = "libdd-profiling-ffi" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "allocator-api2", "anyhow", @@ -1849,7 +1883,7 @@ dependencies = [ [[package]] name = "libdd-profiling-protobuf" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "prost", ] @@ -1857,20 +1891,24 @@ dependencies = [ [[package]] name = "libdd-remote-config" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "base64", "bytes", + "chrono", + "futures", "futures-util", "hashbrown 0.15.5", - "http", + "http 1.4.0", "http-body-util", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", "libdd-trace-protobuf", "manual_future", + "prost", + "rand 0.8.5", "serde", "serde_json", "serde_with", @@ -1882,13 +1920,14 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "tuf", "uuid", ] [[package]] name = "libdd-shared-runtime" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "async-trait", "futures", @@ -1905,7 +1944,7 @@ dependencies = [ [[package]] name = "libdd-shared-runtime-ffi" version = "40.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "build_common", "libdd-shared-runtime", @@ -1915,7 +1954,7 @@ dependencies = [ [[package]] name = "libdd-telemetry" version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "async-trait", @@ -1924,7 +1963,7 @@ dependencies = [ "futures", "getrandom 0.2.17", "hashbrown 0.15.5", - "http", + "http 1.4.0", "libc", "libdd-capabilities", "libdd-common", @@ -1946,7 +1985,7 @@ dependencies = [ [[package]] name = "libdd-tinybytes" version = "1.1.1" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "serde", ] @@ -1954,7 +1993,7 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" version = "3.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "libdd-trace-protobuf", @@ -1963,7 +2002,7 @@ dependencies = [ [[package]] name = "libdd-trace-obfuscation" version = "5.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "fluent-uri", @@ -1979,7 +2018,7 @@ dependencies = [ [[package]] name = "libdd-trace-protobuf" version = "4.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "prost", "serde", @@ -1989,14 +2028,14 @@ dependencies = [ [[package]] name = "libdd-trace-stats" version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "arc-swap", "async-trait", "futures", "hashbrown 0.15.5", - "http", + "http 1.4.0", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", @@ -2018,7 +2057,7 @@ dependencies = [ [[package]] name = "libdd-trace-utils" version = "9.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "base64", @@ -2026,11 +2065,11 @@ dependencies = [ "futures", "getrandom 0.2.17", "hex", - "http", + "http 1.4.0", "http-body", "http-body-util", "indexmap 2.14.0", - "itoa", + "itoa 1.0.18", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", @@ -2054,11 +2093,11 @@ dependencies = [ [[package]] name = "libdd-tracer-flare" version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v40.0.0#ea75b04c3547037937730a14cf8a72a5ebf702d7" +source = "git+https://github.com/DataDog/libdatadog?rev=4504bc0b22c2890193749292c5e0004e834e10f0#4504bc0b22c2890193749292c5e0004e834e10f0" dependencies = [ "anyhow", "bytes", - "http", + "http 1.4.0", "libdd-capabilities-impl", "libdd-common", "libdd-remote-config", @@ -2220,7 +2259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" dependencies = [ "bitflags", - "itoa", + "itoa 1.0.18", ] [[package]] @@ -2232,7 +2271,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.4.0", "httparse", "memchr", "mime", @@ -2857,7 +2896,7 @@ dependencies = [ "futures-core", "futures-util", "hickory-resolver", - "http", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -2900,7 +2939,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -3042,7 +3081,7 @@ checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3253,7 +3292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "indexmap 2.14.0", - "itoa", + "itoa 1.0.18", "memchr", "serde", "serde_core", @@ -3307,7 +3346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap 2.14.0", - "itoa", + "itoa 1.0.18", "ryu", "serde", "unsafe-libyaml", @@ -3463,7 +3502,7 @@ version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "933dd3bb26965d682280fcc49400ac2a05036f4ee1e6dbd61bf8402d5a5c3a54" dependencies = [ - "itoa", + "itoa 1.0.18", "ryu", "sval", ] @@ -3474,7 +3513,7 @@ version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0cda08f6d5c9948024a6551077557b1fdcc3880ff2f20ae839667d2ec2d87ed" dependencies = [ - "itoa", + "itoa 1.0.18", "ryu", "sval", ] @@ -3668,7 +3707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", - "itoa", + "itoa 1.0.18", "num-conv", "powerfmt", "serde_core", @@ -3830,7 +3869,7 @@ dependencies = [ "bitflags", "bytes", "futures-util", - "http", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -3913,6 +3952,30 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tuf" +version = "0.3.0-beta10" +source = "git+https://github.com/DataDog/rust-tuf/?rev=eb129ccad320b11e8bf99d2f0ff2c415a0795ccb#eb129ccad320b11e8bf99d2f0ff2c415a0795ccb" +dependencies = [ + "chrono", + "data-encoding", + "derp", + "futures-io", + "futures-util", + "http 0.2.12", + "itoa 0.4.8", + "log", + "percent-encoding", + "ring", + "serde", + "serde_derive", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "untrusted 0.7.1", + "url", +] + [[package]] name = "typeid" version = "1.0.3" @@ -3949,6 +4012,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/src/native/Cargo.toml b/src/native/Cargo.toml index 1144d246496..4a89abd6588 100644 --- a/src/native/Cargo.toml +++ b/src/native/Cargo.toml @@ -29,27 +29,29 @@ serde = "1.0" serde_json = "1.0" smallvec = "1" tokio = "1" -libdd-ffe = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", optional = true, features = ["pyo3"] } -datadog-ipc = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", features = ["one_way_shm_futex"] } -datadog-live-debugger = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-remote-config = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-telemetry = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-tracer-flare = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", optional = true } -libdd-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", optional = true } -libdd-library-config = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-log = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", features = [ +libdd-ffe = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", optional = true, features = ["pyo3"] } +datadog-ipc = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", features = ["one_way_shm_futex"] } +datadog-live-debugger = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-remote-config = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", features = [ + "agentless", +] } +libdd-telemetry = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-tracer-flare = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", optional = true } +libdd-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", optional = true } +libdd-library-config = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-log = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", features = [ "stats-obfuscation" ] } -libdd-http-client = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-shared-runtime = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-profiling-ffi = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", optional = true, features = [ +libdd-http-client = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-shared-runtime = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-profiling-ffi = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", optional = true, features = [ "cbindgen", ] } -libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } -libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } +libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } +libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } native-proc-macro = { path = "native_proc_macro" } strum = { version = "0.26", default-features = false } percent-encoding = "2.3" @@ -58,12 +60,12 @@ tracing = { version = "0.1", default-features = false } pyo3-ffi = { version = "0.28", optional = true } [target.'cfg(target_os = "linux")'.dependencies] -libdd-library-config = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", features = ["otel-thread-ctx"]} -libdd-otel-thread-ctx = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0" } +libdd-library-config = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", features = ["otel-thread-ctx"]} +libdd-otel-thread-ctx = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0" } [build-dependencies] pyo3-build-config = "0.28" -build_common = { git = "https://github.com/DataDog/libdatadog", rev = "v40.0.0", features = [ +build_common = { git = "https://github.com/DataDog/libdatadog", rev = "4504bc0b22c2890193749292c5e0004e834e10f0", features = [ "cbindgen", ] } diff --git a/src/native/rc_shm.rs b/src/native/rc_shm.rs index 64ba6acf379..fb08eca5220 100644 --- a/src/native/rc_shm.rs +++ b/src/native/rc_shm.rs @@ -513,7 +513,7 @@ impl ShmReader { Err(_) => continue, }; - if !enabled.contains(&parsed.product.to_string()) { + if !enabled.contains(&parsed.product().to_string()) { continue; } diff --git a/src/native/remote_config.rs b/src/native/remote_config.rs index e0d34d52f30..957c5ed85aa 100644 --- a/src/native/remote_config.rs +++ b/src/native/remote_config.rs @@ -11,6 +11,7 @@ //! inherit the ShmHandles, to obtain a [`RemoteConfigReader`] via `make_reader()`, //! and diffing successive manifests into add/update/remove changes. +use std::borrow::Cow; use std::collections::HashSet; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; @@ -18,7 +19,7 @@ use std::time::Duration; use libdd_capabilities_impl::{HttpClientCapability, NativeCapabilities}; use libdd_common::Endpoint; use libdd_remote_config::fetch::{ - ConfigApplyState, ConfigInvariants, ConfigOptions, SingleChangesFetcher, + AgentlessConfig, ConfigApplyState, ConfigInvariants, ConfigOptions, SingleChangesFetcher, }; use libdd_remote_config::file_change_tracker::{Change, FilePath}; use libdd_remote_config::{ @@ -80,9 +81,9 @@ impl ChangeRecord { pub fn new(path: &RemoteConfigPath, version: u64, content: Option>) -> Self { Self { path: path.to_string(), - product: RemoteConfigProduct(path.product), - config_id: path.config_id.clone(), - name: path.name.clone(), + product: RemoteConfigProduct(path.product()), + config_id: path.config_id().to_string(), + name: path.name().to_string(), version, content, } @@ -119,6 +120,10 @@ impl RemoteConfigClient { #[pymethods] impl RemoteConfigClient { /// Starts with empty capabilities and products, added from python side. + /// + /// Passing `api_key` (with `site` and `hostname`) selects agentless mode: + /// the client then talks to the Remote Config backend at + /// `config.` directly instead of going through the local agent. #[new] #[allow(clippy::too_many_arguments)] #[pyo3(signature = ( @@ -136,6 +141,9 @@ impl RemoteConfigClient { process_tags=None, timeout_ms=5000, test_session_token=None, + site=None, + api_key=None, + hostname=None, ))] fn new( runtime: PyRef<'_, SharedRuntimePy>, @@ -151,20 +159,43 @@ impl RemoteConfigClient { process_tags: Option>, timeout_ms: u64, test_session_token: Option, + site: Option, + api_key: Option, + hostname: Option, ) -> PyResult { let rt = runtime.as_arc().clone(); + let test_token = test_session_token.map(Cow::Owned); + let mut endpoint = Endpoint::from_slice(&agent_url); endpoint.timeout_ms = timeout_ms; - if let Some(token) = test_session_token { - endpoint.test_token = Some(token.into()); - } + endpoint.test_token = test_token.clone(); + + let agentless = match api_key { + Some(api_key) => { + let site = site.ok_or_else(|| { + PyValueError::new_err("agentless remote config requires a `site`") + })?; + let mut intake = Endpoint::agentless(&site, api_key).map_err(|e| { + PyValueError::new_err(format!("invalid remote config site '{site}': {e}")) + })?; + intake.timeout_ms = timeout_ms; + intake.test_token = test_token; + Some( + AgentlessConfig::new(hostname.unwrap_or_default(), &intake) + .map_err(|e| PyValueError::new_err(e.to_string()))?, + ) + } + None => None, + }; + let is_agentless = agentless.is_some(); let options = ConfigOptions { invariants: ConfigInvariants { language: language.unwrap_or_else(|| "python".to_string()), tracer_version, endpoint, + agentless, }, products: Vec::new(), capabilities: Vec::new(), @@ -180,13 +211,21 @@ impl RemoteConfigClient { // The fetcher owns the storage; it's reached later via // `fetcher.fetcher.file_storage()`. - let fetcher = SingleChangesFetcher::new( - ShmStorage::new(), - target, - runtime_id, - options, - NativeCapabilities::new_without_connection_pooling(), - ) + let storage = ShmStorage::new(); + let http = NativeCapabilities::new_without_connection_pooling(); + let fetcher = if is_agentless { + // Agentless setup parses the embedded TUF trust roots; it performs no + // I/O, so blocking the calling thread here is safe. + rt.block_on(SingleChangesFetcher::new( + storage, target, runtime_id, options, http, + )) + .map_err(|e| PyRuntimeError::new_err(format!("remote config runtime error: {e}")))? + } else { + SingleChangesFetcher::new_no_agentless(storage, target, runtime_id, options, http) + } + .map_err(|e| { + PyRuntimeError::new_err(format!("failed to create the remote config client: {e}")) + })? .with_client_id(client_id); Ok(RemoteConfigClient { @@ -308,7 +347,14 @@ impl RemoteConfigClient { /// The remote config client id (a UUID). Stable for the life of the process. fn get_client_id(&self) -> String { - self.lock().fetcher.get_client_id().clone() + self.lock().fetcher.get_client_id().to_string() + } + + /// Seconds to wait before the next [`poll`]. In agentless mode the backend + /// recommends an interval and updates it on every successful fetch; against + /// the agent this is a fixed default. + fn get_refresh_interval(&self) -> f64 { + self.lock().fetcher.get_refresh_interval().as_secs_f64() } /// Enable cross-process broadcast: move the storage into shared memory. Must diff --git a/supported-configurations.json b/supported-configurations.json index 9af7a007192..c68f035fb28 100644 --- a/supported-configurations.json +++ b/supported-configurations.json @@ -26,6 +26,13 @@ "default": null } ], + "DD_AGENTLESS_ENABLED": [ + { + "implementation": "A", + "type": "boolean", + "default": "false" + } + ], "DD_AGENTLESS_LOG_SUBMISSION_ENABLED": [ { "implementation": "A", diff --git a/tests/internal/remoteconfig/test_remoteconfig_native.py b/tests/internal/remoteconfig/test_remoteconfig_native.py index f1813266321..81dba1d5dc4 100644 --- a/tests/internal/remoteconfig/test_remoteconfig_native.py +++ b/tests/internal/remoteconfig/test_remoteconfig_native.py @@ -418,3 +418,57 @@ def test_enable_builds_native_runtime_before_registering_fork_hook(monkeypatch): assert poller.enable() is True assert order == ["native", "before_fork", "start"], order + + +def test_agentless_client_targets_the_backend_directly(monkeypatch): + # DD_AGENTLESS_ENABLED must hand the native client the credentials it needs to + # reach config. itself, and the poller must skip the agent handshake + # there is no agent to make. + from ddtrace.internal.remoteconfig import client as client_mod + from ddtrace.internal.remoteconfig import worker as worker_mod + from tests.utils import override_global_config + + captured = {} + + def _fake_native(_runtime, **kwargs): + captured.update(kwargs) + return object() + + monkeypatch.setattr(client_mod, "get_hostname", lambda: "a-host") + monkeypatch.setattr("ddtrace.internal.native.RemoteConfigClient", _fake_native) + + with override_global_config(dict(_agentless_enabled=True, _dd_api_key="an-api-key", _dd_site="datad0g.com")): + poller = worker_mod.RemoteConfigPoller() + assert poller._client.agentless is True + assert poller._state == poller._online + + poller._client.ensure_native() + + assert captured["site"] == "datad0g.com" + assert captured["api_key"] == "an-api-key" + assert captured["hostname"] == "a-host" + + +def test_agent_client_is_not_given_intake_credentials(monkeypatch): + from ddtrace.internal.remoteconfig import worker as worker_mod + from tests.utils import override_global_config + + captured = {} + + def _fake_native(_runtime, **kwargs): + captured.update(kwargs) + return object() + + monkeypatch.setattr("ddtrace.internal.native.RemoteConfigClient", _fake_native) + + with override_global_config(dict(_agentless_enabled=False, _dd_api_key="an-api-key")): + poller = worker_mod.RemoteConfigPoller() + assert poller._client.agentless is False + assert poller._state == poller._agent_check + + poller._client.ensure_native() + + assert "site" not in captured + assert "api_key" not in captured + # Only the agentless fetcher carries a server-recommended interval. + assert poller._client.refresh_interval() is None diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 7bbede8e4c6..82f07a8b596 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -45,12 +45,12 @@ class _SyntheticDDConfig(DDConfig): @pytest.fixture(autouse=True) def _no_inherited_api_key(monkeypatch): - """Keep subprocess telemetry writers in non-agentless mode. + """Keep the developer's real ``DD_API_KEY`` out of the subprocesses these tests spawn. - A ``DD_API_KEY`` present in the test environment is inherited by the subprocesses these tests - spawn (``os.environ.copy()``) and flips their telemetry writer into agentless mode, diverting - requests to the Datadog intake instead of the local test agent. Tests that genuinely need an - api key set it explicitly via the subprocess marker env / mock.patch.dict, which overrides + They inherit the environment wholesale (``os.environ.copy()``), so a key present locally + would otherwise be a live credential inside every test process — one stray agentless + setting away from shipping test telemetry to a real org. Tests that genuinely need an api + key set it explicitly via the subprocess marker env / mock.patch.dict, which overrides this removal. """ monkeypatch.delenv("DD_API_KEY", raising=False) @@ -629,17 +629,47 @@ def test_telemetry_writer_agentless_disabled_without_api_key(): @pytest.mark.subprocess(env={"DD_SITE": "datad0g.com", "DD_API_KEY": "foobarkey"}) -def test_telemetry_writer_is_using_agentless_by_default_if_api_key_is_available(): +def test_telemetry_writer_stays_on_the_agent_when_only_an_api_key_is_set(): from ddtrace import config from ddtrace.internal.telemetry import telemetry_writer - from ddtrace.internal.telemetry.writer import _agentless_endpoint_url - # When an api key is present (and agentless not explicitly disabled) the writer defaults - # to agentless mode. + # An api key says nothing about how data should be submitted: without an agentless + # setting, telemetry keeps going through the agent. assert telemetry_writer._enabled - assert telemetry_writer._agentless is True + assert telemetry_writer._agentless is False assert config._dd_api_key == "foobarkey" - assert _agentless_endpoint_url(config._dd_site) == "https://all-http-intake.logs.datad0g.com" + + +@pytest.mark.parametrize( + "agentless_env_var", + [ + "DD_AGENTLESS_ENABLED", + "DD_CIVISIBILITY_AGENTLESS_ENABLED", + "DD_LLMOBS_AGENTLESS_ENABLED", + "_DD_APM_TRACING_AGENTLESS_ENABLED", + ], +) +def test_any_agentless_setting_turns_telemetry_agentless(agentless_env_var): + """Telemetry has no transport setting of its own, so any agentless config turns it agentless.""" + from ddtrace.internal.settings._agentless import AgentlessConfig + from tests.utils import override_env + + with override_env({"DD_API_KEY": "foobarkey"}, replace_os_env=True): + assert AgentlessConfig().any_enabled is False + + with override_env({"DD_API_KEY": "foobarkey", agentless_env_var: "true"}, replace_os_env=True): + assert AgentlessConfig().any_enabled is True + + with override_env({"DD_API_KEY": "foobarkey", agentless_env_var: "false"}, replace_os_env=True): + assert AgentlessConfig().any_enabled is False + + +@pytest.mark.subprocess(env={"DD_API_KEY": "foobarkey", "DD_AGENTLESS_ENABLED": "true"}) +def test_telemetry_writer_agentless_setup_from_the_global_setting(): + from ddtrace.internal.telemetry import telemetry_writer + + assert telemetry_writer._enabled + assert telemetry_writer._agentless is True @pytest.mark.subprocess(env={"DD_API_KEY": "", "DD_CIVISIBILITY_AGENTLESS_ENABLED": "false"}) diff --git a/tests/tracer/test_global_config.py b/tests/tracer/test_global_config.py index b4b1410fb89..ca239c66b0a 100644 --- a/tests/tracer/test_global_config.py +++ b/tests/tracer/test_global_config.py @@ -1,8 +1,10 @@ +import os from unittest import TestCase import pytest from ddtrace import config as global_config +from ddtrace.internal.settings._agentless import AgentlessConfig from ddtrace.internal.settings._config import Config from ddtrace.internal.settings._config import _integration_default_service_names_from_config from ddtrace.internal.settings.integration import IntegrationConfig @@ -175,3 +177,88 @@ def test_raise_property_bridges_to_native(): assert config._raise is False finally: config._raise = original + + +def test_agentless_enabled_requires_an_api_key(): + with override_env(dict(DD_AGENTLESS_ENABLED="true"), replace_os_env=True): + with pytest.raises(ValueError, match="DD_API_KEY"): + AgentlessConfig() + + +def test_agentless_enabled_requires_an_api_key_at_import(run_python_code_in_subprocess): + """The api key check has to fail the process, not just AgentlessConfig().""" + env = os.environ.copy() + env["DD_AGENTLESS_ENABLED"] = "true" + env.pop("DD_API_KEY", None) + + _, stderr, status, _ = run_python_code_in_subprocess("import ddtrace", env=env) + + assert status != 0 + assert b"DD_AGENTLESS_ENABLED is set but DD_API_KEY is not" in stderr + + +def test_agentless_enabled_is_the_default_for_every_product(): + with override_env(dict(DD_AGENTLESS_ENABLED="true", DD_API_KEY="foobar"), replace_os_env=True): + c = AgentlessConfig() + + assert c.enabled is True + assert c.apm_tracing is True + assert c.llmobs is True + assert c.ci_visibility is True + assert c.any_enabled is True + + +def test_agentless_disabled_by_default(): + with override_env(dict(DD_API_KEY="foobar"), replace_os_env=True): + c = AgentlessConfig() + + assert c.enabled is False + assert c.apm_tracing is False + # Left unset, LLM Observability decides at startup instead of here. + assert c.llmobs is None + assert c.ci_visibility is False + assert c.any_enabled is False + + +def test_product_agentless_setting_overrides_the_global_one(): + with override_env( + dict( + DD_AGENTLESS_ENABLED="true", + DD_API_KEY="foobar", + _DD_APM_TRACING_AGENTLESS_ENABLED="false", + DD_LLMOBS_AGENTLESS_ENABLED="false", + ), + replace_os_env=True, + ): + c = AgentlessConfig() + + assert c.enabled is True + assert c.apm_tracing is False + assert c.llmobs is False + # Untouched products still follow the global setting. + assert c.ci_visibility is True + + +def test_a_product_setting_alone_turns_agentless_on(): + """Products can opt in individually, without the global switch.""" + with override_env(dict(DD_API_KEY="foobar", DD_CIVISIBILITY_AGENTLESS_ENABLED="true"), replace_os_env=True): + c = AgentlessConfig() + + assert c.enabled is False + assert c.ci_visibility is True + assert c.apm_tracing is False + assert c.any_enabled is True + + +def test_config_mirrors_the_agentless_settings(): + """ddtrace.config exposes what _agentless resolved; it must not re-derive it.""" + from ddtrace.internal.settings._agentless import config as agentless_config + + c = Config() + + assert c._agentless_enabled is agentless_config.enabled + assert c._trace_agentless_enabled is agentless_config.apm_tracing + assert c._llmobs_agentless_enabled is agentless_config.llmobs + assert c._ci_visibility_agentless_enabled is agentless_config.ci_visibility + assert c._dd_site == agentless_config.site + assert c._dd_api_key == agentless_config.api_key diff --git a/tests/utils.py b/tests/utils.py index 346e156dbd4..887849fd8ec 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -168,6 +168,7 @@ def override_global_config(values: dict[str, Any]): "_trace_resource_renaming_always_simplified_endpoint", "_obfuscation_query_string_pattern", "_global_query_string_obfuscation_disabled", + "_agentless_enabled", "_ci_visibility_agentless_url", "_ci_visibility_agentless_enabled", "_remote_config_enabled",