@@ -37,6 +37,12 @@ pub struct PrometheusRemoteWriteConfig {
3737 #[ configurable( metadata( docs:: examples = "0.0.0.0:9090" ) ) ]
3838 address : SocketAddr ,
3939
40+ /// The URL path on which metric POST requests are accepted.
41+ #[ serde( default = "default_path" ) ]
42+ #[ configurable( metadata( docs:: examples = "/api/v1/write" ) ) ]
43+ #[ configurable( metadata( docs:: examples = "/remote-write" ) ) ]
44+ path : String ,
45+
4046 #[ configurable( derived) ]
4147 tls : Option < TlsEnableableConfig > ,
4248
@@ -66,6 +72,7 @@ impl PrometheusRemoteWriteConfig {
6672 pub fn from_address ( address : SocketAddr ) -> Self {
6773 Self {
6874 address,
75+ path : default_path ( ) ,
6976 tls : None ,
7077 auth : None ,
7178 acknowledgements : false . into ( ) ,
@@ -75,10 +82,15 @@ impl PrometheusRemoteWriteConfig {
7582 }
7683}
7784
85+ fn default_path ( ) -> String {
86+ "/" . to_string ( )
87+ }
88+
7889impl GenerateConfig for PrometheusRemoteWriteConfig {
7990 fn generate_config ( ) -> toml:: Value {
8091 toml:: Value :: try_from ( Self {
8192 address : "127.0.0.1:9090" . parse ( ) . unwrap ( ) ,
93+ path : default_path ( ) ,
8294 tls : None ,
8395 auth : None ,
8496 acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
@@ -98,7 +110,7 @@ impl SourceConfig for PrometheusRemoteWriteConfig {
98110 } ;
99111 source. run (
100112 self . address ,
101- "" ,
113+ self . path . as_str ( ) ,
102114 HttpMethod :: Post ,
103115 StatusCode :: OK ,
104116 true ,
@@ -203,6 +215,7 @@ mod test {
203215 . http_protocol_name ( ) ;
204216 let source = PrometheusRemoteWriteConfig {
205217 address,
218+ path : default_path ( ) ,
206219 auth : None ,
207220 tls : tls. clone ( ) ,
208221 acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
@@ -317,6 +330,7 @@ mod test {
317330
318331 let source = PrometheusRemoteWriteConfig {
319332 address,
333+ path : default_path ( ) ,
320334 auth : None ,
321335 tls : None ,
322336 acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
@@ -387,6 +401,7 @@ mod test {
387401
388402 let source = PrometheusRemoteWriteConfig {
389403 address,
404+ path : default_path ( ) ,
390405 auth : None ,
391406 tls : None ,
392407 acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
@@ -456,6 +471,7 @@ mod test {
456471
457472 let source = PrometheusRemoteWriteConfig {
458473 address,
474+ path : default_path ( ) ,
459475 auth : None ,
460476 tls : None ,
461477 acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
@@ -531,6 +547,93 @@ mod test {
531547 assert_eq ! ( valid_metric. name( ) , "test_metric_valid" ) ;
532548 assert_eq ! ( valid_metric. value( ) , & MetricValue :: Gauge { value: 42.0 } ) ;
533549 }
550+
551+ #[ tokio:: test]
552+ async fn receives_metrics_on_custom_path ( ) {
553+ let address = test_util:: next_addr ( ) ;
554+ let ( tx, rx) = SourceSender :: new_test_finalize ( EventStatus :: Delivered ) ;
555+
556+ let source = PrometheusRemoteWriteConfig {
557+ address,
558+ path : "/api/v1/write" . to_string ( ) ,
559+ auth : None ,
560+ tls : None ,
561+ acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
562+ keepalive : KeepaliveConfig :: default ( ) ,
563+ skip_nan_values : false ,
564+ } ;
565+ let source = source
566+ . build ( SourceContext :: new_test ( tx, None ) )
567+ . await
568+ . unwrap ( ) ;
569+ tokio:: spawn ( source) ;
570+ wait_for_tcp ( address) . await ;
571+
572+ let sink = RemoteWriteConfig {
573+ endpoint : format ! ( "http://localhost:{}/api/v1/write" , address. port( ) ) ,
574+ ..Default :: default ( )
575+ } ;
576+ let ( sink, _) = sink
577+ . build ( SinkContext :: default ( ) )
578+ . await
579+ . expect ( "Error building config." ) ;
580+
581+ let events = make_events ( ) ;
582+ let events_copy = events. clone ( ) ;
583+ let mut output = test_util:: spawn_collect_ready (
584+ async move {
585+ sink. run_events ( events_copy) . await . unwrap ( ) ;
586+ } ,
587+ rx,
588+ 1 ,
589+ )
590+ . await ;
591+
592+ // The MetricBuffer used by the sink may reorder the metrics, so
593+ // put them back into order before comparing.
594+ output. sort_unstable_by_key ( |event| event. as_metric ( ) . name ( ) . to_owned ( ) ) ;
595+
596+ vector_lib:: assert_event_data_eq!( events, output) ;
597+ }
598+
599+ #[ tokio:: test]
600+ async fn rejects_metrics_on_wrong_path ( ) {
601+ let address = test_util:: next_addr ( ) ;
602+ let ( tx, _rx) = SourceSender :: new_test_finalize ( EventStatus :: Delivered ) ;
603+
604+ let source = PrometheusRemoteWriteConfig {
605+ address,
606+ path : "/api/v1/write" . to_string ( ) ,
607+ auth : None ,
608+ tls : None ,
609+ acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
610+ keepalive : KeepaliveConfig :: default ( ) ,
611+ skip_nan_values : false ,
612+ } ;
613+ let source = source
614+ . build ( SourceContext :: new_test ( tx, None ) )
615+ . await
616+ . unwrap ( ) ;
617+ tokio:: spawn ( source) ;
618+ wait_for_tcp ( address) . await ;
619+
620+ // Try to send to the root path, which should be rejected
621+ let client = reqwest:: Client :: new ( ) ;
622+ let response = client
623+ . post ( format ! ( "http://localhost:{}/wrong/path" , address. port( ) ) )
624+ . header ( "Content-Type" , "application/x-protobuf" )
625+ . body ( vec ! [ ] )
626+ . send ( )
627+ . await
628+ . unwrap ( ) ;
629+
630+ // Should return an error status code since we're sending to the wrong path
631+ assert ! (
632+ response. status( ) . is_client_error( ) ,
633+ "Expected 4xx error, got {}" ,
634+ response. status( )
635+ ) ;
636+ }
534637}
535638
536639#[ cfg( all( test, feature = "prometheus-integration-tests" ) ) ]
@@ -565,6 +668,7 @@ mod integration_tests {
565668 // maybe there's a way to do a one-shot remote write from Prometheus? Not sure.
566669 let config = PrometheusRemoteWriteConfig {
567670 address : source_receive_address ( ) ,
671+ path : default_path ( ) ,
568672 auth : None ,
569673 tls : None ,
570674 acknowledgements : SourceAcknowledgementsConfig :: default ( ) ,
0 commit comments