@@ -596,6 +596,9 @@ fn load_otel_receiver(otel_dir: &Path) -> Result<serde_yaml::Value, Error> {
596596 // Convert to Datadog schema dialect.
597597 convert_to_datadog_dialect ( & mut protocols) ;
598598
599+ // Tag env bindings. Only a subset is is registered and gets an explicit `env_vars` designation; the rest get `no-env`.
600+ tag_otel_env_bindings ( & mut protocols, & [ "protocols" ] ) ;
601+
599602 // Wrap in a "receiver" section to match the Datadog schema structure.
600603 let mut receiver_section = serde_yaml:: Mapping :: new ( ) ;
601604 receiver_section. insert (
@@ -658,7 +661,7 @@ fn merge_receiver_node(otel: &mut serde_yaml::Value, datadog: &serde_yaml::Value
658661 otel_map. get_mut ( "properties" ) . and_then ( |v| v. as_mapping_mut ( ) ) ,
659662 datadog_map. get ( "properties" ) . and_then ( |v| v. as_mapping ( ) ) ,
660663 ) {
661- // Merge overlapping OTEL properties with corresponding Datadog properties.
664+ // Merge overlapping OTel properties with corresponding Datadog properties.
662665 let otel_keys: Vec < serde_yaml:: Value > = otel_props. keys ( ) . cloned ( ) . collect ( ) ;
663666 for key in & otel_keys {
664667 if let Some ( datadog_val) = datadog_props. get ( key) {
@@ -667,7 +670,7 @@ fn merge_receiver_node(otel: &mut serde_yaml::Value, datadog: &serde_yaml::Value
667670 }
668671 }
669672 }
670- // Add Datadog-only properties that don't exist in OTEL .
673+ // Add Datadog-only properties that don't exist in OTel .
671674 for ( key, val) in datadog_props. iter ( ) {
672675 if !otel_props. contains_key ( key) {
673676 otel_props. insert ( key. clone ( ) , val. clone ( ) ) ;
@@ -857,6 +860,58 @@ fn convert_to_datadog_dialect(value: &mut serde_yaml::Value) {
857860 }
858861}
859862
863+ /// List of env-reachable `otlp_config.receiver` keys
864+ const ENV_REGISTERED_OTEL_KEYS : & [ & str ] = & [
865+ "otlp_config.receiver.protocols.grpc.endpoint" ,
866+ "otlp_config.receiver.protocols.grpc.transport" ,
867+ "otlp_config.receiver.protocols.grpc.max_recv_msg_size_mib" ,
868+ "otlp_config.receiver.protocols.grpc.max_concurrent_streams" ,
869+ "otlp_config.receiver.protocols.grpc.read_buffer_size" ,
870+ "otlp_config.receiver.protocols.grpc.write_buffer_size" ,
871+ "otlp_config.receiver.protocols.grpc.include_metadata" ,
872+ "otlp_config.receiver.protocols.grpc.keepalive.enforcement_policy.min_time" ,
873+ "otlp_config.receiver.protocols.http.endpoint" ,
874+ "otlp_config.receiver.protocols.http.max_request_body_size" ,
875+ "otlp_config.receiver.protocols.http.include_metadata" ,
876+ "otlp_config.receiver.protocols.http.cors.allowed_headers" ,
877+ "otlp_config.receiver.protocols.http.cors.allowed_origins" ,
878+ ] ;
879+
880+ /// Walks the OTel receiver subtree and tags each setting with `env_vars` or `no-env`.
881+ fn tag_otel_env_bindings ( value : & mut serde_yaml:: Value , path_parts : & [ & str ] ) {
882+ let Some ( map) = value. as_mapping_mut ( ) else { return } ;
883+
884+ if map. get ( "node_type" ) . and_then ( |v| v. as_str ( ) ) == Some ( "setting" ) {
885+ let full_path = format ! ( "otlp_config.receiver.{}" , path_parts. join( "." ) ) ;
886+ if ENV_REGISTERED_OTEL_KEYS . contains ( & full_path. as_str ( ) ) {
887+ let env_var = format ! ( "DD_{}" , full_path. replace( '.' , "_" ) . to_uppercase( ) ) ;
888+ let env_vars = serde_yaml:: Value :: Sequence ( vec ! [ serde_yaml:: Value :: String ( env_var) ] ) ;
889+ map. insert ( serde_yaml:: Value :: String ( "env_vars" . to_string ( ) ) , env_vars) ;
890+ } else {
891+ let tags_key = serde_yaml:: Value :: String ( "tags" . to_string ( ) ) ;
892+ let mut tags = map
893+ . get ( & tags_key)
894+ . and_then ( |v| v. as_sequence ( ) . cloned ( ) )
895+ . unwrap_or_default ( ) ;
896+ if !tags. iter ( ) . any ( |t| t. as_str ( ) == Some ( "no-env" ) ) {
897+ tags. push ( serde_yaml:: Value :: String ( "no-env" . to_string ( ) ) ) ;
898+ }
899+ map. insert ( tags_key, serde_yaml:: Value :: Sequence ( tags) ) ;
900+ }
901+ return ;
902+ }
903+
904+ if let Some ( props) = map. get_mut ( "properties" ) . and_then ( |v| v. as_mapping_mut ( ) ) {
905+ for ( key, val) in props. iter_mut ( ) {
906+ if let Some ( key_str) = key. as_str ( ) {
907+ let mut parts = path_parts. to_vec ( ) ;
908+ parts. push ( key_str) ;
909+ tag_otel_env_bindings ( val, & parts) ;
910+ }
911+ }
912+ }
913+ }
914+
860915const VALIDATION_RULES : & str = "\n \
861916 \n \
862917 Rules that must hold in schema_overlay.yaml:\n \
@@ -1344,4 +1399,35 @@ excluded: {}
13441399 "unexpected error: {err}"
13451400 ) ;
13461401 }
1402+
1403+ #[ test]
1404+ fn verify_otel_receiver_env_bindings ( ) {
1405+ let schema_path = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) )
1406+ . join ( ".." )
1407+ . join ( "config" )
1408+ . join ( "schema" )
1409+ . join ( "core" )
1410+ . join ( "core_schema.yaml" ) ;
1411+ let otel_schema_dir = otel_schema_dir_for_tests ( ) ;
1412+ let schema_map = crate :: schema_gen:: load_schema ( & schema_path, & otel_schema_dir) ;
1413+
1414+ let registered: HashSet < & str > = super :: ENV_REGISTERED_OTEL_KEYS . iter ( ) . copied ( ) . collect ( ) ;
1415+
1416+ for ( key, info) in & schema_map {
1417+ if !key. starts_with ( "otlp_config.receiver.protocols." ) {
1418+ continue ;
1419+ }
1420+ if registered. contains ( key. as_str ( ) ) {
1421+ assert ! (
1422+ matches!( info. env, crate :: schema_gen:: EnvBinding :: Overridden ( _) ) ,
1423+ "registered key `{key}` should have explicit env_vars"
1424+ ) ;
1425+ } else {
1426+ assert ! (
1427+ matches!( info. env, crate :: schema_gen:: EnvBinding :: None ) ,
1428+ "unregistered key `{key}` should be no-env"
1429+ ) ;
1430+ }
1431+ }
1432+ }
13471433}
0 commit comments