diff --git a/Cargo.lock b/Cargo.lock index c06f2c6249..84d1ff1cf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,7 +1032,7 @@ checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86" dependencies = [ "prost 0.11.0", "prost-types 0.11.1", - "tonic 0.8.0", + "tonic 0.8.1", "tracing-core", ] @@ -1054,7 +1054,7 @@ dependencies = [ "thread_local", "tokio", "tokio-stream", - "tonic 0.8.0", + "tonic 0.8.1", "tracing", "tracing-core", "tracing-subscriber", @@ -5566,9 +5566,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498f271adc46acce75d66f639e4d35b31b2394c295c82496727dafa16d465dd2" +checksum = "11cd56bdb54ef93935a6a79dbd1d91f1ebd4c64150fd61654031fd6b8b775c91" dependencies = [ "async-stream", "async-trait", diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 62cbce4aa5..96c4f85100 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -27,6 +27,66 @@ By [@USERNAME](https://github.com/USERNAME) in https://github.com/apollographql/ ## ❗ BREAKING ❗ +### Configuration: Update metrics and healthcheck web endpoints, and make them configurable ([#1500](https://github.com/apollographql/router/issues/1500)) + +The web endpoints exposed by the router listen to 127.0.0.1 by default, and the ports and paths for health check and prometheus have changed. + +Here's the list of the endpoints exposed by the router: + +- GraphQL: http://127.0.0.1:4000/ (unchanged) +- The GraphQL sandbox: http://127.0.0.1:4000/ (unchanged) +- Prometheus metrics: http://127.0.0.1:9090/metrics (used to be http://127.0.0.1:4000/plugins/apollo.telemetry/prometheus) +- Healthcheck: http://127.0.0.1:9494/health (used to be http://127.0.0.1:4000/.well-known/apollo/server-health) + +While you could previously only customize the path for these endpoints, you can now customize the full IP address, PORT and PATH. + +In order to enable this new feature, various `server` attributes such as `listen`, `graphql_path` and `landing_page` moved to more relevant sections. +Likewise, `introspection` and `preview_defer_support` have moved from the `server` section to the `supergraph` section: + +This previous configuration: +```yaml +server: + listen: 127.0.0.1:4000 + graphql_path: /graphql + health_check_path: /health + introspection: false + preview_defer_support: true + landing_page: true +telemetry: + metrics: + prometheus: + enabled: true +``` + +Now becomes: +```yaml +# landing_page configuration +sandbox: + listen: 127.0.0.1:4000 + path: / + enabled: false # default +# graphql_path configuration +supergraph: + listen: 127.0.0.1:4000 + path: / + introspection: false + preview_defer_support: true +# health_check_path configuration +health-check: + listen: 127.0.0.1:9494 + path: /health + enabled: true # default +# prometheus scraper configuration +telemetry: + metrics: + prometheus: + listen: 127.0.0.1:9090 + path: /metrics + enabled: true +``` + +By [@o0Ignition0o](https://github.com/o0Ignition0o) in https://github.com/apollographql/router/pull/1718 + ### `apollo-spaceport` and `uplink` are now part of `apollo-router` ([Issue #491](https://github.com/apollographql/router/issues/491)) Instead of being dependencies, they are now part of the `apollo-router` crate. diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index b7c4b82580..e10624215d 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -36,8 +36,7 @@ in lieu of an official changelog. 5. Update the `PACKAGE_VERSION` value in `scripts/install.sh` (it should be prefixed with `v`!) 6. Update `docker.mdx` and `kubernetes.mdx` with the release version. 7. Update `helm/chart/router/Chart.yaml` as follows: - - increment the version. e.g. `version: 0.1.2` becomes `version: 0.1.3` - - update the appVersion to the release version. e.g.: `appVersion: "v0.9.0"` + - update the version and the appVersion to the release version. e.g.: `appVersion: "v0.9.0"` 8 Update `helm/chart/router/README.md` by running this from the repo root: `(cd helm/chart && helm-docs router)`. (If not installed, you should [install `helm-docs`](https://github.com/norwoodj/helm-docs)) 9. Update `federation-version-support.mdx` with the latest version info. Use https://github.com/apollographql/version_matrix to generate the version matrix. diff --git a/apollo-router/src/axum_http_server_factory.rs b/apollo-router/src/axum_http_server_factory.rs index 9b60542a68..006f0c2147 100644 --- a/apollo-router/src/axum_http_server_factory.rs +++ b/apollo-router/src/axum_http_server_factory.rs @@ -1,4 +1,5 @@ //! Axum http server factory. Axum provides routing capability on top of Hyper HTTP. +use std::collections::HashMap; use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; @@ -44,12 +45,12 @@ use multimap::MultiMap; use opentelemetry::global; use opentelemetry::trace::SpanKind; use opentelemetry::trace::TraceContextExt; -use serde_json::json; use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; #[cfg(unix)] use tokio::net::UnixListener; use tokio::sync::Notify; +use tower::service_fn; use tower::util::BoxService; use tower::BoxError; use tower::ServiceExt; @@ -76,11 +77,11 @@ use crate::plugins::traffic_shaping::RateLimited; use crate::router::ApolloRouterError; use crate::router_factory::Endpoint; use crate::router_factory::SupergraphServiceFactory; +use crate::services::transport; use crate::services::MULTIPART_DEFER_CONTENT_TYPE; /// A basic http server using Axum. /// Uses streaming as primary method of response. -/// Redirects to studio for GET requests. #[derive(Debug)] pub(crate) struct AxumHttpServerFactory; @@ -102,12 +103,56 @@ pub(crate) struct ListenersAndRouters { pub(crate) fn make_axum_router( service_factory: RF, configuration: &Configuration, - endpoints: MultiMap, + mut endpoints: MultiMap, ) -> Result where RF: SupergraphServiceFactory, { - let mut main_endpoint = main_endpoint(service_factory, configuration)?; + ensure_listenaddrs_consistency(configuration, &endpoints)?; + if configuration.sandbox.enabled && !sandbox_on_main_endpoint(configuration) { + endpoints.insert( + configuration.sandbox.listen.clone(), + Endpoint::new( + configuration.sandbox.path.clone(), + service_fn(|_req: transport::Request| async move { + Ok::<_, BoxError>( + http::Response::builder() + .header(CONTENT_TYPE, "text/html") + .body( + Bytes::from_static(include_bytes!("../resources/index.html")) + .into(), + ) + .unwrap(), + ) + }) + .boxed(), + ), + ); + } + + endpoints.insert( + configuration.health_check.listen.clone(), + Endpoint::new( + configuration.health_check.path.clone(), + service_fn(|_req: transport::Request| async move { + Ok::<_, BoxError>( + http::Response::builder() + .header(CONTENT_TYPE, "application/json") + .body(Bytes::from_static(b"{ \"status\": \"pass\" }").into()) + .unwrap(), + ) + }) + .boxed(), + ), + ); + + let mut main_endpoint = main_endpoint( + service_factory, + configuration, + endpoints + .remove(&configuration.supergraph.listen) + .unwrap_or_default(), + )?; let mut extra_endpoints = extra_endpoints(endpoints); // put any extra endpoint that uses the main ListenAddr into the main router @@ -123,9 +168,59 @@ where }) } +/// Binding different listen addresses to the same port will "relax" the requirements, which +/// could result in a security issue: +/// If endpoint A is exposed to 127.0.0.1:4000/foo and endpoint B is exposed to 0.0.0.0:4000/bar +/// 0.0.0.0:4000/foo would be accessible. +/// +/// `ensure_listenaddrs_consistency` makes sure listen addresses that bind to the same port +/// have the same IP: +/// 127.0.0.1:4000 and 127.0.0.1:4000 will not trigger an error +/// 127.0.0.1:4000 and 0.0.0.0:4001 will not trigger an error +/// +/// 127.0.0.1:4000 and 0.0.0.0:4000 will trigger an error +fn ensure_listenaddrs_consistency( + configuration: &Configuration, + endpoints: &MultiMap, +) -> Result<(), ApolloRouterError> { + let mut all_ports = HashMap::new(); + if let Some((main_ip, main_port)) = configuration.supergraph.listen.ip_and_port() { + all_ports.insert(main_port, main_ip); + } + + if let Some((ip, port)) = configuration.sandbox.listen.ip_and_port() { + if let Some(previous_ip) = all_ports.insert(port, ip) { + if ip != previous_ip { + return Err(ApolloRouterError::DifferentListenAddrsOnSamePort( + previous_ip, + ip, + port, + )); + } + } + } + + for addr in endpoints.keys() { + if let Some((ip, port)) = addr.ip_and_port() { + if let Some(previous_ip) = all_ports.insert(port, ip) { + if ip != previous_ip { + return Err(ApolloRouterError::DifferentListenAddrsOnSamePort( + previous_ip, + ip, + port, + )); + } + } + } + } + + Ok(()) +} + fn main_endpoint( service_factory: RF, configuration: &Configuration, + endpoints_on_main_listener: Vec, ) -> Result where RF: SupergraphServiceFactory, @@ -133,42 +228,8 @@ where let cors = configuration.cors.clone().into_layer().map_err(|e| { ApolloRouterError::ServiceCreationError(format!("CORS configuration error: {e}").into()) })?; - let graphql_path = if configuration.server.graphql_path.ends_with("/*") { - // Needed for axum (check the axum docs for more information about wildcards https://docs.rs/axum/latest/axum/struct.Router.html#wildcards) - format!("{}router_extra_path", configuration.server.graphql_path) - } else { - configuration.server.graphql_path.clone() - }; - let route = Router::::new() - .route( - &graphql_path, - get({ - let display_landing_page = configuration.server.landing_page; - move |host: Host, Extension(service): Extension, http_request: Request| { - handle_get( - host, - service.new_service().boxed(), - http_request, - display_landing_page, - ) - } - }) - .post({ - move |host: Host, - uri: OriginalUri, - request: Json, - Extension(service): Extension, - header_map: HeaderMap| { - handle_post( - host, - uri, - request, - service.new_service().boxed(), - header_map, - ) - } - }), - ) + + let main_route = main_router::(configuration) .layer(middleware::from_fn(decompress_request_body)) .layer( TraceLayer::new_for_http() @@ -189,7 +250,6 @@ where } }), ) - .route(&configuration.server.health_check_path, get(health_check)) .layer(Extension(service_factory)) .layer(cors) // Compress the response body, except for multipart responses such as with `@defer`. @@ -198,7 +258,11 @@ where DefaultPredicate::new().and(NotForContentType::const_new("multipart/")), )); - let listener = configuration.server.listen.clone(); + let route = endpoints_on_main_listener + .into_iter() + .fold(main_route, |acc, r| acc.merge(r.into_router())); + + let listener = configuration.supergraph.listen.clone(); Ok(ListenAddrAndRouter(listener, route)) } @@ -230,9 +294,6 @@ impl HttpServerFactory for AxumHttpServerFactory { Box::pin(async move { let all_routers = make_axum_router(service_factory, &configuration, extra_endpoints)?; - // TODO [igni]: I believe configuring the router - // To listen to port 0 would lead it to change ports on every restart Oo - // serve main router // if we received a TCP listener, reuse it, otherwise create a new one @@ -262,7 +323,7 @@ impl HttpServerFactory for AxumHttpServerFactory { tracing::info!( "GraphQL endpoint exposed at {}{} 🚀", actual_main_listen_address, - configuration.server.graphql_path + configuration.supergraph.path ); // serve extra routers @@ -330,14 +391,14 @@ impl HttpServerFactory for AxumHttpServerFactory { async fn get_extra_listeners( previous_listeners: Vec<(ListenAddr, Listener)>, - extra_routers: MultiMap, + mut extra_routers: MultiMap, ) -> Result, ApolloRouterError> { let mut listeners_and_routers: Vec<((ListenAddr, Listener), axum::Router)> = Vec::with_capacity(extra_routers.len()); // reuse previous extra listen addrs for (listen_addr, listener) in previous_listeners.into_iter() { - if let Some(routers) = extra_routers.get_vec(&listen_addr) { + if let Some(routers) = extra_routers.remove(&listen_addr) { listeners_and_routers.push(( (listen_addr, listener), routers @@ -551,7 +612,7 @@ struct CustomRejection { msg: String, } -async fn handle_get( +async fn handle_get_with_sandbox( Host(host): Host, service: BoxService< http::Request, @@ -559,9 +620,8 @@ async fn handle_get( BoxError, >, http_request: Request, - display_landing_page: bool, ) -> impl IntoResponse { - if prefers_html(http_request.headers()) && display_landing_page { + if prefers_html(http_request.headers()) { return display_home_page().into_response(); } @@ -581,6 +641,82 @@ async fn handle_get( (StatusCode::BAD_REQUEST, "Invalid Graphql request").into_response() } +fn main_router(configuration: &Configuration) -> axum::Router +where + RF: SupergraphServiceFactory, +{ + let mut graphql_configuration = configuration.supergraph.clone(); + if graphql_configuration.path.ends_with("/*") { + // Needed for axum (check the axum docs for more information about wildcards https://docs.rs/axum/latest/axum/struct.Router.html#wildcards) + graphql_configuration.path = format!("{}router_extra_path", graphql_configuration.path); + } + + let get_handler = if sandbox_on_main_endpoint(configuration) { + get({ + move |host: Host, Extension(service): Extension, http_request: Request| { + handle_get_with_sandbox(host, service.new_service().boxed(), http_request) + } + }) + } else { + get({ + move |host: Host, Extension(service): Extension, http_request: Request| { + handle_get(host, service.new_service().boxed(), http_request) + } + }) + }; + + Router::::new().route( + &graphql_configuration.path, + get_handler.post({ + move |host: Host, + uri: OriginalUri, + request: Json, + Extension(service): Extension, + header_map: HeaderMap| { + handle_post( + host, + uri, + request, + service.new_service().boxed(), + header_map, + ) + } + }), + ) +} + +async fn handle_get( + Host(host): Host, + service: BoxService< + http::Request, + http::Response, + BoxError, + >, + http_request: Request, +) -> impl IntoResponse { + if let Some(request) = http_request + .uri() + .query() + .and_then(|q| graphql::Request::from_urlencoded_query(q.to_string()).ok()) + { + let mut http_request = http_request.map(|_| request); + *http_request.uri_mut() = Uri::from_str(&format!("http://{}{}", host, http_request.uri())) + .expect("the URL is already valid because it comes from axum; qed"); + return run_graphql_request(service, http_request) + .await + .into_response(); + } + + (StatusCode::BAD_REQUEST, "Invalid Graphql request").into_response() +} + +// Returns true if the sandbox is enabled, and on the same url as the graphql endpoint +fn sandbox_on_main_endpoint(configuration: &Configuration) -> bool { + configuration.sandbox.enabled + && configuration.sandbox.listen == configuration.supergraph.listen + && configuration.sandbox.path == configuration.supergraph.path +} + async fn handle_post( Host(host): Host, OriginalUri(uri): OriginalUri, @@ -610,10 +746,6 @@ fn display_home_page() -> Html { Html(html) } -async fn health_check() -> impl IntoResponse { - Json(json!({ "status": "pass" })) -} - // Process the headers to make sure that `VARY` is set correctly fn process_vary_header(headers: &mut HeaderMap) { if headers.get(VARY).is_none() { @@ -898,6 +1030,9 @@ mod tests { use super::*; use crate::configuration::Cors; + use crate::configuration::HealthCheck; + use crate::configuration::Sandbox; + use crate::configuration::Supergraph; use crate::json_ext::Path; use crate::services::new_service::NewService; use crate::services::transport; @@ -1003,8 +1138,18 @@ mod tests { }, Arc::new( Configuration::builder() - .server( - crate::configuration::Server::builder() + .sandbox( + crate::configuration::Sandbox::builder() + .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) + .build(), + ) + .supergraph( + crate::configuration::Supergraph::builder() + .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) + .build(), + ) + .health_check( + crate::configuration::HealthCheck::builder() .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) .build(), ) @@ -1092,9 +1237,9 @@ mod tests { inner: service.into_inner(), }, Arc::new( - Configuration::builder() - .server( - crate::configuration::Server::builder() + Configuration::fake_builder() + .supergraph( + crate::configuration::Supergraph::fake_builder() .listen(ListenAddr::UnixSocket(temp_dir.as_ref().join("sock"))) .build(), ) @@ -1111,14 +1256,14 @@ mod tests { } #[tokio::test] - async fn it_display_home_page() -> Result<(), ApolloRouterError> { - // TODO re-enable after the release - // test_span::init(); - // let root_span = info_span!("root"); - // { - // let _guard = root_span.enter(); + async fn it_display_home_page_on_same_endpoint() -> Result<(), ApolloRouterError> { let expectations = MockSupergraphService::new(); - let (server, client) = init(expectations).await; + + let conf = Configuration::fake_builder() + .sandbox(Sandbox::fake_builder().enabled(true).build()) + .build(); + + let (server, client) = init_with_config(expectations, conf, MultiMap::new()).await; // Regular studio redirect let response = client @@ -1137,11 +1282,77 @@ mod tests { response.text().await.unwrap() ); assert_eq!(response.bytes().await.unwrap(), display_home_page().0); - // } - // insta::assert_json_snapshot!(test_span::get_spans_for_root( - // &root_span.id().unwrap(), - // &test_span::Filter::new(Level::INFO) - // )); + + Ok(()) + } + + #[tokio::test] + async fn it_display_home_page_on_different_path() -> Result<(), ApolloRouterError> { + let expectations = MockSupergraphService::new(); + + let conf = Configuration::fake_builder() + .sandbox( + Sandbox::fake_builder() + .path("/a-custom-path") + .enabled(true) + .build(), + ) + .build(); + + let (server, client) = init_with_config(expectations, conf, Default::default()).await; + + // Regular studio redirect + let response = client + .get(&format!( + "{}/a-custom-path", + server.graphql_listen_address().as_ref().unwrap() + )) + .header(ACCEPT, "text/html") + .send() + .await + .unwrap(); + assert_eq!( + response.status(), + StatusCode::OK, + "{}", + response.text().await.unwrap() + ); + assert_eq!(response.bytes().await.unwrap(), display_home_page().0); + Ok(()) + } + + #[tokio::test] + async fn it_display_home_page_on_different_endpoint() -> Result<(), ApolloRouterError> { + let expectations = MockSupergraphService::new(); + + let conf = Configuration::fake_builder() + .sandbox( + Sandbox::fake_builder() + .path("/a-custom-path") + .enabled(true) + .build(), + ) + .build(); + + let (server, client) = init_with_config(expectations, conf, Default::default()).await; + + // Regular studio redirect + let response = client + .get(&format!( + "{}/a-custom-path", + server.graphql_listen_address().as_ref().unwrap() + )) + .header(ACCEPT, "text/html") + .send() + .await + .unwrap(); + assert_eq!( + response.status(), + StatusCode::OK, + "{}", + response.text().await.unwrap() + ); + assert_eq!(response.bytes().await.unwrap(), display_home_page().0); Ok(()) } @@ -1298,11 +1509,6 @@ mod tests { #[tokio::test] async fn response() -> Result<(), ApolloRouterError> { - // TODO re-enable after the release - // test_span::init(); - // let root_span = info_span!("root"); - // { - // let _guard = root_span.enter(); let expected_response = graphql::Response::builder() .data(json!({"response": "yay"})) .build(); @@ -1359,11 +1565,6 @@ mod tests { ); server.shutdown().await?; - // } - // insta::assert_json_snapshot!(test_span::get_spans_for_root( - // &root_span.id().unwrap(), - // &test_span::Filter::new(Level::INFO) - // )); Ok(()) } @@ -1422,16 +1623,10 @@ mod tests { .unwrap(), )) }); - let conf = Configuration::builder() - .cors( - Cors::builder() - .origins(vec!["http://studio".to_string()]) - .build(), - ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .graphql_path(String::from("/graphql")) + let conf = Configuration::fake_builder() + .supergraph( + crate::configuration::Supergraph::fake_builder() + .path(String::from("/graphql")) .build(), ) .build(); @@ -1494,16 +1689,10 @@ mod tests { .unwrap(), )) }); - let conf = Configuration::builder() - .cors( - Cors::builder() - .origins(vec!["http://studio".to_string()]) - .build(), - ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .graphql_path(String::from("/:my_prefix/graphql")) + let conf = Configuration::fake_builder() + .supergraph( + crate::configuration::Supergraph::fake_builder() + .path(String::from("/:my_prefix/graphql")) .build(), ) .build(); @@ -1566,16 +1755,10 @@ mod tests { .unwrap(), )) }); - let conf = Configuration::builder() - .cors( - Cors::builder() - .origins(vec!["http://studio".to_string()]) - .build(), - ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .graphql_path(String::from("/graphql/*")) + let conf = Configuration::fake_builder() + .supergraph( + crate::configuration::Supergraph::fake_builder() + .path(String::from("/graphql/*")) .build(), ) .build(); @@ -1798,12 +1981,11 @@ mod tests { #[tokio::test] async fn cors_preflight() -> Result<(), ApolloRouterError> { let expectations = MockSupergraphService::new(); - let conf = Configuration::builder() + let conf = Configuration::fake_builder() .cors(Cors::builder().build()) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .graphql_path(String::from("/graphql/*")) + .supergraph( + crate::configuration::Supergraph::fake_builder() + .path(String::from("/graphql/*")) .build(), ) .build(); @@ -1812,7 +1994,10 @@ mod tests { let response = client .request( Method::OPTIONS, - &format!("{}/", server.graphql_listen_address().as_ref().unwrap()), + &format!( + "{}/graphql/", + server.graphql_listen_address().as_ref().unwrap() + ), ) .header(ACCEPT, "text/html") .header(ORIGIN, "https://studio.apollographql.com") @@ -1967,41 +2152,30 @@ Content-Type: application/json\r #[tokio::test] async fn test_health_check() { - // TODO re-enable after the release - // test_span::init(); - // let root_span = info_span!("root"); - // { - // let _guard = root_span.enter(); let expectations = MockSupergraphService::new(); let (server, client) = init(expectations).await; let url = format!( - "{}/.well-known/apollo/server-health", + "{}/health", server.graphql_listen_address().as_ref().unwrap() ); let response = client.get(url).send().await.unwrap(); assert_eq!(response.status(), StatusCode::OK); - // } - // insta::assert_json_snapshot!(test_span::get_spans_for_root( - // &root_span.id().unwrap(), - // &test_span::Filter::new(Level::INFO) - // )); } #[tokio::test] async fn test_custom_health_check() { - let conf = Configuration::builder() - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .health_check_path("/health") + let conf = Configuration::fake_builder() + .health_check( + HealthCheck::fake_builder() + .path("/custom-health".to_string()) .build(), ) .build(); let expectations = MockSupergraphService::new(); let (server, client) = init_with_config(expectations, conf, MultiMap::new()).await; let url = format!( - "{}/health", + "{}/custom-health", server.graphql_listen_address().as_ref().unwrap() ); @@ -2033,16 +2207,10 @@ Content-Type: application/json\r #[test(tokio::test)] async fn it_doesnt_display_disabled_home_page() -> Result<(), ApolloRouterError> { let expectations = MockSupergraphService::new(); - let conf = Configuration::builder() - .cors( - Cors::builder() - .origins(vec!["http://studio".to_string()]) - .build(), - ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .landing_page(false) + let conf = Configuration::fake_builder() + .sandbox( + crate::configuration::Sandbox::fake_builder() + .enabled(false) .build(), ) .build(); @@ -2084,18 +2252,7 @@ Content-Type: application/json\r Endpoint::new("/an-other-custom-path".to_string(), endpoint.boxed()), ); - let conf = Configuration::builder() - .cors( - Cors::builder() - .origins(vec!["http://studio".to_string()]) - .build(), - ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .build(), - ) - .build(); + let conf = Configuration::fake_builder().build(); let (server, client) = init_with_config(expectations, conf, web_endpoints).await; for path in &["/a-custom-path", "/an-other-custom-path"] { @@ -2215,13 +2372,8 @@ Content-Type: application/json\r #[tokio::test] async fn cors_allow_any_origin() -> Result<(), ApolloRouterError> { - let conf = Configuration::builder() + let conf = Configuration::fake_builder() .cors(Cors::builder().allow_any_origin(true).build()) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .build(), - ) .build(); let (server, client) = init_with_config(MockSupergraphService::new(), conf, MultiMap::new()).await; @@ -2238,17 +2390,12 @@ Content-Type: application/json\r async fn cors_origin_list() -> Result<(), ApolloRouterError> { let valid_origin = "https://thisoriginisallowed.com"; - let conf = Configuration::builder() + let conf = Configuration::fake_builder() .cors( Cors::builder() .origins(vec![valid_origin.to_string()]) .build(), ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .build(), - ) .build(); let (server, client) = init_with_config(MockSupergraphService::new(), conf, MultiMap::new()).await; @@ -2268,18 +2415,13 @@ Content-Type: application/json\r async fn cors_origin_regex() -> Result<(), ApolloRouterError> { let apollo_subdomains = "https://([a-z0-9]+[.])*apollographql[.]com"; - let conf = Configuration::builder() + let conf = Configuration::fake_builder() .cors( Cors::builder() .origins(vec!["https://anexactmatchorigin.com".to_string()]) .match_origins(vec![apollo_subdomains.to_string()]) .build(), ) - .server( - crate::configuration::Server::builder() - .listen(SocketAddr::from_str("127.0.0.1:0").unwrap()) - .build(), - ) .build(); let (server, client) = init_with_config(MockSupergraphService::new(), conf, MultiMap::new()).await; @@ -2508,6 +2650,121 @@ Content-Type: application/json\r } } + #[tokio::test] + async fn it_makes_sure_same_listenaddrs_are_accepted() { + let configuration = Configuration::fake_builder().build(); + + init_with_config(MockSupergraphService::new(), configuration, MultiMap::new()).await; + } + + #[tokio::test] + #[should_panic( + expected = "Failed to create server factory: DifferentListenAddrsOnSamePort(127.0.0.1, 0.0.0.0, 4010)" + )] + async fn it_makes_sure_different_listenaddrs_but_same_port_are_not_accepted() { + let configuration = Configuration::fake_builder() + .supergraph( + Supergraph::fake_builder() + .listen(SocketAddr::from_str("127.0.0.1:4010").unwrap()) + .build(), + ) + .sandbox( + Sandbox::fake_builder() + .listen(SocketAddr::from_str("0.0.0.0:4010").unwrap()) + .build(), + ) + .build(); + + init_with_config(MockSupergraphService::new(), configuration, MultiMap::new()).await; + } + + // TODO: axum just panics here. + // While this is ok for now, we probably want to check it ourselves and return a meaningful error + #[tokio::test] + #[should_panic( + expected = "Invalid route: insertion failed due to conflict with previously registered route: /" + )] + async fn it_makes_sure_extra_endpoints_cant_use_the_same_listenaddr_and_path() { + let configuration = Configuration::fake_builder() + .supergraph( + Supergraph::fake_builder() + .listen(SocketAddr::from_str("127.0.0.1:4010").unwrap()) + .build(), + ) + .build(); + let endpoint = service_fn(|_req: transport::Request| async move { + Ok::<_, BoxError>( + http::Response::builder() + .body("this is a test".to_string().into()) + .unwrap(), + ) + }) + .boxed(); + + let mut mm = MultiMap::new(); + mm.insert( + SocketAddr::from_str("127.0.0.1:4010").unwrap().into(), + Endpoint::new("/".to_string(), endpoint), + ); + + init_with_config(MockSupergraphService::new(), configuration, mm).await; + } + + #[tokio::test] + async fn it_supports_server_restart() { + let configuration = Arc::new( + Configuration::fake_builder() + .supergraph( + Supergraph::fake_builder() + .listen(SocketAddr::from_str("127.0.0.1:4010").unwrap()) + .build(), + ) + .build(), + ); + let endpoint = service_fn(|_req: transport::Request| async move { + Ok::<_, BoxError>( + http::Response::builder() + .body("this is a test".to_string().into()) + .unwrap(), + ) + }) + .boxed(); + + let mut web_endpoints = MultiMap::new(); + web_endpoints.insert( + SocketAddr::from_str("127.0.0.1:5000").unwrap().into(), + Endpoint::new("/".to_string(), endpoint), + ); + + let server_factory = AxumHttpServerFactory::new(); + let (service, _) = tower_test::mock::spawn(); + + let supergraph_service_factory = TestSupergraphServiceFactory { + inner: service.into_inner(), + }; + + let server = server_factory + .create( + supergraph_service_factory.clone(), + Arc::clone(&configuration), + None, + vec![], + web_endpoints.clone(), + ) + .await + .expect("Failed to create server factory"); + + server + .restart( + &server_factory, + supergraph_service_factory, + Arc::clone(&configuration), + web_endpoints, + ) + .await + .unwrap(); + } + /// A counter of how many GraphQL responses have been sent by an Apollo Router /// /// When `@defer` is used, it should increment multiple times for a single HTTP request. diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index 6e7a731518..e7264480f3 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -5,6 +5,7 @@ mod yaml; use std::borrow::Cow; use std::cmp::Ordering; use std::fmt; +use std::net::IpAddr; use std::net::SocketAddr; use std::str::FromStr; @@ -68,6 +69,15 @@ pub struct Configuration { #[serde(default)] pub(crate) server: Server, + #[serde(default)] + #[serde(rename = "health-check")] + pub(crate) health_check: HealthCheck, + + #[serde(default)] + pub(crate) sandbox: Sandbox, + + #[serde(default)] + pub(crate) supergraph: Supergraph, /// Cross origin request headers. #[serde(default)] pub(crate) cors: Cors, @@ -85,21 +95,33 @@ pub struct Configuration { const APOLLO_PLUGIN_PREFIX: &str = "apollo."; const TELEMETRY_KEY: &str = "telemetry"; -fn default_listen() -> ListenAddr { +fn default_graphql_listen() -> ListenAddr { SocketAddr::from_str("127.0.0.1:4000").unwrap().into() } +// This isn't dead code! we use it in buildstructor's fake_new +#[allow(dead_code)] +fn test_listen() -> ListenAddr { + SocketAddr::from_str("127.0.0.1:0").unwrap().into() +} + #[buildstructor::buildstructor] impl Configuration { #[builder] pub(crate) fn new( server: Option, + supergraph: Option, + health_check: Option, + sandbox: Option, cors: Option, plugins: Map, apollo_plugins: Map, ) -> Self { Self { server: server.unwrap_or_default(), + supergraph: supergraph.unwrap_or_default(), + health_check: health_check.unwrap_or_default(), + sandbox: sandbox.unwrap_or_default(), cors: cors.unwrap_or_default(), plugins: UserPlugins { plugins: Some(plugins), @@ -110,11 +132,6 @@ impl Configuration { } } - #[cfg(test)] - pub(crate) fn boxed(self) -> Box { - Box::new(self) - } - pub(crate) fn plugins(&self) -> Vec<(String, Value)> { let mut plugins = vec![]; @@ -157,6 +174,39 @@ impl Configuration { } } +#[cfg(test)] +#[buildstructor::buildstructor] +impl Configuration { + #[builder] + pub(crate) fn fake_new( + server: Option, + supergraph: Option, + health_check: Option, + sandbox: Option, + cors: Option, + plugins: Map, + apollo_plugins: Map, + ) -> Self { + Self { + server: server.unwrap_or_default(), + supergraph: supergraph.unwrap_or_else(|| Supergraph::fake_builder().build()), + health_check: health_check.unwrap_or_else(|| HealthCheck::fake_builder().build()), + sandbox: sandbox.unwrap_or_else(|| Sandbox::fake_builder().build()), + cors: cors.unwrap_or_default(), + plugins: UserPlugins { + plugins: Some(plugins), + }, + apollo_plugins: ApolloPlugins { + plugins: apollo_plugins, + }, + } + } + + pub(crate) fn boxed(self) -> Box { + Box::new(self) + } +} + /// Parse configuration from a string in YAML syntax impl FromStr for Configuration { type Err = serde_yaml::Error; @@ -246,37 +296,207 @@ impl JsonSchema for UserPlugins { /// Configuration options pertaining to the http server component. #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] -pub(crate) struct Server { +pub(crate) struct Supergraph { /// The socket address and port to listen on /// Defaults to 127.0.0.1:4000 - #[serde(default = "default_listen")] + #[serde(default = "default_graphql_listen")] pub(crate) listen: ListenAddr, - /// introspection queries - /// enabled by default + /// The HTTP path on which GraphQL requests will be served. + /// default: "/" + #[serde(default = "default_graphql_path")] + pub(crate) path: String, + #[serde(default = "default_introspection")] pub(crate) introspection: bool, - /// display landing page - /// enabled by default - #[serde(default = "default_landing_page")] - pub(crate) landing_page: bool, + #[serde(default = "default_defer_support")] + pub(crate) preview_defer_support: bool, +} + +fn default_introspection() -> bool { + true +} + +fn default_defer_support() -> bool { + true +} + +#[buildstructor::buildstructor] +impl Supergraph { + #[builder] + pub(crate) fn new( + listen: Option, + path: Option, + introspection: Option, + preview_defer_support: Option, + ) -> Self { + Self { + listen: listen.unwrap_or_else(default_graphql_listen), + path: path.unwrap_or_else(default_graphql_path), + introspection: introspection.unwrap_or_else(default_introspection), + preview_defer_support: preview_defer_support.unwrap_or_else(default_defer_support), + } + } +} + +#[cfg(test)] +#[buildstructor::buildstructor] +impl Supergraph { + #[builder] + pub(crate) fn fake_new( + listen: Option, + path: Option, + introspection: Option, + preview_defer_support: Option, + ) -> Self { + Self { + listen: listen.unwrap_or_else(test_listen), + path: path.unwrap_or_else(default_graphql_path), + introspection: introspection.unwrap_or_else(default_introspection), + preview_defer_support: preview_defer_support.unwrap_or_else(default_defer_support), + } + } +} + +impl Default for Supergraph { + fn default() -> Self { + Self::builder().build() + } +} + +/// Configuration options pertaining to the http server component. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub(crate) struct Sandbox { + /// The socket address and port to listen on + /// Defaults to 127.0.0.1:4000 + #[serde(default = "default_graphql_listen")] + pub(crate) listen: ListenAddr, /// The HTTP path on which GraphQL requests will be served. /// default: "/" #[serde(default = "default_graphql_path")] - pub(crate) graphql_path: String, + pub(crate) path: String, + + #[serde(default = "default_sandbox")] + pub(crate) enabled: bool, +} + +fn default_sandbox() -> bool { + false +} - /// healthCheck path - /// default: "/.well-known/apollo/server-health" +#[buildstructor::buildstructor] +impl Sandbox { + #[builder] + pub(crate) fn new( + listen: Option, + path: Option, + enabled: Option, + ) -> Self { + Self { + listen: listen.unwrap_or_else(default_graphql_listen), + path: path.unwrap_or_else(default_graphql_path), + enabled: enabled.unwrap_or_else(default_sandbox), + } + } +} + +#[cfg(test)] +#[buildstructor::buildstructor] +impl Sandbox { + #[builder] + pub(crate) fn fake_new( + listen: Option, + path: Option, + enabled: Option, + ) -> Self { + Self { + listen: listen.unwrap_or_else(test_listen), + path: path.unwrap_or_else(default_graphql_path), + enabled: enabled.unwrap_or_else(default_sandbox), + } + } +} + +impl Default for Sandbox { + fn default() -> Self { + Self::builder().build() + } +} + +/// Configuration options pertaining to the http server component. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub(crate) struct HealthCheck { + /// The socket address and port to listen on + /// Defaults to 127.0.0.1:9494 + #[serde(default = "default_health_check_listen")] + pub(crate) listen: ListenAddr, + + /// The HTTP path on which GraphQL requests will be served. + /// default: "/" #[serde(default = "default_health_check_path")] - pub(crate) health_check_path: String, + pub(crate) path: String, - /// Preview @defer directive support - /// default: true - #[serde(default = "default_defer_support")] - pub(crate) preview_defer_support: bool, + #[serde(default = "default_health_check")] + pub(crate) enabled: bool, +} + +fn default_health_check_listen() -> ListenAddr { + SocketAddr::from_str("127.0.0.1:9494").unwrap().into() +} + +fn default_health_check_path() -> String { + "/health".to_string() +} + +fn default_health_check() -> bool { + true +} +#[buildstructor::buildstructor] +impl HealthCheck { + #[builder] + pub(crate) fn new( + listen: Option, + path: Option, + enabled: Option, + ) -> Self { + Self { + listen: listen.unwrap_or_else(default_health_check_listen), + path: path.unwrap_or_else(default_health_check_path), + enabled: enabled.unwrap_or_else(default_health_check), + } + } + + // Used in tests + #[allow(dead_code)] + #[builder] + pub(crate) fn fake_new( + listen: Option, + path: Option, + enabled: Option, + ) -> Self { + Self { + listen: listen.unwrap_or_else(test_listen), + path: path.unwrap_or_else(default_health_check_path), + enabled: enabled.unwrap_or_else(default_health_check), + } + } +} + +impl Default for HealthCheck { + fn default() -> Self { + Self::builder().build() + } +} + +/// Configuration options pertaining to the http server component. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub(crate) struct Server { /// Experimental limitation of query depth /// default: 4096 #[serde(default = "default_parser_recursion_limit")] @@ -287,22 +507,8 @@ pub(crate) struct Server { impl Server { #[builder] #[allow(clippy::too_many_arguments)] // Used through a builder, not directly - pub(crate) fn new( - listen: Option, - introspection: Option, - landing_page: Option, - graphql_path: Option, - health_check_path: Option, - defer_support: Option, - parser_recursion_limit: Option, - ) -> Self { + pub(crate) fn new(parser_recursion_limit: Option) -> Self { Self { - listen: listen.unwrap_or_else(default_listen), - introspection: introspection.unwrap_or_else(default_introspection), - landing_page: landing_page.unwrap_or_else(default_landing_page), - graphql_path: graphql_path.unwrap_or_else(default_graphql_path), - health_check_path: health_check_path.unwrap_or_else(default_health_check_path), - preview_defer_support: defer_support.unwrap_or_else(default_defer_support), experimental_parser_recursion_limit: parser_recursion_limit .unwrap_or_else(default_parser_recursion_limit), } @@ -320,6 +526,17 @@ pub enum ListenAddr { UnixSocket(std::path::PathBuf), } +impl ListenAddr { + pub(crate) fn ip_and_port(&self) -> Option<(IpAddr, u16)> { + #[cfg_attr(not(unix), allow(irrefutable_let_patterns))] + if let Self::SocketAddr(addr) = self { + Some((addr.ip(), addr.port())) + } else { + None + } + } +} + impl From for ListenAddr { fn from(addr: SocketAddr) -> Self { Self::SocketAddr(addr) @@ -425,26 +642,10 @@ fn default_cors_methods() -> Vec { vec!["GET".into(), "POST".into(), "OPTIONS".into()] } -fn default_introspection() -> bool { - true -} - -fn default_landing_page() -> bool { - true -} - fn default_graphql_path() -> String { String::from("/") } -fn default_health_check_path() -> String { - String::from("/.well-known/apollo/server-health") -} - -fn default_defer_support() -> bool { - true -} - fn default_parser_recursion_limit() -> usize { // This is `apollo-parser`’s default, which protects against stack overflow // but is still very high for "reasonable" queries. @@ -807,32 +1008,32 @@ pub(crate) fn validate_configuration(raw_yaml: &str) -> Result Additional properties are not allowed ('listen' was unexpected) + +2. /cors/allow_headers/1 # The socket address and port to listen on # Defaults to 127.0.0.1:4000 diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index b6c04dff4a..d1e2fe67be 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -351,6 +351,41 @@ expression: "&schema" }, "additionalProperties": false }, + "health-check": { + "description": "Configuration options pertaining to the http server component.", + "default": { + "listen": "127.0.0.1:9494", + "path": "/health", + "enabled": true + }, + "type": "object", + "properties": { + "enabled": { + "default": true, + "type": "boolean" + }, + "listen": { + "description": "The socket address and port to listen on Defaults to 127.0.0.1:9494", + "default": "127.0.0.1:9494", + "anyOf": [ + { + "description": "Socket address.", + "type": "string" + }, + { + "description": "Unix socket.", + "type": "string" + } + ] + }, + "path": { + "description": "The HTTP path on which GraphQL requests will be served. default: \"/\"", + "default": "/health", + "type": "string" + } + }, + "additionalProperties": false + }, "override_subgraph_url": { "type": "object", "additionalProperties": { @@ -400,15 +435,44 @@ expression: "&schema" }, "additionalProperties": false }, - "server": { + "sandbox": { "description": "Configuration options pertaining to the http server component.", "default": { "listen": "127.0.0.1:4000", - "introspection": true, - "landing_page": true, - "graphql_path": "/", - "health_check_path": "/.well-known/apollo/server-health", - "preview_defer_support": true, + "path": "/", + "enabled": false + }, + "type": "object", + "properties": { + "enabled": { + "default": false, + "type": "boolean" + }, + "listen": { + "description": "The socket address and port to listen on Defaults to 127.0.0.1:4000", + "default": "127.0.0.1:4000", + "anyOf": [ + { + "description": "Socket address.", + "type": "string" + }, + { + "description": "Unix socket.", + "type": "string" + } + ] + }, + "path": { + "description": "The HTTP path on which GraphQL requests will be served. default: \"/\"", + "default": "/", + "type": "string" + } + }, + "additionalProperties": false + }, + "server": { + "description": "Configuration options pertaining to the http server component.", + "default": { "experimental_parser_recursion_limit": 4096 }, "type": "object", @@ -419,24 +483,21 @@ expression: "&schema" "type": "integer", "format": "uint", "minimum": 0.0 - }, - "graphql_path": { - "description": "The HTTP path on which GraphQL requests will be served. default: \"/\"", - "default": "/", - "type": "string" - }, - "health_check_path": { - "description": "healthCheck path default: \"/.well-known/apollo/server-health\"", - "default": "/.well-known/apollo/server-health", - "type": "string" - }, + } + }, + "additionalProperties": false + }, + "supergraph": { + "description": "Configuration options pertaining to the http server component.", + "default": { + "listen": "127.0.0.1:4000", + "path": "/", + "introspection": true, + "preview_defer_support": true + }, + "type": "object", + "properties": { "introspection": { - "description": "introspection queries enabled by default", - "default": true, - "type": "boolean" - }, - "landing_page": { - "description": "display landing page enabled by default", "default": true, "type": "boolean" }, @@ -454,8 +515,12 @@ expression: "&schema" } ] }, + "path": { + "description": "The HTTP path on which GraphQL requests will be served. default: \"/\"", + "default": "/", + "type": "string" + }, "preview_defer_support": { - "description": "Preview @defer directive support default: true", "default": true, "type": "boolean" } diff --git a/apollo-router/src/configuration/testdata/config_basic.router.yaml b/apollo-router/src/configuration/testdata/config_basic.router.yaml index d1b76d4ced..ea2ed7249d 100644 --- a/apollo-router/src/configuration/testdata/config_basic.router.yaml +++ b/apollo-router/src/configuration/testdata/config_basic.router.yaml @@ -1,2 +1,2 @@ -server: +supergraph: listen: 1.2.3.4:5 diff --git a/apollo-router/src/configuration/testdata/config_full.router.yaml b/apollo-router/src/configuration/testdata/config_full.router.yaml index 0a089aa567..6d658bfaae 100644 --- a/apollo-router/src/configuration/testdata/config_full.router.yaml +++ b/apollo-router/src/configuration/testdata/config_full.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 cors: origins: [foo, bar, baz] diff --git a/apollo-router/src/configuration/testdata/invalid_url.router.yaml b/apollo-router/src/configuration/testdata/invalid_url.router.yaml index 6870d47adc..3ea17ea8a5 100644 --- a/apollo-router/src/configuration/testdata/invalid_url.router.yaml +++ b/apollo-router/src/configuration/testdata/invalid_url.router.yaml @@ -1,2 +1,2 @@ -server: +supergraph: listen: 127.0.0.1:0 diff --git a/apollo-router/src/configuration/testdata/supergraph_config.router.yaml b/apollo-router/src/configuration/testdata/supergraph_config.router.yaml index cfc6b13cc8..a8bcd9692f 100644 --- a/apollo-router/src/configuration/testdata/supergraph_config.router.yaml +++ b/apollo-router/src/configuration/testdata/supergraph_config.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: "127.0.0.1:4001" cors: origins: diff --git a/apollo-router/src/configuration/testdata/tracing_apollo.router.yaml b/apollo-router/src/configuration/testdata/tracing_apollo.router.yaml index a1e3db69a9..fcb7356126 100644 --- a/apollo-router/src/configuration/testdata/tracing_apollo.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_apollo.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: apollo: diff --git a/apollo-router/src/configuration/testdata/tracing_apollo_env.router.yaml b/apollo-router/src/configuration/testdata/tracing_apollo_env.router.yaml index 14b5a34f23..ca87c06774 100644 --- a/apollo-router/src/configuration/testdata/tracing_apollo_env.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_apollo_env.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: apollo: diff --git a/apollo-router/src/configuration/testdata/tracing_config.router.yaml b/apollo-router/src/configuration/testdata/tracing_config.router.yaml index 6c8c77df2a..9b071d72ad 100644 --- a/apollo-router/src/configuration/testdata/tracing_config.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_config.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_datadog.router.yaml b/apollo-router/src/configuration/testdata/tracing_datadog.router.yaml index 8c7d10becc..e557b3d4db 100644 --- a/apollo-router/src/configuration/testdata/tracing_datadog.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_datadog.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_datadog_env.router.yaml b/apollo-router/src/configuration/testdata/tracing_datadog_env.router.yaml index f7d23821a1..41e6b12fce 100644 --- a/apollo-router/src/configuration/testdata/tracing_datadog_env.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_datadog_env.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_jaeger_agent.router.yaml b/apollo-router/src/configuration/testdata/tracing_jaeger_agent.router.yaml index da43c9fb0f..de854410b1 100644 --- a/apollo-router/src/configuration/testdata/tracing_jaeger_agent.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_jaeger_agent.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_jaeger_collector.router.yaml b/apollo-router/src/configuration/testdata/tracing_jaeger_collector.router.yaml index 161fabf623..f1c9631995 100644 --- a/apollo-router/src/configuration/testdata/tracing_jaeger_collector.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_jaeger_collector.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_jaeger_collector_env.router.yaml b/apollo-router/src/configuration/testdata/tracing_jaeger_collector_env.router.yaml index 656d52c89c..966fc24091 100644 --- a/apollo-router/src/configuration/testdata/tracing_jaeger_collector_env.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_jaeger_collector_env.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_jaeger_full.router.yaml b/apollo-router/src/configuration/testdata/tracing_jaeger_full.router.yaml index cac9c7f9b4..51c3461cd1 100644 --- a/apollo-router/src/configuration/testdata/tracing_jaeger_full.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_jaeger_full.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_otlp_full.router.yaml b/apollo-router/src/configuration/testdata/tracing_otlp_full.router.yaml index a5c3530c9d..465be8782f 100644 --- a/apollo-router/src/configuration/testdata/tracing_otlp_full.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_otlp_full.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic.router.yaml b/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic.router.yaml index cb67346643..b4e4c9faf2 100644 --- a/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic_env.router.yaml b/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic_env.router.yaml index 10a550abe9..66fd034d17 100644 --- a/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic_env.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_otlp_grpc_basic_env.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/configuration/testdata/tracing_otlp_http_basic.router.yaml b/apollo-router/src/configuration/testdata/tracing_otlp_http_basic.router.yaml index cb67346643..b4e4c9faf2 100644 --- a/apollo-router/src/configuration/testdata/tracing_otlp_http_basic.router.yaml +++ b/apollo-router/src/configuration/testdata/tracing_otlp_http_basic.router.yaml @@ -1,4 +1,4 @@ -server: +supergraph: listen: 1.2.3.4:5 telemetry: tracing: diff --git a/apollo-router/src/introspection.rs b/apollo-router/src/introspection.rs index a0de3d5430..7a65d4fe4a 100644 --- a/apollo-router/src/introspection.rs +++ b/apollo-router/src/introspection.rs @@ -22,7 +22,7 @@ impl Introspection { pub(crate) async fn with_capacity(configuration: &Configuration, capacity: usize) -> Self { Self { cache: CacheStorage::new(capacity).await, - defer_support: configuration.server.preview_defer_support, + defer_support: configuration.supergraph.preview_defer_support, } } diff --git a/apollo-router/src/plugins/csrf.rs b/apollo-router/src/plugins/csrf.rs index 61f6857b9a..55ebc3e6ff 100644 --- a/apollo-router/src/plugins/csrf.rs +++ b/apollo-router/src/plugins/csrf.rs @@ -227,6 +227,7 @@ mod csrf_tests { .unwrap(); } + use http::header::CONTENT_TYPE; use serde_json_bytes::json; use tower::ServiceExt; @@ -237,7 +238,7 @@ mod csrf_tests { async fn it_lets_preflighted_request_pass_through() { let config = CSRFConfig::default(); let with_preflight_content_type = SupergraphRequest::fake_builder() - .header("content-type", "application/json") + .header(CONTENT_TYPE, "application/json") .build() .unwrap(); assert_accepted(config.clone(), with_preflight_content_type).await; @@ -266,13 +267,13 @@ mod csrf_tests { async fn it_rejects_non_preflighted_content_type_request() { let config = CSRFConfig::default(); let non_preflighted_request = SupergraphRequest::fake_builder() - .header("content-type", "text/plain") + .header(CONTENT_TYPE, "text/plain") .build() .unwrap(); assert_rejected(config.clone(), non_preflighted_request).await; let non_preflighted_request = SupergraphRequest::fake_builder() - .header("content-type", "text/plain; charset=utf8") + .header(CONTENT_TYPE, "text/plain; charset=utf8") .build() .unwrap(); assert_rejected(config, non_preflighted_request).await; diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index 4a6760f7f0..45ed665527 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -55,7 +55,7 @@ impl BridgeQueryPlanner { schema.as_string().to_string(), QueryPlannerConfig { incremental_delivery: Some(IncrementalDeliverySupport { - enable_defer: Some(configuration.server.preview_defer_support), + enable_defer: Some(configuration.supergraph.preview_defer_support), }), }, ) diff --git a/apollo-router/src/router.rs b/apollo-router/src/router.rs index 7cb53e2757..b20ad0154c 100644 --- a/apollo-router/src/router.rs +++ b/apollo-router/src/router.rs @@ -1,6 +1,7 @@ #![allow(missing_docs)] // FIXME use std::fs; +use std::net::IpAddr; use std::path::Path; use std::path::PathBuf; use std::pin::Pin; @@ -107,6 +108,9 @@ pub enum ApolloRouterError { /// could not create the HTTP server: {0} ServerCreationError(std::io::Error), + + /// tried to bind {0} and {1} on port {2} + DifferentListenAddrsOnSamePort(IpAddr, IpAddr, u16), } /// The user supplied schema. Either a static string or a stream for hot reloading. diff --git a/apollo-router/src/services/subgraph_service.rs b/apollo-router/src/services/subgraph_service.rs index 7b750def54..da5966e8c1 100644 --- a/apollo-router/src/services/subgraph_service.rs +++ b/apollo-router/src/services/subgraph_service.rs @@ -352,7 +352,7 @@ mod tests { async fn emulate_subgraph_bad_request(socket_addr: SocketAddr) { async fn handle(_request: http::Request) -> Result, Infallible> { Ok(http::Response::builder() - .header("Content-Type", "application/json") + .header(CONTENT_TYPE, "application/json") .status(StatusCode::BAD_REQUEST) .body( serde_json::to_string(&Response { @@ -376,7 +376,7 @@ mod tests { async fn emulate_subgraph_bad_response_format(socket_addr: SocketAddr) { async fn handle(_request: http::Request) -> Result, Infallible> { Ok(http::Response::builder() - .header("Content-Type", "text/html") + .header(CONTENT_TYPE, "text/html") .status(StatusCode::OK) .body(r#"TEST"#.into()) .unwrap()) @@ -424,7 +424,7 @@ mod tests { let compressed_body = encoder.into_inner(); Ok(http::Response::builder() - .header("Content-Type", "application/json") + .header(CONTENT_TYPE, "application/json") .header(CONTENT_ENCODING, "gzip") .status(StatusCode::OK) .body(compressed_body.into()) diff --git a/apollo-router/src/services/supergraph_service.rs b/apollo-router/src/services/supergraph_service.rs index 20d49e247f..402bafb6a8 100644 --- a/apollo-router/src/services/supergraph_service.rs +++ b/apollo-router/src/services/supergraph_service.rs @@ -445,7 +445,7 @@ impl PluggableSupergraphServiceBuilder { .and_then(|x| x.parse().ok()) .unwrap_or(100); - let introspection = if configuration.server.introspection { + let introspection = if configuration.supergraph.introspection { Some(Arc::new(Introspection::new(&configuration).await)) } else { None diff --git a/apollo-router/src/spaceport/server.rs b/apollo-router/src/spaceport/server.rs index 55a0b322ab..e9de562679 100644 --- a/apollo-router/src/spaceport/server.rs +++ b/apollo-router/src/spaceport/server.rs @@ -8,6 +8,7 @@ use bytes::BytesMut; use flate2::write::GzEncoder; use flate2::Compression; use prost::Message; +use reqwest::header::CONTENT_TYPE; use reqwest::Client; use tokio::net::TcpListener; use tokio::sync::mpsc::error::TrySendError; @@ -127,7 +128,7 @@ impl ReportSpaceport { .body(compressed_content) .header("X-Api-Key", key) .header("Content-Encoding", "gzip") - .header("Content-Type", "application/protobuf") + .header(CONTENT_TYPE, "application/protobuf") .header("Accept", "application/json") .header( "User-Agent", diff --git a/apollo-router/src/state_machine.rs b/apollo-router/src/state_machine.rs index ea98f3f88b..aa97014c8f 100644 --- a/apollo-router/src/state_machine.rs +++ b/apollo-router/src/state_machine.rs @@ -545,8 +545,8 @@ mod tests { UpdateSchema(example_schema()), UpdateConfiguration( Configuration::builder() - .server( - crate::configuration::Server::builder() + .supergraph( + crate::configuration::Supergraph::builder() .listen(SocketAddr::from_str("127.0.0.1:4001").unwrap()) .build() ) @@ -785,7 +785,7 @@ mod tests { Ok(HttpServerHandle::new( shutdown_sender, Box::pin(server), - Some(configuration.server.listen.clone()), + Some(configuration.supergraph.listen.clone()), vec![], )) }, diff --git a/apollo-router/src/test_harness.rs b/apollo-router/src/test_harness.rs index c34e9cd424..fd96f5cb41 100644 --- a/apollo-router/src/test_harness.rs +++ b/apollo-router/src/test_harness.rs @@ -44,7 +44,7 @@ pub(crate) mod http_client; /// use tower::util::ServiceExt; /// /// # #[tokio::main] async fn main() -> Result<(), tower::BoxError> { -/// let config = serde_json::json!({"server": {"introspection": false}}); +/// let config = serde_json::json!({"supergraph": { "introspection": false }}); /// let request = supergraph::Request::fake_builder() /// // Request building here /// .build() diff --git a/apollo-router/src/testdata/supergraph_config.yaml b/apollo-router/src/testdata/supergraph_config.yaml index 6870d47adc..3ea17ea8a5 100644 --- a/apollo-router/src/testdata/supergraph_config.yaml +++ b/apollo-router/src/testdata/supergraph_config.yaml @@ -1,2 +1,2 @@ -server: +supergraph: listen: 127.0.0.1:0 diff --git a/apollo-router/tests/integration_tests.rs b/apollo-router/tests/integration_tests.rs index fc9cddbddf..eda1f6381d 100644 --- a/apollo-router/tests/integration_tests.rs +++ b/apollo-router/tests/integration_tests.rs @@ -14,6 +14,7 @@ use apollo_router::plugin::PluginInit; use apollo_router::services::subgraph; use apollo_router::services::supergraph; use http::header::ACCEPT; +use http::header::CONTENT_TYPE; use http::Method; use http::StatusCode; use insta::internals::Content; @@ -220,7 +221,7 @@ async fn queries_should_work_with_compression() { .variable("topProductsFirst", 2_i32) .variable("reviewsForAuthorAuthorId", 1_i32) .method(Method::POST) - .header("content-type", "application/json") + .header(CONTENT_TYPE, "application/json") .header("accept-encoding", "gzip") .build() .expect("expecting valid request"); @@ -535,7 +536,7 @@ async fn query_just_at_recursion_limit() { #[tokio::test(flavor = "multi_thread")] async fn defer_path_with_disabled_config() { let config = serde_json::json!({ - "server": { + "supergraph": { "preview_defer_support": false, }, "plugins": { @@ -725,7 +726,6 @@ async fn query_rust_with_config( async fn setup_router_and_registry( config: serde_json::Value, ) -> (supergraph::BoxCloneService, CountingServiceRegistry) { - let config = serde_json::from_value(config).unwrap(); let counting_registry = CountingServiceRegistry::new(); let telemetry = TelemetryPlugin::new_with_subscriber( serde_json::json!({ diff --git a/apollo-router/tests/jaeger_test.rs b/apollo-router/tests/jaeger_test.rs index d3c0a27bc5..5a4d46c5dd 100644 --- a/apollo-router/tests/jaeger_test.rs +++ b/apollo-router/tests/jaeger_test.rs @@ -8,6 +8,7 @@ use std::path::Path; use std::time::Duration; use std::time::SystemTime; +use http::header::CONTENT_TYPE; use http::Request; use http::Response; use http::StatusCode; @@ -262,7 +263,7 @@ async fn subgraph() { std::str::from_utf8(&body_bytes).unwrap() ); Ok(Response::builder() - .header("Content-Type", "application/json") + .header(CONTENT_TYPE, "application/json") .status(StatusCode::OK) .body( r#"{"data":{"topProducts":[{"name":"Table"},{"name":"Couch"},{"name":"Chair"}]}}"# diff --git a/apollo-router/tests/snapshots/integration_tests__defer_query_without_accept.snap b/apollo-router/tests/snapshots/integration_tests__defer_query_without_accept.snap index 33aaaf7516..49a4aca3d9 100644 --- a/apollo-router/tests/snapshots/integration_tests__defer_query_without_accept.snap +++ b/apollo-router/tests/snapshots/integration_tests__defer_query_without_accept.snap @@ -1,6 +1,5 @@ --- source: apollo-router/tests/integration_tests.rs -assertion_line: 673 expression: first --- { diff --git a/dockerfiles/router.yaml b/dockerfiles/router.yaml index 86da497188..90fd0994d9 100644 --- a/dockerfiles/router.yaml +++ b/dockerfiles/router.yaml @@ -1,5 +1,5 @@ # Configuration of the router's HTTP server # Default configuration for container -server: +graphql: # The socket address and port to listen on listen: 0.0.0.0:4000 diff --git a/docs/source/configuration/health-checks.mdx b/docs/source/configuration/health-checks.mdx index d8e089bb87..d4770d34fe 100644 --- a/docs/source/configuration/health-checks.mdx +++ b/docs/source/configuration/health-checks.mdx @@ -5,9 +5,11 @@ description: Determining the router's status Health checks are often used by load balancers to determine whether a server is available and ready to start serving traffic. -The Apollo Router supports a basic HTTP-level health check. This is enabled by default and is served at the URL path `/.well-known/apollo/server-health`. This returns a `200` status code if the HTTP server is successfully serving. It does not invoke any GraphQL execution machinery. -You can change this path by setting `server.health_check_path`: +The Apollo Router supports a basic HTTP-level health check. This is enabled by default and is served at the URL path `/health`. This returns a `200` status code if the HTTP server is successfully serving. It does not invoke any GraphQL execution machinery. +You can change this path by setting `health-check`: ```yaml title="router.yaml" -server: - health_check_path: /health +health-check: + listen: 127.0.0.1:9494 + path: /health + enabled: true ``` diff --git a/docs/source/configuration/metrics.mdx b/docs/source/configuration/metrics.mdx index 5f4605d8e9..57e1d575f6 100644 --- a/docs/source/configuration/metrics.mdx +++ b/docs/source/configuration/metrics.mdx @@ -19,8 +19,9 @@ telemetry: prometheus: # By setting this endpoint you enable the prometheus exporter # All our endpoints exposed by plugins are namespaced by the name of the plugin - # Then to access to this prometheus endpoint, the full url path will be `/plugins/apollo.telemetry/prometheus` enabled: true + listen: 127.0.0.1:9090 + path: /metrics ``` Assuming you're running locally: diff --git a/docs/source/configuration/overview.mdx b/docs/source/configuration/overview.mdx index 50abe97747..ccaa9dbab2 100644 --- a/docs/source/configuration/overview.mdx +++ b/docs/source/configuration/overview.mdx @@ -220,18 +220,17 @@ By default, the router starts an HTTP server that listens on `127.0.0.1:4000`. Y ```yaml title="router_unix.yaml" # -# server: Configuration of the HTTP server +# supergraph: Configuration of the Supergraph server # -server: +supergraph: # The socket address and port to listen on - # (Defaults to 127.0.0.1:4000) - listen: 127.0.0.1 + listen: 127.0.0.1:4000 ``` The router can also listen on a Unix socket (not supported on Windows): ```yaml title="router_unix.yaml" -server: +supergraph: # Absolute path to a Unix socket listen: /tmp/router.sock ``` @@ -240,16 +239,16 @@ server: By default, the router starts an HTTP server that exposes a `POST`/`GET` endpoint at path `/`. -You can change this path by setting `server.graphql_path`: +You can change this path by setting `server.path`: ```yaml title="router.yaml" # -# server: Configuration of the HTTP server +# supergraph: Configuration of the Supergraph server # -server: +supergraph: # The path for GraphQL execution # (Defaults to /) - graphql_path: /graphql + path: /graphql ``` The path must start with `/`. @@ -267,22 +266,24 @@ By default, the router answers to some introspection queries. You can override t ```yaml title="router.yaml" # -# server: Configuration of the HTTP server +# supergraph: Configuration of the Supergraph server # -server: +supergraph: introspection: false ``` ### Landing page -By default, the router displays a landing page if you access its endpoint path via your browser. You can override this behavior to disable the landing page like so: +By default, the router displays a landing page if you access its endpoint path via your browser. You can override this behavior to disable the landing page by changing the defaults: ```yaml title="router.yaml" # -# server: Configuration of the HTTP server +# router: Configuration of the GraphQL server # -server: - landing_page: false +sandbox: + listen: 127.0.0.1:4000 + path: / + enabled: true ``` ### Subgraph routing URLs @@ -343,7 +344,7 @@ Unix-style expansion is used. For example: Environment variable expansions are valid only for YAML _values_, not keys: ```yaml {4,8} title="router.yaml" -server: +supergraph: listen: "${MY_LISTEN_ADDRESS}" example: password: "${MY_PASSWORD}" diff --git a/docs/source/containerization/kubernetes.mdx b/docs/source/containerization/kubernetes.mdx index a1a3ccdb9a..b2a5c2cd57 100644 --- a/docs/source/containerization/kubernetes.mdx +++ b/docs/source/containerization/kubernetes.mdx @@ -29,7 +29,7 @@ router docker images. You can use helm to install charts from an OCI registry as follows: ```bash -helm install --set router.configuration.telemetry.metrics.prometheus.enabled=true --set managedFederation.apiKey="REDACTED" --set managedFederation.graphRef="REDACTED" --create-namespace --namespace router-deploy router-test oci://ghcr.io/apollographql/helm-charts/router --version 0.1.20 --values router/values.yaml +helm install --set router.configuration.telemetry.metrics.prometheus.enabled=true --set managedFederation.apiKey="REDACTED" --set managedFederation.graphRef="REDACTED" --create-namespace --namespace router-deploy router-test oci://ghcr.io/apollographql/helm-charts/router --version 1.0.0-alpha.3 --values router/values.yaml ``` For more details about using helm with OCI based registries, see [here](https://helm.sh/docs/topics/registries/) @@ -62,39 +62,48 @@ Note: This example is generated using the helm template capability to generate t apiVersion: v1 kind: ServiceAccount metadata: - name: router-test + name: release-name-router labels: + helm.sh/chart: router-1.0.0-alpha.3 app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name app.kubernetes.io/version: "v1.0.0-alpha.3" + app.kubernetes.io/managed-by: Helm --- # Source: router/templates/secret.yaml apiVersion: v1 kind: Secret metadata: - name: router-test + name: "release-name-router" labels: + helm.sh/chart: router-1.0.0-alpha.3 app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name app.kubernetes.io/version: "v1.0.0-alpha.3" + app.kubernetes.io/managed-by: Helm data: - managedFederationApiKey: "REDACTED" + managedFederationApiKey: "UkVEQUNURUQ=" --- # Source: router/templates/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: - name: router-test + name: release-name-router labels: + helm.sh/chart: router-1.0.0-alpha.3 app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name app.kubernetes.io/version: "v1.0.0-alpha.3" + app.kubernetes.io/managed-by: Helm data: configuration.yaml: | - server: + graphql: listen: 0.0.0.0:80 telemetry: metrics: + common: + resources: + service.name: release-name-router prometheus: enabled: true --- @@ -102,11 +111,13 @@ data: apiVersion: v1 kind: Service metadata: - name: router-test + name: release-name-router labels: + helm.sh/chart: router-1.0.0-alpha.3 app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name app.kubernetes.io/version: "v1.0.0-alpha.3" + app.kubernetes.io/managed-by: Helm spec: type: ClusterIP ports: @@ -116,34 +127,37 @@ spec: name: http selector: app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name --- # Source: router/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: - name: router-test + name: release-name-router labels: + helm.sh/chart: router-1.0.0-alpha.3 app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name app.kubernetes.io/version: "v1.0.0-alpha.3" + app.kubernetes.io/managed-by: Helm + annotations: - prometheus.io/path: /plugins/apollo.telemetry/prometheus - prometheus.io/port: "80" + prometheus.io/path: /metrics + prometheus.io/port: 9090 prometheus.io/scrape: "true" spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name template: metadata: labels: app.kubernetes.io/name: router - app.kubernetes.io/instance: router-test + app.kubernetes.io/instance: release-name spec: - serviceAccountName: router-test + serviceAccountName: release-name-router securityContext: {} containers: @@ -160,22 +174,23 @@ spec: - name: APOLLO_KEY valueFrom: secretKeyRef: - name: router-test + name: "release-name-router" key: managedFederationApiKey + optional: true - name: APOLLO_GRAPH_REF - value: "REDACTED" + value: REDACTED ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: - path: /.well-known/apollo/server-health - port: http + path: /health + port: 9494 readinessProbe: httpGet: - path: /.well-known/apollo/server-health - port: http + path: /health + port: 9494 resources: {} volumeMounts: @@ -186,17 +201,38 @@ spec: volumes: - name: router-configuration configMap: - name: router-test + name: release-name-router +--- +# Source: router/templates/tests/test-connection.yaml +apiVersion: v1 +kind: Pod +metadata: + name: "release-name-router-test-connection" + labels: + helm.sh/chart: router-1.0.0-alpha.3 + app.kubernetes.io/name: router + app.kubernetes.io/instance: release-name + app.kubernetes.io/version: "v1.0.0-alpha.3" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['release-name-router:80'] + restartPolicy: Never ``` ## The health endpoint The router supports a health endpoint. You can see from the examples above how it can be used in a kubernetes deployment. -If you had a router running on port 4000 on your localhost, you could exercise the health endpoint as follows: +If you had a router running on port 9090 on your localhost, you could exercise the health endpoint as follows: ```bash -curl "http://localhost:4000/.well-known/apollo/server-health" +curl "http://localhost:9090/health" {"status":"pass"} ``` diff --git a/examples/unix-sockets/router_unix.yaml b/examples/unix-sockets/router_unix.yaml index c2ac809896..09ec4a9d19 100644 --- a/examples/unix-sockets/router_unix.yaml +++ b/examples/unix-sockets/router_unix.yaml @@ -1,2 +1,2 @@ -server: +supergraph: listen: /tmp/router.sock diff --git a/helm/chart/router/Chart.yaml b/helm/chart/router/Chart.yaml index d67f0466c1..03365f4fb7 100644 --- a/helm/chart/router/Chart.yaml +++ b/helm/chart/router/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.24 +version: 1.0.0-alpha.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/helm/chart/router/README.md b/helm/chart/router/README.md index 8ec74e2054..936f9c95b8 100644 --- a/helm/chart/router/README.md +++ b/helm/chart/router/README.md @@ -2,7 +2,7 @@ [router](https://github.com/apollographql/router) Rust Graph Routing runtime for Apollo Federation -![Version: 0.1.24](https://img.shields.io/badge/Version-0.1.24-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.0.0-alpha.3](https://img.shields.io/badge/AppVersion-v1.0.0--alpha.3-informational?style=flat-square) +![Version: 1.0.0-alpha.3](https://img.shields.io/badge/Version-1.0.0--alpha.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.0.0-alpha.3](https://img.shields.io/badge/AppVersion-v1.0.0--alpha.3-informational?style=flat-square) ## Prerequisites @@ -11,7 +11,7 @@ ## Get Repo Info ```console -helm pull oci://ghcr.io/apollographql/helm-charts/router --version 0.1.24 +helm pull oci://ghcr.io/apollographql/helm-charts/router --version 1.0.0-alpha.3 ``` ## Install Chart @@ -19,7 +19,7 @@ helm pull oci://ghcr.io/apollographql/helm-charts/router --version 0.1.24 **Important:** only helm3 is supported ```console -helm upgrade --install [RELEASE_NAME] oci://ghcr.io/apollographql/helm-charts/router --version 0.1.24 --values my-values.yaml +helm upgrade --install [RELEASE_NAME] oci://ghcr.io/apollographql/helm-charts/router --version 1.0.0-alpha.3 --values my-values.yaml ``` _See [configuration](#configuration) below._ @@ -70,7 +70,7 @@ helm show values apollographql/router | resources | object | `{}` | | | rhai | object | `{"input_file":""}` | If using rhai, specify the location of your input file | | rhai.input_file | string | `""` | input rhai file, contents will be stored in a ConfigMap | -| router | object | `{"args":["--hot-reload"],"configuration":{"server":{"listen":"0.0.0.0:80"}}}` | See https://www.apollographql.com/docs/router/configuration/overview#configuration-file for yaml structure | +| router | object | `{"args":["--hot-reload"],"configuration":{"graphql":{"listen":"0.0.0.0:80"}}}` | See https://www.apollographql.com/docs/router/configuration/overview#configuration-file for yaml structure | | securityContext | object | `{}` | | | service.annotations | object | `{}` | | | service.port | int | `80` | | diff --git a/helm/chart/router/templates/deployment.yaml b/helm/chart/router/templates/deployment.yaml index 9827505ecd..b3f6b2ea4e 100644 --- a/helm/chart/router/templates/deployment.yaml +++ b/helm/chart/router/templates/deployment.yaml @@ -7,8 +7,8 @@ metadata: {{/* There may not be much configuration so check that there is something */}} {{- if (((((.Values.router).configuration).telemetry).metrics).prometheus).enabled }} annotations: - prometheus.io/path: /plugins/apollo.telemetry/prometheus - prometheus.io/port: "{{ .Values.containerPorts.http }}" + prometheus.io/path: {{ .Values.router.configuration.telemetry.metrics.prometheus.path | default "/metrics" }} + prometheus.io/port: {{ (splitList ":" (.Values.router.configuration.telemetry.metrics.prometheus.listen | default ":9090")) | last }} prometheus.io/scrape: "true" {{- end }} spec: @@ -85,12 +85,12 @@ spec: protocol: TCP livenessProbe: httpGet: - path: /.well-known/apollo/server-health - port: http + path: {{ (index .Values.router.configuration "health-check").path | default "/health" }} + port: {{ splitList ":" ((index .Values.router.configuration "health-check").listen | default ":9494") | last }} readinessProbe: httpGet: - path: /.well-known/apollo/server-health - port: http + path: {{ (index .Values.router.configuration "health-check").path | default "/health" }} + port: {{ (splitList ":" ((index .Values.router.configuration "health-check").listen | default ":9494")) | last }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- if or .Values.router.configuration .Values.extraVolumeMounts }} diff --git a/helm/chart/router/values.yaml b/helm/chart/router/values.yaml index 08ca5fd715..59fa8cc87d 100644 --- a/helm/chart/router/values.yaml +++ b/helm/chart/router/values.yaml @@ -7,7 +7,7 @@ replicaCount: 1 # -- See https://www.apollographql.com/docs/router/configuration/overview#configuration-file for yaml structure router: configuration: - server: + graphql: listen: 0.0.0.0:80 args: - --hot-reload diff --git a/licenses.html b/licenses.html index a9ed911147..6ac427c693 100644 --- a/licenses.html +++ b/licenses.html @@ -44,7 +44,7 @@

Third Party Licenses

Overview of licenses:

    -
  • MIT License (79)
  • +
  • MIT License (80)
  • Apache License 2.0 (53)
  • BSD 3-Clause "New" or "Revised" License (8)
  • ISC License (8)
  • @@ -12292,6 +12292,35 @@

    Used by:

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 Apollo GraphQL
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE