Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3147,6 +3147,78 @@ supportedConfigurations:
documentation: |-
Enables Feature Flags Provider (Experimental).
Default value is <c>false</c> (disabled).
DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS:
- implementation: B
scope: managed
type: int
default: '10000'
product: FeatureFlags
const_name: FlaggingProviderInitializationTimeoutMs
documentation: |-
Configuration key for how long, in milliseconds, provider initialization waits for the first
flag configuration to arrive before returning.
Default value is <c>10000</c> (10 seconds).
Initialization does not fail when the timeout expires: the provider stays not-ready, evaluations
return the caller's default value, and the provider becomes ready when configuration arrives.
DD_FEATURE_FLAGS_ENABLED:
- implementation: A
scope: managed
type: boolean
default: 'true'
product: FeatureFlags
const_name: FeatureFlagsEnabled
documentation: |-
Configuration key to enable or disable Feature Flags.
Default value is <c>true</c> (enabled).
Feature Flags only contact Datadog once application code initializes the provider, so enabling
this alone does not start requesting flag configuration.
This supersedes <see cref="ConfigurationKeys.FeatureFlags.FlaggingProviderEnabled"/>.
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE:
- implementation: A
scope: managed
type: string
default: agentless
product: FeatureFlags
const_name: FeatureFlagsConfigurationSource
documentation: |-
Configuration key for selecting where flag configuration is loaded from.
Supported values are <c>agentless</c> (direct HTTP delivery, the default) and
<c>remote_config</c> (delivery through the Datadog Agent's Remote Configuration).
Any other value disables Feature Flags, which fails closed and contacts nothing.
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL:
- implementation: A
scope: managed
type: string
default: null
product: FeatureFlags
const_name: FeatureFlagsConfigurationSourceAgentlessBaseUrl
documentation: |-
Configuration key for overriding the endpoint used by the <c>agentless</c> configuration source.
When the URL has no path, or a path of <c>/</c>, the standard rules-based server path is appended;
any other path is used verbatim as the exact endpoint.
If unset, the endpoint is derived from <see cref="ConfigurationKeys.Site"/>.
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS:
- implementation: A
scope: managed
type: int
default: '30'
product: FeatureFlags
const_name: FeatureFlagsConfigurationSourceAgentlessPollIntervalSeconds
documentation: |-
Configuration key for how often, in seconds, the <c>agentless</c> configuration source polls for
flag configuration.
Default value is <c>30</c>. Values outside <c>(0, 3600]</c> are rejected and the default is used.
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS:
- implementation: A
scope: managed
type: int
default: '5'
product: FeatureFlags
const_name: FeatureFlagsConfigurationSourceAgentlessRequestTimeoutSeconds
documentation: |-
Configuration key for the request timeout, in seconds, used by the <c>agentless</c> configuration
source.
Default value is <c>5</c>. Non-positive values are rejected and the default is used.
DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED:
- implementation: A
scope: managed
Expand Down
116 changes: 116 additions & 0 deletions tracer/src/Datadog.Trace/FeatureFlags/Agentless/AgentlessEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// <copyright file="AgentlessEndpoint.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using Datadog.Trace.Util;

namespace Datadog.Trace.FeatureFlags.Agentless;

/// <summary>
/// The agentless endpoint, derived from the Datadog site or a custom base URL.
/// </summary>
internal readonly struct AgentlessEndpoint
{
/// <summary>
/// Canonical rules-based server path, appended to the managed CDN host and to custom base
/// URLs that only supply an origin.
/// </summary>
internal const string DefaultPath = "/api/v2/feature-flagging/config/rules-based/server";

/// <summary>
/// The prefix prepended to the site to form the managed CDN host.
/// </summary>
internal const string ManagedHostPrefix = "ufc-server.ff-cdn.";

private AgentlessEndpoint(Uri uri, bool isManaged)
{
Uri = uri;
IsManaged = isManaged;
}

/// <summary>
/// Gets the endpoint URI.
/// </summary>
public Uri Uri { get; }

/// <summary>
/// Gets a value indicating whether this is the endpoint derived from the site. The API key is
/// only sent there: a custom endpoint reports its own authentication failure rather than
/// having the credential guessed onto it.
/// </summary>
public bool IsManaged { get; }

/// <summary>
/// Builds the endpoint. Without a custom <paramref name="baseUrl"/> the managed Datadog CDN
/// endpoint is derived from the (lowercased) site, so staging and government sites resolve
/// with no allowlist, and <c>dd_env</c> is added only when an environment is configured.
/// A custom base URL that is an origin receives the canonical path; one that carries a path
/// is used verbatim.
/// </summary>
/// <param name="site">The Datadog site, for example <c>datadoghq.com</c>.</param>
/// <param name="env">The configured environment, or <c>null</c>.</param>
/// <param name="baseUrl">The configured endpoint override, or <c>null</c>.</param>
/// <param name="endpoint">The resulting endpoint.</param>
/// <param name="error">Why the configured base URL was rejected. Never contains the URL, which may carry credentials.</param>
/// <returns><c>true</c> when an endpoint could be built.</returns>
public static bool TryCreate(string? site, string? env, string? baseUrl, out AgentlessEndpoint endpoint, out string? error)
{
endpoint = default;
error = null;

var configured = baseUrl?.Trim();
if (StringUtil.IsNullOrEmpty(configured))
{
var trimmedSite = site?.Trim();
if (StringUtil.IsNullOrEmpty(trimmedSite))
{
error = "No Datadog site is configured";
return false;
}

var managed = new UriBuilder("https", ManagedHostPrefix + trimmedSite!.ToLowerInvariant()) { Path = DefaultPath };
Comment thread
pavlokhrebto marked this conversation as resolved.
Outdated
if (!StringUtil.IsNullOrEmpty(env))
{
managed.Query = "dd_env=" + Uri.EscapeDataString(env!);
}

endpoint = new AgentlessEndpoint(managed.Uri, isManaged: true);
return true;
}

// A URL with internal whitespace is malformed, and Uri parsing is lenient enough to accept it.
foreach (var character in configured!)
{
if (char.IsWhiteSpace(character))
{
error = "The configured Feature Flags agentless URL is not a valid URL";
return false;
}
}

if (!Uri.TryCreate(configured, UriKind.Absolute, out var custom) || StringUtil.IsNullOrEmpty(custom.Host))
{
error = "The configured Feature Flags agentless URL is not a valid absolute URL";
return false;
}

// http is accepted for a custom endpoint only: pointing at one is an operator decision.
if (custom.Scheme != Uri.UriSchemeHttps && custom.Scheme != Uri.UriSchemeHttp)
{
error = "The configured Feature Flags agentless URL must use HTTP or HTTPS";
return false;
}

if (custom.AbsolutePath is "" or "/")
{
custom = new UriBuilder(custom) { Path = DefaultPath }.Uri;
}

endpoint = new AgentlessEndpoint(custom, isManaged: false);
return true;
}
}
Loading
Loading