From f4032c49bb4e0ab42dbcab0624f48f62302e5e0f Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 14 Mar 2024 16:24:58 +0100 Subject: [PATCH 01/68] temporarily make build scripts faster --- crates/rover-client/Cargo.toml | 12 ++++++------ crates/rover-client/build.rs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 8be0b2f55..6b5ee286d 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -42,13 +42,13 @@ regex = { workspace = true } [build-dependencies] anyhow = { workspace = true } camino = { workspace = true } -rover-std = { workspace = true } +#rover-std = { workspace = true } serde_json = { workspace = true } -reqwest = { workspace = true, features = [ - "json", - "blocking", - "native-tls-vendored", -] } +#reqwest = { workspace = true, features = [ +# "json", +# "blocking", +# "native-tls-vendored", +#] } [dev-dependencies] indoc = { workspace = true} diff --git a/crates/rover-client/build.rs b/crates/rover-client/build.rs index ff9861a59..0f79309cb 100644 --- a/crates/rover-client/build.rs +++ b/crates/rover-client/build.rs @@ -1,9 +1,9 @@ use anyhow::{Context, Result}; -use rover_std::Fs; +//use rover_std::Fs; fn main() -> Result<()> { println!("cargo:rerun-if-changed=.schema/schema.graphql"); - Fs::read_file(".schema/schema.graphql") - .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; + //Fs::read_file(".schema/schema.graphql") + // .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; Ok(()) } From 9d19548df86f94a8d1c451aeee38f87f66b77c2e Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 14 Mar 2024 17:21:41 +0100 Subject: [PATCH 02/68] move to an async reqwest client --- Cargo.lock | 5 + Cargo.toml | 3 + crates/rover-client/Cargo.toml | 1 + crates/rover-client/src/blocking/client.rs | 110 ++++++++++-------- .../src/blocking/studio_client.rs | 8 +- .../operations/config/is_federated/runner.rs | 4 +- .../src/operations/config/who_am_i/runner.rs | 4 +- .../operations/contract/describe/runner.rs | 4 +- .../src/operations/contract/publish/runner.rs | 4 +- .../src/operations/graph/check/runner.rs | 4 +- .../operations/graph/check_workflow/runner.rs | 4 +- .../src/operations/graph/delete/runner.rs | 20 ++-- .../src/operations/graph/fetch/runner.rs | 4 +- .../src/operations/graph/introspect/runner.rs | 18 +-- .../src/operations/graph/lint/runner.rs | 6 +- .../src/operations/graph/publish/runner.rs | 4 +- .../src/operations/graph/variant/runner.rs | 4 +- .../src/operations/license/fetch/runner.rs | 7 +- .../persisted_queries/name/runner.rs | 4 +- .../persisted_queries/publish/runner.rs | 4 +- .../persisted_queries/resolve/runner.rs | 6 +- .../src/operations/readme/fetch/runner.rs | 4 +- .../src/operations/readme/publish/runner.rs | 4 +- .../src/operations/subgraph/check/runner.rs | 6 +- .../subgraph/check_workflow/runner.rs | 4 +- .../src/operations/subgraph/delete/runner.rs | 4 +- .../src/operations/subgraph/fetch/runner.rs | 6 +- .../operations/subgraph/introspect/runner.rs | 6 +- .../src/operations/subgraph/lint/runner.rs | 24 ++-- .../src/operations/subgraph/list/runner.rs | 4 +- .../src/operations/subgraph/publish/runner.rs | 8 +- .../operations/subgraph/routing_url/runner.rs | 4 +- .../src/operations/supergraph/fetch/runner.rs | 4 +- crates/rover-client/src/releases.rs | 18 ++- installers/binstall/src/install.rs | 19 +-- src/bin/rover.rs | 4 +- src/cli.rs | 40 ++++--- src/command/config/mod.rs | 4 +- src/command/config/whoami.rs | 4 +- src/command/contract/describe.rs | 5 +- src/command/contract/mod.rs | 6 +- src/command/contract/publish.rs | 5 +- src/command/dev/compose.rs | 8 +- src/command/dev/do_dev.rs | 14 ++- src/command/dev/introspect.rs | 16 +-- src/command/dev/protocol/leader.rs | 69 +++++++---- src/command/dev/router/command.rs | 4 +- src/command/dev/router/runner.rs | 47 +++++--- src/command/dev/schema.rs | 4 +- src/command/dev/watcher.rs | 28 ++--- src/command/graph/check.rs | 8 +- src/command/graph/delete.rs | 5 +- src/command/graph/fetch.rs | 5 +- src/command/graph/introspect.rs | 14 ++- src/command/graph/lint.rs | 5 +- src/command/graph/mod.rs | 16 +-- src/command/graph/publish.rs | 5 +- src/command/install/mod.rs | 8 +- src/command/install/plugin.rs | 57 +++++---- src/command/license/fetch.rs | 5 +- src/command/license/mod.rs | 4 +- src/command/persisted_queries/mod.rs | 4 +- src/command/persisted_queries/publish.rs | 8 +- src/command/readme/fetch.rs | 5 +- src/command/readme/mod.rs | 6 +- src/command/readme/publish.rs | 4 +- src/command/subgraph/check.rs | 6 +- src/command/subgraph/delete.rs | 6 +- src/command/subgraph/fetch.rs | 5 +- src/command/subgraph/introspect.rs | 14 ++- src/command/subgraph/lint.rs | 4 +- src/command/subgraph/list.rs | 5 +- src/command/subgraph/mod.rs | 18 +-- src/command/subgraph/publish.rs | 14 ++- src/command/supergraph/compose/do_compose.rs | 50 ++++---- src/command/supergraph/fetch.rs | 5 +- src/command/supergraph/mod.rs | 6 +- src/command/supergraph/resolve_config.rs | 4 +- src/command/template/mod.rs | 4 +- src/command/template/use.rs | 4 +- src/command/update/check.rs | 6 +- src/command/update/mod.rs | 4 +- src/options/introspect.rs | 6 +- src/options/template.rs | 9 +- src/utils/client.rs | 4 +- src/utils/telemetry.rs | 2 +- src/utils/version.rs | 12 +- 87 files changed, 534 insertions(+), 414 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 561504096..d0c010a89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,9 +577,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ + "futures-core", "getrandom", "instant", + "pin-project-lite", "rand 0.8.5", + "tokio", ] [[package]] @@ -4181,6 +4184,7 @@ dependencies = [ "tempdir", "termimad", "timber", + "tokio", "toml", "tracing", "url", @@ -4218,6 +4222,7 @@ dependencies = [ "serde_json", "strip-ansi-escapes", "thiserror", + "tokio", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index 3f325addc..38ebbf329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -186,6 +186,9 @@ tracing = { workspace = true } which = { workspace = true } uuid = { workspace = true } url = { workspace = true, features = ["serde"] } +tokio = { workspace = true, features = ["rt", "macros"] } +futures.workspace = true +backoff = { workspace = true, features = ["tokio"] } [dev-dependencies] assert_cmd = { workspace = true } diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 6b5ee286d..0a531bc31 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -38,6 +38,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } regex = { workspace = true } +tokio = { workspace = true, features = ["rt", "macros"] } [build-dependencies] anyhow = { workspace = true } diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index 1361796d2..a19193196 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -2,9 +2,8 @@ use crate::error::{EndpointKind, RoverClientError}; use graphql_client::{Error as GraphQLError, GraphQLQuery, Response as GraphQLResponse}; use reqwest::{ - blocking::{Client as ReqwestClient, Response}, header::{HeaderMap, HeaderValue}, - StatusCode, + Client as ReqwestClient, Response, StatusCode, }; pub(crate) const JSON_CONTENT_TYPE: &str = "application/json"; @@ -34,7 +33,7 @@ impl GraphQLClient { /// /// Takes one argument, `variables`. Returns an optional response. /// Automatically retries requests. - pub fn post( + pub async fn post( &self, variables: Q::Variables, header_map: &mut HeaderMap, @@ -45,15 +44,17 @@ impl GraphQLClient { { let request_body = self.get_request_body::(variables)?; header_map.append("Content-Type", HeaderValue::from_str(JSON_CONTENT_TYPE)?); - let response = self.execute(request_body, header_map, true, endpoint_kind); - GraphQLClient::handle_response::(response?, endpoint_kind) + let response = self + .execute(request_body, header_map, true, endpoint_kind) + .await; + GraphQLClient::handle_response::(response?, endpoint_kind).await } /// Client method for making a GraphQL request. /// /// Takes one argument, `variables`. Returns an optional response. /// Does not automatically retry requests. - pub fn post_no_retry( + pub async fn post_no_retry( &self, variables: Q::Variables, header_map: &mut HeaderMap, @@ -64,8 +65,10 @@ impl GraphQLClient { { let request_body = self.get_request_body::(variables)?; header_map.append("Content-Type", HeaderValue::from_str(JSON_CONTENT_TYPE)?); - let response = self.execute(request_body, header_map, false, endpoint_kind); - GraphQLClient::handle_response::(response?, endpoint_kind) + let response = self + .execute(request_body, header_map, false, endpoint_kind) + .await; + GraphQLClient::handle_response::(response?, endpoint_kind).await } fn get_request_body( @@ -76,24 +79,25 @@ impl GraphQLClient { Ok(serde_json::to_string(&body)?) } - fn execute( + async fn execute( &self, request_body: String, header_map: &HeaderMap, should_retry: bool, endpoint_kind: EndpointKind, ) -> Result { - use backoff::{retry, Error as BackoffError, ExponentialBackoff}; + use backoff::{future::retry, Error as BackoffError, ExponentialBackoff}; tracing::trace!(request_headers = ?header_map); tracing::debug!("Request Body: {}", request_body); - let graphql_operation = || { + let graphql_operation = || async { let response = self .client .post(&self.graphql_endpoint) .headers(header_map.clone()) .body(request_body.clone()) - .send(); + .send() + .await; match response { Err(client_error) => { @@ -129,7 +133,7 @@ impl GraphQLClient { || response_status.is_redirection() { if matches!(response_status, StatusCode::BAD_REQUEST) { - if let Ok(text) = success.text() { + if let Ok(text) = success.text().await { tracing::debug!("{}", text); } Err(BackoffError::Permanent(status_error)) @@ -166,7 +170,7 @@ impl GraphQLClient { }, }) } else { - graphql_operation().map_err(|e| match e { + graphql_operation().await.map_err(|e| match e { BackoffError::Permanent(reqwest_error) | BackoffError::Transient { err: reqwest_error, @@ -187,13 +191,13 @@ impl GraphQLClient { /// body.data, it will also error, as this shouldn't be possible. /// /// If successful, it will return body.data, unwrapped - pub(crate) fn handle_response( + pub(crate) async fn handle_response( response: Response, endpoint_kind: EndpointKind, ) -> Result { let response_status = response.status(); tracing::debug!(response_status = ?response_status, response_headers = ?response.headers()); - match response.json::>() { + match response.json::>().await { Ok(response_body) => { if let Some(response_body_errors) = response_body.errors { handle_graphql_body_errors(response_body_errors)?; @@ -310,8 +314,8 @@ mod tests { assert_eq!(actual_error, expected_error); } - #[test] - fn test_successful_response() { + #[tokio::test] + async fn test_successful_response() { let server = MockServer::start(); let success_path = "/throw-me-a-frickin-bone-here"; let success_mock = server.mock(|when, then| { @@ -322,12 +326,14 @@ mod tests { let client = ReqwestClient::new(); let graphql_client = GraphQLClient::new(&server.url(success_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = success_mock.hits(); @@ -335,8 +341,8 @@ mod tests { assert!(response.is_ok()) } - #[test] - fn test_unrecoverable_server_error() { + #[tokio::test] + async fn test_unrecoverable_server_error() { let server = MockServer::start(); let internal_server_error_path = "/this-is-me-in-a-nutshell"; let internal_server_error_mock = server.mock(|when, then| { @@ -347,12 +353,14 @@ mod tests { let client = ReqwestClient::new(); let graphql_client = GraphQLClient::new(&server.url(internal_server_error_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = internal_server_error_mock.hits(); @@ -360,8 +368,8 @@ mod tests { assert!(response.is_err()); } - #[test] - fn test_unrecoverable_client_error() { + #[tokio::test] + async fn test_unrecoverable_client_error() { let server = MockServer::start(); let not_found_path = "/austin-powers-the-musical"; let not_found_mock = server.mock(|when, then| { @@ -372,12 +380,14 @@ mod tests { let client = ReqwestClient::new(); let graphql_client = GraphQLClient::new(&server.url(not_found_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = not_found_mock.hits(); @@ -387,8 +397,8 @@ mod tests { assert!(error.to_string().contains("Not Found")); } - #[test] - fn test_timeout_error() { + #[tokio::test] + async fn test_timeout_error() { let server = MockServer::start(); let timeout_path = "/i-timeout-easily"; let timeout_mock = server.mock(|when, then| { @@ -398,18 +408,20 @@ mod tests { .delay(Duration::from_secs(3)); }); - let client = reqwest::blocking::ClientBuilder::new() + let client = reqwest::ClientBuilder::new() .timeout(Duration::from_secs(1)) .build() .unwrap(); let graphql_client = GraphQLClient::new(&server.url(timeout_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = timeout_mock.hits(); diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index 133e1ae00..1a709e46d 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -7,8 +7,8 @@ use crate::{ use houston::{Credential, CredentialOrigin}; use graphql_client::GraphQLQuery; -use reqwest::blocking::Client as ReqwestClient; use reqwest::header::{HeaderMap, HeaderValue}; +use reqwest::Client as ReqwestClient; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { @@ -40,26 +40,28 @@ impl StudioClient { /// /// Takes one argument, `variables`. Returns a Response or a RoverClientError. /// Automatically retries requests. - pub fn post( + pub async fn post( &self, variables: Q::Variables, ) -> Result { let mut header_map = self.build_studio_headers()?; self.client .post::(variables, &mut header_map, EndpointKind::ApolloStudio) + .await } /// Client method for making a GraphQL request to Apollo Studio. /// /// Takes one argument, `variables`. Returns a Response or a RoverClientError. /// Does not automatically retry requests. - pub fn post_no_retry( + pub async fn post_no_retry( &self, variables: Q::Variables, ) -> Result { let mut header_map = self.build_studio_headers()?; self.client .post_no_retry::(variables, &mut header_map, EndpointKind::ApolloStudio) + .await } /// Function for building a [HeaderMap] for making http requests. Use for making diff --git a/crates/rover-client/src/operations/config/is_federated/runner.rs b/crates/rover-client/src/operations/config/is_federated/runner.rs index 52d928ff7..4f0e5418b 100644 --- a/crates/rover-client/src/operations/config/is_federated/runner.rs +++ b/crates/rover-client/src/operations/config/is_federated/runner.rs @@ -19,12 +19,12 @@ use crate::RoverClientError; /// Snake case of this name is the mod name. i.e. publish_partial_schema_mutation pub(crate) struct IsFederatedGraph; -pub(crate) fn run( +pub(crate) async fn run( input: IsFederatedInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_ref) } diff --git a/crates/rover-client/src/operations/config/who_am_i/runner.rs b/crates/rover-client/src/operations/config/who_am_i/runner.rs index c6e42349e..23c6eceee 100644 --- a/crates/rover-client/src/operations/config/who_am_i/runner.rs +++ b/crates/rover-client/src/operations/config/who_am_i/runner.rs @@ -25,11 +25,11 @@ pub(crate) struct ConfigWhoAmIQuery; /// Get info from the registry about an API key, i.e. the name/id of the /// user/graph and what kind of key it is (GRAPH/USER/Other) -pub fn run( +pub async fn run( input: ConfigWhoAmIInput, client: &StudioClient, ) -> Result { - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; get_identity_from_response_data(response_data, client.get_credential_origin()) } diff --git a/crates/rover-client/src/operations/contract/describe/runner.rs b/crates/rover-client/src/operations/contract/describe/runner.rs index cc3148d47..0eb75accf 100644 --- a/crates/rover-client/src/operations/contract/describe/runner.rs +++ b/crates/rover-client/src/operations/contract/describe/runner.rs @@ -20,12 +20,12 @@ use crate::RoverClientError; pub(crate) struct ContractDescribeQuery; /// Fetches the description of the configuration for a given contract variant -pub fn run( +pub async fn run( input: ContractDescribeInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let root_url = response_data.frontend_url_root.clone(); let description = get_description_from_response_data(response_data, graph_ref.clone())?; Ok(ContractDescribeResponse { diff --git a/crates/rover-client/src/operations/contract/publish/runner.rs b/crates/rover-client/src/operations/contract/publish/runner.rs index 37406f046..c5cc8b2b1 100644 --- a/crates/rover-client/src/operations/contract/publish/runner.rs +++ b/crates/rover-client/src/operations/contract/publish/runner.rs @@ -20,13 +20,13 @@ use crate::RoverClientError; pub(crate) struct ContractPublishMutation; /// Fetches the description of the configuration for a given contract variant -pub fn run( +pub async fn run( input: ContractPublishInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); let no_launch = input.no_launch; - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let publish_response = get_publish_response_from_response_data(response_data, graph_ref, no_launch)?; Ok(publish_response) diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs index 85fb84d6e..e8d151071 100644 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -24,12 +24,12 @@ pub(crate) struct GraphCheckMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: CheckSchemaAsyncInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; get_check_response_from_data(data, graph_ref) } diff --git a/crates/rover-client/src/operations/graph/check_workflow/runner.rs b/crates/rover-client/src/operations/graph/check_workflow/runner.rs index 93d1f5c31..bc754d09b 100644 --- a/crates/rover-client/src/operations/graph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/graph/check_workflow/runner.rs @@ -37,7 +37,7 @@ pub(crate) struct GraphCheckWorkflowQuery; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: CheckWorkflowInput, client: &StudioClient, ) -> Result { @@ -45,7 +45,7 @@ pub fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()); + let result = client.post::(input.clone().into()).await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/graph/delete/runner.rs b/crates/rover-client/src/operations/graph/delete/runner.rs index 2c4a5f2f8..442c94ec3 100644 --- a/crates/rover-client/src/operations/graph/delete/runner.rs +++ b/crates/rover-client/src/operations/graph/delete/runner.rs @@ -21,23 +21,27 @@ pub(crate) struct GraphDeleteMutation; /// The main function to be used from this module. /// This function deletes a single graph variant from the graph registry -pub fn run(input: GraphDeleteInput, client: &StudioClient) -> Result<(), RoverClientError> { +pub async fn run(input: GraphDeleteInput, client: &StudioClient) -> Result<(), RoverClientError> { let graph_ref = input.graph_ref.clone(); - let response_data = client - .post::(input.into()) - .map_err(|e| { + let response_data = match client.post::(input.into()).await { + Ok(data) => data, + Err(e) => { if e.to_string().contains("Variant not found") { if let Err(no_variant_err) = variant::run( VariantListInput { graph_ref: graph_ref.clone(), }, client, - ) { - return no_variant_err; + ) + .await + { + return Err(no_variant_err); } } - e - })?; + return Err(e); + } + }; + let graph = response_data.graph.ok_or(RoverClientError::GraphNotFound { graph_ref: graph_ref.clone(), })?; diff --git a/crates/rover-client/src/operations/graph/fetch/runner.rs b/crates/rover-client/src/operations/graph/fetch/runner.rs index ded79af89..5f643bc18 100644 --- a/crates/rover-client/src/operations/graph/fetch/runner.rs +++ b/crates/rover-client/src/operations/graph/fetch/runner.rs @@ -25,12 +25,12 @@ pub(crate) struct GraphFetchQuery; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( +pub async fn run( input: GraphFetchInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let sdl_contents = get_schema_from_response_data(response_data, graph_ref)?; Ok(FetchResponse { sdl: Sdl { diff --git a/crates/rover-client/src/operations/graph/introspect/runner.rs b/crates/rover-client/src/operations/graph/introspect/runner.rs index 71ab02e60..f6f5655b7 100644 --- a/crates/rover-client/src/operations/graph/introspect/runner.rs +++ b/crates/rover-client/src/operations/graph/introspect/runner.rs @@ -22,7 +22,7 @@ pub(crate) struct GraphIntrospectQuery; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( +pub async fn run( input: GraphIntrospectInput, client: &GraphQLClient, should_retry: bool, @@ -36,13 +36,17 @@ pub fn run( ); } let response_data = if should_retry { - client.post::(variables, &mut header_map, EndpointKind::Customer) + client + .post::(variables, &mut header_map, EndpointKind::Customer) + .await } else { - client.post_no_retry::( - variables, - &mut header_map, - EndpointKind::Customer, - ) + client + .post_no_retry::( + variables, + &mut header_map, + EndpointKind::Customer, + ) + .await }?; build_response(response_data) diff --git a/crates/rover-client/src/operations/graph/lint/runner.rs b/crates/rover-client/src/operations/graph/lint/runner.rs index 2d9356e33..d46f99d1f 100644 --- a/crates/rover-client/src/operations/graph/lint/runner.rs +++ b/crates/rover-client/src/operations/graph/lint/runner.rs @@ -27,7 +27,7 @@ pub(crate) struct LintGraphMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run(input: LintGraphInput, client: &StudioClient) -> Result { +pub async fn run(input: LintGraphInput, client: &StudioClient) -> Result { let graph_ref = input.graph_ref.clone(); let base_schema = if input.ignore_existing { @@ -36,7 +36,7 @@ pub fn run(input: LintGraphInput, client: &StudioClient) -> Result Result Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; let publish_response = get_publish_response_from_data(data, graph_ref)?; build_response(publish_response) } diff --git a/crates/rover-client/src/operations/graph/variant/runner.rs b/crates/rover-client/src/operations/graph/variant/runner.rs index c54a14888..d9570d82c 100644 --- a/crates/rover-client/src/operations/graph/variant/runner.rs +++ b/crates/rover-client/src/operations/graph/variant/runner.rs @@ -18,9 +18,9 @@ pub(crate) struct VariantListQuery; /// The main function to be used from this module. /// This function lists all the variants for a given graph ref -pub fn run(input: VariantListInput, client: &StudioClient) -> Result<(), RoverClientError> { +pub async fn run(input: VariantListInput, client: &StudioClient) -> Result<(), RoverClientError> { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let graph = response_data.graph.ok_or(RoverClientError::GraphNotFound { graph_ref: graph_ref.clone(), })?; diff --git a/crates/rover-client/src/operations/license/fetch/runner.rs b/crates/rover-client/src/operations/license/fetch/runner.rs index 6dbf04f43..b0aa188b6 100644 --- a/crates/rover-client/src/operations/license/fetch/runner.rs +++ b/crates/rover-client/src/operations/license/fetch/runner.rs @@ -19,9 +19,12 @@ use graphql_client::*; pub(crate) struct LicenseFetchQuery; /// The main function to be used from this module. This function fetches an offline license if permitted to do so. -pub fn run(input: LicenseFetchInput, client: &StudioClient) -> Result { +pub async fn run( + input: LicenseFetchInput, + client: &StudioClient, +) -> Result { let graph_id = input.graph_id.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let license = get_license_response_from_data(response_data, &graph_id)?; Ok(license) } diff --git a/crates/rover-client/src/operations/persisted_queries/name/runner.rs b/crates/rover-client/src/operations/persisted_queries/name/runner.rs index 17de315d8..806537a6d 100644 --- a/crates/rover-client/src/operations/persisted_queries/name/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/name/runner.rs @@ -16,13 +16,13 @@ use graphql_client::*; )] pub struct PersistedQueryListNameQuery; -pub fn run( +pub async fn run( input: PersistedQueryListNameInput, client: &StudioClient, ) -> Result { let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_id, list_id) } diff --git a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs index b5a6e9243..e13af29ca 100644 --- a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs @@ -19,14 +19,14 @@ type GraphQLDocument = String; )] pub struct PublishOperationsMutation; -pub fn run( +pub async fn run( input: PersistedQueriesPublishInput, client: &StudioClient, ) -> Result { let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); let total_operations = input.operation_manifest.operations.len(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_id, list_id, total_operations) } diff --git a/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs b/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs index 23300366d..7dafd29d9 100644 --- a/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs @@ -15,12 +15,14 @@ use graphql_client::*; )] pub struct ResolvePersistedQueryListQuery; -pub fn run( +pub async fn run( input: ResolvePersistedQueryListInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client + .post::(input.into()) + .await?; build_response(data, graph_ref) } diff --git a/crates/rover-client/src/operations/readme/fetch/runner.rs b/crates/rover-client/src/operations/readme/fetch/runner.rs index 447a94a2f..f734ccb08 100644 --- a/crates/rover-client/src/operations/readme/fetch/runner.rs +++ b/crates/rover-client/src/operations/readme/fetch/runner.rs @@ -18,12 +18,12 @@ type Timestamp = String; )] pub struct ReadmeFetchQuery; -pub fn run( +pub async fn run( input: ReadmeFetchInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_ref) } diff --git a/crates/rover-client/src/operations/readme/publish/runner.rs b/crates/rover-client/src/operations/readme/publish/runner.rs index 10208f254..2b0227373 100644 --- a/crates/rover-client/src/operations/readme/publish/runner.rs +++ b/crates/rover-client/src/operations/readme/publish/runner.rs @@ -19,12 +19,12 @@ type Timestamp = String; pub struct ReadmePublishMutation; -pub fn run( +pub async fn run( input: ReadmePublishInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response = client.post::(input.into())?; + let response = client.post::(input.into()).await?; build_response(response, graph_ref) } diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 49785b0d0..69cc59f65 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -27,7 +27,7 @@ pub(crate) struct SubgraphCheckMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: SubgraphCheckAsyncInput, client: &StudioClient, ) -> Result { @@ -38,14 +38,14 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, can_operation_convert: false, }); } - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; get_check_response_from_data(data, graph_ref) } diff --git a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs index 55a6683c3..20a439738 100644 --- a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs @@ -43,7 +43,7 @@ pub(crate) struct SubgraphCheckWorkflowQuery; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: CheckWorkflowInput, subgraph: String, client: &StudioClient, @@ -52,7 +52,7 @@ pub fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()); + let result = client.post::(input.clone().into()).await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs index 7a11a9795..324378bd1 100644 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -23,12 +23,12 @@ pub(crate) struct SubgraphDeleteMutation; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( +pub async fn run( input: SubgraphDeleteInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let data = get_delete_data_from_response(response_data, graph_ref)?; Ok(build_response(data)) } diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index 006360517..fdd3b38d7 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -21,7 +21,7 @@ use graphql_client::*; pub(crate) struct SubgraphFetchQuery; /// Fetches a schema from apollo studio and returns its SDL (String) -pub fn run( +pub async fn run( input: SubgraphFetchInput, client: &StudioClient, ) -> Result { @@ -31,7 +31,7 @@ pub fn run( graph_ref: input.graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref: input.graph_ref, @@ -39,7 +39,7 @@ pub fn run( }); } let variables = input.clone().into(); - let response_data = client.post::(variables)?; + let response_data = client.post::(variables).await?; get_sdl_from_response_data(input, response_data) } diff --git a/crates/rover-client/src/operations/subgraph/introspect/runner.rs b/crates/rover-client/src/operations/subgraph/introspect/runner.rs index 0159162d4..ac3d38eee 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/runner.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/runner.rs @@ -17,7 +17,7 @@ use graphql_client::*; pub(crate) struct SubgraphIntrospectQuery; -pub fn run( +pub async fn run( input: SubgraphIntrospectInput, client: &GraphQLClient, should_retry: bool, @@ -34,13 +34,13 @@ pub fn run( input.into(), &mut header_map, EndpointKind::Customer, - ) + ).await } else { client.post_no_retry::( input.into(), &mut header_map, EndpointKind::Customer, - ) + ).await }; match response_data { diff --git a/crates/rover-client/src/operations/subgraph/lint/runner.rs b/crates/rover-client/src/operations/subgraph/lint/runner.rs index edce4e5e5..330b2ecea 100644 --- a/crates/rover-client/src/operations/subgraph/lint/runner.rs +++ b/crates/rover-client/src/operations/subgraph/lint/runner.rs @@ -29,7 +29,7 @@ pub(crate) struct LintSubgraphMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: LintSubgraphInput, client: &StudioClient, ) -> Result { @@ -40,7 +40,7 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, @@ -55,20 +55,22 @@ pub fn run( subgraph_name: input.subgraph_name, }, client, - )?; + ).await?; Some(fetch_response.sdl.contents) } else { None }; - let data = client.post::( - LintSubgraphMutationInput { - graph_ref, - proposed_schema: input.proposed_schema.clone(), - base_schema, - } - .into(), - )?; + let data = client + .post::( + LintSubgraphMutationInput { + graph_ref, + proposed_schema: input.proposed_schema.clone(), + base_schema, + } + .into(), + ) + .await?; get_lint_response_from_result( data, diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs index c7aca0ae2..c78f9bb7d 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -22,12 +22,12 @@ type Timestamp = String; pub(crate) struct SubgraphListQuery; /// Fetches list of subgraphs for a given graph, returns name & url of each -pub fn run( +pub async fn run( input: SubgraphListInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let root_url = response_data.frontend_url_root.clone(); let subgraphs = get_subgraphs_from_response_data(response_data, graph_ref.clone())?; Ok(SubgraphListResponse { diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index d9ffe81d0..d4ba95d55 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -26,7 +26,7 @@ use apollo_federation_types::build::{BuildError, BuildErrors}; /// Snake case of this name is the mod name. i.e. subgraph_publish_mutation pub(crate) struct SubgraphPublishMutation; -pub fn run( +pub async fn run( input: SubgraphPublishInput, client: &StudioClient, ) -> Result { @@ -44,7 +44,7 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - ) + ).await .is_ok(); if variant_exists { @@ -54,7 +54,7 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { @@ -70,7 +70,7 @@ pub fn run( ); } } - let data = client.post::(variables)?; + let data = client.post::(variables).await?; let publish_response = get_publish_response_from_data(data, graph_ref)?; Ok(build_response(publish_response)) } diff --git a/crates/rover-client/src/operations/subgraph/routing_url/runner.rs b/crates/rover-client/src/operations/subgraph/routing_url/runner.rs index 567c1d033..a4846a898 100644 --- a/crates/rover-client/src/operations/subgraph/routing_url/runner.rs +++ b/crates/rover-client/src/operations/subgraph/routing_url/runner.rs @@ -19,12 +19,12 @@ use graphql_client::*; pub(crate) struct SubgraphRoutingUrlQuery; /// Fetches a subgraph's routing URL (String) -pub fn run( +pub async fn run( input: SubgraphRoutingUrlInput, client: &StudioClient, ) -> Result { let variables = input.clone().into(); - let response_data = client.post::(variables)?; + let response_data = client.post::(variables).await?; get_routing_url_from_response_data(input, response_data) } diff --git a/crates/rover-client/src/operations/supergraph/fetch/runner.rs b/crates/rover-client/src/operations/supergraph/fetch/runner.rs index 1e2d4da51..b96bb295d 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/supergraph/fetch/runner.rs @@ -26,12 +26,12 @@ pub(crate) struct SupergraphFetchQuery; /// The main function to be used from this module. This function fetches a /// core schema from apollo studio -pub fn run( +pub async fn run( input: SupergraphFetchInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; get_supergraph_sdl_from_response_data(response_data, graph_ref) } diff --git a/crates/rover-client/src/releases.rs b/crates/rover-client/src/releases.rs index 19879fe24..eda181804 100644 --- a/crates/rover-client/src/releases.rs +++ b/crates/rover-client/src/releases.rs @@ -1,21 +1,19 @@ use crate::{error::EndpointKind, RoverClientError}; -use reqwest::blocking::Client; +use reqwest::Client; pub use semver::Version; const LATEST_RELEASE_URL: &str = "https://github.com/apollographql/rover/releases/latest"; /// Looks up and parses the latest release version -pub fn get_latest_release(client: Client) -> Result { +pub async fn get_latest_release(client: Client) -> Result { // send a request to the latest GitHub release - let response = - client - .head(LATEST_RELEASE_URL) - .send() - .map_err(|e| RoverClientError::SendRequest { - source: e, - endpoint_kind: EndpointKind::Orbiter, - })?; + let response = client.head(LATEST_RELEASE_URL).send().await.map_err(|e| { + RoverClientError::SendRequest { + source: e, + endpoint_kind: EndpointKind::Orbiter, + } + })?; // this will return a response with a redirect to the latest tagged release let url_path_segments = response diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index e509093fc..7143e779e 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -41,11 +41,11 @@ impl Installer { /// Checks if a binary already exists, and if it does not, /// downloads a plugin tarball from a URL, extracts the binary, /// and puts it in the `bin` directory for the main tool - pub fn install_plugin( + pub async fn install_plugin( &self, plugin_name: &str, plugin_tarball_url: &str, - client: &reqwest::blocking::Client, + client: &reqwest::Client, is_latest: bool, ) -> Result, InstallerError> { let version = self.get_plugin_version(plugin_tarball_url, is_latest)?; @@ -63,8 +63,9 @@ impl Installer { return Ok(None); } - let plugin_bin_path = - self.extract_plugin_tarball(plugin_name, plugin_tarball_url, client)?; + let plugin_bin_path = self + .extract_plugin_tarball(plugin_name, plugin_tarball_url, client) + .await?; self.write_plugin_bin_to_fs(plugin_name, &plugin_bin_path, &version)?; eprintln!( @@ -234,11 +235,11 @@ impl Installer { } } - fn extract_plugin_tarball( + async fn extract_plugin_tarball( &self, plugin_name: &str, plugin_tarball_url: &str, - client: &reqwest::blocking::Client, + client: &reqwest::Client, ) -> Result { let download_dir = tempdir::TempDir::new(plugin_name)?; let download_dir_path = Utf8PathBuf::try_from(download_dir.into_path())?; @@ -248,9 +249,11 @@ impl Installer { .get(plugin_tarball_url) .header(reqwest::header::USER_AGENT, "rover-client") .header(reqwest::header::ACCEPT, "application/octet-stream") - .send()? + .send() + .await? .error_for_status()? - .bytes()?; + .bytes() + .await?; f.write_all(&response_bytes[..])?; f.sync_all()?; let f = std::fs::File::open(&tarball_path)?; diff --git a/src/bin/rover.rs b/src/bin/rover.rs index 40ead2101..1f112834b 100644 --- a/src/bin/rover.rs +++ b/src/bin/rover.rs @@ -10,5 +10,7 @@ fn main() -> Result<_, std::io::Error> { homepage: rover::PKG_HOMEPAGE.into(), repository: rover::PKG_REPOSITORY.into() }); - Ok(Rover::run_from_args()) + + let rt = tokio::runtime::Runtime::new().unwrap(); + Ok(rt.block_on(Rover::run_from_args())) } diff --git a/src/cli.rs b/src/cli.rs index ab170a544..fb4063849 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use camino::Utf8PathBuf; use clap::{Parser, ValueEnum}; use lazycell::{AtomicLazyCell, LazyCell}; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use crate::command::{self, RoverOutput}; @@ -109,11 +109,11 @@ pub struct Rover { } impl Rover { - pub fn run_from_args() -> RoverResult<()> { - Rover::parse().run() + pub async fn run_from_args() -> RoverResult<()> { + Rover::parse().run().await } - pub fn run(&self) -> RoverResult<()> { + pub async fn run(&self) -> RoverResult<()> { timber::init(self.log_level); tracing::trace!(command_structure = ?self); self.output_opts.validate_options(); @@ -134,7 +134,7 @@ impl Rover { // kicks off the app on the main thread // don't return an error with ? quite yet // since we still want to report the usage data - let app_result = self.execute_command(); + let app_result = self.execute_command().await; // makes sure the reporting finishes in the background // before continuing. @@ -147,7 +147,7 @@ impl Rover { } // otherwise just run the app without reporting - Err(_) => self.execute_command(), + Err(_) => self.execute_command().await, }; match rover_output { @@ -164,7 +164,7 @@ impl Rover { } } - pub fn execute_command(&self) -> RoverResult { + pub async fn execute_command(&self) -> RoverResult { // before running any commands, we check if rover is up to date // this only happens once a day automatically // we skip this check for the `rover update` commands, since they @@ -174,19 +174,21 @@ impl Rover { } else if !self.skip_update_check { let config = self.get_rover_config(); if let Ok(config) = config { - let _ = version::check_for_update(config, false, self.get_reqwest_client()?); + let _ = version::check_for_update(config, false, self.get_reqwest_client()?).await; } } match &self.command { - Command::Config(command) => command.run(self.get_client_config()?), - Command::Contract(command) => command.run(self.get_client_config()?), + Command::Config(command) => command.run(self.get_client_config()?).await, + Command::Contract(command) => command.run(self.get_client_config()?).await, Command::Dev(command) => { - command.run(self.get_install_override_path()?, self.get_client_config()?) + command + .run(self.get_install_override_path()?, self.get_client_config()?) + .await } Command::Fed2(command) => command.run(self.get_client_config()?), Command::Supergraph(command) => { - command.run(self.get_install_override_path()?, self.get_client_config()?) + command.run(self.get_install_override_path()?, self.get_client_config()?).await } Command::Docs(command) => command.run(), Command::Graph(command) => command.run( @@ -194,25 +196,25 @@ impl Rover { self.get_git_context()?, self.get_checks_timeout_seconds()?, &self.output_opts, - ), - Command::Template(command) => command.run(self.get_client_config()?), - Command::Readme(command) => command.run(self.get_client_config()?), + ).await, + Command::Template(command) => command.run(self.get_client_config()?).await, + Command::Readme(command) => command.run(self.get_client_config()?).await, Command::Subgraph(command) => command.run( self.get_client_config()?, self.get_git_context()?, self.get_checks_timeout_seconds()?, &self.output_opts, - ), + ).await, Command::Update(command) => { command.run(self.get_rover_config()?, self.get_reqwest_client()?) } Command::Install(command) => { - command.do_install(self.get_install_override_path()?, self.get_client_config()?) + command.do_install(self.get_install_override_path()?, self.get_client_config()?).await } Command::Info(command) => command.run(), Command::Explain(command) => command.run(), - Command::PersistedQueries(command) => command.run(self.get_client_config()?), - Command::License(command) => command.run(self.get_client_config()?), + Command::PersistedQueries(command) => command.run(self.get_client_config()?).await, + Command::License(command) => command.run(self.get_client_config()?).await, } } diff --git a/src/command/config/mod.rs b/src/command/config/mod.rs index e78009333..08e791d56 100644 --- a/src/command/config/mod.rs +++ b/src/command/config/mod.rs @@ -35,13 +35,13 @@ pub enum Command { } impl Config { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { Command::Auth(command) => command.run(client_config.config), Command::List(command) => command.run(client_config.config), Command::Delete(command) => command.run(client_config.config), Command::Clear(command) => command.run(client_config.config), - Command::Whoami(command) => command.run(client_config), + Command::Whoami(command) => command.run(client_config).await, } } } diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 1859b0c7c..0943e7a7d 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -30,11 +30,11 @@ pub struct WhoAmI { } impl WhoAmI { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!("Checking identity of your API key against the registry."); - let identity = who_am_i::run(ConfigWhoAmIInput {}, &client)?; + let identity = who_am_i::run(ConfigWhoAmIInput {}, &client).await?; if !self.is_valid_actor_type(&identity) { return Err(RoverError::from(anyhow!( diff --git a/src/command/contract/describe.rs b/src/command/contract/describe.rs index 5bc4364a4..b6fd8806e 100644 --- a/src/command/contract/describe.rs +++ b/src/command/contract/describe.rs @@ -18,7 +18,7 @@ pub struct Describe { } impl Describe { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Fetching description for configuration of {} using credentials from the {} profile.\n", @@ -31,7 +31,8 @@ impl Describe { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::ContractDescribe(describe_response)) } diff --git a/src/command/contract/mod.rs b/src/command/contract/mod.rs index 4d042497e..312e83448 100644 --- a/src/command/contract/mod.rs +++ b/src/command/contract/mod.rs @@ -23,10 +23,10 @@ pub enum Command { } impl Contract { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Describe(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config), + Command::Describe(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config).await, } } } diff --git a/src/command/contract/publish.rs b/src/command/contract/publish.rs index 13712e5bf..627fadb65 100644 --- a/src/command/contract/publish.rs +++ b/src/command/contract/publish.rs @@ -69,7 +69,7 @@ pub struct Publish { } impl Publish { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Publishing configuration to {} using credentials from the {} profile.\n", @@ -101,7 +101,8 @@ impl Publish { no_launch: self.no_launch, }, &client, - )?; + ) + .await?; Ok(RoverOutput::ContractPublish(publish_response)) } diff --git a/src/command/dev/compose.rs b/src/command/dev/compose.rs index 486d74d53..442f6d2b2 100644 --- a/src/command/dev/compose.rs +++ b/src/command/dev/compose.rs @@ -39,7 +39,7 @@ impl ComposeRunner { } } - pub fn maybe_install_supergraph( + pub async fn maybe_install_supergraph( &mut self, federation_version: FederationVersion, ) -> RoverResult { @@ -50,13 +50,13 @@ impl ComposeRunner { self.override_install_path.clone(), self.client_config.clone(), federation_version, - )?; + ).await?; self.plugin_exe = Some(plugin_exe.clone()); Ok(plugin_exe) } } - pub fn run( + pub async fn run( &mut self, supergraph_config: &mut SupergraphConfig, ) -> std::result::Result, String> { @@ -65,7 +65,7 @@ impl ComposeRunner { self.override_install_path.clone(), self.client_config.clone(), supergraph_config, - )); + ).await); let new_state = self.composition_state(); match (prev_state, new_state) { diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 36f51ff9f..9638f9620 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -18,7 +18,7 @@ pub fn log_err_and_continue(err: RoverError) -> RoverError { } impl Dev { - pub fn run( + pub async fn run( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -48,7 +48,7 @@ impl Dev { follower_channel.clone(), self.opts.plugin_opts.clone(), router_config_handler, - )? { + ).await? { eprintln!("{0}Do not run this command in production! {0}It is intended for local development.", Emoji::Warn); let (ready_sender, ready_receiver) = sync_channel(1); let follower_messenger = FollowerMessenger::from_main_session( @@ -56,7 +56,7 @@ impl Dev { leader_channel.receiver, ); - tp.spawn(move || { + tokio::task::spawn_blocking(move || { ctrlc::set_handler(move || { eprintln!( "\n{}shutting down the `rover dev` session and all attached processes...", @@ -92,6 +92,7 @@ impl Dev { self.opts.subgraph_opts.subgraph_polling_interval, &self.opts.plugin_opts.profile, ) + .await .transpose() .unwrap_or_else(|| { self.opts @@ -105,9 +106,10 @@ impl Dev { })?; subgraph_watchers.into_iter().for_each(|mut watcher| { - std::thread::spawn(move || { + tokio::task::spawn(async move { let _ = watcher .watch_subgraph_for_changes() + .await .map_err(log_err_and_continue); }); }); @@ -128,7 +130,7 @@ impl Dev { // start the interprocess socket health check in the background let health_messenger = follower_messenger.clone(); - tp.spawn(move || { + tokio::task::spawn_blocking(move || { let _ = health_messenger.health_check().map_err(|_| { eprintln!("{}shutting down...", Emoji::Stop); std::process::exit(1); @@ -148,7 +150,7 @@ impl Dev { // watch for subgraph changes on the main thread // it will take care of updating the main `rover dev` session - subgraph_refresher.watch_subgraph_for_changes()?; + subgraph_refresher.watch_subgraph_for_changes().await?; } unreachable!("watch_subgraph_for_changes never returns") diff --git a/src/command/dev/introspect.rs b/src/command/dev/introspect.rs index 194aaedf4..ae371b3cd 100644 --- a/src/command/dev/introspect.rs +++ b/src/command/dev/introspect.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use reqwest::blocking::Client; +use reqwest::Client; use rover_std::Style; use crate::command::dev::protocol::{SubgraphSdl, SubgraphUrl}; @@ -28,7 +28,7 @@ impl UnknownIntrospectRunner { } } - pub fn run(&self) -> RoverResult<(SubgraphSdl, IntrospectRunnerKind)> { + pub async fn run(&self) -> RoverResult<(SubgraphSdl, IntrospectRunnerKind)> { let subgraph_runner = SubgraphIntrospectRunner { endpoint: self.endpoint.clone(), client: self.client.clone(), @@ -48,8 +48,8 @@ impl UnknownIntrospectRunner { // in which case we may incorrectly assume // they do not support federated introspection // so, run the graph query first and _then_ the subgraph query - let graph_result = graph_runner.run(); - let subgraph_result = subgraph_runner.run(); + let graph_result = graph_runner.run().await; + let subgraph_result = subgraph_runner.run().await; match (subgraph_result, graph_result) { (Ok(s), _) => { @@ -104,7 +104,7 @@ pub struct SubgraphIntrospectRunner { } impl SubgraphIntrospectRunner { - pub fn run(&self) -> RoverResult { + pub async fn run(&self) -> RoverResult { tracing::debug!( "running `rover subgraph introspect --endpoint {}`", &self.endpoint @@ -116,7 +116,7 @@ impl SubgraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false) + .exec(&self.client, false).await } } @@ -128,7 +128,7 @@ pub struct GraphIntrospectRunner { } impl GraphIntrospectRunner { - pub fn run(&self) -> RoverResult { + pub async fn run(&self) -> RoverResult { tracing::debug!( "running `rover graph introspect --endpoint {}`", &self.endpoint @@ -140,6 +140,6 @@ impl GraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false) + .exec(&self.client, false).await } } diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index 685a40a4a..ec1decbdb 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -50,7 +50,7 @@ impl LeaderSession { /// Ok(Some(Self)) when successfully initiated /// Ok(None) when a LeaderSession already exists for that address /// Err(RoverError) when something went wrong. - pub fn new( + pub async fn new( override_install_path: Option, client_config: &StudioClientConfig, leader_channel: LeaderChannel, @@ -114,8 +114,10 @@ impl LeaderSession { None => FederationVersion::LatestFedTwo, }; - router_runner.maybe_install_router()?; - compose_runner.maybe_install_supergraph(federation_version.clone())?; + router_runner.maybe_install_router().await?; + compose_runner + .maybe_install_supergraph(federation_version.clone()) + .await?; router_config_handler.start()?; @@ -131,18 +133,26 @@ impl LeaderSession { } /// Start the session by watching for incoming subgraph updates and re-composing when needed - pub fn listen_for_all_subgraph_updates(&mut self, ready_sender: Sender<()>) -> RoverResult<()> { + pub async fn listen_for_all_subgraph_updates( + &mut self, + ready_sender: futures::channel::mpsc::Sender<()>, + ) -> RoverResult<()> { self.receive_messages_from_attached_sessions()?; - self.receive_all_subgraph_updates(ready_sender); + self.receive_all_subgraph_updates(ready_sender).await; } /// Listen for incoming subgraph updates and re-compose the supergraph - fn receive_all_subgraph_updates(&mut self, ready_sender: Sender<()>) -> ! { - ready_sender.send(()).unwrap(); + async fn receive_all_subgraph_updates( + &mut self, + mut ready_sender: futures::channel::mpsc::Sender<()>, + ) -> ! { + ready_sender.try_send(()).unwrap(); loop { tracing::trace!("main session waiting for follower message"); let follower_message = self.follower_channel.receiver.recv().unwrap(); - let leader_message = self.handle_follower_message_kind(follower_message.kind()); + let leader_message = self + .handle_follower_message_kind(follower_message.kind()) + .await; if !follower_message.is_from_main_session() { leader_message.print(); @@ -212,7 +222,7 @@ impl LeaderSession { } /// Adds a subgraph to the internal supergraph representation. - fn add_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { + async fn add_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { let is_first_subgraph = self.subgraphs.is_empty(); let ((name, url), sdl) = subgraph_entry; if self @@ -231,7 +241,7 @@ impl LeaderSession { } else { self.subgraphs .insert((name.to_string(), url.clone()), sdl.to_string()); - let composition_result = self.compose(); + let composition_result = self.compose().await; if let Err(composition_err) = composition_result { LeaderMessageKind::error(composition_err) } else if composition_result.transpose().is_some() && !is_first_subgraph { @@ -243,12 +253,12 @@ impl LeaderSession { } /// Updates a subgraph in the internal supergraph representation. - fn update_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { + async fn update_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { let ((name, url), sdl) = &subgraph_entry; if let Some(prev_sdl) = self.subgraphs.get_mut(&(name.to_string(), url.clone())) { if prev_sdl != sdl { *prev_sdl = sdl.to_string(); - let composition_result = self.compose(); + let composition_result = self.compose().await; if let Err(composition_err) = composition_result { LeaderMessageKind::error(composition_err) } else if composition_result.transpose().is_some() { @@ -260,12 +270,12 @@ impl LeaderSession { LeaderMessageKind::message_received() } } else { - self.add_subgraph(subgraph_entry) + self.add_subgraph(subgraph_entry).await } } /// Removes a subgraph from the internal subgraph representation. - fn remove_subgraph(&mut self, subgraph_name: &SubgraphName) -> LeaderMessageKind { + async fn remove_subgraph(&mut self, subgraph_name: &SubgraphName) -> LeaderMessageKind { let found = self .subgraphs .keys() @@ -274,7 +284,7 @@ impl LeaderSession { if let Some((name, url)) = found { self.subgraphs.remove(&(name.to_string(), url)); - let composition_result = self.compose(); + let composition_result = self.compose().await; if let Err(composition_err) = composition_result { LeaderMessageKind::error(composition_err) } else if composition_result.transpose().is_some() { @@ -288,19 +298,24 @@ impl LeaderSession { } /// Reruns composition, which triggers the router to reload. - fn compose(&mut self) -> CompositionResult { + async fn compose(&mut self) -> CompositionResult { self.compose_runner .run(&mut self.supergraph_config()) + .await .and_then(|maybe_new_schema| { if maybe_new_schema.is_some() { - if let Err(err) = self.router_runner.spawn() { + if let Err(err) = self.router_runner.spawn().await { return Err(err.to_string()); } } Ok(maybe_new_schema) }) .map_err(|e| { - let _ = self.router_runner.kill().map_err(log_err_and_continue); + let _ = self + .router_runner + .kill() + .await + .map_err(log_err_and_continue); e }) } @@ -347,30 +362,34 @@ impl LeaderSession { } /// Shuts the router down, removes the socket file, and exits the process. - pub fn shutdown(&mut self) { - let _ = self.router_runner.kill().map_err(log_err_and_continue); + pub async fn shutdown(&mut self) { + let _ = self + .router_runner + .kill() + .await + .map_err(log_err_and_continue); let _ = std::fs::remove_file(&self.ipc_socket_addr); std::process::exit(1) } /// Handles a follower message by updating the internal subgraph representation if needed, /// and returns a [`LeaderMessageKind`] that can be sent over a socket or printed by the main session - fn handle_follower_message_kind( + async fn handle_follower_message_kind( &mut self, follower_message: &FollowerMessageKind, ) -> LeaderMessageKind { use FollowerMessageKind::*; match follower_message { - AddSubgraph { subgraph_entry } => self.add_subgraph(subgraph_entry), + AddSubgraph { subgraph_entry } => self.add_subgraph(subgraph_entry).await, - UpdateSubgraph { subgraph_entry } => self.update_subgraph(subgraph_entry), + UpdateSubgraph { subgraph_entry } => self.update_subgraph(subgraph_entry).await, - RemoveSubgraph { subgraph_name } => self.remove_subgraph(subgraph_name), + RemoveSubgraph { subgraph_name } => self.remove_subgraph(subgraph_name).await, GetSubgraphs => LeaderMessageKind::current_subgraphs(self.get_subgraphs()), Shutdown => { - self.shutdown(); + self.shutdown().await; LeaderMessageKind::message_received() } diff --git a/src/command/dev/router/command.rs b/src/command/dev/router/command.rs index fe90598fe..60513813e 100644 --- a/src/command/dev/router/command.rs +++ b/src/command/dev/router/command.rs @@ -25,7 +25,7 @@ pub enum BackgroundTaskLog { } impl BackgroundTask { - pub fn new( + pub async fn new( command: String, log_sender: Sender, client_config: &StudioClientConfig, @@ -56,7 +56,7 @@ impl BackgroundTask { eprintln!("{} APOLLO_GRAPH_REF is set, but credentials could not be loaded. \ Enterprise features within the router will not function. {err}", Emoji::Warn); }).ok().and_then(|client| { - who_am_i::run(ConfigWhoAmIInput {}, &client).map_or_else(|err| { + who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { eprintln!("{} Could not determine the type of configured credentials, \ Router may fail to start if Enterprise features are enabled. {err}", Emoji::Warn); Some(client.credential.api_key.clone()) diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index 30ccc6044..5cb082945 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context}; use apollo_federation_types::config::RouterVersion; use camino::Utf8PathBuf; use crossbeam_channel::bounded; -use reqwest::blocking::Client; +use reqwest::Client; use reqwest::Url; use rover_std::{Emoji, Style}; use semver::Version; @@ -69,31 +69,33 @@ impl RouterRunner { }) } - pub fn maybe_install_router(&mut self) -> RoverResult { + pub async fn maybe_install_router(&mut self) -> RoverResult { if let Some(plugin_exe) = &self.plugin_exe { Ok(plugin_exe.clone()) } else { let install_command = self.install_command()?; - let plugin_exe = install_command.get_versioned_plugin( - self.override_install_path.clone(), - self.client_config.clone(), - self.plugin_opts.skip_update, - )?; + let plugin_exe = install_command + .get_versioned_plugin( + self.override_install_path.clone(), + self.client_config.clone(), + self.plugin_opts.skip_update, + ) + .await?; self.plugin_exe = Some(plugin_exe.clone()); Ok(plugin_exe) } } - pub fn get_command_to_spawn(&mut self) -> RoverResult { + pub async fn get_command_to_spawn(&mut self) -> RoverResult { Ok(format!( "{plugin_exe} --supergraph {supergraph} --hot-reload --config {config} --log trace --dev", - plugin_exe = self.maybe_install_router()?, + plugin_exe = self.maybe_install_router().await?, supergraph = self.supergraph_schema_path.as_str(), config = self.router_config_path.as_str(), )) } - pub fn wait_for_startup(&mut self, client: Client) -> RoverResult<()> { + pub async fn wait_for_startup(&mut self, client: Client) -> RoverResult<()> { let mut ready = false; let now = Instant::now(); let seconds = 10; @@ -110,6 +112,7 @@ impl RouterRunner { .get(&endpoint) .header("Content-Type", "application/json") .send() + .await .map(|_| { ready = true; }); @@ -137,7 +140,7 @@ impl RouterRunner { } } - pub fn wait_for_stop(&mut self, client: Client) -> RoverResult<()> { + pub async fn wait_for_stop(&mut self, client: Client) -> RoverResult<()> { let mut ready = true; let now = Instant::now(); let seconds = 5; @@ -149,6 +152,7 @@ impl RouterRunner { )) .header("Content-Type", "application/json") .send() + .await .and_then(|r| r.error_for_status()) .map_err(|_| { ready = false; @@ -164,17 +168,18 @@ impl RouterRunner { } } - pub fn spawn(&mut self) -> RoverResult<()> { + pub async fn spawn(&mut self) -> RoverResult<()> { if self.router_handle.is_none() { let client = self.client_config.get_reqwest_client()?; - self.maybe_install_router()?; + self.maybe_install_router().await?; let (router_log_sender, router_log_receiver) = bounded(0); let router_handle = BackgroundTask::new( - self.get_command_to_spawn()?, + self.get_command_to_spawn().await?, router_log_sender, &self.client_config, &self.plugin_opts.profile, - )?; + ) + .await?; tracing::info!("spawning router with `{}`", router_handle.descriptor()); let warn_prefix = Style::WarningPrefix.paint("WARN:"); @@ -228,7 +233,7 @@ impl RouterRunner { } }); - self.wait_for_startup(client)?; + self.wait_for_startup(client).await?; self.router_handle = Some(router_handle); Ok(()) @@ -237,12 +242,15 @@ impl RouterRunner { } } - pub fn kill(&mut self) -> RoverResult<()> { + pub async fn kill(&mut self) -> RoverResult<()> { if self.router_handle.is_some() { tracing::info!("killing the router"); self.router_handle = None; if let Ok(client) = self.client_config.get_reqwest_client() { - let _ = self.wait_for_stop(client).map_err(log_err_and_continue); + let _ = self + .wait_for_stop(client) + .await + .map_err(log_err_and_continue); } } Ok(()) @@ -251,6 +259,7 @@ impl RouterRunner { impl Drop for RouterRunner { fn drop(&mut self) { - let _ = self.kill().map_err(log_err_and_continue); + //let _ = self.kill().map_err(log_err_and_continue); + todo!() } } diff --git a/src/command/dev/schema.rs b/src/command/dev/schema.rs index 3396f46ac..4775c16ad 100644 --- a/src/command/dev/schema.rs +++ b/src/command/dev/schema.rs @@ -93,7 +93,7 @@ impl OptionalSubgraphOpts { } impl SupergraphOpts { - pub fn get_subgraph_watchers( + pub async fn get_subgraph_watchers( &self, client_config: &StudioClientConfig, follower_messenger: FollowerMessenger, @@ -174,7 +174,7 @@ impl SupergraphOpts { yaml_subgraph_name, follower_messenger.clone(), studio_client, - ) + ).await } } }) diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index a64c92a59..fc2c768e5 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -12,7 +12,7 @@ use std::str::FromStr; use apollo_federation_types::build::SubgraphDefinition; use camino::{Utf8Path, Utf8PathBuf}; use crossbeam_channel::unbounded; -use reqwest::blocking::Client; +use reqwest::Client; use rover_client::blocking::StudioClient; use rover_client::operations::subgraph::fetch; use rover_client::operations::subgraph::fetch::SubgraphFetchInput; @@ -74,7 +74,7 @@ impl SubgraphSchemaWatcher { }) } - pub fn new_from_graph_ref( + pub async fn new_from_graph_ref( graph_ref: &str, graphos_subgraph_name: String, routing_url: Option, @@ -90,7 +90,7 @@ impl SubgraphSchemaWatcher { subgraph_name: graphos_subgraph_name.clone(), }, client, - ) + ).await .map_err(RoverError::from)?; let routing_url = match (routing_url, response.sdl.r#type) { (Some(routing_url), _) => routing_url, @@ -139,7 +139,7 @@ impl SubgraphSchemaWatcher { }) } - pub fn get_subgraph_definition_and_maybe_new_runner( + pub async fn get_subgraph_definition_and_maybe_new_runner( &self, ) -> RoverResult<(SubgraphDefinition, Option)> { let (name, url) = self.subgraph_key.clone(); @@ -147,15 +147,15 @@ impl SubgraphSchemaWatcher { SubgraphSchemaWatcherKind::Introspect(introspect_runner_kind, polling_interval) => { match introspect_runner_kind { IntrospectRunnerKind::Graph(graph_runner) => { - let sdl = graph_runner.run()?; + let sdl = graph_runner.run().await?; (sdl, None) } IntrospectRunnerKind::Subgraph(subgraph_runner) => { - let sdl = subgraph_runner.run()?; + let sdl = subgraph_runner.run().await?; (sdl, None) } IntrospectRunnerKind::Unknown(unknown_runner) => { - let (sdl, specific_runner) = unknown_runner.run()?; + let (sdl, specific_runner) = unknown_runner.run().await?; ( sdl, Some(SubgraphSchemaWatcherKind::Introspect( @@ -178,12 +178,12 @@ impl SubgraphSchemaWatcher { Ok((subgraph_definition, refresher)) } - fn update_subgraph(&mut self, last_message: Option<&String>) -> RoverResult> { + async fn update_subgraph(&mut self, last_message: Option<&String>) -> RoverResult> { let print_error = |e: RoverError| { let _ = e.print(); }; - let maybe_update_message = match self.get_subgraph_definition_and_maybe_new_runner() { + let maybe_update_message = match self.get_subgraph_definition_and_maybe_new_runner().await { Ok((subgraph_definition, maybe_new_refresher)) => { if let Some(new_refresher) = maybe_new_refresher { self.set_schema_refresher(new_refresher); @@ -225,7 +225,7 @@ impl SubgraphSchemaWatcher { /// /// This function will block forever for `SubgraphSchemaWatcherKind` that poll for changesβ€”so it /// should be started in a separate thread. - pub fn watch_subgraph_for_changes(&mut self) -> RoverResult<()> { + pub async fn watch_subgraph_for_changes(&mut self) -> RoverResult<()> { let mut last_message = None; match self.schema_watcher_kind.clone() { SubgraphSchemaWatcherKind::Introspect(introspect_runner_kind, polling_interval) => { @@ -241,13 +241,13 @@ impl SubgraphSchemaWatcher { } ); loop { - last_message = self.update_subgraph(last_message.as_ref())?; + last_message = self.update_subgraph(last_message.as_ref()).await?; std::thread::sleep(std::time::Duration::from_secs(polling_interval)); } } SubgraphSchemaWatcherKind::File(path) => { // populate the schema for the first time (last_message is always None to start) - last_message = self.update_subgraph(last_message.as_ref())?; + last_message = self.update_subgraph(last_message.as_ref()).await?; let (tx, rx) = unbounded(); @@ -259,11 +259,11 @@ impl SubgraphSchemaWatcher { rx.recv().unwrap_or_else(|_| { panic!("an unexpected error occurred while watching {}", &path) }); - last_message = self.update_subgraph(last_message.as_ref())?; + last_message = self.update_subgraph(last_message.as_ref()).await?; } } SubgraphSchemaWatcherKind::Once(_) => { - self.update_subgraph(None)?; + self.update_subgraph(None).await?; } } Ok(()) diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index 4890ebc33..725e0c8f2 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -29,7 +29,7 @@ pub struct Check { } impl Check { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -56,7 +56,8 @@ impl Check { }, }, &client, - )?; + ) + .await?; if self.config.background { Ok(RoverOutput::AsyncCheckResponse(workflow_res)) } else { @@ -67,7 +68,8 @@ impl Check { checks_timeout_seconds, }, &client, - )?; + ) + .await?; Ok(RoverOutput::CheckWorkflowResponse(check_res)) } } diff --git a/src/command/graph/delete.rs b/src/command/graph/delete.rs index c21f90e29..cf990dc8e 100644 --- a/src/command/graph/delete.rs +++ b/src/command/graph/delete.rs @@ -23,7 +23,7 @@ pub struct Delete { } impl Delete { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); @@ -43,7 +43,8 @@ impl Delete { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; eprintln!("Successfully deleted {}.", Style::Link.paint(&graph_ref)); Ok(RoverOutput::EmptySuccess) diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index d579c013c..da838013d 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -18,7 +18,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -32,7 +32,8 @@ impl Fetch { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::FetchResponse(fetch_response)) } diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index fd569d369..ee9cce832 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -1,5 +1,5 @@ use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use std::collections::HashMap; @@ -20,16 +20,16 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { + pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { self.exec_and_watch(&client, output_opts) } else { - let sdl = self.exec(&client, true)?; + let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) } } - pub fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { + pub async fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { let client = GraphQLClient::new(self.opts.endpoint.as_ref(), client.clone()); // add the flag headers to a hashmap to pass along to rover-client @@ -40,7 +40,11 @@ impl Introspect { } }; - Ok(introspect::run(GraphIntrospectInput { headers }, &client, should_retry)?.schema_sdl) + Ok( + introspect::run(GraphIntrospectInput { headers }, &client, should_retry) + .await? + .schema_sdl, + ) } pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { diff --git a/src/command/graph/lint.rs b/src/command/graph/lint.rs index eaa0ab2be..bdc9b91a2 100644 --- a/src/command/graph/lint.rs +++ b/src/command/graph/lint.rs @@ -24,7 +24,7 @@ pub struct Lint { } impl Lint { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let file_with_metadata = self @@ -39,7 +39,8 @@ impl Lint { ignore_existing: self.lint.ignore_existing_lint_violations, }, &client, - )?; + ) + .await?; Ok(RoverOutput::LintResponse(lint_result)) } diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index ba06dfa91..34f5a08db 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -44,7 +44,7 @@ pub enum Command { } impl Graph { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -53,14 +53,16 @@ impl Graph { ) -> RoverResult { match &self.command { Command::Check(command) => { - command.run(client_config, git_context, checks_timeout_seconds) + command + .run(client_config, git_context, checks_timeout_seconds) + .await } - Command::Delete(command) => command.run(client_config), - Command::Fetch(command) => command.run(client_config), - Command::Lint(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config, git_context), + Command::Delete(command) => command.run(client_config).await, + Command::Fetch(command) => command.run(client_config).await, + Command::Lint(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config, git_context).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts) + command.run(client_config.get_reqwest_client()?, output_opts).await } } } diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index c3a758434..4fb3bc5ea 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -23,7 +23,7 @@ pub struct Publish { } impl Publish { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -49,7 +49,8 @@ impl Publish { git_context, }, &client, - )?; + ) + .await?; Ok(RoverOutput::GraphPublishResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/install/mod.rs b/src/command/install/mod.rs index b52f84f15..ef3927830 100644 --- a/src/command/install/mod.rs +++ b/src/command/install/mod.rs @@ -32,7 +32,7 @@ pub struct Install { } impl Install { - pub fn do_install( + pub async fn do_install( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -47,7 +47,7 @@ impl Install { .require_elv2_license(&client_config)?; } let plugin_installer = PluginInstaller::new(client_config, rover_installer); - plugin_installer.install(plugin, false)?; + plugin_installer.install(plugin, false).await?; Ok(RoverOutput::EmptySuccess) } else { @@ -101,7 +101,7 @@ impl Install { } #[cfg(feature = "composition-js")] - pub(crate) fn get_versioned_plugin( + pub(crate) async fn get_versioned_plugin( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -110,7 +110,7 @@ impl Install { let rover_installer = self.get_installer(PKG_NAME.to_string(), override_install_path)?; if let Some(plugin) = &self.plugin { let plugin_installer = PluginInstaller::new(client_config, rover_installer); - plugin_installer.install(plugin, skip_update) + plugin_installer.install(plugin, skip_update).await } else { let mut err = RoverError::new(anyhow!("Could not find a plugin to get a version from.")); diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index c62a52298..7f86a7ffb 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -193,7 +193,7 @@ impl PluginInstaller { } } - pub fn install(&self, plugin: &Plugin, skip_update: bool) -> RoverResult { + pub async fn install(&self, plugin: &Plugin, skip_update: bool) -> RoverResult { let skip_update_err = |plugin_name: &str, version: &str| { let mut err = RoverError::new(anyhow!( "You do not have the '{}-v{}' plugin installed.", @@ -228,7 +228,8 @@ impl PluginInstaller { self.find_existing_exact(plugin, &version)? .ok_or_else(|| skip_update_err(&plugin.get_name(), &version)) } else { - self.install_exact(plugin, &version)? + self.install_exact(plugin, &version) + .await? .ok_or_else(|| could_not_install_plugin(&plugin.get_name(), &version)) } } @@ -243,12 +244,13 @@ impl PluginInstaller { ) }) } else { - self.install_latest_major(plugin)?.ok_or_else(|| { - could_not_install_plugin( - &plugin.get_name(), - major_version.to_string().as_str(), - ) - }) + self.install_latest_major(plugin).await? + .ok_or_else(|| { + could_not_install_plugin( + &plugin.get_name(), + major_version.to_string().as_str(), + ) + }) } } }, @@ -260,7 +262,7 @@ impl PluginInstaller { self.find_existing_exact(plugin, &version)? .ok_or_else(|| skip_update_err(&plugin.get_name(), &version)) } else { - self.install_exact(plugin, &version)? + self.install_exact(plugin, &version).await? .ok_or_else(|| could_not_install_plugin(&plugin.get_name(), &version)) } } @@ -272,7 +274,7 @@ impl PluginInstaller { skip_update_err(&plugin.get_name(), version.to_string().as_str()) }) } else { - self.install_latest_major(plugin)?.ok_or_else(|| { + self.install_latest_major(plugin).await?.ok_or_else(|| { could_not_install_plugin( &plugin.get_name(), major_version.to_string().as_str(), @@ -292,7 +294,7 @@ impl PluginInstaller { ) })?) } else { - self.install_latest_major(plugin)?.ok_or_else(|| { + self.install_latest_major(plugin).await?.ok_or_else(|| { could_not_install_plugin( &plugin.get_name(), major_version.to_string().as_str(), @@ -333,7 +335,7 @@ impl PluginInstaller { } } - fn install_latest_major(&self, plugin: &Plugin) -> RoverResult> { + async fn install_latest_major(&self, plugin: &Plugin) -> RoverResult> { let latest_version = self .rover_installer .get_plugin_version(&plugin.get_tarball_url()?, true)?; @@ -342,7 +344,7 @@ impl PluginInstaller { Ok(Some(exe)) } else { // do the install. - self.do_install(plugin, true)?; + self.do_install(plugin, true).await?; self.find_existing_exact(plugin, &latest_version) } } @@ -357,15 +359,23 @@ impl PluginInstaller { Ok(find_installed_plugin(&plugin_dir, &plugin_name, version).ok()) } - fn install_exact(&self, plugin: &Plugin, version: &str) -> RoverResult> { + async fn install_exact( + &self, + plugin: &Plugin, + version: &str, + ) -> RoverResult> { if let Ok(Some(exe)) = self.find_existing_exact(plugin, version) { Ok(Some(exe)) } else { - self.do_install(plugin, false) + self.do_install(plugin, false).await } } - fn do_install(&self, plugin: &Plugin, is_latest: bool) -> RoverResult> { + async fn do_install( + &self, + plugin: &Plugin, + is_latest: bool, + ) -> RoverResult> { let plugin_name = plugin.get_name(); let plugin_tarball_url = plugin.get_tarball_url()?; // only print the download message if the username and password have been stripped from the URL @@ -374,12 +384,15 @@ impl PluginInstaller { } else { eprintln!("downloading the '{plugin_name}' plugin"); } - Ok(self.rover_installer.install_plugin( - &plugin_name, - &plugin_tarball_url, - &self.client_config.get_reqwest_client()?, - is_latest, - )?) + Ok(self + .rover_installer + .install_plugin( + &plugin_name, + &plugin_tarball_url, + &self.client_config.get_reqwest_client()?, + is_latest, + ) + .await?) } } diff --git a/src/command/license/fetch.rs b/src/command/license/fetch.rs index 352f5a87a..c95eac327 100644 --- a/src/command/license/fetch.rs +++ b/src/command/license/fetch.rs @@ -18,7 +18,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Fetching license for {} using credentials from the {} profile.", @@ -30,7 +30,8 @@ impl Fetch { graph_id: self.graph_id.to_string(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::LicenseResponse { graph_id: self.graph_id.to_string(), diff --git a/src/command/license/mod.rs b/src/command/license/mod.rs index 498439986..4f7023451 100644 --- a/src/command/license/mod.rs +++ b/src/command/license/mod.rs @@ -20,9 +20,9 @@ pub enum Command { } impl License { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Fetch(command) => command.run(client_config), + Command::Fetch(command) => command.run(client_config).await, } } } diff --git a/src/command/persisted_queries/mod.rs b/src/command/persisted_queries/mod.rs index 997218b3b..f570b3484 100644 --- a/src/command/persisted_queries/mod.rs +++ b/src/command/persisted_queries/mod.rs @@ -22,9 +22,9 @@ pub enum Command { } impl PersistedQueries { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Publish(command) => command.run(client_config), + Command::Publish(command) => command.run(client_config).await, } } } diff --git a/src/command/persisted_queries/publish.rs b/src/command/persisted_queries/publish.rs index 755ae5a9d..5840deb5f 100644 --- a/src/command/persisted_queries/publish.rs +++ b/src/command/persisted_queries/publish.rs @@ -46,7 +46,7 @@ pub struct Publish { } impl Publish { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let raw_manifest = self @@ -71,11 +71,11 @@ impl Publish { let (graph_id, list_id, list_name) = match (&self.graph.graph_ref, &self.graph_id, &self.list_id) { (Some(graph_ref), None, None) => { - let persisted_query_list = resolve::run(ResolvePersistedQueryListInput { graph_ref: graph_ref.clone() }, &client)?; + let persisted_query_list = resolve::run(ResolvePersistedQueryListInput { graph_ref: graph_ref.clone() }, &client).await?; (graph_ref.clone().name, persisted_query_list.id, persisted_query_list.name) }, (None, Some(graph_id), Some(list_id)) => { - let list_name = name::run(PersistedQueryListNameInput { graph_id: graph_id.clone(), list_id: list_id.clone() }, &client)?.name; + let list_name = name::run(PersistedQueryListNameInput { graph_id: graph_id.clone(), list_id: list_id.clone() }, &client).await?.name; (graph_id.to_string(), list_id.to_string(), list_name) }, (None, Some(graph_id), None) => { @@ -104,7 +104,7 @@ impl Publish { operation_manifest, }, &client, - )?; + ).await?; Ok(RoverOutput::PersistedQueriesPublishResponse(result)) } } diff --git a/src/command/readme/fetch.rs b/src/command/readme/fetch.rs index 4dd343fda..0f70a136f 100644 --- a/src/command/readme/fetch.rs +++ b/src/command/readme/fetch.rs @@ -18,7 +18,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); @@ -32,7 +32,8 @@ impl Fetch { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::ReadmeFetchResponse { graph_ref: self.graph.graph_ref.clone(), content: readme.content, diff --git a/src/command/readme/mod.rs b/src/command/readme/mod.rs index bc60b6291..a1365fde0 100644 --- a/src/command/readme/mod.rs +++ b/src/command/readme/mod.rs @@ -22,10 +22,10 @@ pub enum Command { } impl Readme { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Fetch(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config), + Command::Fetch(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config).await, } } } diff --git a/src/command/readme/publish.rs b/src/command/readme/publish.rs index 7b7a68cee..67d1b86d3 100644 --- a/src/command/readme/publish.rs +++ b/src/command/readme/publish.rs @@ -24,7 +24,7 @@ pub struct Publish { } impl Publish { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -44,7 +44,7 @@ impl Publish { readme: new_readme, }, &client, - )?; + ).await?; Ok(RoverOutput::ReadmePublishResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index e469a0227..18c9bf191 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -30,7 +30,7 @@ pub struct Check { } impl Check { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -61,7 +61,7 @@ impl Check { }, }, &client, - )?; + ).await?; if self.config.background { Ok(RoverOutput::AsyncCheckResponse(workflow_res)) } else { @@ -73,7 +73,7 @@ impl Check { }, self.subgraph.subgraph_name.clone(), &client, - )?; + ).await?; Ok(RoverOutput::CheckWorkflowResponse(check_res)) } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index 20c8da9c3..1e831ea29 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -27,7 +27,7 @@ pub struct Delete { } impl Delete { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Checking for build errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", @@ -48,7 +48,7 @@ impl Delete { dry_run, }, &client, - )?; + ).await?; RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), @@ -74,7 +74,7 @@ impl Delete { dry_run, }, &client, - )?; + ).await?; Ok(RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index 4ffd8277d..52153e66f 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -21,7 +21,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -37,7 +37,8 @@ impl Fetch { subgraph_name: self.subgraph.subgraph_name.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::FetchResponse(fetch_response)) } diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index b429bd828..bb9d99c88 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -1,5 +1,5 @@ use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use std::collections::HashMap; @@ -18,16 +18,16 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { + pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { self.exec_and_watch(&client, output_opts) } else { - let sdl = self.exec(&client, true)?; + let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) } } - pub fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { + pub async fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { let client = GraphQLClient::new(self.opts.endpoint.as_ref(), client.clone()); // add the flag headers to a hashmap to pass along to rover-client @@ -38,7 +38,11 @@ impl Introspect { } }; - Ok(introspect::run(SubgraphIntrospectInput { headers }, &client, should_retry)?.result) + Ok( + introspect::run(SubgraphIntrospectInput { headers }, &client, should_retry) + .await? + .result, + ) } pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { diff --git a/src/command/subgraph/lint.rs b/src/command/subgraph/lint.rs index c69db250a..0291883e8 100644 --- a/src/command/subgraph/lint.rs +++ b/src/command/subgraph/lint.rs @@ -27,7 +27,7 @@ pub struct Lint { } impl Lint { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let file_with_metadata = self @@ -43,7 +43,7 @@ impl Lint { ignore_existing: self.lint.ignore_existing_lint_violations, }, &client, - )?; + ).await?; Ok(RoverOutput::LintResponse(lint_result)) } diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index 4bb1eb13e..026dd2dd5 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -18,7 +18,7 @@ pub struct List { } impl List { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( @@ -32,7 +32,8 @@ impl List { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::SubgraphList(list_details)) } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index ca4fd44e1..e56e9e734 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -49,7 +49,7 @@ pub enum Command { } impl Subgraph { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -58,16 +58,18 @@ impl Subgraph { ) -> RoverResult { match &self.command { Command::Check(command) => { - command.run(client_config, git_context, checks_timeout_seconds) + command + .run(client_config, git_context, checks_timeout_seconds) + .await } - Command::Delete(command) => command.run(client_config), + Command::Delete(command) => command.run(client_config).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts) + command.run(client_config.get_reqwest_client()?, output_opts).await } - Command::Fetch(command) => command.run(client_config), - Command::Lint(command) => command.run(client_config), - Command::List(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config, git_context), + Command::Fetch(command) => command.run(client_config).await, + Command::Lint(command) => command.run(client_config).await, + Command::List(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config, git_context).await, } } } diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 3c9300eb0..eb7219696 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -53,7 +53,7 @@ pub struct Publish { } impl Publish { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -71,7 +71,8 @@ impl Publish { subgraph_name: self.subgraph.subgraph_name.clone(), }, &client, - )?) + ) + .await?) }, &mut io::stderr(), &mut io::stdin(), @@ -101,7 +102,8 @@ impl Publish { convert_to_federated_graph: self.convert, }, &client, - )?; + ) + .await?; Ok(RoverOutput::SubgraphPublishResponse { graph_ref: self.graph.graph_ref.clone(), @@ -110,7 +112,7 @@ impl Publish { }) } - fn determine_routing_url( + async fn determine_routing_url( no_url: bool, routing_url: &Option, allow_invalid_routing_url: bool, @@ -126,7 +128,7 @@ impl Publish { is_atty: bool, ) -> RoverResult> where - F: Fn() -> RoverResult, + F: Fn() -> impl Future>, { if no_url && routing_url.is_some() { return Err(RoverError::new(anyhow!( @@ -145,7 +147,7 @@ impl Publish { // --no-url is set let mut routing_url = routing_url.clone(); if !no_url && routing_url.is_none() { - let fetch_response = fetch()?; + let fetch_response = fetch().await?; Self::handle_maybe_invalid_routing_url( &Some(fetch_response.clone()), writer, diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index f5f491a0d..216424a47 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -44,7 +44,7 @@ impl Compose { } } - pub(crate) fn maybe_install_supergraph( + pub(crate) async fn maybe_install_supergraph( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -65,15 +65,13 @@ impl Compose { }; // maybe do the install, maybe find a pre-existing installation, maybe fail - let plugin_exe = install_command.get_versioned_plugin( - override_install_path, - client_config, - self.opts.skip_update, - )?; + let plugin_exe = install_command + .get_versioned_plugin(override_install_path, client_config, self.opts.skip_update) + .await?; Ok(plugin_exe) } - pub fn run( + pub async fn run( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -87,21 +85,22 @@ impl Compose { &self.supergraph_yaml, client_config.clone(), &self.opts.profile, - )?; - self.compose(override_install_path, client_config, &mut supergraph_config) + ) + .await?; + self.compose(override_install_path, client_config, &mut supergraph_config).await } - pub fn compose( + pub async fn compose( &self, override_install_path: Option, client_config: StudioClientConfig, supergraph_config: &mut SupergraphConfig, ) -> RoverResult { - let output = self.exec(override_install_path, client_config, supergraph_config)?; + let output = self.exec(override_install_path, client_config, supergraph_config).await?; Ok(RoverOutput::CompositionResult(output)) } - pub fn exec( + pub async fn exec( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -110,11 +109,13 @@ impl Compose { // first, grab the _actual_ federation version from the config we just resolved // (this will always be `Some` as long as we have created with `resolve_supergraph_yaml` so it is safe to unwrap) let federation_version = supergraph_config.get_federation_version().unwrap(); - let exe = self.maybe_install_supergraph( - override_install_path, - client_config, - federation_version.clone(), - )?; + let exe = self + .maybe_install_supergraph( + override_install_path, + client_config, + federation_version.clone(), + ) + .await?; // _then_, overwrite the federation_version with _only_ the major version // before sending it to the supergraph plugin. @@ -199,8 +200,8 @@ mod tests { ) } - #[test] - fn it_errs_on_invalid_subgraph_path() { + #[tokio::test] + async fn it_errs_on_invalid_subgraph_path() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com @@ -221,11 +222,12 @@ mod tests { profile_name: "profile".to_string() } ) + .await .is_err()) } - #[test] - fn it_can_get_subgraph_definitions_from_fs() { + #[tokio::test] + async fn it_can_get_subgraph_definitions_from_fs() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com @@ -251,11 +253,12 @@ mod tests { profile_name: "profile".to_string() } ) + .await .is_ok()) } - #[test] - fn it_can_compute_relative_schema_paths() { + #[tokio::test] + async fn it_can_compute_relative_schema_paths() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com @@ -284,6 +287,7 @@ mod tests { profile_name: "profile".to_string(), }, ) + .await .unwrap() .get_subgraph_definitions() .unwrap(); diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index b01f62427..d7e48c1ff 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -20,7 +20,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -34,7 +34,8 @@ impl Fetch { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::FetchResponse(fetch_response)) } diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index decf897de..057c32c71 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -29,14 +29,14 @@ pub enum Command { } impl Supergraph { - pub fn run( + pub async fn run( &self, override_install_path: Option, client_config: StudioClientConfig, ) -> RoverResult { match &self.command { - Command::Fetch(command) => command.run(client_config), - Command::Compose(command) => command.run(override_install_path, client_config), + Command::Fetch(command) => command.run(client_config).await, + Command::Compose(command) => command.run(override_install_path, client_config).await, } } } diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 94e8402c1..c92d1698e 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -45,7 +45,7 @@ subgraphs: } } -pub(crate) fn resolve_supergraph_yaml( +pub(crate) async fn resolve_supergraph_yaml( unresolved_supergraph_yaml: &FileDescriptorType, client_config: StudioClientConfig, profile_opt: &ProfileOpt, @@ -117,6 +117,7 @@ pub(crate) fn resolve_supergraph_yaml( &client, false, ) + .await .map(|introspection_response| { let schema = introspection_response.result; @@ -148,6 +149,7 @@ pub(crate) fn resolve_supergraph_yaml( }, &authenticated_client, ) + .await .map_err(RoverError::from) .and_then(|result| { // We don't require a routing_url in config for this variant of a schema, diff --git a/src/command/template/mod.rs b/src/command/template/mod.rs index 2f10f16d7..baacb9981 100644 --- a/src/command/template/mod.rs +++ b/src/command/template/mod.rs @@ -29,9 +29,9 @@ enum Command { } impl Template { - pub(crate) fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub(crate) async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Use(use_template) => use_template.run(client_config), + Command::Use(use_template) => use_template.run(client_config).await, Command::List(list) => list.run(), } } diff --git a/src/command/template/use.rs b/src/command/template/use.rs index 0d8aa7808..4e1d80b8c 100644 --- a/src/command/template/use.rs +++ b/src/command/template/use.rs @@ -34,7 +34,7 @@ pub struct Use { } impl Use { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { // find the template to extract let (template_id, download_url) = if let Some(template_id) = &self.template { // if they specify an ID, get it @@ -60,7 +60,7 @@ impl Use { let path = self.get_or_prompt_path()?; // download and extract a tarball from github - extract_tarball(download_url, &path, &client_config.get_reqwest_client()?)?; + extract_tarball(download_url, &path, &client_config.get_reqwest_client()?).await?; Ok(RoverOutput::TemplateUseSuccess { template_id, path }) } diff --git a/src/command/update/check.rs b/src/command/update/check.rs index 3ec01cf10..4ad355ace 100644 --- a/src/command/update/check.rs +++ b/src/command/update/check.rs @@ -1,5 +1,5 @@ use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use crate::{utils::version, RoverOutput, RoverResult}; @@ -12,8 +12,8 @@ pub struct Check { } impl Check { - pub fn run(&self, config: config::Config, client: Client) -> RoverResult { - version::check_for_update(config, true, client)?; + pub async fn run(&self, config: config::Config, client: Client) -> RoverResult { + version::check_for_update(config, true, client).await?; Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index eb5fa0d1f..871f41a71 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -21,9 +21,9 @@ pub enum Command { } impl Update { - pub fn run(&self, config: config::Config, client: Client) -> RoverResult { + pub async fn run(&self, config: config::Config, client: Client) -> RoverResult { match &self.command { - Command::Check(command) => command.run(config, client), + Command::Check(command) => command.run(config, client).await, } } } diff --git a/src/options/introspect.rs b/src/options/introspect.rs index a2fa2446c..1017e3ef9 100644 --- a/src/options/introspect.rs +++ b/src/options/introspect.rs @@ -30,13 +30,13 @@ pub struct IntrospectOpts { } impl IntrospectOpts { - pub fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! + pub async fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! where - F: Fn() -> RoverResult, + F: Fn() -> impl Future>, { let mut last_result = None; loop { - match exec_fn() { + match exec_fn().await { Ok(sdl) => { let mut was_updated = true; if let Some(last) = last_result { diff --git a/src/options/template.rs b/src/options/template.rs index af4caf418..14767fc74 100644 --- a/src/options/template.rs +++ b/src/options/template.rs @@ -41,10 +41,10 @@ impl TemplateOpt { } } -pub(crate) fn extract_tarball( +pub(crate) async fn extract_tarball( download_url: Url, template_path: &Utf8PathBuf, - client: &reqwest::blocking::Client, + client: &reqwest::Client, ) -> RoverResult<()> { let download_dir = tempdir::TempDir::new("rover-template")?; let download_dir_path = Utf8PathBuf::try_from(download_dir.into_path())?; @@ -56,9 +56,10 @@ pub(crate) fn extract_tarball( .get(download_url) .header(reqwest::header::USER_AGENT, "rover-client") .header(reqwest::header::ACCEPT, "application/octet-stream") - .send()? + .send() + .await? .error_for_status()? - .bytes()?; + .bytes().await?; f.write_all(&response_bytes[..])?; f.sync_all()?; let f = std::fs::File::open(&tarball_path)?; diff --git a/src/utils/client.rs b/src/utils/client.rs index f6d9d6d34..b12fb1c3d 100644 --- a/src/utils/client.rs +++ b/src/utils/client.rs @@ -5,7 +5,7 @@ use crate::{options::ProfileOpt, PKG_NAME, PKG_VERSION}; use anyhow::Result; use houston as config; -use reqwest::blocking::Client; +use reqwest::Client; use rover_client::blocking::StudioClient; use serde::Serialize; @@ -62,7 +62,7 @@ impl ClientBuilder { .brotli(true) .danger_accept_invalid_certs(self.accept_invalid_certs) .danger_accept_invalid_hostnames(self.accept_invalid_hostnames) - .timeout(self.timeout) + .timeout(self.timeout.unwrap()) .user_agent(format!("{}/{}", PKG_NAME, PKG_VERSION)) .build()?; diff --git a/src/utils/telemetry.rs b/src/utils/telemetry.rs index 6e6b31317..a6aba2a5b 100644 --- a/src/utils/telemetry.rs +++ b/src/utils/telemetry.rs @@ -1,5 +1,5 @@ use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use url::Url; use crate::utils::env::RoverEnvKey; diff --git a/src/utils/version.rs b/src/utils/version.rs index 5894bd434..29e140c41 100644 --- a/src/utils/version.rs +++ b/src/utils/version.rs @@ -3,7 +3,7 @@ use std::time::SystemTime; use anyhow::Result; use billboard::{Alignment, Billboard}; use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use rover_std::{Fs, Style}; use crate::PKG_VERSION; @@ -19,7 +19,7 @@ const ONE_DAY: u64 = ONE_HOUR * 24; /// check for newer versions, even if we recently checked for updates. /// /// If `force` is not passed, we check for updates every day at most -pub fn check_for_update(config: config::Config, force: bool, client: Client) -> Result<()> { +pub async fn check_for_update(config: config::Config, force: bool, client: Client) -> Result<()> { let version_file = config.home.join("version.toml"); let current_time = SystemTime::now(); // if we don't end up checking, we don't want to overwrite the last checked time @@ -29,7 +29,7 @@ pub fn check_for_update(config: config::Config, force: bool, client: Client) -> let last_checked_time = get_last_checked_time_from_disk(&version_file); if force || last_checked_time.is_none() { - do_update_check(&mut checked, force, client)?; + do_update_check(&mut checked, force, client).await?; } else if let Some(last_checked_time) = last_checked_time { let time_since_check = current_time.duration_since(last_checked_time)?.as_secs(); tracing::trace!( @@ -38,7 +38,7 @@ pub fn check_for_update(config: config::Config, force: bool, client: Client) -> ); if time_since_check > ONE_DAY { - do_update_check(&mut checked, force, client)?; + do_update_check(&mut checked, force, client).await?; } } @@ -50,12 +50,12 @@ pub fn check_for_update(config: config::Config, force: bool, client: Client) -> Ok(()) } -fn do_update_check( +async fn do_update_check( checked: &mut bool, should_output_if_updated: bool, client: Client, ) -> Result<()> { - let latest_version = get_latest_release(client)?; + let latest_version = get_latest_release(client).await?; let pretty_latest = Style::Version.paint(format!("v{}", latest_version)); if latest_version > Version::parse(PKG_VERSION)? { let message = format!( From a1442327f85369a37339f6fb791ac14cd0cdf0de Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 14 Mar 2024 18:46:11 +0100 Subject: [PATCH 03/68] more fixes --- crates/sputnik/src/report.rs | 2 +- crates/sputnik/src/session.rs | 6 +- src/cli.rs | 50 ++++-- src/command/dev/do_dev.rs | 9 +- src/command/dev/protocol/leader.rs | 21 ++- src/command/dev/router/command.rs | 23 ++- src/command/dev/schema.rs | 122 ++++++------- src/command/graph/introspect.rs | 6 +- src/command/subgraph/introspect.rs | 6 +- src/command/subgraph/publish.rs | 53 +++--- src/command/supergraph/resolve_config.rs | 220 +++++++++++------------ src/command/update/mod.rs | 2 +- src/options/introspect.rs | 6 +- 13 files changed, 283 insertions(+), 243 deletions(-) diff --git a/crates/sputnik/src/report.rs b/crates/sputnik/src/report.rs index c9886f759..c22f4226c 100644 --- a/crates/sputnik/src/report.rs +++ b/crates/sputnik/src/report.rs @@ -1,5 +1,5 @@ use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use url::Url; use uuid::Uuid; diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index ea0c49925..e24bc4e3b 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -1,6 +1,6 @@ use camino::Utf8PathBuf; use ci_info::types::Vendor as CiVendor; -use reqwest::blocking::Client; +use reqwest::Client; use reqwest::Url; use rover_client::shared::GitContext; use semver::Version; @@ -132,7 +132,7 @@ impl Session { } /// sends anonymous usage data to the endpoint defined in ReportingInfo. - pub fn report(&self) -> Result<(), SputnikError> { + pub async fn report(&self) -> Result<(), SputnikError> { if self.reporting_info.is_telemetry_enabled && !cfg!(debug_assertions) { // set timeout to 400 ms to prevent blocking for too long on reporting let timeout = Duration::from_millis(4000); @@ -145,7 +145,7 @@ impl Session { .header("User-Agent", &self.reporting_info.user_agent) .header("Content-Type", "application/json") .timeout(timeout) - .send()?; + .send().await?; } Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index fb4063849..8e2957adb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -123,9 +123,9 @@ impl Rover { // if successful, report the usage data in the background Ok(session) => { // kicks off the reporting on a background thread - let report_thread = thread::spawn(move || { + let report_thread = tokio::task::spawn(async move { // log + ignore errors because it is not in the critical path - let _ = session.report().map_err(|telemetry_error| { + let _ = session.report().await.map_err(|telemetry_error| { tracing::debug!(?telemetry_error); telemetry_error }); @@ -139,7 +139,7 @@ impl Rover { // makes sure the reporting finishes in the background // before continuing. // ignore errors because it is not in the critical path - let _ = report_thread.join(); + let _ = report_thread.await; // return result of app execution // now that we have reported our usage data @@ -188,28 +188,42 @@ impl Rover { } Command::Fed2(command) => command.run(self.get_client_config()?), Command::Supergraph(command) => { - command.run(self.get_install_override_path()?, self.get_client_config()?).await + command + .run(self.get_install_override_path()?, self.get_client_config()?) + .await } Command::Docs(command) => command.run(), - Command::Graph(command) => command.run( - self.get_client_config()?, - self.get_git_context()?, - self.get_checks_timeout_seconds()?, - &self.output_opts, - ).await, + Command::Graph(command) => { + command + .run( + self.get_client_config()?, + self.get_git_context()?, + self.get_checks_timeout_seconds()?, + &self.output_opts, + ) + .await + } Command::Template(command) => command.run(self.get_client_config()?).await, Command::Readme(command) => command.run(self.get_client_config()?).await, - Command::Subgraph(command) => command.run( - self.get_client_config()?, - self.get_git_context()?, - self.get_checks_timeout_seconds()?, - &self.output_opts, - ).await, + Command::Subgraph(command) => { + command + .run( + self.get_client_config()?, + self.get_git_context()?, + self.get_checks_timeout_seconds()?, + &self.output_opts, + ) + .await + } Command::Update(command) => { - command.run(self.get_rover_config()?, self.get_reqwest_client()?) + command + .run(self.get_rover_config()?, self.get_reqwest_client()?) + .await } Command::Install(command) => { - command.do_install(self.get_install_override_path()?, self.get_client_config()?).await + command + .do_install(self.get_install_override_path()?, self.get_client_config()?) + .await } Command::Info(command) => command.run(), Command::Explain(command) => command.run(), diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 9638f9620..353360a42 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -48,7 +48,9 @@ impl Dev { follower_channel.clone(), self.opts.plugin_opts.clone(), router_config_handler, - ).await? { + ) + .await? + { eprintln!("{0}Do not run this command in production! {0}It is intended for local development.", Emoji::Warn); let (ready_sender, ready_receiver) = sync_channel(1); let follower_messenger = FollowerMessenger::from_main_session( @@ -75,9 +77,10 @@ impl Dev { .unwrap(); }); - let subgraph_watcher_handle = std::thread::spawn(move || { + let subgraph_watcher_handle = tokio::task::spawn(async move { let _ = leader_session .listen_for_all_subgraph_updates(ready_sender) + .await .map_err(log_err_and_continue); }); @@ -115,7 +118,7 @@ impl Dev { }); subgraph_watcher_handle - .join() + .await .expect("could not wait for subgraph watcher thread"); } else { let follower_messenger = FollowerMessenger::from_attached_session(&ipc_socket_addr); diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index ec1decbdb..a004611b9 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -16,6 +16,7 @@ use apollo_federation_types::{ }; use camino::Utf8PathBuf; use crossbeam_channel::{bounded, Receiver, Sender}; +use futures::TryFutureExt; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use rover_std::Emoji; use semver::Version; @@ -139,6 +140,7 @@ impl LeaderSession { ) -> RoverResult<()> { self.receive_messages_from_attached_sessions()?; self.receive_all_subgraph_updates(ready_sender).await; + Ok(()) } /// Listen for incoming subgraph updates and re-compose the supergraph @@ -299,10 +301,10 @@ impl LeaderSession { /// Reruns composition, which triggers the router to reload. async fn compose(&mut self) -> CompositionResult { - self.compose_runner + match self + .compose_runner .run(&mut self.supergraph_config()) - .await - .and_then(|maybe_new_schema| { + .and_then(|maybe_new_schema| async { if maybe_new_schema.is_some() { if let Err(err) = self.router_runner.spawn().await { return Err(err.to_string()); @@ -310,14 +312,18 @@ impl LeaderSession { } Ok(maybe_new_schema) }) - .map_err(|e| { + .await + { + Ok(res) => Ok(res), + Err(e) => { let _ = self .router_runner .kill() .await .map_err(log_err_and_continue); - e - }) + Err(e) + } + } } /// Reads a [`FollowerMessage`] from an open socket connection. @@ -402,7 +408,8 @@ impl LeaderSession { impl Drop for LeaderSession { fn drop(&mut self) { - self.shutdown(); + todo!(); + //self.shutdown().await; } } diff --git a/src/command/dev/router/command.rs b/src/command/dev/router/command.rs index 60513813e..594a93eea 100644 --- a/src/command/dev/router/command.rs +++ b/src/command/dev/router/command.rs @@ -52,11 +52,18 @@ impl BackgroundTask { if let Ok(apollo_graph_ref) = var("APOLLO_GRAPH_REF") { command.env("APOLLO_GRAPH_REF", apollo_graph_ref); - if let Some(api_key) = client_config.get_authenticated_client(profile_opt).map_err(|err| { - eprintln!("{} APOLLO_GRAPH_REF is set, but credentials could not be loaded. \ - Enterprise features within the router will not function. {err}", Emoji::Warn); - }).ok().and_then(|client| { - who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { + if let Some(client) = client_config + .get_authenticated_client(profile_opt) + .map_err(|err| { + eprintln!( + "{} APOLLO_GRAPH_REF is set, but credentials could not be loaded. \ + Enterprise features within the router will not function. {err}", + Emoji::Warn + ); + }) + .ok() + { + if let Some(api_key) = who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { eprintln!("{} Could not determine the type of configured credentials, \ Router may fail to start if Enterprise features are enabled. {err}", Emoji::Warn); Some(client.credential.api_key.clone()) @@ -74,8 +81,10 @@ impl BackgroundTask { None } } - }) - }) { command.env("APOLLO_KEY", api_key); } + }) { + command.env("APOLLO_KEY", api_key); + } + } } let mut child = command diff --git a/src/command/dev/schema.rs b/src/command/dev/schema.rs index 4775c16ad..a508564e3 100644 --- a/src/command/dev/schema.rs +++ b/src/command/dev/schema.rs @@ -117,68 +117,70 @@ impl SupergraphOpts { .with_timeout(Duration::from_secs(5)) .build()?; let mut studio_client: Option = None; - supergraph_config - .into_iter() - .map(|(yaml_subgraph_name, subgraph_config)| { - let routing_url = subgraph_config - .routing_url - .map(|url_str| Url::parse(&url_str).map_err(RoverError::from)) - .transpose()?; - match subgraph_config.schema { - SchemaSource::File { file } => { - let routing_url = routing_url.ok_or_else(|| { - anyhow!("`routing_url` must be set when using a local schema file") - })?; - SubgraphSchemaWatcher::new_from_file_path( - (yaml_subgraph_name, routing_url), - file, - follower_messenger.clone(), - ) - } - SchemaSource::SubgraphIntrospection { - subgraph_url, - introspection_headers, - } => SubgraphSchemaWatcher::new_from_url( - (yaml_subgraph_name, subgraph_url), - client.clone(), + + let mut res = Vec::new(); + for (yaml_subgraph_name, subgraph_config) in supergraph_config.into_iter() { + let routing_url = subgraph_config + .routing_url + .map(|url_str| Url::parse(&url_str).map_err(RoverError::from)) + .transpose()?; + let elem = match subgraph_config.schema { + SchemaSource::File { file } => { + let routing_url = routing_url.ok_or_else(|| { + anyhow!("`routing_url` must be set when using a local schema file") + })?; + SubgraphSchemaWatcher::new_from_file_path( + (yaml_subgraph_name, routing_url), + file, follower_messenger.clone(), - polling_interval, - introspection_headers, - ), - SchemaSource::Sdl { sdl } => { - let routing_url = routing_url.ok_or_else(|| { - anyhow!("`routing_url` must be set when providing SDL directly") - })?; - SubgraphSchemaWatcher::new_from_sdl( - (yaml_subgraph_name, routing_url), - sdl, - follower_messenger.clone(), - ) - } - SchemaSource::Subgraph { - graphref, - subgraph: graphos_subgraph_name, - } => { - let studio_client = if let Some(studio_client) = studio_client.as_ref() { - studio_client - } else { - let client = client_config.get_authenticated_client(profile_opt)?; - studio_client = Some(client); - studio_client.as_ref().unwrap() - }; + ) + } + SchemaSource::SubgraphIntrospection { + subgraph_url, + introspection_headers, + } => SubgraphSchemaWatcher::new_from_url( + (yaml_subgraph_name, subgraph_url), + client.clone(), + follower_messenger.clone(), + polling_interval, + introspection_headers, + ), + SchemaSource::Sdl { sdl } => { + let routing_url = routing_url.ok_or_else(|| { + anyhow!("`routing_url` must be set when providing SDL directly") + })?; + SubgraphSchemaWatcher::new_from_sdl( + (yaml_subgraph_name, routing_url), + sdl, + follower_messenger.clone(), + ) + } + SchemaSource::Subgraph { + graphref, + subgraph: graphos_subgraph_name, + } => { + let studio_client = if let Some(studio_client) = studio_client.as_ref() { + studio_client + } else { + let client = client_config.get_authenticated_client(profile_opt)?; + studio_client = Some(client); + studio_client.as_ref().unwrap() + }; - SubgraphSchemaWatcher::new_from_graph_ref( - &graphref, - graphos_subgraph_name, - routing_url, - yaml_subgraph_name, - follower_messenger.clone(), - studio_client, - ).await - } + SubgraphSchemaWatcher::new_from_graph_ref( + &graphref, + graphos_subgraph_name, + routing_url, + yaml_subgraph_name, + follower_messenger.clone(), + studio_client, + ) + .await } - }) - .collect::>>() - .map(Some) + }; + res.push(elem?); + } + + Ok(Some(res)) } } diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index ee9cce832..513d461ff 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -22,7 +22,7 @@ pub struct Introspect { impl Introspect { pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { - self.exec_and_watch(&client, output_opts) + self.exec_and_watch(&client, output_opts).await } else { let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) @@ -47,8 +47,8 @@ impl Introspect { ) } - pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { + pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts) + .exec_and_watch(|| self.exec(client, false), output_opts).await } } diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index bb9d99c88..9b637e95c 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -20,7 +20,7 @@ pub struct Introspect { impl Introspect { pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { - self.exec_and_watch(&client, output_opts) + self.exec_and_watch(&client, output_opts).await } else { let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) @@ -45,8 +45,8 @@ impl Introspect { ) } - pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { + pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts) + .exec_and_watch(|| self.exec(client, false), output_opts).await } } diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index eb7219696..09c6f91a4 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -2,6 +2,7 @@ use std::io::{self, IsTerminal}; use anyhow::anyhow; use clap::Parser; +use futures::Future; use reqwest::Url; use rover_client::operations::subgraph::routing_url::{self, SubgraphRoutingUrlInput}; use serde::Serialize; @@ -64,7 +65,7 @@ impl Publish { self.no_url, &self.routing_url, self.allow_invalid_routing_url, - || { + || async { Ok(routing_url::run( SubgraphRoutingUrlInput { graph_ref: self.graph.graph_ref.clone(), @@ -77,7 +78,8 @@ impl Publish { &mut io::stderr(), &mut io::stdin(), io::stderr().is_terminal() && io::stdin().is_terminal(), - )?; + ) + .await?; eprintln!( "Publishing SDL to {} (subgraph: {}) using credentials from the {} profile.", @@ -112,7 +114,7 @@ impl Publish { }) } - async fn determine_routing_url( + async fn determine_routing_url( no_url: bool, routing_url: &Option, allow_invalid_routing_url: bool, @@ -128,7 +130,8 @@ impl Publish { is_atty: bool, ) -> RoverResult> where - F: Fn() -> impl Future>, + F: Fn() -> G, + G: Future>, { if no_url && routing_url.is_some() { return Err(RoverError::new(anyhow!( @@ -262,53 +265,56 @@ impl Publish { mod tests { use crate::command::subgraph::publish::Publish; - #[test] - fn test_no_url() { + #[tokio::test] + async fn test_no_url() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( true, &None, false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("".to_string())); } - #[test] - fn test_routing_url_provided() { + #[tokio::test] + async fn test_routing_url_provided() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( false, &Some("https://provided".to_string()), false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("https://provided".to_string())); } - #[test] - fn test_no_url_and_routing_url_provided() { + #[tokio::test] + async fn test_no_url_and_routing_url_provided() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( true, &Some("https://provided".to_string()), false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap_err(); assert_eq!( result.message(), @@ -316,26 +322,27 @@ mod tests { ); } - #[test] - fn test_routing_url_not_provided_already_exists() { + #[tokio::test] + async fn test_routing_url_not_provided_already_exists() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( false, &None, false, - || Ok("https://fromstudio".to_string()), + || async { Ok("https://fromstudio".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("https://fromstudio".to_string())); } - #[test] - fn test_routing_url_invalid_provided() { + #[tokio::test] + async fn test_routing_url_invalid_provided() { let mut input = "y".as_bytes(); let mut output: Vec = Vec::new(); @@ -343,19 +350,20 @@ mod tests { false, &Some("invalid".to_string()), false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("invalid".to_string())); assert!(std::str::from_utf8(&output).unwrap().contains("is not a valid routing URL. Continuing the publish will make this subgraph unreachable by your supergraph. Would you still like to publish?")); } - #[test] - fn test_not_url_invalid_from_studio() { + #[tokio::test] + async fn test_not_url_invalid_from_studio() { let mut input = "y".as_bytes(); let mut output: Vec = Vec::new(); @@ -363,11 +371,12 @@ mod tests { true, &None, false, - || Ok("invalid".to_string()), + ||async{Ok("invalid".to_string())}, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("".to_string())); diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index c92d1698e..e0845f0e2 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -64,125 +64,119 @@ pub(crate) async fn resolve_supergraph_yaml( .into_iter() .collect::>(); - let subgraph_definition_results: Vec<(String, RoverResult)> = - supergraph_config - .into_par_iter() - .map(|(subgraph_name, subgraph_data)| { - let cloned_subgraph_name = subgraph_name.to_string(); - let result = match &subgraph_data.schema { - SchemaSource::File { file } => { - let relative_schema_path = match unresolved_supergraph_yaml { - FileDescriptorType::File(config_path) => match config_path.parent() { - Some(parent) => { - let mut schema_path = parent.to_path_buf(); - schema_path.push(file); - schema_path - } - None => file.clone(), - }, - FileDescriptorType::Stdin => file.clone(), - }; + let mut subgraph_definition_results: Vec<(String, RoverResult)> = + Vec::new(); - Fs::read_file(relative_schema_path) - .map_err(|e| { - let mut err = RoverError::new(e); - err.set_suggestion(RoverErrorSuggestion::ValidComposeFile); - err - }) - .and_then(|schema| { - subgraph_data - .routing_url - .clone() - .ok_or_else(err_no_routing_url) - .map(|url| SubgraphDefinition::new(subgraph_name, url, &schema)) - }) - } - SchemaSource::SubgraphIntrospection { - subgraph_url, - introspection_headers, - } => { - client_config - .get_reqwest_client() - .map_err(RoverError::from) - .and_then(|reqwest_client| { - let client = - GraphQLClient::new(subgraph_url.as_ref(), reqwest_client); + for (subgraph_name, subgraph_data) in supergraph_config.into_iter() { + let cloned_subgraph_name = subgraph_name.to_string(); + let result = match &subgraph_data.schema { + SchemaSource::File { file } => { + let relative_schema_path = match unresolved_supergraph_yaml { + FileDescriptorType::File(config_path) => match config_path.parent() { + Some(parent) => { + let mut schema_path = parent.to_path_buf(); + schema_path.push(file); + schema_path + } + None => file.clone(), + }, + FileDescriptorType::Stdin => file.clone(), + }; - // given a federated introspection URL, use subgraph introspect to - // obtain SDL and add it to subgraph_definition. - introspect::run( - SubgraphIntrospectInput { - headers: introspection_headers.clone().unwrap_or_default(), - }, - &client, - false, - ) - .await - .map(|introspection_response| { - let schema = introspection_response.result; + Fs::read_file(relative_schema_path) + .map_err(|e| { + let mut err = RoverError::new(e); + err.set_suggestion(RoverErrorSuggestion::ValidComposeFile); + err + }) + .and_then(|schema| { + subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url) + .map(|url| SubgraphDefinition::new(subgraph_name, url, &schema)) + }) + } + SchemaSource::SubgraphIntrospection { + subgraph_url, + introspection_headers, + } => { + let reqwest_client = client_config + .get_reqwest_client() + .map_err(RoverError::from)?; + let client = GraphQLClient::new(subgraph_url.as_ref(), reqwest_client); - // We don't require a routing_url in config for this variant of a schema, - // if one isn't provided, just use the URL they passed for introspection. - let url = &subgraph_data - .routing_url - .clone() - .unwrap_or_else(|| subgraph_url.to_string()); - SubgraphDefinition::new(subgraph_name, url, schema) - }) - .map_err(RoverError::from) - }) - } - SchemaSource::Subgraph { - graphref: graph_ref, - subgraph, - } => { - client_config - .get_authenticated_client(profile_opt) - .map_err(RoverError::from) - .and_then(|authenticated_client| { - // given a graph_ref and subgraph, run subgraph fetch to - // obtain SDL and add it to subgraph_definition. - fetch::run( - SubgraphFetchInput { - graph_ref: GraphRef::from_str(graph_ref)?, - subgraph_name: subgraph.clone(), - }, - &authenticated_client, - ) - .await - .map_err(RoverError::from) - .and_then(|result| { - // We don't require a routing_url in config for this variant of a schema, - // if one isn't provided, just use the routing URL from the graph registry (if it exists). - if let rover_client::shared::SdlType::Subgraph { - routing_url: Some(graph_registry_routing_url), - } = result.sdl.r#type - { - let url = subgraph_data - .routing_url - .clone() - .unwrap_or(graph_registry_routing_url); - Ok(SubgraphDefinition::new( - subgraph_name, - url, - &result.sdl.contents, - )) - } else { - Err(err_no_routing_url()) - } - }) - }) - } - SchemaSource::Sdl { sdl } => subgraph_data + // given a federated introspection URL, use subgraph introspect to + // obtain SDL and add it to subgraph_definition. + introspect::run( + SubgraphIntrospectInput { + headers: introspection_headers.clone().unwrap_or_default(), + }, + &client, + false, + ) + .await + .map(|introspection_response| { + let schema = introspection_response.result; + + // We don't require a routing_url in config for this variant of a schema, + // if one isn't provided, just use the URL they passed for introspection. + let url = &subgraph_data .routing_url .clone() - .ok_or_else(err_no_routing_url) - .map(|url| SubgraphDefinition::new(subgraph_name, url, sdl)), - }; + .unwrap_or_else(|| subgraph_url.to_string()); + SubgraphDefinition::new(subgraph_name, url, schema) + }) + .map_err(RoverError::from) + } + SchemaSource::Subgraph { + graphref: graph_ref, + subgraph, + } => { + let authenticated_client = client_config + .get_authenticated_client(profile_opt) + .map_err(RoverError::from)?; + // given a graph_ref and subgraph, run subgraph fetch to + // obtain SDL and add it to subgraph_definition. + fetch::run( + SubgraphFetchInput { + graph_ref: GraphRef::from_str(&graph_ref)?, + subgraph_name: subgraph.clone(), + }, + &authenticated_client, + ) + .await + .map_err(RoverError::from) + .and_then(|result| { + // We don't require a routing_url in config for this variant of a schema, + // if one isn't provided, just use the routing URL from the graph registry (if it exists). + if let rover_client::shared::SdlType::Subgraph { + routing_url: Some(graph_registry_routing_url), + } = result.sdl.r#type + { + let url = subgraph_data + .routing_url + .clone() + .unwrap_or(graph_registry_routing_url); + Ok(SubgraphDefinition::new( + subgraph_name, + url, + &result.sdl.contents, + )) + } else { + Err(err_no_routing_url()) + } + }) + } + SchemaSource::Sdl { sdl } => subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url) + .map(|url| SubgraphDefinition::new(subgraph_name, url, sdl)), + }; - (cloned_subgraph_name, result) - }) - .collect(); + subgraph_definition_results.push((cloned_subgraph_name, result)); + } let mut subgraph_definitions = Vec::new(); let mut subgraph_definition_errors = Vec::new(); diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index 871f41a71..ef10b782c 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -1,7 +1,7 @@ mod check; use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use crate::{RoverOutput, RoverResult}; diff --git a/src/options/introspect.rs b/src/options/introspect.rs index 1017e3ef9..93218f3b1 100644 --- a/src/options/introspect.rs +++ b/src/options/introspect.rs @@ -1,4 +1,5 @@ use clap::Parser; +use futures::Future; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -30,9 +31,10 @@ pub struct IntrospectOpts { } impl IntrospectOpts { - pub async fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! + pub async fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! where - F: Fn() -> impl Future>, + F: Fn() -> G, + G: Future>, { let mut last_result = None; loop { From 9d0a3fd38884ab272070a9efa4b76e6658173ada Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 10:16:09 +0100 Subject: [PATCH 04/68] fix issues --- Cargo.lock | 1 + Cargo.toml | 1 - crates/rover-client/Cargo.toml | 2 +- crates/rover-client/src/blocking/client.rs | 14 +++----- src/command/dev/protocol/leader.rs | 34 ++++++++++--------- src/command/dev/router/runner.rs | 39 ++++++++++++++++++++-- 6 files changed, 62 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0c010a89..9f6572e3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4154,6 +4154,7 @@ dependencies = [ "ctrlc", "dialoguer", "flate2", + "futures", "graphql_client", "heck 0.4.1", "houston", diff --git a/Cargo.toml b/Cargo.toml index 38ebbf329..77631f581 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,7 +188,6 @@ uuid = { workspace = true } url = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["rt", "macros"] } futures.workspace = true -backoff = { workspace = true, features = ["tokio"] } [dev-dependencies] assert_cmd = { workspace = true } diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 0a531bc31..9ec98f0da 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -12,7 +12,7 @@ ariadne = { workspace = true } apollo-federation-types = { workspace = true } apollo-parser = { workspace = true } apollo-encoder = { workspace = true } -backoff = { workspace = true } +backoff = { workspace = true, features = ["tokio", "futures"] } chrono = { workspace = true, features = ["serde"] } git-url-parse = { workspace = true } git2 = { workspace = true, features = [ diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index a19193196..eacf67b23 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -159,16 +159,12 @@ impl GraphQLClient { ..Default::default() }; - retry(backoff_strategy, graphql_operation).map_err(|e| match e { - BackoffError::Permanent(reqwest_error) - | BackoffError::Transient { - err: reqwest_error, - retry_after: _, - } => RoverClientError::SendRequest { - source: reqwest_error, + retry(backoff_strategy, graphql_operation) + .await + .map_err(|e| RoverClientError::SendRequest { + source: e, endpoint_kind, - }, - }) + }) } else { graphql_operation().await.map_err(|e| match e { BackoffError::Permanent(reqwest_error) diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index a004611b9..1a6ae6311 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -37,7 +37,7 @@ pub struct LeaderSession { subgraphs: HashMap, ipc_socket_addr: String, compose_runner: ComposeRunner, - router_runner: RouterRunner, + router_runner: Option, follower_channel: FollowerChannel, leader_channel: LeaderChannel, federation_version: FederationVersion, @@ -126,7 +126,7 @@ impl LeaderSession { subgraphs: HashMap::new(), ipc_socket_addr, compose_runner, - router_runner, + router_runner: Some(router_runner), follower_channel, leader_channel, federation_version, @@ -306,8 +306,10 @@ impl LeaderSession { .run(&mut self.supergraph_config()) .and_then(|maybe_new_schema| async { if maybe_new_schema.is_some() { - if let Err(err) = self.router_runner.spawn().await { - return Err(err.to_string()); + if let Some(runner) = self.router_runner.as_mut() { + if let Err(err) = runner.spawn().await { + return Err(err.to_string()); + } } } Ok(maybe_new_schema) @@ -316,11 +318,9 @@ impl LeaderSession { { Ok(res) => Ok(res), Err(e) => { - let _ = self - .router_runner - .kill() - .await - .map_err(log_err_and_continue); + if let Some(runner) = self.router_runner.as_mut() { + let _ = runner.kill().await.map_err(log_err_and_continue); + } Err(e) } } @@ -369,13 +369,15 @@ impl LeaderSession { /// Shuts the router down, removes the socket file, and exits the process. pub async fn shutdown(&mut self) { - let _ = self - .router_runner - .kill() - .await - .map_err(log_err_and_continue); - let _ = std::fs::remove_file(&self.ipc_socket_addr); - std::process::exit(1) + let router_runner = self.router_runner.take(); + let ipc_socket_addr = self.ipc_socket_addr.clone(); + tokio::task::spawn(async move { + if let Some(mut runner) = router_runner { + let _ = runner.kill().await.map_err(log_err_and_continue); + } + let _ = std::fs::remove_file(&ipc_socket_addr); + std::process::exit(1) + }); } /// Handles a follower message by updating the internal subgraph representation if needed, diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index 5cb082945..acd0e5b11 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -259,7 +259,42 @@ impl RouterRunner { impl Drop for RouterRunner { fn drop(&mut self) { - //let _ = self.kill().map_err(log_err_and_continue); - todo!() + let router_handle = self.router_handle.take(); + let client_config = self.client_config.clone(); + let router_socket_addr = self.router_socket_addr; + // copying the kill procedure here to emulate an async drop + tokio::task::spawn(async move { + if router_handle.is_some() { + tracing::info!("killing the router"); + if let Ok(client) = client_config.get_reqwest_client() { + let mut ready = true; + let now = Instant::now(); + let seconds = 5; + while ready && now.elapsed() < Duration::from_secs(seconds) { + let _ = client + .get(format!( + "http://{}/?query={{__typename}}", + &router_socket_addr + )) + .header("Content-Type", "application/json") + .send() + .await + .and_then(|r| r.error_for_status()) + .map_err(|_| { + ready = false; + }); + std::thread::sleep(Duration::from_millis(250)); + } + + if !ready { + tracing::info!("router stopped successfully"); + } else { + log_err_and_continue(RoverError::new(anyhow!( + "the router was unable to stop", + ))); + } + } + } + }); } } From 120a17df8bbc85d177ef9c2340043ae266bd1dd2 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 11:04:01 +0100 Subject: [PATCH 05/68] lint --- crates/rover-client/Cargo.toml | 12 ++++----- crates/rover-client/build.rs | 6 ++--- .../operations/graph/check_workflow/runner.rs | 4 ++- .../src/operations/graph/delete/runner.rs | 7 ++--- .../src/operations/graph/lint/runner.rs | 26 +++++++++++------- .../persisted_queries/name/runner.rs | 4 ++- .../persisted_queries/publish/runner.rs | 4 ++- .../src/operations/subgraph/check/runner.rs | 3 ++- .../subgraph/check_workflow/runner.rs | 4 ++- .../src/operations/subgraph/fetch/runner.rs | 3 ++- .../operations/subgraph/introspect/runner.rs | 20 +++++++------- .../src/operations/subgraph/lint/runner.rs | 6 +++-- .../src/operations/subgraph/publish/runner.rs | 6 +++-- crates/sputnik/src/session.rs | 3 ++- src/cli.rs | 2 +- src/command/dev/compose.rs | 27 ++++++++++++------- src/command/dev/introspect.rs | 6 +++-- src/command/dev/router/command.rs | 3 +-- src/command/dev/watcher.rs | 8 ++++-- src/command/graph/introspect.rs | 3 ++- src/command/graph/mod.rs | 4 ++- src/command/install/plugin.rs | 16 +++++------ src/command/persisted_queries/publish.rs | 3 ++- src/command/readme/publish.rs | 3 ++- src/command/subgraph/check.rs | 6 +++-- src/command/subgraph/delete.rs | 6 +++-- src/command/subgraph/introspect.rs | 3 ++- src/command/subgraph/lint.rs | 3 ++- src/command/subgraph/mod.rs | 4 ++- src/command/subgraph/publish.rs | 2 +- src/command/supergraph/compose/do_compose.rs | 7 +++-- src/command/supergraph/resolve_config.rs | 3 +-- src/options/template.rs | 3 ++- xtask/Cargo.toml | 2 +- xtask/src/commands/prep/mod.rs | 4 +-- xtask/src/commands/prep/templates_schema.rs | 9 ++++--- xtask/src/main.rs | 9 ++++--- 37 files changed, 146 insertions(+), 98 deletions(-) diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 9ec98f0da..21c2f7957 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -43,13 +43,13 @@ tokio = { workspace = true, features = ["rt", "macros"] } [build-dependencies] anyhow = { workspace = true } camino = { workspace = true } -#rover-std = { workspace = true } +rover-std = { workspace = true } serde_json = { workspace = true } -#reqwest = { workspace = true, features = [ -# "json", -# "blocking", -# "native-tls-vendored", -#] } +reqwest = { workspace = true, features = [ + "json", + "blocking", + "native-tls-vendored", +] } [dev-dependencies] indoc = { workspace = true} diff --git a/crates/rover-client/build.rs b/crates/rover-client/build.rs index 0f79309cb..ff9861a59 100644 --- a/crates/rover-client/build.rs +++ b/crates/rover-client/build.rs @@ -1,9 +1,9 @@ use anyhow::{Context, Result}; -//use rover_std::Fs; +use rover_std::Fs; fn main() -> Result<()> { println!("cargo:rerun-if-changed=.schema/schema.graphql"); - //Fs::read_file(".schema/schema.graphql") - // .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; + Fs::read_file(".schema/schema.graphql") + .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; Ok(()) } diff --git a/crates/rover-client/src/operations/graph/check_workflow/runner.rs b/crates/rover-client/src/operations/graph/check_workflow/runner.rs index bc754d09b..aec6e73a2 100644 --- a/crates/rover-client/src/operations/graph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/graph/check_workflow/runner.rs @@ -45,7 +45,9 @@ pub async fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()).await; + let result = client + .post::(input.clone().into()) + .await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/graph/delete/runner.rs b/crates/rover-client/src/operations/graph/delete/runner.rs index 442c94ec3..82cebf5f9 100644 --- a/crates/rover-client/src/operations/graph/delete/runner.rs +++ b/crates/rover-client/src/operations/graph/delete/runner.rs @@ -27,16 +27,13 @@ pub async fn run(input: GraphDeleteInput, client: &StudioClient) -> Result<(), R Ok(data) => data, Err(e) => { if e.to_string().contains("Variant not found") { - if let Err(no_variant_err) = variant::run( + variant::run( VariantListInput { graph_ref: graph_ref.clone(), }, client, ) - .await - { - return Err(no_variant_err); - } + .await?; } return Err(e); } diff --git a/crates/rover-client/src/operations/graph/lint/runner.rs b/crates/rover-client/src/operations/graph/lint/runner.rs index d46f99d1f..ab69cfaed 100644 --- a/crates/rover-client/src/operations/graph/lint/runner.rs +++ b/crates/rover-client/src/operations/graph/lint/runner.rs @@ -27,7 +27,10 @@ pub(crate) struct LintGraphMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub async fn run(input: LintGraphInput, client: &StudioClient) -> Result { +pub async fn run( + input: LintGraphInput, + client: &StudioClient, +) -> Result { let graph_ref = input.graph_ref.clone(); let base_schema = if input.ignore_existing { @@ -36,20 +39,23 @@ pub async fn run(input: LintGraphInput, client: &StudioClient) -> Result( - LintGraphMutationInput { - graph_ref, - proposed_schema: input.proposed_schema.clone(), - base_schema, - } - .into(), - ).await?; + let data = client + .post::( + LintGraphMutationInput { + graph_ref, + proposed_schema: input.proposed_schema.clone(), + base_schema, + } + .into(), + ) + .await?; get_lint_response_from_result( data, diff --git a/crates/rover-client/src/operations/persisted_queries/name/runner.rs b/crates/rover-client/src/operations/persisted_queries/name/runner.rs index 806537a6d..69792c395 100644 --- a/crates/rover-client/src/operations/persisted_queries/name/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/name/runner.rs @@ -22,7 +22,9 @@ pub async fn run( ) -> Result { let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); - let data = client.post::(input.into()).await?; + let data = client + .post::(input.into()) + .await?; build_response(data, graph_id, list_id) } diff --git a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs index e13af29ca..774eb6f09 100644 --- a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs @@ -26,7 +26,9 @@ pub async fn run( let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); let total_operations = input.operation_manifest.operations.len(); - let data = client.post::(input.into()).await?; + let data = client + .post::(input.into()) + .await?; build_response(data, graph_id, list_id, total_operations) } diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 69cc59f65..cdc2329bb 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -38,7 +38,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, diff --git a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs index 20a439738..d768e5525 100644 --- a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs @@ -52,7 +52,9 @@ pub async fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()).await; + let result = client + .post::(input.clone().into()) + .await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index fdd3b38d7..522c20776 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -31,7 +31,8 @@ pub async fn run( graph_ref: input.graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref: input.graph_ref, diff --git a/crates/rover-client/src/operations/subgraph/introspect/runner.rs b/crates/rover-client/src/operations/subgraph/introspect/runner.rs index ac3d38eee..95024a1e9 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/runner.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/runner.rs @@ -30,17 +30,17 @@ pub async fn run( ); } let response_data = if should_retry { - client.post::( - input.into(), - &mut header_map, - EndpointKind::Customer, - ).await + client + .post::(input.into(), &mut header_map, EndpointKind::Customer) + .await } else { - client.post_no_retry::( - input.into(), - &mut header_map, - EndpointKind::Customer, - ).await + client + .post_no_retry::( + input.into(), + &mut header_map, + EndpointKind::Customer, + ) + .await }; match response_data { diff --git a/crates/rover-client/src/operations/subgraph/lint/runner.rs b/crates/rover-client/src/operations/subgraph/lint/runner.rs index 330b2ecea..22f40d0c3 100644 --- a/crates/rover-client/src/operations/subgraph/lint/runner.rs +++ b/crates/rover-client/src/operations/subgraph/lint/runner.rs @@ -40,7 +40,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, @@ -55,7 +56,8 @@ pub async fn run( subgraph_name: input.subgraph_name, }, client, - ).await?; + ) + .await?; Some(fetch_response.sdl.contents) } else { None diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index d4ba95d55..bd55d825b 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -44,7 +44,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await + ) + .await .is_ok(); if variant_exists { @@ -54,7 +55,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index e24bc4e3b..1be43a3d7 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -145,7 +145,8 @@ impl Session { .header("User-Agent", &self.reporting_info.user_agent) .header("Content-Type", "application/json") .timeout(timeout) - .send().await?; + .send() + .await?; } Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index 8e2957adb..686d69b60 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -20,7 +20,7 @@ use rover_client::shared::GitContext; use sputnik::Session; use timber::Level; -use std::{io, process, thread}; +use std::{io, process}; #[derive(Debug, Serialize, Parser)] #[command( diff --git a/src/command/dev/compose.rs b/src/command/dev/compose.rs index 442f6d2b2..489cea96c 100644 --- a/src/command/dev/compose.rs +++ b/src/command/dev/compose.rs @@ -46,11 +46,14 @@ impl ComposeRunner { if let Some(plugin_exe) = &self.plugin_exe { Ok(plugin_exe.clone()) } else { - let plugin_exe = self.compose.maybe_install_supergraph( - self.override_install_path.clone(), - self.client_config.clone(), - federation_version, - ).await?; + let plugin_exe = self + .compose + .maybe_install_supergraph( + self.override_install_path.clone(), + self.client_config.clone(), + federation_version, + ) + .await?; self.plugin_exe = Some(plugin_exe.clone()); Ok(plugin_exe) } @@ -61,11 +64,15 @@ impl ComposeRunner { supergraph_config: &mut SupergraphConfig, ) -> std::result::Result, String> { let prev_state = self.composition_state(); - self.composition_state = Some(self.compose.exec( - self.override_install_path.clone(), - self.client_config.clone(), - supergraph_config, - ).await); + self.composition_state = Some( + self.compose + .exec( + self.override_install_path.clone(), + self.client_config.clone(), + supergraph_config, + ) + .await, + ); let new_state = self.composition_state(); match (prev_state, new_state) { diff --git a/src/command/dev/introspect.rs b/src/command/dev/introspect.rs index ae371b3cd..888db124d 100644 --- a/src/command/dev/introspect.rs +++ b/src/command/dev/introspect.rs @@ -116,7 +116,8 @@ impl SubgraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false).await + .exec(&self.client, false) + .await } } @@ -140,6 +141,7 @@ impl GraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false).await + .exec(&self.client, false) + .await } } diff --git a/src/command/dev/router/command.rs b/src/command/dev/router/command.rs index 594a93eea..fadd40327 100644 --- a/src/command/dev/router/command.rs +++ b/src/command/dev/router/command.rs @@ -52,7 +52,7 @@ impl BackgroundTask { if let Ok(apollo_graph_ref) = var("APOLLO_GRAPH_REF") { command.env("APOLLO_GRAPH_REF", apollo_graph_ref); - if let Some(client) = client_config + if let Ok(client) = client_config .get_authenticated_client(profile_opt) .map_err(|err| { eprintln!( @@ -61,7 +61,6 @@ impl BackgroundTask { Emoji::Warn ); }) - .ok() { if let Some(api_key) = who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { eprintln!("{} Could not determine the type of configured credentials, \ diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index fc2c768e5..310e1b55a 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -90,7 +90,8 @@ impl SubgraphSchemaWatcher { subgraph_name: graphos_subgraph_name.clone(), }, client, - ).await + ) + .await .map_err(RoverError::from)?; let routing_url = match (routing_url, response.sdl.r#type) { (Some(routing_url), _) => routing_url, @@ -178,7 +179,10 @@ impl SubgraphSchemaWatcher { Ok((subgraph_definition, refresher)) } - async fn update_subgraph(&mut self, last_message: Option<&String>) -> RoverResult> { + async fn update_subgraph( + &mut self, + last_message: Option<&String>, + ) -> RoverResult> { let print_error = |e: RoverError| { let _ = e.print(); }; diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index 513d461ff..74726f0dc 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -49,6 +49,7 @@ impl Introspect { pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts).await + .exec_and_watch(|| self.exec(client, false), output_opts) + .await } } diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 34f5a08db..bee04a01b 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -62,7 +62,9 @@ impl Graph { Command::Lint(command) => command.run(client_config).await, Command::Publish(command) => command.run(client_config, git_context).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts).await + command + .run(client_config.get_reqwest_client()?, output_opts) + .await } } } diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index 7f86a7ffb..b1a03d7ff 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -244,13 +244,12 @@ impl PluginInstaller { ) }) } else { - self.install_latest_major(plugin).await? - .ok_or_else(|| { - could_not_install_plugin( - &plugin.get_name(), - major_version.to_string().as_str(), - ) - }) + self.install_latest_major(plugin).await?.ok_or_else(|| { + could_not_install_plugin( + &plugin.get_name(), + major_version.to_string().as_str(), + ) + }) } } }, @@ -262,7 +261,8 @@ impl PluginInstaller { self.find_existing_exact(plugin, &version)? .ok_or_else(|| skip_update_err(&plugin.get_name(), &version)) } else { - self.install_exact(plugin, &version).await? + self.install_exact(plugin, &version) + .await? .ok_or_else(|| could_not_install_plugin(&plugin.get_name(), &version)) } } diff --git a/src/command/persisted_queries/publish.rs b/src/command/persisted_queries/publish.rs index 5840deb5f..39c41f883 100644 --- a/src/command/persisted_queries/publish.rs +++ b/src/command/persisted_queries/publish.rs @@ -104,7 +104,8 @@ impl Publish { operation_manifest, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::PersistedQueriesPublishResponse(result)) } } diff --git a/src/command/readme/publish.rs b/src/command/readme/publish.rs index 67d1b86d3..f6f0552f5 100644 --- a/src/command/readme/publish.rs +++ b/src/command/readme/publish.rs @@ -44,7 +44,8 @@ impl Publish { readme: new_readme, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::ReadmePublishResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index 18c9bf191..1616bb486 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -61,7 +61,8 @@ impl Check { }, }, &client, - ).await?; + ) + .await?; if self.config.background { Ok(RoverOutput::AsyncCheckResponse(workflow_res)) } else { @@ -73,7 +74,8 @@ impl Check { }, self.subgraph.subgraph_name.clone(), &client, - ).await?; + ) + .await?; Ok(RoverOutput::CheckWorkflowResponse(check_res)) } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index 1e831ea29..a634be8ce 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -48,7 +48,8 @@ impl Delete { dry_run, }, &client, - ).await?; + ) + .await?; RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), @@ -74,7 +75,8 @@ impl Delete { dry_run, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index 9b637e95c..d15bfd584 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -47,6 +47,7 @@ impl Introspect { pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts).await + .exec_and_watch(|| self.exec(client, false), output_opts) + .await } } diff --git a/src/command/subgraph/lint.rs b/src/command/subgraph/lint.rs index 0291883e8..e562fadab 100644 --- a/src/command/subgraph/lint.rs +++ b/src/command/subgraph/lint.rs @@ -43,7 +43,8 @@ impl Lint { ignore_existing: self.lint.ignore_existing_lint_violations, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::LintResponse(lint_result)) } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index e56e9e734..8a3696cb4 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -64,7 +64,9 @@ impl Subgraph { } Command::Delete(command) => command.run(client_config).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts).await + command + .run(client_config.get_reqwest_client()?, output_opts) + .await } Command::Fetch(command) => command.run(client_config).await, Command::Lint(command) => command.run(client_config).await, diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 09c6f91a4..4ea877583 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -371,7 +371,7 @@ mod tests { true, &None, false, - ||async{Ok("invalid".to_string())}, + || async { Ok("invalid".to_string()) }, &mut output, &mut input, true, diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 216424a47..97f1d0345 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -87,7 +87,8 @@ impl Compose { &self.opts.profile, ) .await?; - self.compose(override_install_path, client_config, &mut supergraph_config).await + self.compose(override_install_path, client_config, &mut supergraph_config) + .await } pub async fn compose( @@ -96,7 +97,9 @@ impl Compose { client_config: StudioClientConfig, supergraph_config: &mut SupergraphConfig, ) -> RoverResult { - let output = self.exec(override_install_path, client_config, supergraph_config).await?; + let output = self + .exec(override_install_path, client_config, supergraph_config) + .await?; Ok(RoverOutput::CompositionResult(output)) } diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index e0845f0e2..7d1612483 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -4,7 +4,6 @@ use apollo_federation_types::{ config::{FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig}, }; use apollo_parser::{ast, Parser}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rover_std::{Fs, Style}; use std::str::FromStr; @@ -140,7 +139,7 @@ pub(crate) async fn resolve_supergraph_yaml( // obtain SDL and add it to subgraph_definition. fetch::run( SubgraphFetchInput { - graph_ref: GraphRef::from_str(&graph_ref)?, + graph_ref: GraphRef::from_str(graph_ref)?, subgraph_name: subgraph.clone(), }, &authenticated_client, diff --git a/src/options/template.rs b/src/options/template.rs index 14767fc74..f5f1ce6df 100644 --- a/src/options/template.rs +++ b/src/options/template.rs @@ -59,7 +59,8 @@ pub(crate) async fn extract_tarball( .send() .await? .error_for_status()? - .bytes().await?; + .bytes() + .await?; f.write_all(&response_bytes[..])?; f.sync_all()?; let f = std::fs::File::open(&tarball_path)?; diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 39eff406f..ec2085452 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -28,7 +28,7 @@ semver = { workspace = true } serde_json_traversal = { workspace = true } shell-candy = { workspace = true } tempfile = { workspace = true } -tokio = { workspace = true, features = ["rt"] } +tokio = { workspace = true, features = ["rt", "macros"] } tokio-stream = { workspace = true } uuid = { workspace = true, features = ["v4"] } which = { workspace = true } diff --git a/xtask/src/commands/prep/mod.rs b/xtask/src/commands/prep/mod.rs index 2ea0d9c5b..b92fb7194 100644 --- a/xtask/src/commands/prep/mod.rs +++ b/xtask/src/commands/prep/mod.rs @@ -19,10 +19,10 @@ pub struct Prep { } impl Prep { - pub fn run(&self) -> Result<()> { + pub async fn run(&self) -> Result<()> { if !self.offline { main_schema::update()?; - templates_schema::update()?; + templates_schema::update().await?; } if self.schema_only { diff --git a/xtask/src/commands/prep/templates_schema.rs b/xtask/src/commands/prep/templates_schema.rs index 88d3ba4bd..a7e30b68c 100644 --- a/xtask/src/commands/prep/templates_schema.rs +++ b/xtask/src/commands/prep/templates_schema.rs @@ -3,7 +3,7 @@ use std::process::Command; use anyhow::{anyhow, Result}; use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use rover_client::{ blocking::GraphQLClient, @@ -20,15 +20,15 @@ const QUERIES_PATH: &str = "./src/command/template/queries.graphql"; /// If the user is offline and the schema already exists in the file system, the script does nothing. /// /// The URL to fetch the schema can be overridden with the APOLLO_GRAPHQL_SCHEMA_URL environment variable. -pub fn update() -> Result<()> { - let sdl = introspect()?; +pub async fn update() -> Result<()> { + let sdl = introspect().await?; let schema_path = Utf8PathBuf::from(SCHEMA_PATH); Fs::write_file(schema_path, sdl)?; regenerate_queries() } -fn introspect() -> Result { +async fn introspect() -> Result { let graphql_endpoint = "https://rover.apollo.dev/templates"; crate::info!( "fetching the latest templates schema by introspecting {}...", @@ -42,6 +42,7 @@ fn introspect() -> Result { &graphql_client, false, ) + .await .map(|response| response.schema_sdl) .map_err(|err| err.into()) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f7589d05b..d36d9979b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -8,8 +8,9 @@ use anyhow::Result; use clap::Parser; use console::style; -fn main() -> Result<()> { - Xtask::parse().run() +#[tokio::main] +async fn main() -> Result<()> { + Xtask::parse().run().await } #[derive(Debug, Parser)] @@ -50,7 +51,7 @@ pub enum Command { } impl Xtask { - pub fn run(&self) -> Result<()> { + pub async fn run(&self) -> Result<()> { match &self.command { Command::Docs(command) => command.run(), Command::Dist(command) => command.run(), @@ -58,7 +59,7 @@ impl Xtask { Command::UnitTest(command) => command.run(), Command::IntegrationTest(command) => command.run(), Command::Test(command) => command.run(), - Command::Prep(command) => command.run(), + Command::Prep(command) => command.run().await, Command::Package(command) => command.run(), }?; eprintln!("{}", style("Success!").green().bold()); From 1b2d6e65255d04e3e0e5f064ea9ade242d7e800e Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 12:29:37 +0100 Subject: [PATCH 06/68] use futures aware channel --- src/command/dev/do_dev.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 353360a42..edea38335 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Context}; use camino::Utf8PathBuf; +use futures::channel::mpsc::channel; +use futures::stream::StreamExt; use rover_std::Emoji; use super::protocol::{FollowerChannel, FollowerMessenger, LeaderChannel, LeaderSession}; @@ -10,8 +12,6 @@ use crate::command::dev::protocol::FollowerMessage; use crate::utils::client::StudioClientConfig; use crate::{RoverError, RoverOutput, RoverResult}; -use crossbeam_channel::bounded as sync_channel; - pub fn log_err_and_continue(err: RoverError) -> RoverError { let _ = err.print(); err @@ -52,7 +52,7 @@ impl Dev { .await? { eprintln!("{0}Do not run this command in production! {0}It is intended for local development.", Emoji::Warn); - let (ready_sender, ready_receiver) = sync_channel(1); + let (ready_sender, mut ready_receiver) = channel(1); let follower_messenger = FollowerMessenger::from_main_session( follower_channel.clone().sender, leader_channel.receiver, @@ -77,14 +77,14 @@ impl Dev { .unwrap(); }); - let subgraph_watcher_handle = tokio::task::spawn(async move { + let subgraph_watcher_handle = tokio::task::spawn(async move { let _ = leader_session .listen_for_all_subgraph_updates(ready_sender) .await .map_err(log_err_and_continue); }); - ready_receiver.recv().unwrap(); + ready_receiver.next().await.unwrap(); let subgraph_watchers = self .opts From 6cc15fe7fa65f382577c2b5e364908adaf2623dc Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 12:32:31 +0100 Subject: [PATCH 07/68] fix --- src/utils/client.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/utils/client.rs b/src/utils/client.rs index b12fb1c3d..26a9ef386 100644 --- a/src/utils/client.rs +++ b/src/utils/client.rs @@ -57,12 +57,17 @@ impl ClientBuilder { } pub(crate) fn build(self) -> Result { - let client = Client::builder() + let mut builder = Client::builder() .gzip(true) .brotli(true) .danger_accept_invalid_certs(self.accept_invalid_certs) - .danger_accept_invalid_hostnames(self.accept_invalid_hostnames) - .timeout(self.timeout.unwrap()) + .danger_accept_invalid_hostnames(self.accept_invalid_hostnames); + + if let Some(timeout) = self.timeout { + builder = builder.timeout(timeout); + } + + let client = builder .user_agent(format!("{}/{}", PKG_NAME, PKG_VERSION)) .build()?; From abe8dee75695d8060a79b1e9b84c82b3dc00a1fa Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 12:48:09 +0100 Subject: [PATCH 08/68] sleep --- src/command/dev/do_dev.rs | 8 -------- src/command/dev/router/runner.rs | 4 ++-- src/command/dev/watcher.rs | 2 +- src/options/introspect.rs | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index edea38335..02faadf8b 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -33,14 +33,6 @@ impl Dev { let leader_channel = LeaderChannel::new(); let follower_channel = FollowerChannel::new(); - // Build a Rayon Thread pool - let tp = rayon::ThreadPoolBuilder::new() - .num_threads(1) - .thread_name(|idx| format!("router-do-dev-{idx}")) - .build() - .map_err(|err| { - RoverError::new(anyhow!("could not create router do dev thread pool: {err}",)) - })?; if let Some(mut leader_session) = LeaderSession::new( override_install_path, &client_config, diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index acd0e5b11..0d8d3ffbb 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -116,7 +116,7 @@ impl RouterRunner { .map(|_| { ready = true; }); - std::thread::sleep(Duration::from_millis(250)); + tokio::time::sleep(Duration::from_millis(250)).await; } if ready { @@ -283,7 +283,7 @@ impl Drop for RouterRunner { .map_err(|_| { ready = false; }); - std::thread::sleep(Duration::from_millis(250)); + tokio::time::sleep(Duration::from_millis(250)).await; } if !ready { diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index 310e1b55a..28fed2510 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -246,7 +246,7 @@ impl SubgraphSchemaWatcher { ); loop { last_message = self.update_subgraph(last_message.as_ref()).await?; - std::thread::sleep(std::time::Duration::from_secs(polling_interval)); + tokio::time::sleep(std::time::Duration::from_secs(polling_interval)).await; } } SubgraphSchemaWatcherKind::File(path) => { diff --git a/src/options/introspect.rs b/src/options/introspect.rs index 93218f3b1..dacfd9e26 100644 --- a/src/options/introspect.rs +++ b/src/options/introspect.rs @@ -67,7 +67,7 @@ impl IntrospectOpts { last_result = Some(e); } } - std::thread::sleep(std::time::Duration::from_secs(1)) + tokio::time::sleep(std::time::Duration::from_secs(1)).await } } } From 40b35ab8aa7ebd14096555a3c7008827454456dc Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 13:53:12 +0100 Subject: [PATCH 09/68] lint --- xtask/src/commands/lint.rs | 10 +++---- xtask/src/main.rs | 2 +- xtask/src/tools/lychee.rs | 55 +++++++++++++++++--------------------- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/xtask/src/commands/lint.rs b/xtask/src/commands/lint.rs index a7dc50cdb..28d089f6e 100644 --- a/xtask/src/commands/lint.rs +++ b/xtask/src/commands/lint.rs @@ -10,20 +10,20 @@ use crate::tools::{CargoRunner, NpmRunner}; pub struct Lint {} impl Lint { - pub fn run(&self) -> Result<()> { + pub async fn run(&self) -> Result<()> { CargoRunner::new()?.lint()?; NpmRunner::new()?.lint()?; - lint_links() + lint_links().await } } #[cfg(not(windows))] -fn lint_links() -> Result<()> { - LycheeRunner::new()?.lint() +async fn lint_links() -> Result<()> { + LycheeRunner::new()?.lint().await } #[cfg(windows)] -fn lint_links() -> Result<()> { +async fn lint_links() -> Result<()> { eprintln!("Skipping the lint checker."); Ok(()) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d36d9979b..0fd80353b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -55,7 +55,7 @@ impl Xtask { match &self.command { Command::Docs(command) => command.run(), Command::Dist(command) => command.run(), - Command::Lint(command) => command.run(), + Command::Lint(command) => command.run().await, Command::UnitTest(command) => command.run(), Command::IntegrationTest(command) => command.run(), Command::Test(command) => command.run(), diff --git a/xtask/src/tools/lychee.rs b/xtask/src/tools/lychee.rs index 179869560..02795cfad 100644 --- a/xtask/src/tools/lychee.rs +++ b/xtask/src/tools/lychee.rs @@ -8,7 +8,6 @@ use lychee_lib::{ }; use reqwest::StatusCode; use std::{collections::HashSet, fs, path::PathBuf, time::Duration}; -use tokio::runtime::Runtime; use tokio_stream::StreamExt; pub(crate) struct LycheeRunner { @@ -34,7 +33,7 @@ impl LycheeRunner { Ok(Self { client }) } - pub(crate) fn lint(&self) -> Result<()> { + pub(crate) async fn lint(&self) -> Result<()> { crate::info!("Checking HTTP links in repository"); let inputs: Vec = get_md_files() @@ -46,45 +45,39 @@ impl LycheeRunner { }) .collect(); - let rt = Runtime::new()?; - let lychee_client = self.client.clone(); - rt.block_on(async move { - let links: Vec = Collector::new(None) - .collect_links(inputs) - .await - .collect::>>() - .await?; + let links: Vec = Collector::new(None) + .collect_links(inputs) + .await + .collect::>>() + .await?; - let failed_link_futures: Vec<_> = links - .into_iter() - .map(|link| tokio::spawn(get_failed_request(lychee_client.clone(), link))) - .collect(); + let failed_link_futures: Vec<_> = links + .into_iter() + .map(|link| tokio::spawn(get_failed_request(lychee_client.clone(), link))) + .collect(); - let links_size = failed_link_futures.len(); + let links_size = failed_link_futures.len(); - let mut failed_checks = Vec::with_capacity(links_size); - for f in failed_link_futures.into_iter() { - if let Some(failure) = f.await.expect("unexpected error while processing links") { - failed_checks.push(failure); - } + let mut failed_checks = Vec::with_capacity(links_size); + for f in failed_link_futures.into_iter() { + if let Some(failure) = f.await.expect("unexpected error while processing links") { + failed_checks.push(failure); } + } - crate::info!("{} links checked.", links_size); - - if !failed_checks.is_empty() { - for failed_check in failed_checks { - crate::info!("❌ {}", failed_check.as_str()); - } + crate::info!("{} links checked.", links_size); - Err(anyhow!("Some links in markdown documentation are down.")) - } else { - Ok(()) + if !failed_checks.is_empty() { + for failed_check in failed_checks { + crate::info!("❌ {}", failed_check.as_str()); } - })?; - Ok(()) + Err(anyhow!("Some links in markdown documentation are down.")) + } else { + Ok(()) + } } } From b75a5311d3f1f208efbe23a0d1adb47c91a164b5 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:18:59 +0100 Subject: [PATCH 10/68] backtrace --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1274aebd3..937274345 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -460,6 +460,8 @@ commands: steps: - run: command: cargo xtask << parameters.command >> << parameters.options >> + environment: + RUST_BACKTRACE: 1 - unless: condition: From bc95dd366ce0203edf9c8a2704a57369558735cb Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:31:46 +0100 Subject: [PATCH 11/68] fixes --- installers/binstall/src/install.rs | 11 +++++++---- src/command/install/plugin.rs | 3 ++- src/command/supergraph/compose/no_compose.rs | 2 +- src/command/template/list.rs | 2 +- src/command/template/templates.rs | 17 +++++++++-------- src/command/template/use.rs | 4 ++-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index 7143e779e..3a50ad9a2 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -48,7 +48,9 @@ impl Installer { client: &reqwest::Client, is_latest: bool, ) -> Result, InstallerError> { - let version = self.get_plugin_version(plugin_tarball_url, is_latest)?; + let version = self + .get_plugin_version(plugin_tarball_url, is_latest) + .await?; let bin_dir_path = self.get_bin_dir_path()?; if !bin_dir_path.exists() { @@ -76,18 +78,19 @@ impl Installer { Ok(Some(plugin_bin_destination)) } - pub fn get_plugin_version( + pub async fn get_plugin_version( &self, plugin_tarball_url: &str, is_latest: bool, ) -> Result { if is_latest { - let no_redirect_client = reqwest::blocking::Client::builder() + let no_redirect_client = reqwest::Client::builder() .redirect(reqwest::redirect::Policy::none()) .build()?; let response = no_redirect_client .head(plugin_tarball_url) - .send()? + .send() + .await? .error_for_status()?; if let Some(version) = response.headers().get("x-version") { diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index b1a03d7ff..e5bc0e1d2 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -338,7 +338,8 @@ impl PluginInstaller { async fn install_latest_major(&self, plugin: &Plugin) -> RoverResult> { let latest_version = self .rover_installer - .get_plugin_version(&plugin.get_tarball_url()?, true)?; + .get_plugin_version(&plugin.get_tarball_url()?, true) + .await?; if let Ok(Some(exe)) = self.find_existing_exact(plugin, &latest_version) { tracing::debug!("{} exists, skipping install", &exe); Ok(Some(exe)) diff --git a/src/command/supergraph/compose/no_compose.rs b/src/command/supergraph/compose/no_compose.rs index 4946d9497..433097c30 100644 --- a/src/command/supergraph/compose/no_compose.rs +++ b/src/command/supergraph/compose/no_compose.rs @@ -21,7 +21,7 @@ pub struct Compose { } impl Compose { - pub fn run( + pub async fn run( &self, _override_install_path: Option, _client_config: StudioClientConfig, diff --git a/src/command/template/list.rs b/src/command/template/list.rs index 084fd43cd..fe764e462 100644 --- a/src/command/template/list.rs +++ b/src/command/template/list.rs @@ -14,7 +14,7 @@ pub struct List { impl List { pub fn run(&self) -> RoverResult { - let templates = list_templates(self.options.language.clone())?; + let templates = list_templates(self.options.language.clone()).await?; Ok(RoverOutput::TemplateList(templates)) } } diff --git a/src/command/template/templates.rs b/src/command/template/templates.rs index dabd5f7b2..4a3693738 100644 --- a/src/command/template/templates.rs +++ b/src/command/template/templates.rs @@ -3,7 +3,7 @@ use std::env; use anyhow::anyhow; use console::Term; use dialoguer::Select; -use reqwest::blocking::Client; +use reqwest::Client; use serde::de::DeserializeOwned; use serde::Serialize; @@ -18,13 +18,14 @@ use super::queries::{ list_templates_for_language::ListTemplatesForLanguageTemplates, *, }; -fn request(body: &Body) -> RoverResult { +async fn request(body: &Body) -> RoverResult { let uri = env::var("APOLLO_TEMPLATES_API") .unwrap_or_else(|_| "https://rover.apollo.dev/templates".to_string()); let resp = Client::new() .post(uri) .json(body) .send() + .await .map_err(|e| anyhow!("Could not reach templates server: {}", e))?; let response: Response = resp .json() @@ -35,34 +36,34 @@ fn request(body: &Body) -> RoverResult< } /// Get a template by ID -pub fn get_template(template_id: &str) -> RoverResult> { +pub async fn get_template(template_id: &str) -> RoverResult> { use super::queries::get_template_by_id::*; let query = GetTemplateById::build_query(Variables { id: template_id.to_string(), }); - let resp: ResponseData = request(&query)?; + let resp: ResponseData = request(&query).await?; Ok(resp.template) } -pub fn get_templates_for_language( +pub async fn get_templates_for_language( language: ProjectLanguage, ) -> RoverResult> { use super::queries::get_templates_for_language::*; let query = GetTemplatesForLanguage::build_query(Variables { language: Some(language.into()), }); - let resp: ResponseData = request(&query)?; + let resp: ResponseData = request(&query).await?; error_if_empty(resp.templates) } -pub fn list_templates( +pub async fn list_templates( language: Option, ) -> RoverResult> { use super::queries::list_templates_for_language::*; let query = ListTemplatesForLanguage::build_query(Variables { language: language.map(Into::into), }); - let resp: ResponseData = request(&query)?; + let resp: ResponseData = request(&query).await?; error_if_empty(resp.templates) } diff --git a/src/command/template/use.rs b/src/command/template/use.rs index 4e1d80b8c..cdeeeb9e2 100644 --- a/src/command/template/use.rs +++ b/src/command/template/use.rs @@ -38,7 +38,7 @@ impl Use { // find the template to extract let (template_id, download_url) = if let Some(template_id) = &self.template { // if they specify an ID, get it - let result = get_template(template_id)?; + let result = get_template(template_id).await?; if let Some(result) = result { (template_id.clone(), result.download_url) } else { @@ -51,7 +51,7 @@ impl Use { } else { // otherwise, ask them what language they want to use let project_language = self.options.get_or_prompt_language()?; - let templates = get_templates_for_language(project_language)?; + let templates = get_templates_for_language(project_language).await?; let template = selection_prompt(templates)?; (template.id, template.download_url) }; From c6c4d03491faf038fe65eca914a1197a94f90d4f Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:41:07 +0100 Subject: [PATCH 12/68] fixes --- src/command/template/list.rs | 2 +- src/command/template/mod.rs | 2 +- src/command/template/templates.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/command/template/list.rs b/src/command/template/list.rs index fe764e462..0b0a907c5 100644 --- a/src/command/template/list.rs +++ b/src/command/template/list.rs @@ -13,7 +13,7 @@ pub struct List { } impl List { - pub fn run(&self) -> RoverResult { + pub async fn run(&self) -> RoverResult { let templates = list_templates(self.options.language.clone()).await?; Ok(RoverOutput::TemplateList(templates)) } diff --git a/src/command/template/mod.rs b/src/command/template/mod.rs index baacb9981..6a8501620 100644 --- a/src/command/template/mod.rs +++ b/src/command/template/mod.rs @@ -32,7 +32,7 @@ impl Template { pub(crate) async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { Command::Use(use_template) => use_template.run(client_config).await, - Command::List(list) => list.run(), + Command::List(list) => list.run().await, } } } diff --git a/src/command/template/templates.rs b/src/command/template/templates.rs index 4a3693738..6450339b8 100644 --- a/src/command/template/templates.rs +++ b/src/command/template/templates.rs @@ -28,7 +28,7 @@ async fn request(body: &Body) -> RoverR .await .map_err(|e| anyhow!("Could not reach templates server: {}", e))?; let response: Response = resp - .json() + .json().await .map_err(|e| anyhow!("Could not parse response from templates server: {}", e))?; response .data From 73ad05028aa539da370c32eaa1bb0e82a0ceaa0f Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:49:17 +0100 Subject: [PATCH 13/68] fmt --- src/command/template/templates.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/command/template/templates.rs b/src/command/template/templates.rs index 6450339b8..f7bda0260 100644 --- a/src/command/template/templates.rs +++ b/src/command/template/templates.rs @@ -28,7 +28,8 @@ async fn request(body: &Body) -> RoverR .await .map_err(|e| anyhow!("Could not reach templates server: {}", e))?; let response: Response = resp - .json().await + .json() + .await .map_err(|e| anyhow!("Could not parse response from templates server: {}", e))?; response .data From f6c98d3a55c436f0f8d0a3fe175c9bc97e2ce967 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:57:27 +0100 Subject: [PATCH 14/68] fix --- src/command/dev/no_dev.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command/dev/no_dev.rs b/src/command/dev/no_dev.rs index 0b3d88746..81cb46b09 100644 --- a/src/command/dev/no_dev.rs +++ b/src/command/dev/no_dev.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use camino::Utf8PathBuf; impl Dev { - pub fn run( + pub async fn run( &self, _override_install_path: Option, _client_config: StudioClientConfig, From d8c7d2e419565f432fab7d6fe2116e02ee5b1fcc Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 16:39:01 +0100 Subject: [PATCH 15/68] change back CI --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 937274345..1274aebd3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -460,8 +460,6 @@ commands: steps: - run: command: cargo xtask << parameters.command >> << parameters.options >> - environment: - RUST_BACKTRACE: 1 - unless: condition: From c5f68aa6ffc19a9d08637b26f60fa25a21f2ad9e Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 14 Mar 2024 16:24:58 +0100 Subject: [PATCH 16/68] temporarily make build scripts faster --- crates/rover-client/Cargo.toml | 12 ++++++------ crates/rover-client/build.rs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 8be0b2f55..6b5ee286d 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -42,13 +42,13 @@ regex = { workspace = true } [build-dependencies] anyhow = { workspace = true } camino = { workspace = true } -rover-std = { workspace = true } +#rover-std = { workspace = true } serde_json = { workspace = true } -reqwest = { workspace = true, features = [ - "json", - "blocking", - "native-tls-vendored", -] } +#reqwest = { workspace = true, features = [ +# "json", +# "blocking", +# "native-tls-vendored", +#] } [dev-dependencies] indoc = { workspace = true} diff --git a/crates/rover-client/build.rs b/crates/rover-client/build.rs index ff9861a59..0f79309cb 100644 --- a/crates/rover-client/build.rs +++ b/crates/rover-client/build.rs @@ -1,9 +1,9 @@ use anyhow::{Context, Result}; -use rover_std::Fs; +//use rover_std::Fs; fn main() -> Result<()> { println!("cargo:rerun-if-changed=.schema/schema.graphql"); - Fs::read_file(".schema/schema.graphql") - .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; + //Fs::read_file(".schema/schema.graphql") + // .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; Ok(()) } From 09d409f4b12b2c6a3f7fa99f22b22f35d1cc6fb2 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 14 Mar 2024 17:21:41 +0100 Subject: [PATCH 17/68] move to an async reqwest client --- Cargo.lock | 5 + Cargo.toml | 3 + crates/rover-client/Cargo.toml | 1 + crates/rover-client/src/blocking/client.rs | 110 ++++++++++-------- .../src/blocking/studio_client.rs | 8 +- .../operations/config/is_federated/runner.rs | 4 +- .../src/operations/config/who_am_i/runner.rs | 4 +- .../operations/contract/describe/runner.rs | 4 +- .../src/operations/contract/publish/runner.rs | 4 +- .../src/operations/graph/check/runner.rs | 4 +- .../operations/graph/check_workflow/runner.rs | 4 +- .../src/operations/graph/delete/runner.rs | 20 ++-- .../src/operations/graph/fetch/runner.rs | 4 +- .../src/operations/graph/introspect/runner.rs | 18 +-- .../src/operations/graph/lint/runner.rs | 6 +- .../src/operations/graph/publish/runner.rs | 4 +- .../src/operations/graph/variant/runner.rs | 4 +- .../src/operations/license/fetch/runner.rs | 7 +- .../persisted_queries/name/runner.rs | 4 +- .../persisted_queries/publish/runner.rs | 4 +- .../persisted_queries/resolve/runner.rs | 6 +- .../src/operations/readme/fetch/runner.rs | 4 +- .../src/operations/readme/publish/runner.rs | 4 +- .../src/operations/subgraph/check/runner.rs | 6 +- .../subgraph/check_workflow/runner.rs | 4 +- .../src/operations/subgraph/delete/runner.rs | 4 +- .../src/operations/subgraph/fetch/runner.rs | 6 +- .../operations/subgraph/introspect/runner.rs | 6 +- .../src/operations/subgraph/lint/runner.rs | 24 ++-- .../src/operations/subgraph/list/runner.rs | 4 +- .../src/operations/subgraph/publish/runner.rs | 8 +- .../operations/subgraph/routing_url/runner.rs | 4 +- .../src/operations/supergraph/fetch/runner.rs | 4 +- crates/rover-client/src/releases.rs | 18 ++- installers/binstall/src/install.rs | 19 +-- src/bin/rover.rs | 4 +- src/cli.rs | 40 ++++--- src/command/config/mod.rs | 4 +- src/command/config/whoami.rs | 4 +- src/command/contract/describe.rs | 5 +- src/command/contract/mod.rs | 6 +- src/command/contract/publish.rs | 5 +- src/command/dev/compose.rs | 8 +- src/command/dev/do_dev.rs | 14 ++- src/command/dev/introspect.rs | 16 +-- src/command/dev/protocol/leader.rs | 69 +++++++---- src/command/dev/router/command.rs | 4 +- src/command/dev/router/runner.rs | 47 +++++--- src/command/dev/schema.rs | 4 +- src/command/dev/watcher.rs | 28 ++--- src/command/graph/check.rs | 8 +- src/command/graph/delete.rs | 5 +- src/command/graph/fetch.rs | 5 +- src/command/graph/introspect.rs | 14 ++- src/command/graph/lint.rs | 5 +- src/command/graph/mod.rs | 16 +-- src/command/graph/publish.rs | 5 +- src/command/install/mod.rs | 8 +- src/command/install/plugin.rs | 57 +++++---- src/command/license/fetch.rs | 5 +- src/command/license/mod.rs | 4 +- src/command/persisted_queries/mod.rs | 4 +- src/command/persisted_queries/publish.rs | 8 +- src/command/readme/fetch.rs | 5 +- src/command/readme/mod.rs | 6 +- src/command/readme/publish.rs | 4 +- src/command/subgraph/check.rs | 6 +- src/command/subgraph/delete.rs | 6 +- src/command/subgraph/fetch.rs | 5 +- src/command/subgraph/introspect.rs | 14 ++- src/command/subgraph/lint.rs | 4 +- src/command/subgraph/list.rs | 5 +- src/command/subgraph/mod.rs | 18 +-- src/command/subgraph/publish.rs | 14 ++- src/command/supergraph/compose/do_compose.rs | 50 ++++---- src/command/supergraph/fetch.rs | 5 +- src/command/supergraph/mod.rs | 6 +- src/command/supergraph/resolve_config.rs | 4 +- src/command/template/mod.rs | 4 +- src/command/template/use.rs | 4 +- src/command/update/check.rs | 6 +- src/command/update/mod.rs | 4 +- src/options/introspect.rs | 6 +- src/options/template.rs | 9 +- src/utils/client.rs | 4 +- src/utils/telemetry.rs | 2 +- src/utils/version.rs | 12 +- 87 files changed, 534 insertions(+), 414 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca8c2d12d..3797c24ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,9 +577,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ + "futures-core", "getrandom", "instant", + "pin-project-lite", "rand 0.8.5", + "tokio", ] [[package]] @@ -4181,6 +4184,7 @@ dependencies = [ "tempdir", "termimad", "timber", + "tokio", "toml", "tracing", "url", @@ -4218,6 +4222,7 @@ dependencies = [ "serde_json", "strip-ansi-escapes", "thiserror", + "tokio", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index 464e5d625..001d16502 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -186,6 +186,9 @@ tracing = { workspace = true } which = { workspace = true } uuid = { workspace = true } url = { workspace = true, features = ["serde"] } +tokio = { workspace = true, features = ["rt", "macros"] } +futures.workspace = true +backoff = { workspace = true, features = ["tokio"] } [dev-dependencies] assert_cmd = { workspace = true } diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 6b5ee286d..0a531bc31 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -38,6 +38,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } regex = { workspace = true } +tokio = { workspace = true, features = ["rt", "macros"] } [build-dependencies] anyhow = { workspace = true } diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index 1361796d2..a19193196 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -2,9 +2,8 @@ use crate::error::{EndpointKind, RoverClientError}; use graphql_client::{Error as GraphQLError, GraphQLQuery, Response as GraphQLResponse}; use reqwest::{ - blocking::{Client as ReqwestClient, Response}, header::{HeaderMap, HeaderValue}, - StatusCode, + Client as ReqwestClient, Response, StatusCode, }; pub(crate) const JSON_CONTENT_TYPE: &str = "application/json"; @@ -34,7 +33,7 @@ impl GraphQLClient { /// /// Takes one argument, `variables`. Returns an optional response. /// Automatically retries requests. - pub fn post( + pub async fn post( &self, variables: Q::Variables, header_map: &mut HeaderMap, @@ -45,15 +44,17 @@ impl GraphQLClient { { let request_body = self.get_request_body::(variables)?; header_map.append("Content-Type", HeaderValue::from_str(JSON_CONTENT_TYPE)?); - let response = self.execute(request_body, header_map, true, endpoint_kind); - GraphQLClient::handle_response::(response?, endpoint_kind) + let response = self + .execute(request_body, header_map, true, endpoint_kind) + .await; + GraphQLClient::handle_response::(response?, endpoint_kind).await } /// Client method for making a GraphQL request. /// /// Takes one argument, `variables`. Returns an optional response. /// Does not automatically retry requests. - pub fn post_no_retry( + pub async fn post_no_retry( &self, variables: Q::Variables, header_map: &mut HeaderMap, @@ -64,8 +65,10 @@ impl GraphQLClient { { let request_body = self.get_request_body::(variables)?; header_map.append("Content-Type", HeaderValue::from_str(JSON_CONTENT_TYPE)?); - let response = self.execute(request_body, header_map, false, endpoint_kind); - GraphQLClient::handle_response::(response?, endpoint_kind) + let response = self + .execute(request_body, header_map, false, endpoint_kind) + .await; + GraphQLClient::handle_response::(response?, endpoint_kind).await } fn get_request_body( @@ -76,24 +79,25 @@ impl GraphQLClient { Ok(serde_json::to_string(&body)?) } - fn execute( + async fn execute( &self, request_body: String, header_map: &HeaderMap, should_retry: bool, endpoint_kind: EndpointKind, ) -> Result { - use backoff::{retry, Error as BackoffError, ExponentialBackoff}; + use backoff::{future::retry, Error as BackoffError, ExponentialBackoff}; tracing::trace!(request_headers = ?header_map); tracing::debug!("Request Body: {}", request_body); - let graphql_operation = || { + let graphql_operation = || async { let response = self .client .post(&self.graphql_endpoint) .headers(header_map.clone()) .body(request_body.clone()) - .send(); + .send() + .await; match response { Err(client_error) => { @@ -129,7 +133,7 @@ impl GraphQLClient { || response_status.is_redirection() { if matches!(response_status, StatusCode::BAD_REQUEST) { - if let Ok(text) = success.text() { + if let Ok(text) = success.text().await { tracing::debug!("{}", text); } Err(BackoffError::Permanent(status_error)) @@ -166,7 +170,7 @@ impl GraphQLClient { }, }) } else { - graphql_operation().map_err(|e| match e { + graphql_operation().await.map_err(|e| match e { BackoffError::Permanent(reqwest_error) | BackoffError::Transient { err: reqwest_error, @@ -187,13 +191,13 @@ impl GraphQLClient { /// body.data, it will also error, as this shouldn't be possible. /// /// If successful, it will return body.data, unwrapped - pub(crate) fn handle_response( + pub(crate) async fn handle_response( response: Response, endpoint_kind: EndpointKind, ) -> Result { let response_status = response.status(); tracing::debug!(response_status = ?response_status, response_headers = ?response.headers()); - match response.json::>() { + match response.json::>().await { Ok(response_body) => { if let Some(response_body_errors) = response_body.errors { handle_graphql_body_errors(response_body_errors)?; @@ -310,8 +314,8 @@ mod tests { assert_eq!(actual_error, expected_error); } - #[test] - fn test_successful_response() { + #[tokio::test] + async fn test_successful_response() { let server = MockServer::start(); let success_path = "/throw-me-a-frickin-bone-here"; let success_mock = server.mock(|when, then| { @@ -322,12 +326,14 @@ mod tests { let client = ReqwestClient::new(); let graphql_client = GraphQLClient::new(&server.url(success_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = success_mock.hits(); @@ -335,8 +341,8 @@ mod tests { assert!(response.is_ok()) } - #[test] - fn test_unrecoverable_server_error() { + #[tokio::test] + async fn test_unrecoverable_server_error() { let server = MockServer::start(); let internal_server_error_path = "/this-is-me-in-a-nutshell"; let internal_server_error_mock = server.mock(|when, then| { @@ -347,12 +353,14 @@ mod tests { let client = ReqwestClient::new(); let graphql_client = GraphQLClient::new(&server.url(internal_server_error_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = internal_server_error_mock.hits(); @@ -360,8 +368,8 @@ mod tests { assert!(response.is_err()); } - #[test] - fn test_unrecoverable_client_error() { + #[tokio::test] + async fn test_unrecoverable_client_error() { let server = MockServer::start(); let not_found_path = "/austin-powers-the-musical"; let not_found_mock = server.mock(|when, then| { @@ -372,12 +380,14 @@ mod tests { let client = ReqwestClient::new(); let graphql_client = GraphQLClient::new(&server.url(not_found_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = not_found_mock.hits(); @@ -387,8 +397,8 @@ mod tests { assert!(error.to_string().contains("Not Found")); } - #[test] - fn test_timeout_error() { + #[tokio::test] + async fn test_timeout_error() { let server = MockServer::start(); let timeout_path = "/i-timeout-easily"; let timeout_mock = server.mock(|when, then| { @@ -398,18 +408,20 @@ mod tests { .delay(Duration::from_secs(3)); }); - let client = reqwest::blocking::ClientBuilder::new() + let client = reqwest::ClientBuilder::new() .timeout(Duration::from_secs(1)) .build() .unwrap(); let graphql_client = GraphQLClient::new(&server.url(timeout_path), client); - let response = graphql_client.execute( - "{}".to_string(), - &HeaderMap::new(), - true, - EndpointKind::ApolloStudio, - ); + let response = graphql_client + .execute( + "{}".to_string(), + &HeaderMap::new(), + true, + EndpointKind::ApolloStudio, + ) + .await; let mock_hits = timeout_mock.hits(); diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index 133e1ae00..1a709e46d 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -7,8 +7,8 @@ use crate::{ use houston::{Credential, CredentialOrigin}; use graphql_client::GraphQLQuery; -use reqwest::blocking::Client as ReqwestClient; use reqwest::header::{HeaderMap, HeaderValue}; +use reqwest::Client as ReqwestClient; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { @@ -40,26 +40,28 @@ impl StudioClient { /// /// Takes one argument, `variables`. Returns a Response or a RoverClientError. /// Automatically retries requests. - pub fn post( + pub async fn post( &self, variables: Q::Variables, ) -> Result { let mut header_map = self.build_studio_headers()?; self.client .post::(variables, &mut header_map, EndpointKind::ApolloStudio) + .await } /// Client method for making a GraphQL request to Apollo Studio. /// /// Takes one argument, `variables`. Returns a Response or a RoverClientError. /// Does not automatically retry requests. - pub fn post_no_retry( + pub async fn post_no_retry( &self, variables: Q::Variables, ) -> Result { let mut header_map = self.build_studio_headers()?; self.client .post_no_retry::(variables, &mut header_map, EndpointKind::ApolloStudio) + .await } /// Function for building a [HeaderMap] for making http requests. Use for making diff --git a/crates/rover-client/src/operations/config/is_federated/runner.rs b/crates/rover-client/src/operations/config/is_federated/runner.rs index 52d928ff7..4f0e5418b 100644 --- a/crates/rover-client/src/operations/config/is_federated/runner.rs +++ b/crates/rover-client/src/operations/config/is_federated/runner.rs @@ -19,12 +19,12 @@ use crate::RoverClientError; /// Snake case of this name is the mod name. i.e. publish_partial_schema_mutation pub(crate) struct IsFederatedGraph; -pub(crate) fn run( +pub(crate) async fn run( input: IsFederatedInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_ref) } diff --git a/crates/rover-client/src/operations/config/who_am_i/runner.rs b/crates/rover-client/src/operations/config/who_am_i/runner.rs index c6e42349e..23c6eceee 100644 --- a/crates/rover-client/src/operations/config/who_am_i/runner.rs +++ b/crates/rover-client/src/operations/config/who_am_i/runner.rs @@ -25,11 +25,11 @@ pub(crate) struct ConfigWhoAmIQuery; /// Get info from the registry about an API key, i.e. the name/id of the /// user/graph and what kind of key it is (GRAPH/USER/Other) -pub fn run( +pub async fn run( input: ConfigWhoAmIInput, client: &StudioClient, ) -> Result { - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; get_identity_from_response_data(response_data, client.get_credential_origin()) } diff --git a/crates/rover-client/src/operations/contract/describe/runner.rs b/crates/rover-client/src/operations/contract/describe/runner.rs index cc3148d47..0eb75accf 100644 --- a/crates/rover-client/src/operations/contract/describe/runner.rs +++ b/crates/rover-client/src/operations/contract/describe/runner.rs @@ -20,12 +20,12 @@ use crate::RoverClientError; pub(crate) struct ContractDescribeQuery; /// Fetches the description of the configuration for a given contract variant -pub fn run( +pub async fn run( input: ContractDescribeInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let root_url = response_data.frontend_url_root.clone(); let description = get_description_from_response_data(response_data, graph_ref.clone())?; Ok(ContractDescribeResponse { diff --git a/crates/rover-client/src/operations/contract/publish/runner.rs b/crates/rover-client/src/operations/contract/publish/runner.rs index 37406f046..c5cc8b2b1 100644 --- a/crates/rover-client/src/operations/contract/publish/runner.rs +++ b/crates/rover-client/src/operations/contract/publish/runner.rs @@ -20,13 +20,13 @@ use crate::RoverClientError; pub(crate) struct ContractPublishMutation; /// Fetches the description of the configuration for a given contract variant -pub fn run( +pub async fn run( input: ContractPublishInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); let no_launch = input.no_launch; - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let publish_response = get_publish_response_from_response_data(response_data, graph_ref, no_launch)?; Ok(publish_response) diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs index 85fb84d6e..e8d151071 100644 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ b/crates/rover-client/src/operations/graph/check/runner.rs @@ -24,12 +24,12 @@ pub(crate) struct GraphCheckMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: CheckSchemaAsyncInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; get_check_response_from_data(data, graph_ref) } diff --git a/crates/rover-client/src/operations/graph/check_workflow/runner.rs b/crates/rover-client/src/operations/graph/check_workflow/runner.rs index 93d1f5c31..bc754d09b 100644 --- a/crates/rover-client/src/operations/graph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/graph/check_workflow/runner.rs @@ -37,7 +37,7 @@ pub(crate) struct GraphCheckWorkflowQuery; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: CheckWorkflowInput, client: &StudioClient, ) -> Result { @@ -45,7 +45,7 @@ pub fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()); + let result = client.post::(input.clone().into()).await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/graph/delete/runner.rs b/crates/rover-client/src/operations/graph/delete/runner.rs index 2c4a5f2f8..442c94ec3 100644 --- a/crates/rover-client/src/operations/graph/delete/runner.rs +++ b/crates/rover-client/src/operations/graph/delete/runner.rs @@ -21,23 +21,27 @@ pub(crate) struct GraphDeleteMutation; /// The main function to be used from this module. /// This function deletes a single graph variant from the graph registry -pub fn run(input: GraphDeleteInput, client: &StudioClient) -> Result<(), RoverClientError> { +pub async fn run(input: GraphDeleteInput, client: &StudioClient) -> Result<(), RoverClientError> { let graph_ref = input.graph_ref.clone(); - let response_data = client - .post::(input.into()) - .map_err(|e| { + let response_data = match client.post::(input.into()).await { + Ok(data) => data, + Err(e) => { if e.to_string().contains("Variant not found") { if let Err(no_variant_err) = variant::run( VariantListInput { graph_ref: graph_ref.clone(), }, client, - ) { - return no_variant_err; + ) + .await + { + return Err(no_variant_err); } } - e - })?; + return Err(e); + } + }; + let graph = response_data.graph.ok_or(RoverClientError::GraphNotFound { graph_ref: graph_ref.clone(), })?; diff --git a/crates/rover-client/src/operations/graph/fetch/runner.rs b/crates/rover-client/src/operations/graph/fetch/runner.rs index ded79af89..5f643bc18 100644 --- a/crates/rover-client/src/operations/graph/fetch/runner.rs +++ b/crates/rover-client/src/operations/graph/fetch/runner.rs @@ -25,12 +25,12 @@ pub(crate) struct GraphFetchQuery; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( +pub async fn run( input: GraphFetchInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let sdl_contents = get_schema_from_response_data(response_data, graph_ref)?; Ok(FetchResponse { sdl: Sdl { diff --git a/crates/rover-client/src/operations/graph/introspect/runner.rs b/crates/rover-client/src/operations/graph/introspect/runner.rs index 71ab02e60..f6f5655b7 100644 --- a/crates/rover-client/src/operations/graph/introspect/runner.rs +++ b/crates/rover-client/src/operations/graph/introspect/runner.rs @@ -22,7 +22,7 @@ pub(crate) struct GraphIntrospectQuery; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( +pub async fn run( input: GraphIntrospectInput, client: &GraphQLClient, should_retry: bool, @@ -36,13 +36,17 @@ pub fn run( ); } let response_data = if should_retry { - client.post::(variables, &mut header_map, EndpointKind::Customer) + client + .post::(variables, &mut header_map, EndpointKind::Customer) + .await } else { - client.post_no_retry::( - variables, - &mut header_map, - EndpointKind::Customer, - ) + client + .post_no_retry::( + variables, + &mut header_map, + EndpointKind::Customer, + ) + .await }?; build_response(response_data) diff --git a/crates/rover-client/src/operations/graph/lint/runner.rs b/crates/rover-client/src/operations/graph/lint/runner.rs index 2d9356e33..d46f99d1f 100644 --- a/crates/rover-client/src/operations/graph/lint/runner.rs +++ b/crates/rover-client/src/operations/graph/lint/runner.rs @@ -27,7 +27,7 @@ pub(crate) struct LintGraphMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run(input: LintGraphInput, client: &StudioClient) -> Result { +pub async fn run(input: LintGraphInput, client: &StudioClient) -> Result { let graph_ref = input.graph_ref.clone(); let base_schema = if input.ignore_existing { @@ -36,7 +36,7 @@ pub fn run(input: LintGraphInput, client: &StudioClient) -> Result Result Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; let publish_response = get_publish_response_from_data(data, graph_ref)?; build_response(publish_response) } diff --git a/crates/rover-client/src/operations/graph/variant/runner.rs b/crates/rover-client/src/operations/graph/variant/runner.rs index c54a14888..d9570d82c 100644 --- a/crates/rover-client/src/operations/graph/variant/runner.rs +++ b/crates/rover-client/src/operations/graph/variant/runner.rs @@ -18,9 +18,9 @@ pub(crate) struct VariantListQuery; /// The main function to be used from this module. /// This function lists all the variants for a given graph ref -pub fn run(input: VariantListInput, client: &StudioClient) -> Result<(), RoverClientError> { +pub async fn run(input: VariantListInput, client: &StudioClient) -> Result<(), RoverClientError> { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let graph = response_data.graph.ok_or(RoverClientError::GraphNotFound { graph_ref: graph_ref.clone(), })?; diff --git a/crates/rover-client/src/operations/license/fetch/runner.rs b/crates/rover-client/src/operations/license/fetch/runner.rs index 6dbf04f43..b0aa188b6 100644 --- a/crates/rover-client/src/operations/license/fetch/runner.rs +++ b/crates/rover-client/src/operations/license/fetch/runner.rs @@ -19,9 +19,12 @@ use graphql_client::*; pub(crate) struct LicenseFetchQuery; /// The main function to be used from this module. This function fetches an offline license if permitted to do so. -pub fn run(input: LicenseFetchInput, client: &StudioClient) -> Result { +pub async fn run( + input: LicenseFetchInput, + client: &StudioClient, +) -> Result { let graph_id = input.graph_id.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let license = get_license_response_from_data(response_data, &graph_id)?; Ok(license) } diff --git a/crates/rover-client/src/operations/persisted_queries/name/runner.rs b/crates/rover-client/src/operations/persisted_queries/name/runner.rs index 17de315d8..806537a6d 100644 --- a/crates/rover-client/src/operations/persisted_queries/name/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/name/runner.rs @@ -16,13 +16,13 @@ use graphql_client::*; )] pub struct PersistedQueryListNameQuery; -pub fn run( +pub async fn run( input: PersistedQueryListNameInput, client: &StudioClient, ) -> Result { let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_id, list_id) } diff --git a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs index b5a6e9243..e13af29ca 100644 --- a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs @@ -19,14 +19,14 @@ type GraphQLDocument = String; )] pub struct PublishOperationsMutation; -pub fn run( +pub async fn run( input: PersistedQueriesPublishInput, client: &StudioClient, ) -> Result { let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); let total_operations = input.operation_manifest.operations.len(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_id, list_id, total_operations) } diff --git a/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs b/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs index 23300366d..7dafd29d9 100644 --- a/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/resolve/runner.rs @@ -15,12 +15,14 @@ use graphql_client::*; )] pub struct ResolvePersistedQueryListQuery; -pub fn run( +pub async fn run( input: ResolvePersistedQueryListInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client + .post::(input.into()) + .await?; build_response(data, graph_ref) } diff --git a/crates/rover-client/src/operations/readme/fetch/runner.rs b/crates/rover-client/src/operations/readme/fetch/runner.rs index 447a94a2f..f734ccb08 100644 --- a/crates/rover-client/src/operations/readme/fetch/runner.rs +++ b/crates/rover-client/src/operations/readme/fetch/runner.rs @@ -18,12 +18,12 @@ type Timestamp = String; )] pub struct ReadmeFetchQuery; -pub fn run( +pub async fn run( input: ReadmeFetchInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; build_response(data, graph_ref) } diff --git a/crates/rover-client/src/operations/readme/publish/runner.rs b/crates/rover-client/src/operations/readme/publish/runner.rs index 10208f254..2b0227373 100644 --- a/crates/rover-client/src/operations/readme/publish/runner.rs +++ b/crates/rover-client/src/operations/readme/publish/runner.rs @@ -19,12 +19,12 @@ type Timestamp = String; pub struct ReadmePublishMutation; -pub fn run( +pub async fn run( input: ReadmePublishInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response = client.post::(input.into())?; + let response = client.post::(input.into()).await?; build_response(response, graph_ref) } diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 49785b0d0..69cc59f65 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -27,7 +27,7 @@ pub(crate) struct SubgraphCheckMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: SubgraphCheckAsyncInput, client: &StudioClient, ) -> Result { @@ -38,14 +38,14 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, can_operation_convert: false, }); } - let data = client.post::(input.into())?; + let data = client.post::(input.into()).await?; get_check_response_from_data(data, graph_ref) } diff --git a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs index 55a6683c3..20a439738 100644 --- a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs @@ -43,7 +43,7 @@ pub(crate) struct SubgraphCheckWorkflowQuery; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: CheckWorkflowInput, subgraph: String, client: &StudioClient, @@ -52,7 +52,7 @@ pub fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()); + let result = client.post::(input.clone().into()).await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs index 7a11a9795..324378bd1 100644 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -23,12 +23,12 @@ pub(crate) struct SubgraphDeleteMutation; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( +pub async fn run( input: SubgraphDeleteInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let data = get_delete_data_from_response(response_data, graph_ref)?; Ok(build_response(data)) } diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index 006360517..fdd3b38d7 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -21,7 +21,7 @@ use graphql_client::*; pub(crate) struct SubgraphFetchQuery; /// Fetches a schema from apollo studio and returns its SDL (String) -pub fn run( +pub async fn run( input: SubgraphFetchInput, client: &StudioClient, ) -> Result { @@ -31,7 +31,7 @@ pub fn run( graph_ref: input.graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref: input.graph_ref, @@ -39,7 +39,7 @@ pub fn run( }); } let variables = input.clone().into(); - let response_data = client.post::(variables)?; + let response_data = client.post::(variables).await?; get_sdl_from_response_data(input, response_data) } diff --git a/crates/rover-client/src/operations/subgraph/introspect/runner.rs b/crates/rover-client/src/operations/subgraph/introspect/runner.rs index 0159162d4..ac3d38eee 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/runner.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/runner.rs @@ -17,7 +17,7 @@ use graphql_client::*; pub(crate) struct SubgraphIntrospectQuery; -pub fn run( +pub async fn run( input: SubgraphIntrospectInput, client: &GraphQLClient, should_retry: bool, @@ -34,13 +34,13 @@ pub fn run( input.into(), &mut header_map, EndpointKind::Customer, - ) + ).await } else { client.post_no_retry::( input.into(), &mut header_map, EndpointKind::Customer, - ) + ).await }; match response_data { diff --git a/crates/rover-client/src/operations/subgraph/lint/runner.rs b/crates/rover-client/src/operations/subgraph/lint/runner.rs index edce4e5e5..330b2ecea 100644 --- a/crates/rover-client/src/operations/subgraph/lint/runner.rs +++ b/crates/rover-client/src/operations/subgraph/lint/runner.rs @@ -29,7 +29,7 @@ pub(crate) struct LintSubgraphMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub fn run( +pub async fn run( input: LintSubgraphInput, client: &StudioClient, ) -> Result { @@ -40,7 +40,7 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, @@ -55,20 +55,22 @@ pub fn run( subgraph_name: input.subgraph_name, }, client, - )?; + ).await?; Some(fetch_response.sdl.contents) } else { None }; - let data = client.post::( - LintSubgraphMutationInput { - graph_ref, - proposed_schema: input.proposed_schema.clone(), - base_schema, - } - .into(), - )?; + let data = client + .post::( + LintSubgraphMutationInput { + graph_ref, + proposed_schema: input.proposed_schema.clone(), + base_schema, + } + .into(), + ) + .await?; get_lint_response_from_result( data, diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/operations/subgraph/list/runner.rs index c7aca0ae2..c78f9bb7d 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/operations/subgraph/list/runner.rs @@ -22,12 +22,12 @@ type Timestamp = String; pub(crate) struct SubgraphListQuery; /// Fetches list of subgraphs for a given graph, returns name & url of each -pub fn run( +pub async fn run( input: SubgraphListInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; let root_url = response_data.frontend_url_root.clone(); let subgraphs = get_subgraphs_from_response_data(response_data, graph_ref.clone())?; Ok(SubgraphListResponse { diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index d9ffe81d0..d4ba95d55 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -26,7 +26,7 @@ use apollo_federation_types::build::{BuildError, BuildErrors}; /// Snake case of this name is the mod name. i.e. subgraph_publish_mutation pub(crate) struct SubgraphPublishMutation; -pub fn run( +pub async fn run( input: SubgraphPublishInput, client: &StudioClient, ) -> Result { @@ -44,7 +44,7 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - ) + ).await .is_ok(); if variant_exists { @@ -54,7 +54,7 @@ pub fn run( graph_ref: graph_ref.clone(), }, client, - )?; + ).await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { @@ -70,7 +70,7 @@ pub fn run( ); } } - let data = client.post::(variables)?; + let data = client.post::(variables).await?; let publish_response = get_publish_response_from_data(data, graph_ref)?; Ok(build_response(publish_response)) } diff --git a/crates/rover-client/src/operations/subgraph/routing_url/runner.rs b/crates/rover-client/src/operations/subgraph/routing_url/runner.rs index 567c1d033..a4846a898 100644 --- a/crates/rover-client/src/operations/subgraph/routing_url/runner.rs +++ b/crates/rover-client/src/operations/subgraph/routing_url/runner.rs @@ -19,12 +19,12 @@ use graphql_client::*; pub(crate) struct SubgraphRoutingUrlQuery; /// Fetches a subgraph's routing URL (String) -pub fn run( +pub async fn run( input: SubgraphRoutingUrlInput, client: &StudioClient, ) -> Result { let variables = input.clone().into(); - let response_data = client.post::(variables)?; + let response_data = client.post::(variables).await?; get_routing_url_from_response_data(input, response_data) } diff --git a/crates/rover-client/src/operations/supergraph/fetch/runner.rs b/crates/rover-client/src/operations/supergraph/fetch/runner.rs index 1e2d4da51..b96bb295d 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/supergraph/fetch/runner.rs @@ -26,12 +26,12 @@ pub(crate) struct SupergraphFetchQuery; /// The main function to be used from this module. This function fetches a /// core schema from apollo studio -pub fn run( +pub async fn run( input: SupergraphFetchInput, client: &StudioClient, ) -> Result { let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; + let response_data = client.post::(input.into()).await?; get_supergraph_sdl_from_response_data(response_data, graph_ref) } diff --git a/crates/rover-client/src/releases.rs b/crates/rover-client/src/releases.rs index 19879fe24..eda181804 100644 --- a/crates/rover-client/src/releases.rs +++ b/crates/rover-client/src/releases.rs @@ -1,21 +1,19 @@ use crate::{error::EndpointKind, RoverClientError}; -use reqwest::blocking::Client; +use reqwest::Client; pub use semver::Version; const LATEST_RELEASE_URL: &str = "https://github.com/apollographql/rover/releases/latest"; /// Looks up and parses the latest release version -pub fn get_latest_release(client: Client) -> Result { +pub async fn get_latest_release(client: Client) -> Result { // send a request to the latest GitHub release - let response = - client - .head(LATEST_RELEASE_URL) - .send() - .map_err(|e| RoverClientError::SendRequest { - source: e, - endpoint_kind: EndpointKind::Orbiter, - })?; + let response = client.head(LATEST_RELEASE_URL).send().await.map_err(|e| { + RoverClientError::SendRequest { + source: e, + endpoint_kind: EndpointKind::Orbiter, + } + })?; // this will return a response with a redirect to the latest tagged release let url_path_segments = response diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index e509093fc..7143e779e 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -41,11 +41,11 @@ impl Installer { /// Checks if a binary already exists, and if it does not, /// downloads a plugin tarball from a URL, extracts the binary, /// and puts it in the `bin` directory for the main tool - pub fn install_plugin( + pub async fn install_plugin( &self, plugin_name: &str, plugin_tarball_url: &str, - client: &reqwest::blocking::Client, + client: &reqwest::Client, is_latest: bool, ) -> Result, InstallerError> { let version = self.get_plugin_version(plugin_tarball_url, is_latest)?; @@ -63,8 +63,9 @@ impl Installer { return Ok(None); } - let plugin_bin_path = - self.extract_plugin_tarball(plugin_name, plugin_tarball_url, client)?; + let plugin_bin_path = self + .extract_plugin_tarball(plugin_name, plugin_tarball_url, client) + .await?; self.write_plugin_bin_to_fs(plugin_name, &plugin_bin_path, &version)?; eprintln!( @@ -234,11 +235,11 @@ impl Installer { } } - fn extract_plugin_tarball( + async fn extract_plugin_tarball( &self, plugin_name: &str, plugin_tarball_url: &str, - client: &reqwest::blocking::Client, + client: &reqwest::Client, ) -> Result { let download_dir = tempdir::TempDir::new(plugin_name)?; let download_dir_path = Utf8PathBuf::try_from(download_dir.into_path())?; @@ -248,9 +249,11 @@ impl Installer { .get(plugin_tarball_url) .header(reqwest::header::USER_AGENT, "rover-client") .header(reqwest::header::ACCEPT, "application/octet-stream") - .send()? + .send() + .await? .error_for_status()? - .bytes()?; + .bytes() + .await?; f.write_all(&response_bytes[..])?; f.sync_all()?; let f = std::fs::File::open(&tarball_path)?; diff --git a/src/bin/rover.rs b/src/bin/rover.rs index 40ead2101..1f112834b 100644 --- a/src/bin/rover.rs +++ b/src/bin/rover.rs @@ -10,5 +10,7 @@ fn main() -> Result<_, std::io::Error> { homepage: rover::PKG_HOMEPAGE.into(), repository: rover::PKG_REPOSITORY.into() }); - Ok(Rover::run_from_args()) + + let rt = tokio::runtime::Runtime::new().unwrap(); + Ok(rt.block_on(Rover::run_from_args())) } diff --git a/src/cli.rs b/src/cli.rs index ab170a544..fb4063849 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use camino::Utf8PathBuf; use clap::{Parser, ValueEnum}; use lazycell::{AtomicLazyCell, LazyCell}; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use crate::command::{self, RoverOutput}; @@ -109,11 +109,11 @@ pub struct Rover { } impl Rover { - pub fn run_from_args() -> RoverResult<()> { - Rover::parse().run() + pub async fn run_from_args() -> RoverResult<()> { + Rover::parse().run().await } - pub fn run(&self) -> RoverResult<()> { + pub async fn run(&self) -> RoverResult<()> { timber::init(self.log_level); tracing::trace!(command_structure = ?self); self.output_opts.validate_options(); @@ -134,7 +134,7 @@ impl Rover { // kicks off the app on the main thread // don't return an error with ? quite yet // since we still want to report the usage data - let app_result = self.execute_command(); + let app_result = self.execute_command().await; // makes sure the reporting finishes in the background // before continuing. @@ -147,7 +147,7 @@ impl Rover { } // otherwise just run the app without reporting - Err(_) => self.execute_command(), + Err(_) => self.execute_command().await, }; match rover_output { @@ -164,7 +164,7 @@ impl Rover { } } - pub fn execute_command(&self) -> RoverResult { + pub async fn execute_command(&self) -> RoverResult { // before running any commands, we check if rover is up to date // this only happens once a day automatically // we skip this check for the `rover update` commands, since they @@ -174,19 +174,21 @@ impl Rover { } else if !self.skip_update_check { let config = self.get_rover_config(); if let Ok(config) = config { - let _ = version::check_for_update(config, false, self.get_reqwest_client()?); + let _ = version::check_for_update(config, false, self.get_reqwest_client()?).await; } } match &self.command { - Command::Config(command) => command.run(self.get_client_config()?), - Command::Contract(command) => command.run(self.get_client_config()?), + Command::Config(command) => command.run(self.get_client_config()?).await, + Command::Contract(command) => command.run(self.get_client_config()?).await, Command::Dev(command) => { - command.run(self.get_install_override_path()?, self.get_client_config()?) + command + .run(self.get_install_override_path()?, self.get_client_config()?) + .await } Command::Fed2(command) => command.run(self.get_client_config()?), Command::Supergraph(command) => { - command.run(self.get_install_override_path()?, self.get_client_config()?) + command.run(self.get_install_override_path()?, self.get_client_config()?).await } Command::Docs(command) => command.run(), Command::Graph(command) => command.run( @@ -194,25 +196,25 @@ impl Rover { self.get_git_context()?, self.get_checks_timeout_seconds()?, &self.output_opts, - ), - Command::Template(command) => command.run(self.get_client_config()?), - Command::Readme(command) => command.run(self.get_client_config()?), + ).await, + Command::Template(command) => command.run(self.get_client_config()?).await, + Command::Readme(command) => command.run(self.get_client_config()?).await, Command::Subgraph(command) => command.run( self.get_client_config()?, self.get_git_context()?, self.get_checks_timeout_seconds()?, &self.output_opts, - ), + ).await, Command::Update(command) => { command.run(self.get_rover_config()?, self.get_reqwest_client()?) } Command::Install(command) => { - command.do_install(self.get_install_override_path()?, self.get_client_config()?) + command.do_install(self.get_install_override_path()?, self.get_client_config()?).await } Command::Info(command) => command.run(), Command::Explain(command) => command.run(), - Command::PersistedQueries(command) => command.run(self.get_client_config()?), - Command::License(command) => command.run(self.get_client_config()?), + Command::PersistedQueries(command) => command.run(self.get_client_config()?).await, + Command::License(command) => command.run(self.get_client_config()?).await, } } diff --git a/src/command/config/mod.rs b/src/command/config/mod.rs index e78009333..08e791d56 100644 --- a/src/command/config/mod.rs +++ b/src/command/config/mod.rs @@ -35,13 +35,13 @@ pub enum Command { } impl Config { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { Command::Auth(command) => command.run(client_config.config), Command::List(command) => command.run(client_config.config), Command::Delete(command) => command.run(client_config.config), Command::Clear(command) => command.run(client_config.config), - Command::Whoami(command) => command.run(client_config), + Command::Whoami(command) => command.run(client_config).await, } } } diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 1859b0c7c..0943e7a7d 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -30,11 +30,11 @@ pub struct WhoAmI { } impl WhoAmI { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!("Checking identity of your API key against the registry."); - let identity = who_am_i::run(ConfigWhoAmIInput {}, &client)?; + let identity = who_am_i::run(ConfigWhoAmIInput {}, &client).await?; if !self.is_valid_actor_type(&identity) { return Err(RoverError::from(anyhow!( diff --git a/src/command/contract/describe.rs b/src/command/contract/describe.rs index 5bc4364a4..b6fd8806e 100644 --- a/src/command/contract/describe.rs +++ b/src/command/contract/describe.rs @@ -18,7 +18,7 @@ pub struct Describe { } impl Describe { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Fetching description for configuration of {} using credentials from the {} profile.\n", @@ -31,7 +31,8 @@ impl Describe { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::ContractDescribe(describe_response)) } diff --git a/src/command/contract/mod.rs b/src/command/contract/mod.rs index 4d042497e..312e83448 100644 --- a/src/command/contract/mod.rs +++ b/src/command/contract/mod.rs @@ -23,10 +23,10 @@ pub enum Command { } impl Contract { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Describe(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config), + Command::Describe(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config).await, } } } diff --git a/src/command/contract/publish.rs b/src/command/contract/publish.rs index 13712e5bf..627fadb65 100644 --- a/src/command/contract/publish.rs +++ b/src/command/contract/publish.rs @@ -69,7 +69,7 @@ pub struct Publish { } impl Publish { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Publishing configuration to {} using credentials from the {} profile.\n", @@ -101,7 +101,8 @@ impl Publish { no_launch: self.no_launch, }, &client, - )?; + ) + .await?; Ok(RoverOutput::ContractPublish(publish_response)) } diff --git a/src/command/dev/compose.rs b/src/command/dev/compose.rs index 486d74d53..442f6d2b2 100644 --- a/src/command/dev/compose.rs +++ b/src/command/dev/compose.rs @@ -39,7 +39,7 @@ impl ComposeRunner { } } - pub fn maybe_install_supergraph( + pub async fn maybe_install_supergraph( &mut self, federation_version: FederationVersion, ) -> RoverResult { @@ -50,13 +50,13 @@ impl ComposeRunner { self.override_install_path.clone(), self.client_config.clone(), federation_version, - )?; + ).await?; self.plugin_exe = Some(plugin_exe.clone()); Ok(plugin_exe) } } - pub fn run( + pub async fn run( &mut self, supergraph_config: &mut SupergraphConfig, ) -> std::result::Result, String> { @@ -65,7 +65,7 @@ impl ComposeRunner { self.override_install_path.clone(), self.client_config.clone(), supergraph_config, - )); + ).await); let new_state = self.composition_state(); match (prev_state, new_state) { diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 36f51ff9f..9638f9620 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -18,7 +18,7 @@ pub fn log_err_and_continue(err: RoverError) -> RoverError { } impl Dev { - pub fn run( + pub async fn run( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -48,7 +48,7 @@ impl Dev { follower_channel.clone(), self.opts.plugin_opts.clone(), router_config_handler, - )? { + ).await? { eprintln!("{0}Do not run this command in production! {0}It is intended for local development.", Emoji::Warn); let (ready_sender, ready_receiver) = sync_channel(1); let follower_messenger = FollowerMessenger::from_main_session( @@ -56,7 +56,7 @@ impl Dev { leader_channel.receiver, ); - tp.spawn(move || { + tokio::task::spawn_blocking(move || { ctrlc::set_handler(move || { eprintln!( "\n{}shutting down the `rover dev` session and all attached processes...", @@ -92,6 +92,7 @@ impl Dev { self.opts.subgraph_opts.subgraph_polling_interval, &self.opts.plugin_opts.profile, ) + .await .transpose() .unwrap_or_else(|| { self.opts @@ -105,9 +106,10 @@ impl Dev { })?; subgraph_watchers.into_iter().for_each(|mut watcher| { - std::thread::spawn(move || { + tokio::task::spawn(async move { let _ = watcher .watch_subgraph_for_changes() + .await .map_err(log_err_and_continue); }); }); @@ -128,7 +130,7 @@ impl Dev { // start the interprocess socket health check in the background let health_messenger = follower_messenger.clone(); - tp.spawn(move || { + tokio::task::spawn_blocking(move || { let _ = health_messenger.health_check().map_err(|_| { eprintln!("{}shutting down...", Emoji::Stop); std::process::exit(1); @@ -148,7 +150,7 @@ impl Dev { // watch for subgraph changes on the main thread // it will take care of updating the main `rover dev` session - subgraph_refresher.watch_subgraph_for_changes()?; + subgraph_refresher.watch_subgraph_for_changes().await?; } unreachable!("watch_subgraph_for_changes never returns") diff --git a/src/command/dev/introspect.rs b/src/command/dev/introspect.rs index 194aaedf4..ae371b3cd 100644 --- a/src/command/dev/introspect.rs +++ b/src/command/dev/introspect.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use reqwest::blocking::Client; +use reqwest::Client; use rover_std::Style; use crate::command::dev::protocol::{SubgraphSdl, SubgraphUrl}; @@ -28,7 +28,7 @@ impl UnknownIntrospectRunner { } } - pub fn run(&self) -> RoverResult<(SubgraphSdl, IntrospectRunnerKind)> { + pub async fn run(&self) -> RoverResult<(SubgraphSdl, IntrospectRunnerKind)> { let subgraph_runner = SubgraphIntrospectRunner { endpoint: self.endpoint.clone(), client: self.client.clone(), @@ -48,8 +48,8 @@ impl UnknownIntrospectRunner { // in which case we may incorrectly assume // they do not support federated introspection // so, run the graph query first and _then_ the subgraph query - let graph_result = graph_runner.run(); - let subgraph_result = subgraph_runner.run(); + let graph_result = graph_runner.run().await; + let subgraph_result = subgraph_runner.run().await; match (subgraph_result, graph_result) { (Ok(s), _) => { @@ -104,7 +104,7 @@ pub struct SubgraphIntrospectRunner { } impl SubgraphIntrospectRunner { - pub fn run(&self) -> RoverResult { + pub async fn run(&self) -> RoverResult { tracing::debug!( "running `rover subgraph introspect --endpoint {}`", &self.endpoint @@ -116,7 +116,7 @@ impl SubgraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false) + .exec(&self.client, false).await } } @@ -128,7 +128,7 @@ pub struct GraphIntrospectRunner { } impl GraphIntrospectRunner { - pub fn run(&self) -> RoverResult { + pub async fn run(&self) -> RoverResult { tracing::debug!( "running `rover graph introspect --endpoint {}`", &self.endpoint @@ -140,6 +140,6 @@ impl GraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false) + .exec(&self.client, false).await } } diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index 685a40a4a..ec1decbdb 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -50,7 +50,7 @@ impl LeaderSession { /// Ok(Some(Self)) when successfully initiated /// Ok(None) when a LeaderSession already exists for that address /// Err(RoverError) when something went wrong. - pub fn new( + pub async fn new( override_install_path: Option, client_config: &StudioClientConfig, leader_channel: LeaderChannel, @@ -114,8 +114,10 @@ impl LeaderSession { None => FederationVersion::LatestFedTwo, }; - router_runner.maybe_install_router()?; - compose_runner.maybe_install_supergraph(federation_version.clone())?; + router_runner.maybe_install_router().await?; + compose_runner + .maybe_install_supergraph(federation_version.clone()) + .await?; router_config_handler.start()?; @@ -131,18 +133,26 @@ impl LeaderSession { } /// Start the session by watching for incoming subgraph updates and re-composing when needed - pub fn listen_for_all_subgraph_updates(&mut self, ready_sender: Sender<()>) -> RoverResult<()> { + pub async fn listen_for_all_subgraph_updates( + &mut self, + ready_sender: futures::channel::mpsc::Sender<()>, + ) -> RoverResult<()> { self.receive_messages_from_attached_sessions()?; - self.receive_all_subgraph_updates(ready_sender); + self.receive_all_subgraph_updates(ready_sender).await; } /// Listen for incoming subgraph updates and re-compose the supergraph - fn receive_all_subgraph_updates(&mut self, ready_sender: Sender<()>) -> ! { - ready_sender.send(()).unwrap(); + async fn receive_all_subgraph_updates( + &mut self, + mut ready_sender: futures::channel::mpsc::Sender<()>, + ) -> ! { + ready_sender.try_send(()).unwrap(); loop { tracing::trace!("main session waiting for follower message"); let follower_message = self.follower_channel.receiver.recv().unwrap(); - let leader_message = self.handle_follower_message_kind(follower_message.kind()); + let leader_message = self + .handle_follower_message_kind(follower_message.kind()) + .await; if !follower_message.is_from_main_session() { leader_message.print(); @@ -212,7 +222,7 @@ impl LeaderSession { } /// Adds a subgraph to the internal supergraph representation. - fn add_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { + async fn add_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { let is_first_subgraph = self.subgraphs.is_empty(); let ((name, url), sdl) = subgraph_entry; if self @@ -231,7 +241,7 @@ impl LeaderSession { } else { self.subgraphs .insert((name.to_string(), url.clone()), sdl.to_string()); - let composition_result = self.compose(); + let composition_result = self.compose().await; if let Err(composition_err) = composition_result { LeaderMessageKind::error(composition_err) } else if composition_result.transpose().is_some() && !is_first_subgraph { @@ -243,12 +253,12 @@ impl LeaderSession { } /// Updates a subgraph in the internal supergraph representation. - fn update_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { + async fn update_subgraph(&mut self, subgraph_entry: &SubgraphEntry) -> LeaderMessageKind { let ((name, url), sdl) = &subgraph_entry; if let Some(prev_sdl) = self.subgraphs.get_mut(&(name.to_string(), url.clone())) { if prev_sdl != sdl { *prev_sdl = sdl.to_string(); - let composition_result = self.compose(); + let composition_result = self.compose().await; if let Err(composition_err) = composition_result { LeaderMessageKind::error(composition_err) } else if composition_result.transpose().is_some() { @@ -260,12 +270,12 @@ impl LeaderSession { LeaderMessageKind::message_received() } } else { - self.add_subgraph(subgraph_entry) + self.add_subgraph(subgraph_entry).await } } /// Removes a subgraph from the internal subgraph representation. - fn remove_subgraph(&mut self, subgraph_name: &SubgraphName) -> LeaderMessageKind { + async fn remove_subgraph(&mut self, subgraph_name: &SubgraphName) -> LeaderMessageKind { let found = self .subgraphs .keys() @@ -274,7 +284,7 @@ impl LeaderSession { if let Some((name, url)) = found { self.subgraphs.remove(&(name.to_string(), url)); - let composition_result = self.compose(); + let composition_result = self.compose().await; if let Err(composition_err) = composition_result { LeaderMessageKind::error(composition_err) } else if composition_result.transpose().is_some() { @@ -288,19 +298,24 @@ impl LeaderSession { } /// Reruns composition, which triggers the router to reload. - fn compose(&mut self) -> CompositionResult { + async fn compose(&mut self) -> CompositionResult { self.compose_runner .run(&mut self.supergraph_config()) + .await .and_then(|maybe_new_schema| { if maybe_new_schema.is_some() { - if let Err(err) = self.router_runner.spawn() { + if let Err(err) = self.router_runner.spawn().await { return Err(err.to_string()); } } Ok(maybe_new_schema) }) .map_err(|e| { - let _ = self.router_runner.kill().map_err(log_err_and_continue); + let _ = self + .router_runner + .kill() + .await + .map_err(log_err_and_continue); e }) } @@ -347,30 +362,34 @@ impl LeaderSession { } /// Shuts the router down, removes the socket file, and exits the process. - pub fn shutdown(&mut self) { - let _ = self.router_runner.kill().map_err(log_err_and_continue); + pub async fn shutdown(&mut self) { + let _ = self + .router_runner + .kill() + .await + .map_err(log_err_and_continue); let _ = std::fs::remove_file(&self.ipc_socket_addr); std::process::exit(1) } /// Handles a follower message by updating the internal subgraph representation if needed, /// and returns a [`LeaderMessageKind`] that can be sent over a socket or printed by the main session - fn handle_follower_message_kind( + async fn handle_follower_message_kind( &mut self, follower_message: &FollowerMessageKind, ) -> LeaderMessageKind { use FollowerMessageKind::*; match follower_message { - AddSubgraph { subgraph_entry } => self.add_subgraph(subgraph_entry), + AddSubgraph { subgraph_entry } => self.add_subgraph(subgraph_entry).await, - UpdateSubgraph { subgraph_entry } => self.update_subgraph(subgraph_entry), + UpdateSubgraph { subgraph_entry } => self.update_subgraph(subgraph_entry).await, - RemoveSubgraph { subgraph_name } => self.remove_subgraph(subgraph_name), + RemoveSubgraph { subgraph_name } => self.remove_subgraph(subgraph_name).await, GetSubgraphs => LeaderMessageKind::current_subgraphs(self.get_subgraphs()), Shutdown => { - self.shutdown(); + self.shutdown().await; LeaderMessageKind::message_received() } diff --git a/src/command/dev/router/command.rs b/src/command/dev/router/command.rs index fe90598fe..60513813e 100644 --- a/src/command/dev/router/command.rs +++ b/src/command/dev/router/command.rs @@ -25,7 +25,7 @@ pub enum BackgroundTaskLog { } impl BackgroundTask { - pub fn new( + pub async fn new( command: String, log_sender: Sender, client_config: &StudioClientConfig, @@ -56,7 +56,7 @@ impl BackgroundTask { eprintln!("{} APOLLO_GRAPH_REF is set, but credentials could not be loaded. \ Enterprise features within the router will not function. {err}", Emoji::Warn); }).ok().and_then(|client| { - who_am_i::run(ConfigWhoAmIInput {}, &client).map_or_else(|err| { + who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { eprintln!("{} Could not determine the type of configured credentials, \ Router may fail to start if Enterprise features are enabled. {err}", Emoji::Warn); Some(client.credential.api_key.clone()) diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index 30ccc6044..5cb082945 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context}; use apollo_federation_types::config::RouterVersion; use camino::Utf8PathBuf; use crossbeam_channel::bounded; -use reqwest::blocking::Client; +use reqwest::Client; use reqwest::Url; use rover_std::{Emoji, Style}; use semver::Version; @@ -69,31 +69,33 @@ impl RouterRunner { }) } - pub fn maybe_install_router(&mut self) -> RoverResult { + pub async fn maybe_install_router(&mut self) -> RoverResult { if let Some(plugin_exe) = &self.plugin_exe { Ok(plugin_exe.clone()) } else { let install_command = self.install_command()?; - let plugin_exe = install_command.get_versioned_plugin( - self.override_install_path.clone(), - self.client_config.clone(), - self.plugin_opts.skip_update, - )?; + let plugin_exe = install_command + .get_versioned_plugin( + self.override_install_path.clone(), + self.client_config.clone(), + self.plugin_opts.skip_update, + ) + .await?; self.plugin_exe = Some(plugin_exe.clone()); Ok(plugin_exe) } } - pub fn get_command_to_spawn(&mut self) -> RoverResult { + pub async fn get_command_to_spawn(&mut self) -> RoverResult { Ok(format!( "{plugin_exe} --supergraph {supergraph} --hot-reload --config {config} --log trace --dev", - plugin_exe = self.maybe_install_router()?, + plugin_exe = self.maybe_install_router().await?, supergraph = self.supergraph_schema_path.as_str(), config = self.router_config_path.as_str(), )) } - pub fn wait_for_startup(&mut self, client: Client) -> RoverResult<()> { + pub async fn wait_for_startup(&mut self, client: Client) -> RoverResult<()> { let mut ready = false; let now = Instant::now(); let seconds = 10; @@ -110,6 +112,7 @@ impl RouterRunner { .get(&endpoint) .header("Content-Type", "application/json") .send() + .await .map(|_| { ready = true; }); @@ -137,7 +140,7 @@ impl RouterRunner { } } - pub fn wait_for_stop(&mut self, client: Client) -> RoverResult<()> { + pub async fn wait_for_stop(&mut self, client: Client) -> RoverResult<()> { let mut ready = true; let now = Instant::now(); let seconds = 5; @@ -149,6 +152,7 @@ impl RouterRunner { )) .header("Content-Type", "application/json") .send() + .await .and_then(|r| r.error_for_status()) .map_err(|_| { ready = false; @@ -164,17 +168,18 @@ impl RouterRunner { } } - pub fn spawn(&mut self) -> RoverResult<()> { + pub async fn spawn(&mut self) -> RoverResult<()> { if self.router_handle.is_none() { let client = self.client_config.get_reqwest_client()?; - self.maybe_install_router()?; + self.maybe_install_router().await?; let (router_log_sender, router_log_receiver) = bounded(0); let router_handle = BackgroundTask::new( - self.get_command_to_spawn()?, + self.get_command_to_spawn().await?, router_log_sender, &self.client_config, &self.plugin_opts.profile, - )?; + ) + .await?; tracing::info!("spawning router with `{}`", router_handle.descriptor()); let warn_prefix = Style::WarningPrefix.paint("WARN:"); @@ -228,7 +233,7 @@ impl RouterRunner { } }); - self.wait_for_startup(client)?; + self.wait_for_startup(client).await?; self.router_handle = Some(router_handle); Ok(()) @@ -237,12 +242,15 @@ impl RouterRunner { } } - pub fn kill(&mut self) -> RoverResult<()> { + pub async fn kill(&mut self) -> RoverResult<()> { if self.router_handle.is_some() { tracing::info!("killing the router"); self.router_handle = None; if let Ok(client) = self.client_config.get_reqwest_client() { - let _ = self.wait_for_stop(client).map_err(log_err_and_continue); + let _ = self + .wait_for_stop(client) + .await + .map_err(log_err_and_continue); } } Ok(()) @@ -251,6 +259,7 @@ impl RouterRunner { impl Drop for RouterRunner { fn drop(&mut self) { - let _ = self.kill().map_err(log_err_and_continue); + //let _ = self.kill().map_err(log_err_and_continue); + todo!() } } diff --git a/src/command/dev/schema.rs b/src/command/dev/schema.rs index 3396f46ac..4775c16ad 100644 --- a/src/command/dev/schema.rs +++ b/src/command/dev/schema.rs @@ -93,7 +93,7 @@ impl OptionalSubgraphOpts { } impl SupergraphOpts { - pub fn get_subgraph_watchers( + pub async fn get_subgraph_watchers( &self, client_config: &StudioClientConfig, follower_messenger: FollowerMessenger, @@ -174,7 +174,7 @@ impl SupergraphOpts { yaml_subgraph_name, follower_messenger.clone(), studio_client, - ) + ).await } } }) diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index a64c92a59..fc2c768e5 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -12,7 +12,7 @@ use std::str::FromStr; use apollo_federation_types::build::SubgraphDefinition; use camino::{Utf8Path, Utf8PathBuf}; use crossbeam_channel::unbounded; -use reqwest::blocking::Client; +use reqwest::Client; use rover_client::blocking::StudioClient; use rover_client::operations::subgraph::fetch; use rover_client::operations::subgraph::fetch::SubgraphFetchInput; @@ -74,7 +74,7 @@ impl SubgraphSchemaWatcher { }) } - pub fn new_from_graph_ref( + pub async fn new_from_graph_ref( graph_ref: &str, graphos_subgraph_name: String, routing_url: Option, @@ -90,7 +90,7 @@ impl SubgraphSchemaWatcher { subgraph_name: graphos_subgraph_name.clone(), }, client, - ) + ).await .map_err(RoverError::from)?; let routing_url = match (routing_url, response.sdl.r#type) { (Some(routing_url), _) => routing_url, @@ -139,7 +139,7 @@ impl SubgraphSchemaWatcher { }) } - pub fn get_subgraph_definition_and_maybe_new_runner( + pub async fn get_subgraph_definition_and_maybe_new_runner( &self, ) -> RoverResult<(SubgraphDefinition, Option)> { let (name, url) = self.subgraph_key.clone(); @@ -147,15 +147,15 @@ impl SubgraphSchemaWatcher { SubgraphSchemaWatcherKind::Introspect(introspect_runner_kind, polling_interval) => { match introspect_runner_kind { IntrospectRunnerKind::Graph(graph_runner) => { - let sdl = graph_runner.run()?; + let sdl = graph_runner.run().await?; (sdl, None) } IntrospectRunnerKind::Subgraph(subgraph_runner) => { - let sdl = subgraph_runner.run()?; + let sdl = subgraph_runner.run().await?; (sdl, None) } IntrospectRunnerKind::Unknown(unknown_runner) => { - let (sdl, specific_runner) = unknown_runner.run()?; + let (sdl, specific_runner) = unknown_runner.run().await?; ( sdl, Some(SubgraphSchemaWatcherKind::Introspect( @@ -178,12 +178,12 @@ impl SubgraphSchemaWatcher { Ok((subgraph_definition, refresher)) } - fn update_subgraph(&mut self, last_message: Option<&String>) -> RoverResult> { + async fn update_subgraph(&mut self, last_message: Option<&String>) -> RoverResult> { let print_error = |e: RoverError| { let _ = e.print(); }; - let maybe_update_message = match self.get_subgraph_definition_and_maybe_new_runner() { + let maybe_update_message = match self.get_subgraph_definition_and_maybe_new_runner().await { Ok((subgraph_definition, maybe_new_refresher)) => { if let Some(new_refresher) = maybe_new_refresher { self.set_schema_refresher(new_refresher); @@ -225,7 +225,7 @@ impl SubgraphSchemaWatcher { /// /// This function will block forever for `SubgraphSchemaWatcherKind` that poll for changesβ€”so it /// should be started in a separate thread. - pub fn watch_subgraph_for_changes(&mut self) -> RoverResult<()> { + pub async fn watch_subgraph_for_changes(&mut self) -> RoverResult<()> { let mut last_message = None; match self.schema_watcher_kind.clone() { SubgraphSchemaWatcherKind::Introspect(introspect_runner_kind, polling_interval) => { @@ -241,13 +241,13 @@ impl SubgraphSchemaWatcher { } ); loop { - last_message = self.update_subgraph(last_message.as_ref())?; + last_message = self.update_subgraph(last_message.as_ref()).await?; std::thread::sleep(std::time::Duration::from_secs(polling_interval)); } } SubgraphSchemaWatcherKind::File(path) => { // populate the schema for the first time (last_message is always None to start) - last_message = self.update_subgraph(last_message.as_ref())?; + last_message = self.update_subgraph(last_message.as_ref()).await?; let (tx, rx) = unbounded(); @@ -259,11 +259,11 @@ impl SubgraphSchemaWatcher { rx.recv().unwrap_or_else(|_| { panic!("an unexpected error occurred while watching {}", &path) }); - last_message = self.update_subgraph(last_message.as_ref())?; + last_message = self.update_subgraph(last_message.as_ref()).await?; } } SubgraphSchemaWatcherKind::Once(_) => { - self.update_subgraph(None)?; + self.update_subgraph(None).await?; } } Ok(()) diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index 4890ebc33..725e0c8f2 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -29,7 +29,7 @@ pub struct Check { } impl Check { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -56,7 +56,8 @@ impl Check { }, }, &client, - )?; + ) + .await?; if self.config.background { Ok(RoverOutput::AsyncCheckResponse(workflow_res)) } else { @@ -67,7 +68,8 @@ impl Check { checks_timeout_seconds, }, &client, - )?; + ) + .await?; Ok(RoverOutput::CheckWorkflowResponse(check_res)) } } diff --git a/src/command/graph/delete.rs b/src/command/graph/delete.rs index c21f90e29..cf990dc8e 100644 --- a/src/command/graph/delete.rs +++ b/src/command/graph/delete.rs @@ -23,7 +23,7 @@ pub struct Delete { } impl Delete { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); @@ -43,7 +43,8 @@ impl Delete { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; eprintln!("Successfully deleted {}.", Style::Link.paint(&graph_ref)); Ok(RoverOutput::EmptySuccess) diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index d579c013c..da838013d 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -18,7 +18,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -32,7 +32,8 @@ impl Fetch { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::FetchResponse(fetch_response)) } diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index fd569d369..ee9cce832 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -1,5 +1,5 @@ use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use std::collections::HashMap; @@ -20,16 +20,16 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { + pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { self.exec_and_watch(&client, output_opts) } else { - let sdl = self.exec(&client, true)?; + let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) } } - pub fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { + pub async fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { let client = GraphQLClient::new(self.opts.endpoint.as_ref(), client.clone()); // add the flag headers to a hashmap to pass along to rover-client @@ -40,7 +40,11 @@ impl Introspect { } }; - Ok(introspect::run(GraphIntrospectInput { headers }, &client, should_retry)?.schema_sdl) + Ok( + introspect::run(GraphIntrospectInput { headers }, &client, should_retry) + .await? + .schema_sdl, + ) } pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { diff --git a/src/command/graph/lint.rs b/src/command/graph/lint.rs index eaa0ab2be..bdc9b91a2 100644 --- a/src/command/graph/lint.rs +++ b/src/command/graph/lint.rs @@ -24,7 +24,7 @@ pub struct Lint { } impl Lint { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let file_with_metadata = self @@ -39,7 +39,8 @@ impl Lint { ignore_existing: self.lint.ignore_existing_lint_violations, }, &client, - )?; + ) + .await?; Ok(RoverOutput::LintResponse(lint_result)) } diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index ba06dfa91..34f5a08db 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -44,7 +44,7 @@ pub enum Command { } impl Graph { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -53,14 +53,16 @@ impl Graph { ) -> RoverResult { match &self.command { Command::Check(command) => { - command.run(client_config, git_context, checks_timeout_seconds) + command + .run(client_config, git_context, checks_timeout_seconds) + .await } - Command::Delete(command) => command.run(client_config), - Command::Fetch(command) => command.run(client_config), - Command::Lint(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config, git_context), + Command::Delete(command) => command.run(client_config).await, + Command::Fetch(command) => command.run(client_config).await, + Command::Lint(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config, git_context).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts) + command.run(client_config.get_reqwest_client()?, output_opts).await } } } diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index c3a758434..4fb3bc5ea 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -23,7 +23,7 @@ pub struct Publish { } impl Publish { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -49,7 +49,8 @@ impl Publish { git_context, }, &client, - )?; + ) + .await?; Ok(RoverOutput::GraphPublishResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/install/mod.rs b/src/command/install/mod.rs index b52f84f15..ef3927830 100644 --- a/src/command/install/mod.rs +++ b/src/command/install/mod.rs @@ -32,7 +32,7 @@ pub struct Install { } impl Install { - pub fn do_install( + pub async fn do_install( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -47,7 +47,7 @@ impl Install { .require_elv2_license(&client_config)?; } let plugin_installer = PluginInstaller::new(client_config, rover_installer); - plugin_installer.install(plugin, false)?; + plugin_installer.install(plugin, false).await?; Ok(RoverOutput::EmptySuccess) } else { @@ -101,7 +101,7 @@ impl Install { } #[cfg(feature = "composition-js")] - pub(crate) fn get_versioned_plugin( + pub(crate) async fn get_versioned_plugin( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -110,7 +110,7 @@ impl Install { let rover_installer = self.get_installer(PKG_NAME.to_string(), override_install_path)?; if let Some(plugin) = &self.plugin { let plugin_installer = PluginInstaller::new(client_config, rover_installer); - plugin_installer.install(plugin, skip_update) + plugin_installer.install(plugin, skip_update).await } else { let mut err = RoverError::new(anyhow!("Could not find a plugin to get a version from.")); diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index c62a52298..7f86a7ffb 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -193,7 +193,7 @@ impl PluginInstaller { } } - pub fn install(&self, plugin: &Plugin, skip_update: bool) -> RoverResult { + pub async fn install(&self, plugin: &Plugin, skip_update: bool) -> RoverResult { let skip_update_err = |plugin_name: &str, version: &str| { let mut err = RoverError::new(anyhow!( "You do not have the '{}-v{}' plugin installed.", @@ -228,7 +228,8 @@ impl PluginInstaller { self.find_existing_exact(plugin, &version)? .ok_or_else(|| skip_update_err(&plugin.get_name(), &version)) } else { - self.install_exact(plugin, &version)? + self.install_exact(plugin, &version) + .await? .ok_or_else(|| could_not_install_plugin(&plugin.get_name(), &version)) } } @@ -243,12 +244,13 @@ impl PluginInstaller { ) }) } else { - self.install_latest_major(plugin)?.ok_or_else(|| { - could_not_install_plugin( - &plugin.get_name(), - major_version.to_string().as_str(), - ) - }) + self.install_latest_major(plugin).await? + .ok_or_else(|| { + could_not_install_plugin( + &plugin.get_name(), + major_version.to_string().as_str(), + ) + }) } } }, @@ -260,7 +262,7 @@ impl PluginInstaller { self.find_existing_exact(plugin, &version)? .ok_or_else(|| skip_update_err(&plugin.get_name(), &version)) } else { - self.install_exact(plugin, &version)? + self.install_exact(plugin, &version).await? .ok_or_else(|| could_not_install_plugin(&plugin.get_name(), &version)) } } @@ -272,7 +274,7 @@ impl PluginInstaller { skip_update_err(&plugin.get_name(), version.to_string().as_str()) }) } else { - self.install_latest_major(plugin)?.ok_or_else(|| { + self.install_latest_major(plugin).await?.ok_or_else(|| { could_not_install_plugin( &plugin.get_name(), major_version.to_string().as_str(), @@ -292,7 +294,7 @@ impl PluginInstaller { ) })?) } else { - self.install_latest_major(plugin)?.ok_or_else(|| { + self.install_latest_major(plugin).await?.ok_or_else(|| { could_not_install_plugin( &plugin.get_name(), major_version.to_string().as_str(), @@ -333,7 +335,7 @@ impl PluginInstaller { } } - fn install_latest_major(&self, plugin: &Plugin) -> RoverResult> { + async fn install_latest_major(&self, plugin: &Plugin) -> RoverResult> { let latest_version = self .rover_installer .get_plugin_version(&plugin.get_tarball_url()?, true)?; @@ -342,7 +344,7 @@ impl PluginInstaller { Ok(Some(exe)) } else { // do the install. - self.do_install(plugin, true)?; + self.do_install(plugin, true).await?; self.find_existing_exact(plugin, &latest_version) } } @@ -357,15 +359,23 @@ impl PluginInstaller { Ok(find_installed_plugin(&plugin_dir, &plugin_name, version).ok()) } - fn install_exact(&self, plugin: &Plugin, version: &str) -> RoverResult> { + async fn install_exact( + &self, + plugin: &Plugin, + version: &str, + ) -> RoverResult> { if let Ok(Some(exe)) = self.find_existing_exact(plugin, version) { Ok(Some(exe)) } else { - self.do_install(plugin, false) + self.do_install(plugin, false).await } } - fn do_install(&self, plugin: &Plugin, is_latest: bool) -> RoverResult> { + async fn do_install( + &self, + plugin: &Plugin, + is_latest: bool, + ) -> RoverResult> { let plugin_name = plugin.get_name(); let plugin_tarball_url = plugin.get_tarball_url()?; // only print the download message if the username and password have been stripped from the URL @@ -374,12 +384,15 @@ impl PluginInstaller { } else { eprintln!("downloading the '{plugin_name}' plugin"); } - Ok(self.rover_installer.install_plugin( - &plugin_name, - &plugin_tarball_url, - &self.client_config.get_reqwest_client()?, - is_latest, - )?) + Ok(self + .rover_installer + .install_plugin( + &plugin_name, + &plugin_tarball_url, + &self.client_config.get_reqwest_client()?, + is_latest, + ) + .await?) } } diff --git a/src/command/license/fetch.rs b/src/command/license/fetch.rs index 352f5a87a..c95eac327 100644 --- a/src/command/license/fetch.rs +++ b/src/command/license/fetch.rs @@ -18,7 +18,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Fetching license for {} using credentials from the {} profile.", @@ -30,7 +30,8 @@ impl Fetch { graph_id: self.graph_id.to_string(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::LicenseResponse { graph_id: self.graph_id.to_string(), diff --git a/src/command/license/mod.rs b/src/command/license/mod.rs index 498439986..4f7023451 100644 --- a/src/command/license/mod.rs +++ b/src/command/license/mod.rs @@ -20,9 +20,9 @@ pub enum Command { } impl License { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Fetch(command) => command.run(client_config), + Command::Fetch(command) => command.run(client_config).await, } } } diff --git a/src/command/persisted_queries/mod.rs b/src/command/persisted_queries/mod.rs index 997218b3b..f570b3484 100644 --- a/src/command/persisted_queries/mod.rs +++ b/src/command/persisted_queries/mod.rs @@ -22,9 +22,9 @@ pub enum Command { } impl PersistedQueries { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Publish(command) => command.run(client_config), + Command::Publish(command) => command.run(client_config).await, } } } diff --git a/src/command/persisted_queries/publish.rs b/src/command/persisted_queries/publish.rs index 755ae5a9d..5840deb5f 100644 --- a/src/command/persisted_queries/publish.rs +++ b/src/command/persisted_queries/publish.rs @@ -46,7 +46,7 @@ pub struct Publish { } impl Publish { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let raw_manifest = self @@ -71,11 +71,11 @@ impl Publish { let (graph_id, list_id, list_name) = match (&self.graph.graph_ref, &self.graph_id, &self.list_id) { (Some(graph_ref), None, None) => { - let persisted_query_list = resolve::run(ResolvePersistedQueryListInput { graph_ref: graph_ref.clone() }, &client)?; + let persisted_query_list = resolve::run(ResolvePersistedQueryListInput { graph_ref: graph_ref.clone() }, &client).await?; (graph_ref.clone().name, persisted_query_list.id, persisted_query_list.name) }, (None, Some(graph_id), Some(list_id)) => { - let list_name = name::run(PersistedQueryListNameInput { graph_id: graph_id.clone(), list_id: list_id.clone() }, &client)?.name; + let list_name = name::run(PersistedQueryListNameInput { graph_id: graph_id.clone(), list_id: list_id.clone() }, &client).await?.name; (graph_id.to_string(), list_id.to_string(), list_name) }, (None, Some(graph_id), None) => { @@ -104,7 +104,7 @@ impl Publish { operation_manifest, }, &client, - )?; + ).await?; Ok(RoverOutput::PersistedQueriesPublishResponse(result)) } } diff --git a/src/command/readme/fetch.rs b/src/command/readme/fetch.rs index 4dd343fda..0f70a136f 100644 --- a/src/command/readme/fetch.rs +++ b/src/command/readme/fetch.rs @@ -18,7 +18,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); @@ -32,7 +32,8 @@ impl Fetch { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::ReadmeFetchResponse { graph_ref: self.graph.graph_ref.clone(), content: readme.content, diff --git a/src/command/readme/mod.rs b/src/command/readme/mod.rs index bc60b6291..a1365fde0 100644 --- a/src/command/readme/mod.rs +++ b/src/command/readme/mod.rs @@ -22,10 +22,10 @@ pub enum Command { } impl Readme { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Fetch(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config), + Command::Fetch(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config).await, } } } diff --git a/src/command/readme/publish.rs b/src/command/readme/publish.rs index 7b7a68cee..67d1b86d3 100644 --- a/src/command/readme/publish.rs +++ b/src/command/readme/publish.rs @@ -24,7 +24,7 @@ pub struct Publish { } impl Publish { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -44,7 +44,7 @@ impl Publish { readme: new_readme, }, &client, - )?; + ).await?; Ok(RoverOutput::ReadmePublishResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index e469a0227..18c9bf191 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -30,7 +30,7 @@ pub struct Check { } impl Check { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -61,7 +61,7 @@ impl Check { }, }, &client, - )?; + ).await?; if self.config.background { Ok(RoverOutput::AsyncCheckResponse(workflow_res)) } else { @@ -73,7 +73,7 @@ impl Check { }, self.subgraph.subgraph_name.clone(), &client, - )?; + ).await?; Ok(RoverOutput::CheckWorkflowResponse(check_res)) } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index 20c8da9c3..1e831ea29 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -27,7 +27,7 @@ pub struct Delete { } impl Delete { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( "Checking for build errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", @@ -48,7 +48,7 @@ impl Delete { dry_run, }, &client, - )?; + ).await?; RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), @@ -74,7 +74,7 @@ impl Delete { dry_run, }, &client, - )?; + ).await?; Ok(RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index 4ffd8277d..52153e66f 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -21,7 +21,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -37,7 +37,8 @@ impl Fetch { subgraph_name: self.subgraph.subgraph_name.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::FetchResponse(fetch_response)) } diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index b429bd828..bb9d99c88 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -1,5 +1,5 @@ use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use std::collections::HashMap; @@ -18,16 +18,16 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { + pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { self.exec_and_watch(&client, output_opts) } else { - let sdl = self.exec(&client, true)?; + let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) } } - pub fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { + pub async fn exec(&self, client: &Client, should_retry: bool) -> RoverResult { let client = GraphQLClient::new(self.opts.endpoint.as_ref(), client.clone()); // add the flag headers to a hashmap to pass along to rover-client @@ -38,7 +38,11 @@ impl Introspect { } }; - Ok(introspect::run(SubgraphIntrospectInput { headers }, &client, should_retry)?.result) + Ok( + introspect::run(SubgraphIntrospectInput { headers }, &client, should_retry) + .await? + .result, + ) } pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { diff --git a/src/command/subgraph/lint.rs b/src/command/subgraph/lint.rs index c69db250a..0291883e8 100644 --- a/src/command/subgraph/lint.rs +++ b/src/command/subgraph/lint.rs @@ -27,7 +27,7 @@ pub struct Lint { } impl Lint { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let file_with_metadata = self @@ -43,7 +43,7 @@ impl Lint { ignore_existing: self.lint.ignore_existing_lint_violations, }, &client, - )?; + ).await?; Ok(RoverOutput::LintResponse(lint_result)) } diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index 4bb1eb13e..026dd2dd5 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -18,7 +18,7 @@ pub struct List { } impl List { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; eprintln!( @@ -32,7 +32,8 @@ impl List { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::SubgraphList(list_details)) } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index ca4fd44e1..e56e9e734 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -49,7 +49,7 @@ pub enum Command { } impl Subgraph { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -58,16 +58,18 @@ impl Subgraph { ) -> RoverResult { match &self.command { Command::Check(command) => { - command.run(client_config, git_context, checks_timeout_seconds) + command + .run(client_config, git_context, checks_timeout_seconds) + .await } - Command::Delete(command) => command.run(client_config), + Command::Delete(command) => command.run(client_config).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts) + command.run(client_config.get_reqwest_client()?, output_opts).await } - Command::Fetch(command) => command.run(client_config), - Command::Lint(command) => command.run(client_config), - Command::List(command) => command.run(client_config), - Command::Publish(command) => command.run(client_config, git_context), + Command::Fetch(command) => command.run(client_config).await, + Command::Lint(command) => command.run(client_config).await, + Command::List(command) => command.run(client_config).await, + Command::Publish(command) => command.run(client_config, git_context).await, } } } diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index fddb264c8..05040e490 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -53,7 +53,7 @@ pub struct Publish { } impl Publish { - pub fn run( + pub async fn run( &self, client_config: StudioClientConfig, git_context: GitContext, @@ -71,7 +71,8 @@ impl Publish { subgraph_name: self.subgraph.subgraph_name.clone(), }, &client, - )?) + ) + .await?) }, &mut io::stderr(), &mut io::stdin(), @@ -101,7 +102,8 @@ impl Publish { convert_to_federated_graph: self.convert, }, &client, - )?; + ) + .await?; Ok(RoverOutput::SubgraphPublishResponse { graph_ref: self.graph.graph_ref.clone(), @@ -110,7 +112,7 @@ impl Publish { }) } - fn determine_routing_url( + async fn determine_routing_url( no_url: bool, routing_url: &Option, allow_invalid_routing_url: bool, @@ -126,7 +128,7 @@ impl Publish { is_atty: bool, ) -> RoverResult> where - F: Fn() -> RoverResult, + F: Fn() -> impl Future>, { if no_url && routing_url.is_some() { return Err(RoverError::new(anyhow!( @@ -145,7 +147,7 @@ impl Publish { // --no-url is set let mut routing_url = routing_url.clone(); if !no_url && routing_url.is_none() { - let fetch_response = fetch()?; + let fetch_response = fetch().await?; Self::handle_maybe_invalid_routing_url( &Some(fetch_response.clone()), writer, diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index f5f491a0d..216424a47 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -44,7 +44,7 @@ impl Compose { } } - pub(crate) fn maybe_install_supergraph( + pub(crate) async fn maybe_install_supergraph( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -65,15 +65,13 @@ impl Compose { }; // maybe do the install, maybe find a pre-existing installation, maybe fail - let plugin_exe = install_command.get_versioned_plugin( - override_install_path, - client_config, - self.opts.skip_update, - )?; + let plugin_exe = install_command + .get_versioned_plugin(override_install_path, client_config, self.opts.skip_update) + .await?; Ok(plugin_exe) } - pub fn run( + pub async fn run( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -87,21 +85,22 @@ impl Compose { &self.supergraph_yaml, client_config.clone(), &self.opts.profile, - )?; - self.compose(override_install_path, client_config, &mut supergraph_config) + ) + .await?; + self.compose(override_install_path, client_config, &mut supergraph_config).await } - pub fn compose( + pub async fn compose( &self, override_install_path: Option, client_config: StudioClientConfig, supergraph_config: &mut SupergraphConfig, ) -> RoverResult { - let output = self.exec(override_install_path, client_config, supergraph_config)?; + let output = self.exec(override_install_path, client_config, supergraph_config).await?; Ok(RoverOutput::CompositionResult(output)) } - pub fn exec( + pub async fn exec( &self, override_install_path: Option, client_config: StudioClientConfig, @@ -110,11 +109,13 @@ impl Compose { // first, grab the _actual_ federation version from the config we just resolved // (this will always be `Some` as long as we have created with `resolve_supergraph_yaml` so it is safe to unwrap) let federation_version = supergraph_config.get_federation_version().unwrap(); - let exe = self.maybe_install_supergraph( - override_install_path, - client_config, - federation_version.clone(), - )?; + let exe = self + .maybe_install_supergraph( + override_install_path, + client_config, + federation_version.clone(), + ) + .await?; // _then_, overwrite the federation_version with _only_ the major version // before sending it to the supergraph plugin. @@ -199,8 +200,8 @@ mod tests { ) } - #[test] - fn it_errs_on_invalid_subgraph_path() { + #[tokio::test] + async fn it_errs_on_invalid_subgraph_path() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com @@ -221,11 +222,12 @@ mod tests { profile_name: "profile".to_string() } ) + .await .is_err()) } - #[test] - fn it_can_get_subgraph_definitions_from_fs() { + #[tokio::test] + async fn it_can_get_subgraph_definitions_from_fs() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com @@ -251,11 +253,12 @@ mod tests { profile_name: "profile".to_string() } ) + .await .is_ok()) } - #[test] - fn it_can_compute_relative_schema_paths() { + #[tokio::test] + async fn it_can_compute_relative_schema_paths() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com @@ -284,6 +287,7 @@ mod tests { profile_name: "profile".to_string(), }, ) + .await .unwrap() .get_subgraph_definitions() .unwrap(); diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index b01f62427..d7e48c1ff 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -20,7 +20,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { let client = client_config.get_authenticated_client(&self.profile)?; let graph_ref = self.graph.graph_ref.to_string(); eprintln!( @@ -34,7 +34,8 @@ impl Fetch { graph_ref: self.graph.graph_ref.clone(), }, &client, - )?; + ) + .await?; Ok(RoverOutput::FetchResponse(fetch_response)) } diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index decf897de..057c32c71 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -29,14 +29,14 @@ pub enum Command { } impl Supergraph { - pub fn run( + pub async fn run( &self, override_install_path: Option, client_config: StudioClientConfig, ) -> RoverResult { match &self.command { - Command::Fetch(command) => command.run(client_config), - Command::Compose(command) => command.run(override_install_path, client_config), + Command::Fetch(command) => command.run(client_config).await, + Command::Compose(command) => command.run(override_install_path, client_config).await, } } } diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 94e8402c1..c92d1698e 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -45,7 +45,7 @@ subgraphs: } } -pub(crate) fn resolve_supergraph_yaml( +pub(crate) async fn resolve_supergraph_yaml( unresolved_supergraph_yaml: &FileDescriptorType, client_config: StudioClientConfig, profile_opt: &ProfileOpt, @@ -117,6 +117,7 @@ pub(crate) fn resolve_supergraph_yaml( &client, false, ) + .await .map(|introspection_response| { let schema = introspection_response.result; @@ -148,6 +149,7 @@ pub(crate) fn resolve_supergraph_yaml( }, &authenticated_client, ) + .await .map_err(RoverError::from) .and_then(|result| { // We don't require a routing_url in config for this variant of a schema, diff --git a/src/command/template/mod.rs b/src/command/template/mod.rs index 2f10f16d7..baacb9981 100644 --- a/src/command/template/mod.rs +++ b/src/command/template/mod.rs @@ -29,9 +29,9 @@ enum Command { } impl Template { - pub(crate) fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub(crate) async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { - Command::Use(use_template) => use_template.run(client_config), + Command::Use(use_template) => use_template.run(client_config).await, Command::List(list) => list.run(), } } diff --git a/src/command/template/use.rs b/src/command/template/use.rs index 0d8aa7808..4e1d80b8c 100644 --- a/src/command/template/use.rs +++ b/src/command/template/use.rs @@ -34,7 +34,7 @@ pub struct Use { } impl Use { - pub fn run(&self, client_config: StudioClientConfig) -> RoverResult { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { // find the template to extract let (template_id, download_url) = if let Some(template_id) = &self.template { // if they specify an ID, get it @@ -60,7 +60,7 @@ impl Use { let path = self.get_or_prompt_path()?; // download and extract a tarball from github - extract_tarball(download_url, &path, &client_config.get_reqwest_client()?)?; + extract_tarball(download_url, &path, &client_config.get_reqwest_client()?).await?; Ok(RoverOutput::TemplateUseSuccess { template_id, path }) } diff --git a/src/command/update/check.rs b/src/command/update/check.rs index 3ec01cf10..4ad355ace 100644 --- a/src/command/update/check.rs +++ b/src/command/update/check.rs @@ -1,5 +1,5 @@ use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use crate::{utils::version, RoverOutput, RoverResult}; @@ -12,8 +12,8 @@ pub struct Check { } impl Check { - pub fn run(&self, config: config::Config, client: Client) -> RoverResult { - version::check_for_update(config, true, client)?; + pub async fn run(&self, config: config::Config, client: Client) -> RoverResult { + version::check_for_update(config, true, client).await?; Ok(RoverOutput::EmptySuccess) } } diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index eb5fa0d1f..871f41a71 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -21,9 +21,9 @@ pub enum Command { } impl Update { - pub fn run(&self, config: config::Config, client: Client) -> RoverResult { + pub async fn run(&self, config: config::Config, client: Client) -> RoverResult { match &self.command { - Command::Check(command) => command.run(config, client), + Command::Check(command) => command.run(config, client).await, } } } diff --git a/src/options/introspect.rs b/src/options/introspect.rs index a2fa2446c..1017e3ef9 100644 --- a/src/options/introspect.rs +++ b/src/options/introspect.rs @@ -30,13 +30,13 @@ pub struct IntrospectOpts { } impl IntrospectOpts { - pub fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! + pub async fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! where - F: Fn() -> RoverResult, + F: Fn() -> impl Future>, { let mut last_result = None; loop { - match exec_fn() { + match exec_fn().await { Ok(sdl) => { let mut was_updated = true; if let Some(last) = last_result { diff --git a/src/options/template.rs b/src/options/template.rs index af4caf418..14767fc74 100644 --- a/src/options/template.rs +++ b/src/options/template.rs @@ -41,10 +41,10 @@ impl TemplateOpt { } } -pub(crate) fn extract_tarball( +pub(crate) async fn extract_tarball( download_url: Url, template_path: &Utf8PathBuf, - client: &reqwest::blocking::Client, + client: &reqwest::Client, ) -> RoverResult<()> { let download_dir = tempdir::TempDir::new("rover-template")?; let download_dir_path = Utf8PathBuf::try_from(download_dir.into_path())?; @@ -56,9 +56,10 @@ pub(crate) fn extract_tarball( .get(download_url) .header(reqwest::header::USER_AGENT, "rover-client") .header(reqwest::header::ACCEPT, "application/octet-stream") - .send()? + .send() + .await? .error_for_status()? - .bytes()?; + .bytes().await?; f.write_all(&response_bytes[..])?; f.sync_all()?; let f = std::fs::File::open(&tarball_path)?; diff --git a/src/utils/client.rs b/src/utils/client.rs index f6d9d6d34..b12fb1c3d 100644 --- a/src/utils/client.rs +++ b/src/utils/client.rs @@ -5,7 +5,7 @@ use crate::{options::ProfileOpt, PKG_NAME, PKG_VERSION}; use anyhow::Result; use houston as config; -use reqwest::blocking::Client; +use reqwest::Client; use rover_client::blocking::StudioClient; use serde::Serialize; @@ -62,7 +62,7 @@ impl ClientBuilder { .brotli(true) .danger_accept_invalid_certs(self.accept_invalid_certs) .danger_accept_invalid_hostnames(self.accept_invalid_hostnames) - .timeout(self.timeout) + .timeout(self.timeout.unwrap()) .user_agent(format!("{}/{}", PKG_NAME, PKG_VERSION)) .build()?; diff --git a/src/utils/telemetry.rs b/src/utils/telemetry.rs index 6e6b31317..a6aba2a5b 100644 --- a/src/utils/telemetry.rs +++ b/src/utils/telemetry.rs @@ -1,5 +1,5 @@ use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use url::Url; use crate::utils::env::RoverEnvKey; diff --git a/src/utils/version.rs b/src/utils/version.rs index 5894bd434..29e140c41 100644 --- a/src/utils/version.rs +++ b/src/utils/version.rs @@ -3,7 +3,7 @@ use std::time::SystemTime; use anyhow::Result; use billboard::{Alignment, Billboard}; use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use rover_std::{Fs, Style}; use crate::PKG_VERSION; @@ -19,7 +19,7 @@ const ONE_DAY: u64 = ONE_HOUR * 24; /// check for newer versions, even if we recently checked for updates. /// /// If `force` is not passed, we check for updates every day at most -pub fn check_for_update(config: config::Config, force: bool, client: Client) -> Result<()> { +pub async fn check_for_update(config: config::Config, force: bool, client: Client) -> Result<()> { let version_file = config.home.join("version.toml"); let current_time = SystemTime::now(); // if we don't end up checking, we don't want to overwrite the last checked time @@ -29,7 +29,7 @@ pub fn check_for_update(config: config::Config, force: bool, client: Client) -> let last_checked_time = get_last_checked_time_from_disk(&version_file); if force || last_checked_time.is_none() { - do_update_check(&mut checked, force, client)?; + do_update_check(&mut checked, force, client).await?; } else if let Some(last_checked_time) = last_checked_time { let time_since_check = current_time.duration_since(last_checked_time)?.as_secs(); tracing::trace!( @@ -38,7 +38,7 @@ pub fn check_for_update(config: config::Config, force: bool, client: Client) -> ); if time_since_check > ONE_DAY { - do_update_check(&mut checked, force, client)?; + do_update_check(&mut checked, force, client).await?; } } @@ -50,12 +50,12 @@ pub fn check_for_update(config: config::Config, force: bool, client: Client) -> Ok(()) } -fn do_update_check( +async fn do_update_check( checked: &mut bool, should_output_if_updated: bool, client: Client, ) -> Result<()> { - let latest_version = get_latest_release(client)?; + let latest_version = get_latest_release(client).await?; let pretty_latest = Style::Version.paint(format!("v{}", latest_version)); if latest_version > Version::parse(PKG_VERSION)? { let message = format!( From 3553d6d2efa9f2edb7e461d731622418d8e463ee Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 14 Mar 2024 18:46:11 +0100 Subject: [PATCH 18/68] more fixes --- Cargo.lock | 2 + crates/sputnik/src/report.rs | 2 +- crates/sputnik/src/session.rs | 6 +- src/cli.rs | 50 ++++-- src/command/dev/do_dev.rs | 9 +- src/command/dev/protocol/leader.rs | 21 ++- src/command/dev/router/command.rs | 23 ++- src/command/dev/schema.rs | 122 ++++++------- src/command/graph/introspect.rs | 6 +- src/command/subgraph/introspect.rs | 6 +- src/command/subgraph/publish.rs | 56 +++--- src/command/supergraph/resolve_config.rs | 220 +++++++++++------------ src/command/update/mod.rs | 2 +- src/options/introspect.rs | 6 +- 14 files changed, 287 insertions(+), 244 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3797c24ce..b45e2f8ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4143,6 +4143,7 @@ dependencies = [ "assert-json-diff", "assert_cmd", "assert_fs", + "backoff", "billboard", "binstall", "calm_io", @@ -4154,6 +4155,7 @@ dependencies = [ "ctrlc", "dialoguer", "flate2", + "futures", "graphql_client", "heck 0.4.1", "houston", diff --git a/crates/sputnik/src/report.rs b/crates/sputnik/src/report.rs index c9886f759..c22f4226c 100644 --- a/crates/sputnik/src/report.rs +++ b/crates/sputnik/src/report.rs @@ -1,5 +1,5 @@ use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use url::Url; use uuid::Uuid; diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index ea0c49925..e24bc4e3b 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -1,6 +1,6 @@ use camino::Utf8PathBuf; use ci_info::types::Vendor as CiVendor; -use reqwest::blocking::Client; +use reqwest::Client; use reqwest::Url; use rover_client::shared::GitContext; use semver::Version; @@ -132,7 +132,7 @@ impl Session { } /// sends anonymous usage data to the endpoint defined in ReportingInfo. - pub fn report(&self) -> Result<(), SputnikError> { + pub async fn report(&self) -> Result<(), SputnikError> { if self.reporting_info.is_telemetry_enabled && !cfg!(debug_assertions) { // set timeout to 400 ms to prevent blocking for too long on reporting let timeout = Duration::from_millis(4000); @@ -145,7 +145,7 @@ impl Session { .header("User-Agent", &self.reporting_info.user_agent) .header("Content-Type", "application/json") .timeout(timeout) - .send()?; + .send().await?; } Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index fb4063849..8e2957adb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -123,9 +123,9 @@ impl Rover { // if successful, report the usage data in the background Ok(session) => { // kicks off the reporting on a background thread - let report_thread = thread::spawn(move || { + let report_thread = tokio::task::spawn(async move { // log + ignore errors because it is not in the critical path - let _ = session.report().map_err(|telemetry_error| { + let _ = session.report().await.map_err(|telemetry_error| { tracing::debug!(?telemetry_error); telemetry_error }); @@ -139,7 +139,7 @@ impl Rover { // makes sure the reporting finishes in the background // before continuing. // ignore errors because it is not in the critical path - let _ = report_thread.join(); + let _ = report_thread.await; // return result of app execution // now that we have reported our usage data @@ -188,28 +188,42 @@ impl Rover { } Command::Fed2(command) => command.run(self.get_client_config()?), Command::Supergraph(command) => { - command.run(self.get_install_override_path()?, self.get_client_config()?).await + command + .run(self.get_install_override_path()?, self.get_client_config()?) + .await } Command::Docs(command) => command.run(), - Command::Graph(command) => command.run( - self.get_client_config()?, - self.get_git_context()?, - self.get_checks_timeout_seconds()?, - &self.output_opts, - ).await, + Command::Graph(command) => { + command + .run( + self.get_client_config()?, + self.get_git_context()?, + self.get_checks_timeout_seconds()?, + &self.output_opts, + ) + .await + } Command::Template(command) => command.run(self.get_client_config()?).await, Command::Readme(command) => command.run(self.get_client_config()?).await, - Command::Subgraph(command) => command.run( - self.get_client_config()?, - self.get_git_context()?, - self.get_checks_timeout_seconds()?, - &self.output_opts, - ).await, + Command::Subgraph(command) => { + command + .run( + self.get_client_config()?, + self.get_git_context()?, + self.get_checks_timeout_seconds()?, + &self.output_opts, + ) + .await + } Command::Update(command) => { - command.run(self.get_rover_config()?, self.get_reqwest_client()?) + command + .run(self.get_rover_config()?, self.get_reqwest_client()?) + .await } Command::Install(command) => { - command.do_install(self.get_install_override_path()?, self.get_client_config()?).await + command + .do_install(self.get_install_override_path()?, self.get_client_config()?) + .await } Command::Info(command) => command.run(), Command::Explain(command) => command.run(), diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 9638f9620..353360a42 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -48,7 +48,9 @@ impl Dev { follower_channel.clone(), self.opts.plugin_opts.clone(), router_config_handler, - ).await? { + ) + .await? + { eprintln!("{0}Do not run this command in production! {0}It is intended for local development.", Emoji::Warn); let (ready_sender, ready_receiver) = sync_channel(1); let follower_messenger = FollowerMessenger::from_main_session( @@ -75,9 +77,10 @@ impl Dev { .unwrap(); }); - let subgraph_watcher_handle = std::thread::spawn(move || { + let subgraph_watcher_handle = tokio::task::spawn(async move { let _ = leader_session .listen_for_all_subgraph_updates(ready_sender) + .await .map_err(log_err_and_continue); }); @@ -115,7 +118,7 @@ impl Dev { }); subgraph_watcher_handle - .join() + .await .expect("could not wait for subgraph watcher thread"); } else { let follower_messenger = FollowerMessenger::from_attached_session(&ipc_socket_addr); diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index ec1decbdb..a004611b9 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -16,6 +16,7 @@ use apollo_federation_types::{ }; use camino::Utf8PathBuf; use crossbeam_channel::{bounded, Receiver, Sender}; +use futures::TryFutureExt; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use rover_std::Emoji; use semver::Version; @@ -139,6 +140,7 @@ impl LeaderSession { ) -> RoverResult<()> { self.receive_messages_from_attached_sessions()?; self.receive_all_subgraph_updates(ready_sender).await; + Ok(()) } /// Listen for incoming subgraph updates and re-compose the supergraph @@ -299,10 +301,10 @@ impl LeaderSession { /// Reruns composition, which triggers the router to reload. async fn compose(&mut self) -> CompositionResult { - self.compose_runner + match self + .compose_runner .run(&mut self.supergraph_config()) - .await - .and_then(|maybe_new_schema| { + .and_then(|maybe_new_schema| async { if maybe_new_schema.is_some() { if let Err(err) = self.router_runner.spawn().await { return Err(err.to_string()); @@ -310,14 +312,18 @@ impl LeaderSession { } Ok(maybe_new_schema) }) - .map_err(|e| { + .await + { + Ok(res) => Ok(res), + Err(e) => { let _ = self .router_runner .kill() .await .map_err(log_err_and_continue); - e - }) + Err(e) + } + } } /// Reads a [`FollowerMessage`] from an open socket connection. @@ -402,7 +408,8 @@ impl LeaderSession { impl Drop for LeaderSession { fn drop(&mut self) { - self.shutdown(); + todo!(); + //self.shutdown().await; } } diff --git a/src/command/dev/router/command.rs b/src/command/dev/router/command.rs index 60513813e..594a93eea 100644 --- a/src/command/dev/router/command.rs +++ b/src/command/dev/router/command.rs @@ -52,11 +52,18 @@ impl BackgroundTask { if let Ok(apollo_graph_ref) = var("APOLLO_GRAPH_REF") { command.env("APOLLO_GRAPH_REF", apollo_graph_ref); - if let Some(api_key) = client_config.get_authenticated_client(profile_opt).map_err(|err| { - eprintln!("{} APOLLO_GRAPH_REF is set, but credentials could not be loaded. \ - Enterprise features within the router will not function. {err}", Emoji::Warn); - }).ok().and_then(|client| { - who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { + if let Some(client) = client_config + .get_authenticated_client(profile_opt) + .map_err(|err| { + eprintln!( + "{} APOLLO_GRAPH_REF is set, but credentials could not be loaded. \ + Enterprise features within the router will not function. {err}", + Emoji::Warn + ); + }) + .ok() + { + if let Some(api_key) = who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { eprintln!("{} Could not determine the type of configured credentials, \ Router may fail to start if Enterprise features are enabled. {err}", Emoji::Warn); Some(client.credential.api_key.clone()) @@ -74,8 +81,10 @@ impl BackgroundTask { None } } - }) - }) { command.env("APOLLO_KEY", api_key); } + }) { + command.env("APOLLO_KEY", api_key); + } + } } let mut child = command diff --git a/src/command/dev/schema.rs b/src/command/dev/schema.rs index 4775c16ad..a508564e3 100644 --- a/src/command/dev/schema.rs +++ b/src/command/dev/schema.rs @@ -117,68 +117,70 @@ impl SupergraphOpts { .with_timeout(Duration::from_secs(5)) .build()?; let mut studio_client: Option = None; - supergraph_config - .into_iter() - .map(|(yaml_subgraph_name, subgraph_config)| { - let routing_url = subgraph_config - .routing_url - .map(|url_str| Url::parse(&url_str).map_err(RoverError::from)) - .transpose()?; - match subgraph_config.schema { - SchemaSource::File { file } => { - let routing_url = routing_url.ok_or_else(|| { - anyhow!("`routing_url` must be set when using a local schema file") - })?; - SubgraphSchemaWatcher::new_from_file_path( - (yaml_subgraph_name, routing_url), - file, - follower_messenger.clone(), - ) - } - SchemaSource::SubgraphIntrospection { - subgraph_url, - introspection_headers, - } => SubgraphSchemaWatcher::new_from_url( - (yaml_subgraph_name, subgraph_url), - client.clone(), + + let mut res = Vec::new(); + for (yaml_subgraph_name, subgraph_config) in supergraph_config.into_iter() { + let routing_url = subgraph_config + .routing_url + .map(|url_str| Url::parse(&url_str).map_err(RoverError::from)) + .transpose()?; + let elem = match subgraph_config.schema { + SchemaSource::File { file } => { + let routing_url = routing_url.ok_or_else(|| { + anyhow!("`routing_url` must be set when using a local schema file") + })?; + SubgraphSchemaWatcher::new_from_file_path( + (yaml_subgraph_name, routing_url), + file, follower_messenger.clone(), - polling_interval, - introspection_headers, - ), - SchemaSource::Sdl { sdl } => { - let routing_url = routing_url.ok_or_else(|| { - anyhow!("`routing_url` must be set when providing SDL directly") - })?; - SubgraphSchemaWatcher::new_from_sdl( - (yaml_subgraph_name, routing_url), - sdl, - follower_messenger.clone(), - ) - } - SchemaSource::Subgraph { - graphref, - subgraph: graphos_subgraph_name, - } => { - let studio_client = if let Some(studio_client) = studio_client.as_ref() { - studio_client - } else { - let client = client_config.get_authenticated_client(profile_opt)?; - studio_client = Some(client); - studio_client.as_ref().unwrap() - }; + ) + } + SchemaSource::SubgraphIntrospection { + subgraph_url, + introspection_headers, + } => SubgraphSchemaWatcher::new_from_url( + (yaml_subgraph_name, subgraph_url), + client.clone(), + follower_messenger.clone(), + polling_interval, + introspection_headers, + ), + SchemaSource::Sdl { sdl } => { + let routing_url = routing_url.ok_or_else(|| { + anyhow!("`routing_url` must be set when providing SDL directly") + })?; + SubgraphSchemaWatcher::new_from_sdl( + (yaml_subgraph_name, routing_url), + sdl, + follower_messenger.clone(), + ) + } + SchemaSource::Subgraph { + graphref, + subgraph: graphos_subgraph_name, + } => { + let studio_client = if let Some(studio_client) = studio_client.as_ref() { + studio_client + } else { + let client = client_config.get_authenticated_client(profile_opt)?; + studio_client = Some(client); + studio_client.as_ref().unwrap() + }; - SubgraphSchemaWatcher::new_from_graph_ref( - &graphref, - graphos_subgraph_name, - routing_url, - yaml_subgraph_name, - follower_messenger.clone(), - studio_client, - ).await - } + SubgraphSchemaWatcher::new_from_graph_ref( + &graphref, + graphos_subgraph_name, + routing_url, + yaml_subgraph_name, + follower_messenger.clone(), + studio_client, + ) + .await } - }) - .collect::>>() - .map(Some) + }; + res.push(elem?); + } + + Ok(Some(res)) } } diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index ee9cce832..513d461ff 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -22,7 +22,7 @@ pub struct Introspect { impl Introspect { pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { - self.exec_and_watch(&client, output_opts) + self.exec_and_watch(&client, output_opts).await } else { let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) @@ -47,8 +47,8 @@ impl Introspect { ) } - pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { + pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts) + .exec_and_watch(|| self.exec(client, false), output_opts).await } } diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index bb9d99c88..9b637e95c 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -20,7 +20,7 @@ pub struct Introspect { impl Introspect { pub async fn run(&self, client: Client, output_opts: &OutputOpts) -> RoverResult { if self.opts.watch { - self.exec_and_watch(&client, output_opts) + self.exec_and_watch(&client, output_opts).await } else { let sdl = self.exec(&client, true).await?; Ok(RoverOutput::Introspection(sdl)) @@ -45,8 +45,8 @@ impl Introspect { ) } - pub fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { + pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts) + .exec_and_watch(|| self.exec(client, false), output_opts).await } } diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 05040e490..ac804130d 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -2,6 +2,7 @@ use std::io::{self, IsTerminal}; use anyhow::anyhow; use clap::Parser; +use futures::Future; use reqwest::Url; use rover_client::operations::subgraph::routing_url::{self, SubgraphRoutingUrlInput}; use serde::Serialize; @@ -64,7 +65,7 @@ impl Publish { self.no_url, &self.routing_url, self.allow_invalid_routing_url, - || { + || async { Ok(routing_url::run( SubgraphRoutingUrlInput { graph_ref: self.graph.graph_ref.clone(), @@ -77,7 +78,8 @@ impl Publish { &mut io::stderr(), &mut io::stdin(), io::stderr().is_terminal() && io::stdin().is_terminal(), - )?; + ) + .await?; eprintln!( "Publishing SDL to {} (subgraph: {}) using credentials from the {} profile.", @@ -112,7 +114,7 @@ impl Publish { }) } - async fn determine_routing_url( + async fn determine_routing_url( no_url: bool, routing_url: &Option, allow_invalid_routing_url: bool, @@ -128,7 +130,8 @@ impl Publish { is_atty: bool, ) -> RoverResult> where - F: Fn() -> impl Future>, + F: Fn() -> G, + G: Future>, { if no_url && routing_url.is_some() { return Err(RoverError::new(anyhow!( @@ -262,53 +265,56 @@ impl Publish { mod tests { use crate::command::subgraph::publish::Publish; - #[test] - fn test_no_url() { + #[tokio::test] + async fn test_no_url() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( true, &None, false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("".to_string())); } - #[test] - fn test_routing_url_provided() { + #[tokio::test] + async fn test_routing_url_provided() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( false, &Some("https://provided".to_string()), false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("https://provided".to_string())); } - #[test] - fn test_no_url_and_routing_url_provided() { + #[tokio::test] + async fn test_no_url_and_routing_url_provided() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( true, &Some("https://provided".to_string()), false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap_err(); assert_eq!( result.message(), @@ -316,25 +322,26 @@ mod tests { ); } - #[test] - fn test_routing_url_not_provided_already_exists() { + #[tokio::test] + async fn test_routing_url_not_provided_already_exists() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); let result = Publish::determine_routing_url( false, &None, false, - || Ok("https://fromstudio".to_string()), + || async { Ok("https://fromstudio".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("https://fromstudio".to_string())); } - #[test] + #[tokio::test] fn test_routing_url_unix_socket() { let mut input: &[u8] = &[]; let mut output: Vec = Vec::new(); @@ -342,7 +349,7 @@ mod tests { false, &None, false, - || Ok("unix:///path/to/subgraph.sock".to_string()), + || async { Ok("unix:///path/to/subgraph.sock".to_string()) }, &mut output, &mut input, true, @@ -353,7 +360,8 @@ mod tests { } #[test] - fn test_routing_url_invalid_provided() { + #[tokio::test] + async fn test_routing_url_invalid_provided() { let mut input = "y".as_bytes(); let mut output: Vec = Vec::new(); @@ -361,19 +369,20 @@ mod tests { false, &Some("invalid".to_string()), false, - || Ok("".to_string()), + || async { Ok("".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("invalid".to_string())); assert!(std::str::from_utf8(&output).unwrap().contains("is not a valid routing URL. Continuing the publish will make this subgraph unreachable by your supergraph. Would you still like to publish?")); } - #[test] - fn test_not_url_invalid_from_studio() { + #[tokio::test] + async fn test_not_url_invalid_from_studio() { let mut input = "y".as_bytes(); let mut output: Vec = Vec::new(); @@ -381,11 +390,12 @@ mod tests { true, &None, false, - || Ok("invalid".to_string()), + || async { Ok("invalid".to_string()) }, &mut output, &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("".to_string())); diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index c92d1698e..e0845f0e2 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -64,125 +64,119 @@ pub(crate) async fn resolve_supergraph_yaml( .into_iter() .collect::>(); - let subgraph_definition_results: Vec<(String, RoverResult)> = - supergraph_config - .into_par_iter() - .map(|(subgraph_name, subgraph_data)| { - let cloned_subgraph_name = subgraph_name.to_string(); - let result = match &subgraph_data.schema { - SchemaSource::File { file } => { - let relative_schema_path = match unresolved_supergraph_yaml { - FileDescriptorType::File(config_path) => match config_path.parent() { - Some(parent) => { - let mut schema_path = parent.to_path_buf(); - schema_path.push(file); - schema_path - } - None => file.clone(), - }, - FileDescriptorType::Stdin => file.clone(), - }; + let mut subgraph_definition_results: Vec<(String, RoverResult)> = + Vec::new(); - Fs::read_file(relative_schema_path) - .map_err(|e| { - let mut err = RoverError::new(e); - err.set_suggestion(RoverErrorSuggestion::ValidComposeFile); - err - }) - .and_then(|schema| { - subgraph_data - .routing_url - .clone() - .ok_or_else(err_no_routing_url) - .map(|url| SubgraphDefinition::new(subgraph_name, url, &schema)) - }) - } - SchemaSource::SubgraphIntrospection { - subgraph_url, - introspection_headers, - } => { - client_config - .get_reqwest_client() - .map_err(RoverError::from) - .and_then(|reqwest_client| { - let client = - GraphQLClient::new(subgraph_url.as_ref(), reqwest_client); + for (subgraph_name, subgraph_data) in supergraph_config.into_iter() { + let cloned_subgraph_name = subgraph_name.to_string(); + let result = match &subgraph_data.schema { + SchemaSource::File { file } => { + let relative_schema_path = match unresolved_supergraph_yaml { + FileDescriptorType::File(config_path) => match config_path.parent() { + Some(parent) => { + let mut schema_path = parent.to_path_buf(); + schema_path.push(file); + schema_path + } + None => file.clone(), + }, + FileDescriptorType::Stdin => file.clone(), + }; - // given a federated introspection URL, use subgraph introspect to - // obtain SDL and add it to subgraph_definition. - introspect::run( - SubgraphIntrospectInput { - headers: introspection_headers.clone().unwrap_or_default(), - }, - &client, - false, - ) - .await - .map(|introspection_response| { - let schema = introspection_response.result; + Fs::read_file(relative_schema_path) + .map_err(|e| { + let mut err = RoverError::new(e); + err.set_suggestion(RoverErrorSuggestion::ValidComposeFile); + err + }) + .and_then(|schema| { + subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url) + .map(|url| SubgraphDefinition::new(subgraph_name, url, &schema)) + }) + } + SchemaSource::SubgraphIntrospection { + subgraph_url, + introspection_headers, + } => { + let reqwest_client = client_config + .get_reqwest_client() + .map_err(RoverError::from)?; + let client = GraphQLClient::new(subgraph_url.as_ref(), reqwest_client); - // We don't require a routing_url in config for this variant of a schema, - // if one isn't provided, just use the URL they passed for introspection. - let url = &subgraph_data - .routing_url - .clone() - .unwrap_or_else(|| subgraph_url.to_string()); - SubgraphDefinition::new(subgraph_name, url, schema) - }) - .map_err(RoverError::from) - }) - } - SchemaSource::Subgraph { - graphref: graph_ref, - subgraph, - } => { - client_config - .get_authenticated_client(profile_opt) - .map_err(RoverError::from) - .and_then(|authenticated_client| { - // given a graph_ref and subgraph, run subgraph fetch to - // obtain SDL and add it to subgraph_definition. - fetch::run( - SubgraphFetchInput { - graph_ref: GraphRef::from_str(graph_ref)?, - subgraph_name: subgraph.clone(), - }, - &authenticated_client, - ) - .await - .map_err(RoverError::from) - .and_then(|result| { - // We don't require a routing_url in config for this variant of a schema, - // if one isn't provided, just use the routing URL from the graph registry (if it exists). - if let rover_client::shared::SdlType::Subgraph { - routing_url: Some(graph_registry_routing_url), - } = result.sdl.r#type - { - let url = subgraph_data - .routing_url - .clone() - .unwrap_or(graph_registry_routing_url); - Ok(SubgraphDefinition::new( - subgraph_name, - url, - &result.sdl.contents, - )) - } else { - Err(err_no_routing_url()) - } - }) - }) - } - SchemaSource::Sdl { sdl } => subgraph_data + // given a federated introspection URL, use subgraph introspect to + // obtain SDL and add it to subgraph_definition. + introspect::run( + SubgraphIntrospectInput { + headers: introspection_headers.clone().unwrap_or_default(), + }, + &client, + false, + ) + .await + .map(|introspection_response| { + let schema = introspection_response.result; + + // We don't require a routing_url in config for this variant of a schema, + // if one isn't provided, just use the URL they passed for introspection. + let url = &subgraph_data .routing_url .clone() - .ok_or_else(err_no_routing_url) - .map(|url| SubgraphDefinition::new(subgraph_name, url, sdl)), - }; + .unwrap_or_else(|| subgraph_url.to_string()); + SubgraphDefinition::new(subgraph_name, url, schema) + }) + .map_err(RoverError::from) + } + SchemaSource::Subgraph { + graphref: graph_ref, + subgraph, + } => { + let authenticated_client = client_config + .get_authenticated_client(profile_opt) + .map_err(RoverError::from)?; + // given a graph_ref and subgraph, run subgraph fetch to + // obtain SDL and add it to subgraph_definition. + fetch::run( + SubgraphFetchInput { + graph_ref: GraphRef::from_str(&graph_ref)?, + subgraph_name: subgraph.clone(), + }, + &authenticated_client, + ) + .await + .map_err(RoverError::from) + .and_then(|result| { + // We don't require a routing_url in config for this variant of a schema, + // if one isn't provided, just use the routing URL from the graph registry (if it exists). + if let rover_client::shared::SdlType::Subgraph { + routing_url: Some(graph_registry_routing_url), + } = result.sdl.r#type + { + let url = subgraph_data + .routing_url + .clone() + .unwrap_or(graph_registry_routing_url); + Ok(SubgraphDefinition::new( + subgraph_name, + url, + &result.sdl.contents, + )) + } else { + Err(err_no_routing_url()) + } + }) + } + SchemaSource::Sdl { sdl } => subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url) + .map(|url| SubgraphDefinition::new(subgraph_name, url, sdl)), + }; - (cloned_subgraph_name, result) - }) - .collect(); + subgraph_definition_results.push((cloned_subgraph_name, result)); + } let mut subgraph_definitions = Vec::new(); let mut subgraph_definition_errors = Vec::new(); diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index 871f41a71..ef10b782c 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -1,7 +1,7 @@ mod check; use clap::Parser; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Serialize; use crate::{RoverOutput, RoverResult}; diff --git a/src/options/introspect.rs b/src/options/introspect.rs index 1017e3ef9..93218f3b1 100644 --- a/src/options/introspect.rs +++ b/src/options/introspect.rs @@ -1,4 +1,5 @@ use clap::Parser; +use futures::Future; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -30,9 +31,10 @@ pub struct IntrospectOpts { } impl IntrospectOpts { - pub async fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! + pub async fn exec_and_watch(&self, exec_fn: F, output_opts: &OutputOpts) -> ! where - F: Fn() -> impl Future>, + F: Fn() -> G, + G: Future>, { let mut last_result = None; loop { From 91c8b4d395598e42518c5f842208c1d48e4d0522 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 10:16:09 +0100 Subject: [PATCH 19/68] fix issues --- Cargo.toml | 1 - crates/rover-client/Cargo.toml | 2 +- crates/rover-client/src/blocking/client.rs | 14 +++----- src/command/dev/protocol/leader.rs | 34 ++++++++++--------- src/command/dev/router/runner.rs | 39 ++++++++++++++++++++-- 5 files changed, 61 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 001d16502..36d2a5fc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,7 +188,6 @@ uuid = { workspace = true } url = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["rt", "macros"] } futures.workspace = true -backoff = { workspace = true, features = ["tokio"] } [dev-dependencies] assert_cmd = { workspace = true } diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 0a531bc31..9ec98f0da 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -12,7 +12,7 @@ ariadne = { workspace = true } apollo-federation-types = { workspace = true } apollo-parser = { workspace = true } apollo-encoder = { workspace = true } -backoff = { workspace = true } +backoff = { workspace = true, features = ["tokio", "futures"] } chrono = { workspace = true, features = ["serde"] } git-url-parse = { workspace = true } git2 = { workspace = true, features = [ diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index a19193196..eacf67b23 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -159,16 +159,12 @@ impl GraphQLClient { ..Default::default() }; - retry(backoff_strategy, graphql_operation).map_err(|e| match e { - BackoffError::Permanent(reqwest_error) - | BackoffError::Transient { - err: reqwest_error, - retry_after: _, - } => RoverClientError::SendRequest { - source: reqwest_error, + retry(backoff_strategy, graphql_operation) + .await + .map_err(|e| RoverClientError::SendRequest { + source: e, endpoint_kind, - }, - }) + }) } else { graphql_operation().await.map_err(|e| match e { BackoffError::Permanent(reqwest_error) diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index a004611b9..1a6ae6311 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -37,7 +37,7 @@ pub struct LeaderSession { subgraphs: HashMap, ipc_socket_addr: String, compose_runner: ComposeRunner, - router_runner: RouterRunner, + router_runner: Option, follower_channel: FollowerChannel, leader_channel: LeaderChannel, federation_version: FederationVersion, @@ -126,7 +126,7 @@ impl LeaderSession { subgraphs: HashMap::new(), ipc_socket_addr, compose_runner, - router_runner, + router_runner: Some(router_runner), follower_channel, leader_channel, federation_version, @@ -306,8 +306,10 @@ impl LeaderSession { .run(&mut self.supergraph_config()) .and_then(|maybe_new_schema| async { if maybe_new_schema.is_some() { - if let Err(err) = self.router_runner.spawn().await { - return Err(err.to_string()); + if let Some(runner) = self.router_runner.as_mut() { + if let Err(err) = runner.spawn().await { + return Err(err.to_string()); + } } } Ok(maybe_new_schema) @@ -316,11 +318,9 @@ impl LeaderSession { { Ok(res) => Ok(res), Err(e) => { - let _ = self - .router_runner - .kill() - .await - .map_err(log_err_and_continue); + if let Some(runner) = self.router_runner.as_mut() { + let _ = runner.kill().await.map_err(log_err_and_continue); + } Err(e) } } @@ -369,13 +369,15 @@ impl LeaderSession { /// Shuts the router down, removes the socket file, and exits the process. pub async fn shutdown(&mut self) { - let _ = self - .router_runner - .kill() - .await - .map_err(log_err_and_continue); - let _ = std::fs::remove_file(&self.ipc_socket_addr); - std::process::exit(1) + let router_runner = self.router_runner.take(); + let ipc_socket_addr = self.ipc_socket_addr.clone(); + tokio::task::spawn(async move { + if let Some(mut runner) = router_runner { + let _ = runner.kill().await.map_err(log_err_and_continue); + } + let _ = std::fs::remove_file(&ipc_socket_addr); + std::process::exit(1) + }); } /// Handles a follower message by updating the internal subgraph representation if needed, diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index 5cb082945..acd0e5b11 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -259,7 +259,42 @@ impl RouterRunner { impl Drop for RouterRunner { fn drop(&mut self) { - //let _ = self.kill().map_err(log_err_and_continue); - todo!() + let router_handle = self.router_handle.take(); + let client_config = self.client_config.clone(); + let router_socket_addr = self.router_socket_addr; + // copying the kill procedure here to emulate an async drop + tokio::task::spawn(async move { + if router_handle.is_some() { + tracing::info!("killing the router"); + if let Ok(client) = client_config.get_reqwest_client() { + let mut ready = true; + let now = Instant::now(); + let seconds = 5; + while ready && now.elapsed() < Duration::from_secs(seconds) { + let _ = client + .get(format!( + "http://{}/?query={{__typename}}", + &router_socket_addr + )) + .header("Content-Type", "application/json") + .send() + .await + .and_then(|r| r.error_for_status()) + .map_err(|_| { + ready = false; + }); + std::thread::sleep(Duration::from_millis(250)); + } + + if !ready { + tracing::info!("router stopped successfully"); + } else { + log_err_and_continue(RoverError::new(anyhow!( + "the router was unable to stop", + ))); + } + } + } + }); } } From bfd326041b21180314b9f433b1db4a57af6ae581 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 11:04:01 +0100 Subject: [PATCH 20/68] lint --- crates/rover-client/Cargo.toml | 12 ++++----- crates/rover-client/build.rs | 6 ++--- .../operations/graph/check_workflow/runner.rs | 4 ++- .../src/operations/graph/delete/runner.rs | 7 ++--- .../src/operations/graph/lint/runner.rs | 26 +++++++++++------- .../persisted_queries/name/runner.rs | 4 ++- .../persisted_queries/publish/runner.rs | 4 ++- .../src/operations/subgraph/check/runner.rs | 3 ++- .../subgraph/check_workflow/runner.rs | 4 ++- .../src/operations/subgraph/fetch/runner.rs | 3 ++- .../operations/subgraph/introspect/runner.rs | 20 +++++++------- .../src/operations/subgraph/lint/runner.rs | 6 +++-- .../src/operations/subgraph/publish/runner.rs | 6 +++-- crates/sputnik/src/session.rs | 3 ++- src/cli.rs | 2 +- src/command/dev/compose.rs | 27 ++++++++++++------- src/command/dev/introspect.rs | 6 +++-- src/command/dev/router/command.rs | 3 +-- src/command/dev/watcher.rs | 8 ++++-- src/command/graph/introspect.rs | 3 ++- src/command/graph/mod.rs | 4 ++- src/command/install/plugin.rs | 16 +++++------ src/command/persisted_queries/publish.rs | 3 ++- src/command/readme/publish.rs | 3 ++- src/command/subgraph/check.rs | 6 +++-- src/command/subgraph/delete.rs | 6 +++-- src/command/subgraph/introspect.rs | 3 ++- src/command/subgraph/lint.rs | 3 ++- src/command/subgraph/mod.rs | 4 ++- src/command/supergraph/compose/do_compose.rs | 7 +++-- src/command/supergraph/resolve_config.rs | 3 +-- src/options/template.rs | 3 ++- xtask/Cargo.toml | 2 +- xtask/src/commands/prep/mod.rs | 4 +-- xtask/src/commands/prep/templates_schema.rs | 9 ++++--- xtask/src/main.rs | 9 ++++--- 36 files changed, 145 insertions(+), 97 deletions(-) diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 9ec98f0da..21c2f7957 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -43,13 +43,13 @@ tokio = { workspace = true, features = ["rt", "macros"] } [build-dependencies] anyhow = { workspace = true } camino = { workspace = true } -#rover-std = { workspace = true } +rover-std = { workspace = true } serde_json = { workspace = true } -#reqwest = { workspace = true, features = [ -# "json", -# "blocking", -# "native-tls-vendored", -#] } +reqwest = { workspace = true, features = [ + "json", + "blocking", + "native-tls-vendored", +] } [dev-dependencies] indoc = { workspace = true} diff --git a/crates/rover-client/build.rs b/crates/rover-client/build.rs index 0f79309cb..ff9861a59 100644 --- a/crates/rover-client/build.rs +++ b/crates/rover-client/build.rs @@ -1,9 +1,9 @@ use anyhow::{Context, Result}; -//use rover_std::Fs; +use rover_std::Fs; fn main() -> Result<()> { println!("cargo:rerun-if-changed=.schema/schema.graphql"); - //Fs::read_file(".schema/schema.graphql") - // .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; + Fs::read_file(".schema/schema.graphql") + .context("no schema found at ./.schema/schema.graphql, which is needed to generate types for Rover's GraphQL queries. You should run `cargo xtask prep --schema-only` to update the schema before building.")?; Ok(()) } diff --git a/crates/rover-client/src/operations/graph/check_workflow/runner.rs b/crates/rover-client/src/operations/graph/check_workflow/runner.rs index bc754d09b..aec6e73a2 100644 --- a/crates/rover-client/src/operations/graph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/graph/check_workflow/runner.rs @@ -45,7 +45,9 @@ pub async fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()).await; + let result = client + .post::(input.clone().into()) + .await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/graph/delete/runner.rs b/crates/rover-client/src/operations/graph/delete/runner.rs index 442c94ec3..82cebf5f9 100644 --- a/crates/rover-client/src/operations/graph/delete/runner.rs +++ b/crates/rover-client/src/operations/graph/delete/runner.rs @@ -27,16 +27,13 @@ pub async fn run(input: GraphDeleteInput, client: &StudioClient) -> Result<(), R Ok(data) => data, Err(e) => { if e.to_string().contains("Variant not found") { - if let Err(no_variant_err) = variant::run( + variant::run( VariantListInput { graph_ref: graph_ref.clone(), }, client, ) - .await - { - return Err(no_variant_err); - } + .await?; } return Err(e); } diff --git a/crates/rover-client/src/operations/graph/lint/runner.rs b/crates/rover-client/src/operations/graph/lint/runner.rs index d46f99d1f..ab69cfaed 100644 --- a/crates/rover-client/src/operations/graph/lint/runner.rs +++ b/crates/rover-client/src/operations/graph/lint/runner.rs @@ -27,7 +27,10 @@ pub(crate) struct LintGraphMutation; /// The main function to be used from this module. /// This function takes a proposed schema and validates it against a published /// schema. -pub async fn run(input: LintGraphInput, client: &StudioClient) -> Result { +pub async fn run( + input: LintGraphInput, + client: &StudioClient, +) -> Result { let graph_ref = input.graph_ref.clone(); let base_schema = if input.ignore_existing { @@ -36,20 +39,23 @@ pub async fn run(input: LintGraphInput, client: &StudioClient) -> Result( - LintGraphMutationInput { - graph_ref, - proposed_schema: input.proposed_schema.clone(), - base_schema, - } - .into(), - ).await?; + let data = client + .post::( + LintGraphMutationInput { + graph_ref, + proposed_schema: input.proposed_schema.clone(), + base_schema, + } + .into(), + ) + .await?; get_lint_response_from_result( data, diff --git a/crates/rover-client/src/operations/persisted_queries/name/runner.rs b/crates/rover-client/src/operations/persisted_queries/name/runner.rs index 806537a6d..69792c395 100644 --- a/crates/rover-client/src/operations/persisted_queries/name/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/name/runner.rs @@ -22,7 +22,9 @@ pub async fn run( ) -> Result { let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); - let data = client.post::(input.into()).await?; + let data = client + .post::(input.into()) + .await?; build_response(data, graph_id, list_id) } diff --git a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs index e13af29ca..774eb6f09 100644 --- a/crates/rover-client/src/operations/persisted_queries/publish/runner.rs +++ b/crates/rover-client/src/operations/persisted_queries/publish/runner.rs @@ -26,7 +26,9 @@ pub async fn run( let graph_id = input.graph_id.clone(); let list_id = input.list_id.clone(); let total_operations = input.operation_manifest.operations.len(); - let data = client.post::(input.into()).await?; + let data = client + .post::(input.into()) + .await?; build_response(data, graph_id, list_id, total_operations) } diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index 69cc59f65..cdc2329bb 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -38,7 +38,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, diff --git a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs index 20a439738..d768e5525 100644 --- a/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check_workflow/runner.rs @@ -52,7 +52,9 @@ pub async fn run( let mut url: Option = None; let now = Instant::now(); loop { - let result = client.post::(input.clone().into()).await; + let result = client + .post::(input.clone().into()) + .await; match result { Ok(data) => { let graph = data.clone().graph.ok_or(RoverClientError::GraphNotFound { diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/operations/subgraph/fetch/runner.rs index fdd3b38d7..522c20776 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch/runner.rs @@ -31,7 +31,8 @@ pub async fn run( graph_ref: input.graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref: input.graph_ref, diff --git a/crates/rover-client/src/operations/subgraph/introspect/runner.rs b/crates/rover-client/src/operations/subgraph/introspect/runner.rs index ac3d38eee..95024a1e9 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/runner.rs +++ b/crates/rover-client/src/operations/subgraph/introspect/runner.rs @@ -30,17 +30,17 @@ pub async fn run( ); } let response_data = if should_retry { - client.post::( - input.into(), - &mut header_map, - EndpointKind::Customer, - ).await + client + .post::(input.into(), &mut header_map, EndpointKind::Customer) + .await } else { - client.post_no_retry::( - input.into(), - &mut header_map, - EndpointKind::Customer, - ).await + client + .post_no_retry::( + input.into(), + &mut header_map, + EndpointKind::Customer, + ) + .await }; match response_data { diff --git a/crates/rover-client/src/operations/subgraph/lint/runner.rs b/crates/rover-client/src/operations/subgraph/lint/runner.rs index 330b2ecea..22f40d0c3 100644 --- a/crates/rover-client/src/operations/subgraph/lint/runner.rs +++ b/crates/rover-client/src/operations/subgraph/lint/runner.rs @@ -40,7 +40,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { graph_ref, @@ -55,7 +56,8 @@ pub async fn run( subgraph_name: input.subgraph_name, }, client, - ).await?; + ) + .await?; Some(fetch_response.sdl.contents) } else { None diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index d4ba95d55..bd55d825b 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -44,7 +44,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await + ) + .await .is_ok(); if variant_exists { @@ -54,7 +55,8 @@ pub async fn run( graph_ref: graph_ref.clone(), }, client, - ).await?; + ) + .await?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index e24bc4e3b..1be43a3d7 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -145,7 +145,8 @@ impl Session { .header("User-Agent", &self.reporting_info.user_agent) .header("Content-Type", "application/json") .timeout(timeout) - .send().await?; + .send() + .await?; } Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index 8e2957adb..686d69b60 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -20,7 +20,7 @@ use rover_client::shared::GitContext; use sputnik::Session; use timber::Level; -use std::{io, process, thread}; +use std::{io, process}; #[derive(Debug, Serialize, Parser)] #[command( diff --git a/src/command/dev/compose.rs b/src/command/dev/compose.rs index 442f6d2b2..489cea96c 100644 --- a/src/command/dev/compose.rs +++ b/src/command/dev/compose.rs @@ -46,11 +46,14 @@ impl ComposeRunner { if let Some(plugin_exe) = &self.plugin_exe { Ok(plugin_exe.clone()) } else { - let plugin_exe = self.compose.maybe_install_supergraph( - self.override_install_path.clone(), - self.client_config.clone(), - federation_version, - ).await?; + let plugin_exe = self + .compose + .maybe_install_supergraph( + self.override_install_path.clone(), + self.client_config.clone(), + federation_version, + ) + .await?; self.plugin_exe = Some(plugin_exe.clone()); Ok(plugin_exe) } @@ -61,11 +64,15 @@ impl ComposeRunner { supergraph_config: &mut SupergraphConfig, ) -> std::result::Result, String> { let prev_state = self.composition_state(); - self.composition_state = Some(self.compose.exec( - self.override_install_path.clone(), - self.client_config.clone(), - supergraph_config, - ).await); + self.composition_state = Some( + self.compose + .exec( + self.override_install_path.clone(), + self.client_config.clone(), + supergraph_config, + ) + .await, + ); let new_state = self.composition_state(); match (prev_state, new_state) { diff --git a/src/command/dev/introspect.rs b/src/command/dev/introspect.rs index ae371b3cd..888db124d 100644 --- a/src/command/dev/introspect.rs +++ b/src/command/dev/introspect.rs @@ -116,7 +116,8 @@ impl SubgraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false).await + .exec(&self.client, false) + .await } } @@ -140,6 +141,7 @@ impl GraphIntrospectRunner { watch: false, }, } - .exec(&self.client, false).await + .exec(&self.client, false) + .await } } diff --git a/src/command/dev/router/command.rs b/src/command/dev/router/command.rs index 594a93eea..fadd40327 100644 --- a/src/command/dev/router/command.rs +++ b/src/command/dev/router/command.rs @@ -52,7 +52,7 @@ impl BackgroundTask { if let Ok(apollo_graph_ref) = var("APOLLO_GRAPH_REF") { command.env("APOLLO_GRAPH_REF", apollo_graph_ref); - if let Some(client) = client_config + if let Ok(client) = client_config .get_authenticated_client(profile_opt) .map_err(|err| { eprintln!( @@ -61,7 +61,6 @@ impl BackgroundTask { Emoji::Warn ); }) - .ok() { if let Some(api_key) = who_am_i::run(ConfigWhoAmIInput {}, &client).await.map_or_else(|err| { eprintln!("{} Could not determine the type of configured credentials, \ diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index fc2c768e5..310e1b55a 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -90,7 +90,8 @@ impl SubgraphSchemaWatcher { subgraph_name: graphos_subgraph_name.clone(), }, client, - ).await + ) + .await .map_err(RoverError::from)?; let routing_url = match (routing_url, response.sdl.r#type) { (Some(routing_url), _) => routing_url, @@ -178,7 +179,10 @@ impl SubgraphSchemaWatcher { Ok((subgraph_definition, refresher)) } - async fn update_subgraph(&mut self, last_message: Option<&String>) -> RoverResult> { + async fn update_subgraph( + &mut self, + last_message: Option<&String>, + ) -> RoverResult> { let print_error = |e: RoverError| { let _ = e.print(); }; diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index 513d461ff..74726f0dc 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -49,6 +49,7 @@ impl Introspect { pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts).await + .exec_and_watch(|| self.exec(client, false), output_opts) + .await } } diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 34f5a08db..bee04a01b 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -62,7 +62,9 @@ impl Graph { Command::Lint(command) => command.run(client_config).await, Command::Publish(command) => command.run(client_config, git_context).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts).await + command + .run(client_config.get_reqwest_client()?, output_opts) + .await } } } diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index 7f86a7ffb..b1a03d7ff 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -244,13 +244,12 @@ impl PluginInstaller { ) }) } else { - self.install_latest_major(plugin).await? - .ok_or_else(|| { - could_not_install_plugin( - &plugin.get_name(), - major_version.to_string().as_str(), - ) - }) + self.install_latest_major(plugin).await?.ok_or_else(|| { + could_not_install_plugin( + &plugin.get_name(), + major_version.to_string().as_str(), + ) + }) } } }, @@ -262,7 +261,8 @@ impl PluginInstaller { self.find_existing_exact(plugin, &version)? .ok_or_else(|| skip_update_err(&plugin.get_name(), &version)) } else { - self.install_exact(plugin, &version).await? + self.install_exact(plugin, &version) + .await? .ok_or_else(|| could_not_install_plugin(&plugin.get_name(), &version)) } } diff --git a/src/command/persisted_queries/publish.rs b/src/command/persisted_queries/publish.rs index 5840deb5f..39c41f883 100644 --- a/src/command/persisted_queries/publish.rs +++ b/src/command/persisted_queries/publish.rs @@ -104,7 +104,8 @@ impl Publish { operation_manifest, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::PersistedQueriesPublishResponse(result)) } } diff --git a/src/command/readme/publish.rs b/src/command/readme/publish.rs index 67d1b86d3..f6f0552f5 100644 --- a/src/command/readme/publish.rs +++ b/src/command/readme/publish.rs @@ -44,7 +44,8 @@ impl Publish { readme: new_readme, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::ReadmePublishResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index 18c9bf191..1616bb486 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -61,7 +61,8 @@ impl Check { }, }, &client, - ).await?; + ) + .await?; if self.config.background { Ok(RoverOutput::AsyncCheckResponse(workflow_res)) } else { @@ -73,7 +74,8 @@ impl Check { }, self.subgraph.subgraph_name.clone(), &client, - ).await?; + ) + .await?; Ok(RoverOutput::CheckWorkflowResponse(check_res)) } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index 1e831ea29..a634be8ce 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -48,7 +48,8 @@ impl Delete { dry_run, }, &client, - ).await?; + ) + .await?; RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), @@ -74,7 +75,8 @@ impl Delete { dry_run, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::SubgraphDeleteResponse { graph_ref: self.graph.graph_ref.clone(), diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index 9b637e95c..d15bfd584 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -47,6 +47,7 @@ impl Introspect { pub async fn exec_and_watch(&self, client: &Client, output_opts: &OutputOpts) -> ! { self.opts - .exec_and_watch(|| self.exec(client, false), output_opts).await + .exec_and_watch(|| self.exec(client, false), output_opts) + .await } } diff --git a/src/command/subgraph/lint.rs b/src/command/subgraph/lint.rs index 0291883e8..e562fadab 100644 --- a/src/command/subgraph/lint.rs +++ b/src/command/subgraph/lint.rs @@ -43,7 +43,8 @@ impl Lint { ignore_existing: self.lint.ignore_existing_lint_violations, }, &client, - ).await?; + ) + .await?; Ok(RoverOutput::LintResponse(lint_result)) } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index e56e9e734..8a3696cb4 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -64,7 +64,9 @@ impl Subgraph { } Command::Delete(command) => command.run(client_config).await, Command::Introspect(command) => { - command.run(client_config.get_reqwest_client()?, output_opts).await + command + .run(client_config.get_reqwest_client()?, output_opts) + .await } Command::Fetch(command) => command.run(client_config).await, Command::Lint(command) => command.run(client_config).await, diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 216424a47..97f1d0345 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -87,7 +87,8 @@ impl Compose { &self.opts.profile, ) .await?; - self.compose(override_install_path, client_config, &mut supergraph_config).await + self.compose(override_install_path, client_config, &mut supergraph_config) + .await } pub async fn compose( @@ -96,7 +97,9 @@ impl Compose { client_config: StudioClientConfig, supergraph_config: &mut SupergraphConfig, ) -> RoverResult { - let output = self.exec(override_install_path, client_config, supergraph_config).await?; + let output = self + .exec(override_install_path, client_config, supergraph_config) + .await?; Ok(RoverOutput::CompositionResult(output)) } diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index e0845f0e2..7d1612483 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -4,7 +4,6 @@ use apollo_federation_types::{ config::{FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig}, }; use apollo_parser::{ast, Parser}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rover_std::{Fs, Style}; use std::str::FromStr; @@ -140,7 +139,7 @@ pub(crate) async fn resolve_supergraph_yaml( // obtain SDL and add it to subgraph_definition. fetch::run( SubgraphFetchInput { - graph_ref: GraphRef::from_str(&graph_ref)?, + graph_ref: GraphRef::from_str(graph_ref)?, subgraph_name: subgraph.clone(), }, &authenticated_client, diff --git a/src/options/template.rs b/src/options/template.rs index 14767fc74..f5f1ce6df 100644 --- a/src/options/template.rs +++ b/src/options/template.rs @@ -59,7 +59,8 @@ pub(crate) async fn extract_tarball( .send() .await? .error_for_status()? - .bytes().await?; + .bytes() + .await?; f.write_all(&response_bytes[..])?; f.sync_all()?; let f = std::fs::File::open(&tarball_path)?; diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 39eff406f..ec2085452 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -28,7 +28,7 @@ semver = { workspace = true } serde_json_traversal = { workspace = true } shell-candy = { workspace = true } tempfile = { workspace = true } -tokio = { workspace = true, features = ["rt"] } +tokio = { workspace = true, features = ["rt", "macros"] } tokio-stream = { workspace = true } uuid = { workspace = true, features = ["v4"] } which = { workspace = true } diff --git a/xtask/src/commands/prep/mod.rs b/xtask/src/commands/prep/mod.rs index 2ea0d9c5b..b92fb7194 100644 --- a/xtask/src/commands/prep/mod.rs +++ b/xtask/src/commands/prep/mod.rs @@ -19,10 +19,10 @@ pub struct Prep { } impl Prep { - pub fn run(&self) -> Result<()> { + pub async fn run(&self) -> Result<()> { if !self.offline { main_schema::update()?; - templates_schema::update()?; + templates_schema::update().await?; } if self.schema_only { diff --git a/xtask/src/commands/prep/templates_schema.rs b/xtask/src/commands/prep/templates_schema.rs index 88d3ba4bd..a7e30b68c 100644 --- a/xtask/src/commands/prep/templates_schema.rs +++ b/xtask/src/commands/prep/templates_schema.rs @@ -3,7 +3,7 @@ use std::process::Command; use anyhow::{anyhow, Result}; use camino::Utf8PathBuf; -use reqwest::blocking::Client; +use reqwest::Client; use rover_client::{ blocking::GraphQLClient, @@ -20,15 +20,15 @@ const QUERIES_PATH: &str = "./src/command/template/queries.graphql"; /// If the user is offline and the schema already exists in the file system, the script does nothing. /// /// The URL to fetch the schema can be overridden with the APOLLO_GRAPHQL_SCHEMA_URL environment variable. -pub fn update() -> Result<()> { - let sdl = introspect()?; +pub async fn update() -> Result<()> { + let sdl = introspect().await?; let schema_path = Utf8PathBuf::from(SCHEMA_PATH); Fs::write_file(schema_path, sdl)?; regenerate_queries() } -fn introspect() -> Result { +async fn introspect() -> Result { let graphql_endpoint = "https://rover.apollo.dev/templates"; crate::info!( "fetching the latest templates schema by introspecting {}...", @@ -42,6 +42,7 @@ fn introspect() -> Result { &graphql_client, false, ) + .await .map(|response| response.schema_sdl) .map_err(|err| err.into()) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f7589d05b..d36d9979b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -8,8 +8,9 @@ use anyhow::Result; use clap::Parser; use console::style; -fn main() -> Result<()> { - Xtask::parse().run() +#[tokio::main] +async fn main() -> Result<()> { + Xtask::parse().run().await } #[derive(Debug, Parser)] @@ -50,7 +51,7 @@ pub enum Command { } impl Xtask { - pub fn run(&self) -> Result<()> { + pub async fn run(&self) -> Result<()> { match &self.command { Command::Docs(command) => command.run(), Command::Dist(command) => command.run(), @@ -58,7 +59,7 @@ impl Xtask { Command::UnitTest(command) => command.run(), Command::IntegrationTest(command) => command.run(), Command::Test(command) => command.run(), - Command::Prep(command) => command.run(), + Command::Prep(command) => command.run().await, Command::Package(command) => command.run(), }?; eprintln!("{}", style("Success!").green().bold()); From 5f62b5b21de4a428b7c2de534d277a180a158207 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 12:29:37 +0100 Subject: [PATCH 21/68] use futures aware channel --- src/command/dev/do_dev.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 353360a42..edea38335 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Context}; use camino::Utf8PathBuf; +use futures::channel::mpsc::channel; +use futures::stream::StreamExt; use rover_std::Emoji; use super::protocol::{FollowerChannel, FollowerMessenger, LeaderChannel, LeaderSession}; @@ -10,8 +12,6 @@ use crate::command::dev::protocol::FollowerMessage; use crate::utils::client::StudioClientConfig; use crate::{RoverError, RoverOutput, RoverResult}; -use crossbeam_channel::bounded as sync_channel; - pub fn log_err_and_continue(err: RoverError) -> RoverError { let _ = err.print(); err @@ -52,7 +52,7 @@ impl Dev { .await? { eprintln!("{0}Do not run this command in production! {0}It is intended for local development.", Emoji::Warn); - let (ready_sender, ready_receiver) = sync_channel(1); + let (ready_sender, mut ready_receiver) = channel(1); let follower_messenger = FollowerMessenger::from_main_session( follower_channel.clone().sender, leader_channel.receiver, @@ -77,14 +77,14 @@ impl Dev { .unwrap(); }); - let subgraph_watcher_handle = tokio::task::spawn(async move { + let subgraph_watcher_handle = tokio::task::spawn(async move { let _ = leader_session .listen_for_all_subgraph_updates(ready_sender) .await .map_err(log_err_and_continue); }); - ready_receiver.recv().unwrap(); + ready_receiver.next().await.unwrap(); let subgraph_watchers = self .opts From f7aaf690fb237a8e62c0f6d64c2c6e30e721afcd Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 12:32:31 +0100 Subject: [PATCH 22/68] fix --- src/utils/client.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/utils/client.rs b/src/utils/client.rs index b12fb1c3d..26a9ef386 100644 --- a/src/utils/client.rs +++ b/src/utils/client.rs @@ -57,12 +57,17 @@ impl ClientBuilder { } pub(crate) fn build(self) -> Result { - let client = Client::builder() + let mut builder = Client::builder() .gzip(true) .brotli(true) .danger_accept_invalid_certs(self.accept_invalid_certs) - .danger_accept_invalid_hostnames(self.accept_invalid_hostnames) - .timeout(self.timeout.unwrap()) + .danger_accept_invalid_hostnames(self.accept_invalid_hostnames); + + if let Some(timeout) = self.timeout { + builder = builder.timeout(timeout); + } + + let client = builder .user_agent(format!("{}/{}", PKG_NAME, PKG_VERSION)) .build()?; From f5de9e03ccc6936fe08ae992f34175bda6982105 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 12:48:09 +0100 Subject: [PATCH 23/68] sleep --- src/command/dev/do_dev.rs | 8 -------- src/command/dev/router/runner.rs | 4 ++-- src/command/dev/watcher.rs | 2 +- src/options/introspect.rs | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index edea38335..02faadf8b 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -33,14 +33,6 @@ impl Dev { let leader_channel = LeaderChannel::new(); let follower_channel = FollowerChannel::new(); - // Build a Rayon Thread pool - let tp = rayon::ThreadPoolBuilder::new() - .num_threads(1) - .thread_name(|idx| format!("router-do-dev-{idx}")) - .build() - .map_err(|err| { - RoverError::new(anyhow!("could not create router do dev thread pool: {err}",)) - })?; if let Some(mut leader_session) = LeaderSession::new( override_install_path, &client_config, diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index acd0e5b11..0d8d3ffbb 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -116,7 +116,7 @@ impl RouterRunner { .map(|_| { ready = true; }); - std::thread::sleep(Duration::from_millis(250)); + tokio::time::sleep(Duration::from_millis(250)).await; } if ready { @@ -283,7 +283,7 @@ impl Drop for RouterRunner { .map_err(|_| { ready = false; }); - std::thread::sleep(Duration::from_millis(250)); + tokio::time::sleep(Duration::from_millis(250)).await; } if !ready { diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index 310e1b55a..28fed2510 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -246,7 +246,7 @@ impl SubgraphSchemaWatcher { ); loop { last_message = self.update_subgraph(last_message.as_ref()).await?; - std::thread::sleep(std::time::Duration::from_secs(polling_interval)); + tokio::time::sleep(std::time::Duration::from_secs(polling_interval)).await; } } SubgraphSchemaWatcherKind::File(path) => { diff --git a/src/options/introspect.rs b/src/options/introspect.rs index 93218f3b1..dacfd9e26 100644 --- a/src/options/introspect.rs +++ b/src/options/introspect.rs @@ -67,7 +67,7 @@ impl IntrospectOpts { last_result = Some(e); } } - std::thread::sleep(std::time::Duration::from_secs(1)) + tokio::time::sleep(std::time::Duration::from_secs(1)).await } } } From 16b0aa2a9f06a48524dd98ad8cabc40a5cc1d84e Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 13:53:12 +0100 Subject: [PATCH 24/68] lint --- xtask/src/commands/lint.rs | 10 +++---- xtask/src/main.rs | 2 +- xtask/src/tools/lychee.rs | 55 +++++++++++++++++--------------------- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/xtask/src/commands/lint.rs b/xtask/src/commands/lint.rs index a7dc50cdb..28d089f6e 100644 --- a/xtask/src/commands/lint.rs +++ b/xtask/src/commands/lint.rs @@ -10,20 +10,20 @@ use crate::tools::{CargoRunner, NpmRunner}; pub struct Lint {} impl Lint { - pub fn run(&self) -> Result<()> { + pub async fn run(&self) -> Result<()> { CargoRunner::new()?.lint()?; NpmRunner::new()?.lint()?; - lint_links() + lint_links().await } } #[cfg(not(windows))] -fn lint_links() -> Result<()> { - LycheeRunner::new()?.lint() +async fn lint_links() -> Result<()> { + LycheeRunner::new()?.lint().await } #[cfg(windows)] -fn lint_links() -> Result<()> { +async fn lint_links() -> Result<()> { eprintln!("Skipping the lint checker."); Ok(()) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d36d9979b..0fd80353b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -55,7 +55,7 @@ impl Xtask { match &self.command { Command::Docs(command) => command.run(), Command::Dist(command) => command.run(), - Command::Lint(command) => command.run(), + Command::Lint(command) => command.run().await, Command::UnitTest(command) => command.run(), Command::IntegrationTest(command) => command.run(), Command::Test(command) => command.run(), diff --git a/xtask/src/tools/lychee.rs b/xtask/src/tools/lychee.rs index 179869560..02795cfad 100644 --- a/xtask/src/tools/lychee.rs +++ b/xtask/src/tools/lychee.rs @@ -8,7 +8,6 @@ use lychee_lib::{ }; use reqwest::StatusCode; use std::{collections::HashSet, fs, path::PathBuf, time::Duration}; -use tokio::runtime::Runtime; use tokio_stream::StreamExt; pub(crate) struct LycheeRunner { @@ -34,7 +33,7 @@ impl LycheeRunner { Ok(Self { client }) } - pub(crate) fn lint(&self) -> Result<()> { + pub(crate) async fn lint(&self) -> Result<()> { crate::info!("Checking HTTP links in repository"); let inputs: Vec = get_md_files() @@ -46,45 +45,39 @@ impl LycheeRunner { }) .collect(); - let rt = Runtime::new()?; - let lychee_client = self.client.clone(); - rt.block_on(async move { - let links: Vec = Collector::new(None) - .collect_links(inputs) - .await - .collect::>>() - .await?; + let links: Vec = Collector::new(None) + .collect_links(inputs) + .await + .collect::>>() + .await?; - let failed_link_futures: Vec<_> = links - .into_iter() - .map(|link| tokio::spawn(get_failed_request(lychee_client.clone(), link))) - .collect(); + let failed_link_futures: Vec<_> = links + .into_iter() + .map(|link| tokio::spawn(get_failed_request(lychee_client.clone(), link))) + .collect(); - let links_size = failed_link_futures.len(); + let links_size = failed_link_futures.len(); - let mut failed_checks = Vec::with_capacity(links_size); - for f in failed_link_futures.into_iter() { - if let Some(failure) = f.await.expect("unexpected error while processing links") { - failed_checks.push(failure); - } + let mut failed_checks = Vec::with_capacity(links_size); + for f in failed_link_futures.into_iter() { + if let Some(failure) = f.await.expect("unexpected error while processing links") { + failed_checks.push(failure); } + } - crate::info!("{} links checked.", links_size); - - if !failed_checks.is_empty() { - for failed_check in failed_checks { - crate::info!("❌ {}", failed_check.as_str()); - } + crate::info!("{} links checked.", links_size); - Err(anyhow!("Some links in markdown documentation are down.")) - } else { - Ok(()) + if !failed_checks.is_empty() { + for failed_check in failed_checks { + crate::info!("❌ {}", failed_check.as_str()); } - })?; - Ok(()) + Err(anyhow!("Some links in markdown documentation are down.")) + } else { + Ok(()) + } } } From 1d9b26cd0e4be8eaf3fa03ac9fa09c95ad0457be Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:18:59 +0100 Subject: [PATCH 25/68] backtrace --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e3014b717..e997d96c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -460,6 +460,8 @@ commands: steps: - run: command: cargo xtask << parameters.command >> << parameters.options >> + environment: + RUST_BACKTRACE: 1 - unless: condition: From d9250e864a07be0c2321e9879a8b8cb32819fc88 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:31:46 +0100 Subject: [PATCH 26/68] fixes --- installers/binstall/src/install.rs | 11 +++++++---- src/command/install/plugin.rs | 3 ++- src/command/supergraph/compose/no_compose.rs | 2 +- src/command/template/list.rs | 2 +- src/command/template/templates.rs | 17 +++++++++-------- src/command/template/use.rs | 4 ++-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index 7143e779e..3a50ad9a2 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -48,7 +48,9 @@ impl Installer { client: &reqwest::Client, is_latest: bool, ) -> Result, InstallerError> { - let version = self.get_plugin_version(plugin_tarball_url, is_latest)?; + let version = self + .get_plugin_version(plugin_tarball_url, is_latest) + .await?; let bin_dir_path = self.get_bin_dir_path()?; if !bin_dir_path.exists() { @@ -76,18 +78,19 @@ impl Installer { Ok(Some(plugin_bin_destination)) } - pub fn get_plugin_version( + pub async fn get_plugin_version( &self, plugin_tarball_url: &str, is_latest: bool, ) -> Result { if is_latest { - let no_redirect_client = reqwest::blocking::Client::builder() + let no_redirect_client = reqwest::Client::builder() .redirect(reqwest::redirect::Policy::none()) .build()?; let response = no_redirect_client .head(plugin_tarball_url) - .send()? + .send() + .await? .error_for_status()?; if let Some(version) = response.headers().get("x-version") { diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index b1a03d7ff..e5bc0e1d2 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -338,7 +338,8 @@ impl PluginInstaller { async fn install_latest_major(&self, plugin: &Plugin) -> RoverResult> { let latest_version = self .rover_installer - .get_plugin_version(&plugin.get_tarball_url()?, true)?; + .get_plugin_version(&plugin.get_tarball_url()?, true) + .await?; if let Ok(Some(exe)) = self.find_existing_exact(plugin, &latest_version) { tracing::debug!("{} exists, skipping install", &exe); Ok(Some(exe)) diff --git a/src/command/supergraph/compose/no_compose.rs b/src/command/supergraph/compose/no_compose.rs index 4946d9497..433097c30 100644 --- a/src/command/supergraph/compose/no_compose.rs +++ b/src/command/supergraph/compose/no_compose.rs @@ -21,7 +21,7 @@ pub struct Compose { } impl Compose { - pub fn run( + pub async fn run( &self, _override_install_path: Option, _client_config: StudioClientConfig, diff --git a/src/command/template/list.rs b/src/command/template/list.rs index 084fd43cd..fe764e462 100644 --- a/src/command/template/list.rs +++ b/src/command/template/list.rs @@ -14,7 +14,7 @@ pub struct List { impl List { pub fn run(&self) -> RoverResult { - let templates = list_templates(self.options.language.clone())?; + let templates = list_templates(self.options.language.clone()).await?; Ok(RoverOutput::TemplateList(templates)) } } diff --git a/src/command/template/templates.rs b/src/command/template/templates.rs index dabd5f7b2..4a3693738 100644 --- a/src/command/template/templates.rs +++ b/src/command/template/templates.rs @@ -3,7 +3,7 @@ use std::env; use anyhow::anyhow; use console::Term; use dialoguer::Select; -use reqwest::blocking::Client; +use reqwest::Client; use serde::de::DeserializeOwned; use serde::Serialize; @@ -18,13 +18,14 @@ use super::queries::{ list_templates_for_language::ListTemplatesForLanguageTemplates, *, }; -fn request(body: &Body) -> RoverResult { +async fn request(body: &Body) -> RoverResult { let uri = env::var("APOLLO_TEMPLATES_API") .unwrap_or_else(|_| "https://rover.apollo.dev/templates".to_string()); let resp = Client::new() .post(uri) .json(body) .send() + .await .map_err(|e| anyhow!("Could not reach templates server: {}", e))?; let response: Response = resp .json() @@ -35,34 +36,34 @@ fn request(body: &Body) -> RoverResult< } /// Get a template by ID -pub fn get_template(template_id: &str) -> RoverResult> { +pub async fn get_template(template_id: &str) -> RoverResult> { use super::queries::get_template_by_id::*; let query = GetTemplateById::build_query(Variables { id: template_id.to_string(), }); - let resp: ResponseData = request(&query)?; + let resp: ResponseData = request(&query).await?; Ok(resp.template) } -pub fn get_templates_for_language( +pub async fn get_templates_for_language( language: ProjectLanguage, ) -> RoverResult> { use super::queries::get_templates_for_language::*; let query = GetTemplatesForLanguage::build_query(Variables { language: Some(language.into()), }); - let resp: ResponseData = request(&query)?; + let resp: ResponseData = request(&query).await?; error_if_empty(resp.templates) } -pub fn list_templates( +pub async fn list_templates( language: Option, ) -> RoverResult> { use super::queries::list_templates_for_language::*; let query = ListTemplatesForLanguage::build_query(Variables { language: language.map(Into::into), }); - let resp: ResponseData = request(&query)?; + let resp: ResponseData = request(&query).await?; error_if_empty(resp.templates) } diff --git a/src/command/template/use.rs b/src/command/template/use.rs index 4e1d80b8c..cdeeeb9e2 100644 --- a/src/command/template/use.rs +++ b/src/command/template/use.rs @@ -38,7 +38,7 @@ impl Use { // find the template to extract let (template_id, download_url) = if let Some(template_id) = &self.template { // if they specify an ID, get it - let result = get_template(template_id)?; + let result = get_template(template_id).await?; if let Some(result) = result { (template_id.clone(), result.download_url) } else { @@ -51,7 +51,7 @@ impl Use { } else { // otherwise, ask them what language they want to use let project_language = self.options.get_or_prompt_language()?; - let templates = get_templates_for_language(project_language)?; + let templates = get_templates_for_language(project_language).await?; let template = selection_prompt(templates)?; (template.id, template.download_url) }; From 737473e16ff230c354b000cc028669d72336b6ff Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:41:07 +0100 Subject: [PATCH 27/68] fixes --- src/command/template/list.rs | 2 +- src/command/template/mod.rs | 2 +- src/command/template/templates.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/command/template/list.rs b/src/command/template/list.rs index fe764e462..0b0a907c5 100644 --- a/src/command/template/list.rs +++ b/src/command/template/list.rs @@ -13,7 +13,7 @@ pub struct List { } impl List { - pub fn run(&self) -> RoverResult { + pub async fn run(&self) -> RoverResult { let templates = list_templates(self.options.language.clone()).await?; Ok(RoverOutput::TemplateList(templates)) } diff --git a/src/command/template/mod.rs b/src/command/template/mod.rs index baacb9981..6a8501620 100644 --- a/src/command/template/mod.rs +++ b/src/command/template/mod.rs @@ -32,7 +32,7 @@ impl Template { pub(crate) async fn run(&self, client_config: StudioClientConfig) -> RoverResult { match &self.command { Command::Use(use_template) => use_template.run(client_config).await, - Command::List(list) => list.run(), + Command::List(list) => list.run().await, } } } diff --git a/src/command/template/templates.rs b/src/command/template/templates.rs index 4a3693738..6450339b8 100644 --- a/src/command/template/templates.rs +++ b/src/command/template/templates.rs @@ -28,7 +28,7 @@ async fn request(body: &Body) -> RoverR .await .map_err(|e| anyhow!("Could not reach templates server: {}", e))?; let response: Response = resp - .json() + .json().await .map_err(|e| anyhow!("Could not parse response from templates server: {}", e))?; response .data From 877fae79ce02662aa6c0871c28386ca05c914e56 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:49:17 +0100 Subject: [PATCH 28/68] fmt --- src/command/template/templates.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/command/template/templates.rs b/src/command/template/templates.rs index 6450339b8..f7bda0260 100644 --- a/src/command/template/templates.rs +++ b/src/command/template/templates.rs @@ -28,7 +28,8 @@ async fn request(body: &Body) -> RoverR .await .map_err(|e| anyhow!("Could not reach templates server: {}", e))?; let response: Response = resp - .json().await + .json() + .await .map_err(|e| anyhow!("Could not parse response from templates server: {}", e))?; response .data From 34d008ee17b09500922a57018ba89bf05a16006c Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 15:57:27 +0100 Subject: [PATCH 29/68] fix --- src/command/dev/no_dev.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command/dev/no_dev.rs b/src/command/dev/no_dev.rs index 0b3d88746..81cb46b09 100644 --- a/src/command/dev/no_dev.rs +++ b/src/command/dev/no_dev.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use camino::Utf8PathBuf; impl Dev { - pub fn run( + pub async fn run( &self, _override_install_path: Option, _client_config: StudioClientConfig, From 8b3eb9fe34aa3f5932bf42b0f9f74f3cb377dee1 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Fri, 15 Mar 2024 16:39:01 +0100 Subject: [PATCH 30/68] change back CI --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e997d96c4..e3014b717 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -460,8 +460,6 @@ commands: steps: - run: command: cargo xtask << parameters.command >> << parameters.options >> - environment: - RUST_BACKTRACE: 1 - unless: condition: From e01259b0dd02d65578842c3edfe1bfe6e28e1d3d Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Tue, 30 Apr 2024 15:25:47 -0400 Subject: [PATCH 31/68] fix(LeaderSesson): impl Drop --- Cargo.lock | 1 - src/command/dev/protocol/leader.rs | 11 +++++++++-- src/command/subgraph/publish.rs | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b45e2f8ff..19aca9edb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4143,7 +4143,6 @@ dependencies = [ "assert-json-diff", "assert_cmd", "assert_fs", - "backoff", "billboard", "binstall", "calm_io", diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index 1a6ae6311..511f33b0d 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -410,8 +410,15 @@ impl LeaderSession { impl Drop for LeaderSession { fn drop(&mut self) { - todo!(); - //self.shutdown().await; + let router_runner = self.router_runner.take(); + let socket_addr = self.ipc_socket_addr.clone(); + tokio::task::spawn(async move { + if let Some(mut runner) = router_runner { + let _ = runner.kill().await.map_err(log_err_and_continue); + } + let _ = std::fs::remove_file(&socket_addr); + std::process::exit(1) + }); } } diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index ac804130d..cc10fd00e 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -354,6 +354,7 @@ mod tests { &mut input, true, ) + .await .unwrap(); assert_eq!(result, Some("unix:///path/to/subgraph.sock".to_string())); From f46bb51d9cfee4cef3c89e396410e00dab73dc93 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 14:01:57 -0400 Subject: [PATCH 32/68] fix(errors, conflicts): merging in main --- Cargo.toml | 2 +- crates/sputnik/src/session.rs | 23 +++++++------ src/bin/rover.rs | 3 +- src/cli.rs | 11 ++++--- src/command/dev/do_dev.rs | 34 ++++++++------------ src/command/dev/protocol/leader.rs | 30 ++++++----------- src/command/dev/remote_subgraphs.rs | 5 +-- src/command/dev/schema.rs | 13 +++++--- src/command/dev/watcher.rs | 28 ++++------------ src/command/supergraph/compose/do_compose.rs | 2 -- src/command/supergraph/resolve_config.rs | 8 ++--- src/utils/client.rs | 2 +- src/utils/telemetry.rs | 2 +- 13 files changed, 65 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 876f964a5..8fefb2976 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,7 +189,7 @@ tracing = { workspace = true } which = { workspace = true } uuid = { workspace = true } url = { workspace = true, features = ["serde"] } -tokio = { workspace = true, features = ["rt", "macros"] } +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } futures.workspace = true [dev-dependencies] diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index 8862d3b04..4e04d189a 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -146,18 +146,17 @@ impl Session { return Ok(()); } - let body = serde_json::to_string(&self)?; - tracing::debug!("POSTing to {}", &self.reporting_info.endpoint); - tracing::debug!("{}", body); - self.client - .post(self.reporting_info.endpoint.clone()) - .body(body) - .header("User-Agent", &self.reporting_info.user_agent) - .header("Content-Type", "application/json") - .timeout(REPORT_TIMEOUT) - .send() - .await?; - } + let body = serde_json::to_string(&self)?; + tracing::debug!("POSTing to {}", &self.reporting_info.endpoint); + tracing::debug!("{}", body); + self.client + .post(self.reporting_info.endpoint.clone()) + .body(body) + .header("User-Agent", &self.reporting_info.user_agent) + .header("Content-Type", "application/json") + .timeout(REPORT_TIMEOUT) + .send() + .await?; Ok(()) } diff --git a/src/bin/rover.rs b/src/bin/rover.rs index 1f112834b..6ce9673ab 100644 --- a/src/bin/rover.rs +++ b/src/bin/rover.rs @@ -1,5 +1,6 @@ use robot_panic::setup_panic; use rover::cli::Rover; +use tokio::runtime::Runtime; #[calm_io::pipefail] fn main() -> Result<_, std::io::Error> { @@ -11,6 +12,6 @@ fn main() -> Result<_, std::io::Error> { repository: rover::PKG_REPOSITORY.into() }); - let rt = tokio::runtime::Runtime::new().unwrap(); + let rt = Runtime::new().expect("failed to start asynchronous runtime"); Ok(rt.block_on(Rover::run_from_args())) } diff --git a/src/cli.rs b/src/cli.rs index e44f01338..85c480a49 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,7 +21,7 @@ use sputnik::Session; use timber::Level; use std::fmt::Display; -use std::{io, process, thread}; +use std::{io, process}; #[derive(Debug, Serialize, Parser)] #[command( @@ -278,13 +278,14 @@ impl Rover { Ok(git_context) } - pub(crate) fn get_reqwest_client(&self) -> reqwest::Result { + // WARNING: I _think_ this should be an anyhow error (it gets converted to a sputnik error and + // there's no impl from a rovererror) + pub(crate) fn get_reqwest_client(&self) -> anyhow::Result { if let Some(client) = self.client.borrow() { Ok(client.clone()) } else { - self.client - .fill(self.get_reqwest_client_builder().build()?) - .ok(); + let client = self.get_reqwest_client_builder().build()?; + let _ = self.client.fill(client); self.get_reqwest_client() } } diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 302043ab8..f0193f795 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -3,8 +3,6 @@ use apollo_federation_types::config::FederationVersion; use camino::Utf8PathBuf; use futures::channel::mpsc::channel; use futures::stream::StreamExt; -use rover_std::Emoji; -use crossbeam_channel::bounded as sync_channel; use rover_std::{Emoji, Fs}; @@ -41,16 +39,19 @@ impl Dev { // Read in Remote subgraphs let remote_subgraphs = match &self.opts.supergraph_opts.graph_ref { - Some(graph_ref) => Some(RemoteSubgraphs::fetch( - &client_config.get_authenticated_client(&self.opts.plugin_opts.profile)?, - &self - .opts - .supergraph_opts - .federation_version - .clone() - .unwrap_or(FederationVersion::LatestFedTwo), - graph_ref, - )?), + Some(graph_ref) => Some( + RemoteSubgraphs::fetch( + &client_config.get_authenticated_client(&self.opts.plugin_opts.profile)?, + &self + .opts + .supergraph_opts + .federation_version + .clone() + .unwrap_or(FederationVersion::LatestFedTwo), + graph_ref, + ) + .await?, + ), None => None, }; @@ -76,15 +77,6 @@ impl Dev { None => supergraph_config, }; - // Build a Rayon Thread pool - let tp = rayon::ThreadPoolBuilder::new() - .num_threads(1) - .thread_name(|idx| format!("router-do-dev-{idx}")) - .build() - .map_err(|err| { - RoverError::new(anyhow!("could not create router do dev thread pool: {err}",)) - })?; - if let Some(mut leader_session) = LeaderSession::new( override_install_path, &client_config, diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index 405d14baf..8a388eaf6 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -13,6 +13,7 @@ use apollo_federation_types::{ }; use camino::Utf8PathBuf; use crossbeam_channel::{bounded, Receiver, Sender}; +use futures::TryFutureExt; use interprocess::local_socket::traits::{ListenerExt, Stream}; use interprocess::local_socket::ListenerOptions; use serde::{Deserialize, Serialize}; @@ -31,20 +32,6 @@ use crate::{ utils::client::StudioClientConfig, RoverError, RoverErrorSuggestion, RoverResult, PKG_VERSION, }; -use anyhow::{anyhow, Context}; -use apollo_federation_types::{ - build::SubgraphDefinition, - config::{FederationVersion, SupergraphConfig}, -}; -use camino::Utf8PathBuf; -use crossbeam_channel::{bounded, Receiver, Sender}; -use futures::TryFutureExt; -use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; -use rover_std::Emoji; -use semver::Version; -use serde::{Deserialize, Serialize}; - -use std::{collections::HashMap, fmt::Debug, io::BufReader, net::TcpListener}; use super::{ create_socket_name, @@ -148,7 +135,6 @@ impl LeaderSession { .maybe_install_supergraph(federation_version.clone()) .await?; - router_config_handler.start()?; Ok(Some(Self { @@ -381,7 +367,7 @@ impl LeaderSession { async fn compose(&mut self) -> CompositionResult { match self .compose_runner - .run(&mut self.supergraph_config()) + .run(&mut self.supergraph_config_internal_representation()) .and_then(|maybe_new_schema| async { if maybe_new_schema.is_some() { if let Some(runner) = self.router_runner.as_mut() { @@ -449,15 +435,16 @@ impl LeaderSession { self.subgraphs.keys().cloned().collect() } - /// Shuts the router down, removes the socket file, and exits the process. pub async fn shutdown(&mut self) { let router_runner = self.router_runner.take(); - let ipc_socket_addr = self.ipc_socket_addr.clone(); + // WARNING: this used to be ipc_socket_addr, but that changted to raw_socket_name; we need + // to validate that that functionality is the same + let raw_socket_name = self.raw_socket_name.clone(); tokio::task::spawn(async move { if let Some(mut runner) = router_runner { let _ = runner.kill().await.map_err(log_err_and_continue); } - let _ = std::fs::remove_file(&ipc_socket_addr); + let _ = std::fs::remove_file(&raw_socket_name); std::process::exit(1) }); } @@ -492,8 +479,11 @@ impl LeaderSession { impl Drop for LeaderSession { fn drop(&mut self) { + // WARNING: geal's branch had this as an awaited future for shutdown(), but we can't get + // that without marrking it as async, which isn't allowed, Drop can only be sync; not sure + // how to DRY this up let router_runner = self.router_runner.take(); - let socket_addr = self.ipc_socket_addr.clone(); + let socket_addr = self.raw_socket_name.clone(); tokio::task::spawn(async move { if let Some(mut runner) = router_runner { let _ = runner.kill().await.map_err(log_err_and_continue); diff --git a/src/command/dev/remote_subgraphs.rs b/src/command/dev/remote_subgraphs.rs index 1e585db74..8d4b4ed15 100644 --- a/src/command/dev/remote_subgraphs.rs +++ b/src/command/dev/remote_subgraphs.rs @@ -16,7 +16,7 @@ pub struct RemoteSubgraphs(SupergraphConfig); impl RemoteSubgraphs { /// Fetches [`RemoteSubgraphs`] from Studio - pub fn fetch( + pub async fn fetch( client: &StudioClient, federation_version: &FederationVersion, graph_ref: &GraphRef, @@ -26,7 +26,8 @@ impl RemoteSubgraphs { graph_ref: graph_ref.clone(), }, client, - )?; + ) + .await?; let subgraphs = subgraphs .subgraphs .iter() diff --git a/src/command/dev/schema.rs b/src/command/dev/schema.rs index 2bccfbf55..999791268 100644 --- a/src/command/dev/schema.rs +++ b/src/command/dev/schema.rs @@ -121,9 +121,9 @@ impl SupergraphOpts { .build()?; let mut studio_client: Option = None; - // WARNING: from here on I took the asynch branch's code; should be validcated against main + // WARNING: from here on I took the asynch branch's code; should be validated against main let mut res = Vec::new(); - for (yaml_subgraph_name, subgraph_config) in supergraph_config.into_iter() { + for (yaml_subgraph_name, subgraph_config) in supergraph_config.unwrap().into_iter() { let routing_url = subgraph_config .routing_url .map(|url_str| Url::parse(&url_str).map_err(RoverError::from)) @@ -133,21 +133,25 @@ impl SupergraphOpts { let routing_url = routing_url.ok_or_else(|| { anyhow!("`routing_url` must be set when using a local schema file") })?; + SubgraphSchemaWatcher::new_from_file_path( (yaml_subgraph_name, routing_url), file, follower_messenger.clone(), + subgraph_retries, ) } SchemaSource::SubgraphIntrospection { subgraph_url, introspection_headers, } => SubgraphSchemaWatcher::new_from_url( - (yaml_subgraph_name, subgraph_url), + (yaml_subgraph_name, subgraph_url.clone()), client.clone(), follower_messenger.clone(), polling_interval, introspection_headers, + subgraph_retries, + subgraph_url, ), SchemaSource::Sdl { sdl } => { let routing_url = routing_url.ok_or_else(|| { @@ -157,6 +161,7 @@ impl SupergraphOpts { (yaml_subgraph_name, routing_url), sdl, follower_messenger.clone(), + subgraph_retries, ) } SchemaSource::Subgraph { @@ -178,13 +183,13 @@ impl SupergraphOpts { yaml_subgraph_name, follower_messenger.clone(), studio_client, + subgraph_retries, ) .await } }; res.push(elem?); } - Ok(Some(res)) } } diff --git a/src/command/dev/watcher.rs b/src/command/dev/watcher.rs index 67a0ca88c..52b47feeb 100644 --- a/src/command/dev/watcher.rs +++ b/src/command/dev/watcher.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Context}; use apollo_federation_types::build::SubgraphDefinition; use camino::{Utf8Path, Utf8PathBuf}; use crossbeam_channel::unbounded; -use reqwest::blocking::Client; +use reqwest::Client; use url::Url; use rover_client::blocking::StudioClient; @@ -21,20 +21,6 @@ use crate::{ }, RoverError, RoverErrorSuggestion, RoverResult, }; -use anyhow::{anyhow, Context}; -use std::collections::HashMap; -use std::str::FromStr; - -use apollo_federation_types::build::SubgraphDefinition; -use camino::{Utf8Path, Utf8PathBuf}; -use crossbeam_channel::unbounded; -use reqwest::Client; -use rover_client::blocking::StudioClient; -use rover_client::operations::subgraph::fetch; -use rover_client::operations::subgraph::fetch::SubgraphFetchInput; -use rover_client::shared::GraphRef; -use rover_std::{Emoji, Fs}; -use url::Url; #[derive(Debug)] pub struct SubgraphSchemaWatcher { @@ -217,10 +203,6 @@ impl SubgraphSchemaWatcher { &mut self, last_message: Option<&String>, ) -> RoverResult> { - let print_error = |e: RoverError| { - let _ = e.print(); - }; - let maybe_update_message = match self.get_subgraph_definition_and_maybe_new_runner().await { Ok((subgraph_definition, maybe_new_refresher)) => { if let Some(new_refresher) = maybe_new_refresher { @@ -313,9 +295,11 @@ impl SubgraphSchemaWatcher { Fs::watch_file(watch_path, tx); loop { - rx.recv().unwrap_or_else(|_| { - panic!("an unexpected error occurred while watching {}", &path) - }); + match rx.recv() { + Ok(Ok(())) => (), + Ok(Err(err)) => return Err(anyhow::Error::from(err).into()), + Err(err) => return Err(anyhow::Error::from(err).into()), + } last_message = self.update_subgraph(last_message.as_ref()).await?; } } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 5210216e7..11252aad0 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -246,8 +246,6 @@ mod tests { #[rstest] #[tokio::test] async fn it_can_get_subgraph_definitions_from_fs() { - #[rstest] - fn it_can_get_subgraph_definitions_from_fs() { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 84db5715c..5c92ef9d2 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -5,17 +5,13 @@ use apollo_federation_types::{ build::{BuildError, BuildErrors, SubgraphDefinition}, config::{FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig}, }; -use apollo_parser::{ast, cst, Parser}; +use apollo_parser::{cst, Parser}; use rover_std::{Fs, Style}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use std::str::FromStr; - use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; use rover_client::operations::subgraph::introspect::{self, SubgraphIntrospectInput}; use rover_client::shared::GraphRef; use rover_client::{blocking::GraphQLClient, RoverClientError}; -use rover_std::{Fs, Style}; use crate::{ options::ProfileOpt, @@ -30,7 +26,7 @@ pub(crate) fn expand_supergraph_yaml(content: &str) -> RoverResult reqwest::Result { + pub(crate) fn get_reqwest_client(&self) -> Result { if let Some(client) = &self.client { Ok(client.clone()) } else { diff --git a/src/utils/telemetry.rs b/src/utils/telemetry.rs index 8830ca02e..61dddefea 100644 --- a/src/utils/telemetry.rs +++ b/src/utils/telemetry.rs @@ -116,7 +116,7 @@ impl Report for Rover { Ok(config.home.join("machine.txt")) } - fn client(&self) -> Result { + fn client(&self) -> anyhow::Result { self.get_reqwest_client().map_err(SputnikError::from) } } From d455082af5d6ab0f4944d7aeb17e2294f724552c Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 14:53:01 -0400 Subject: [PATCH 33/68] fix(xtask): clippy complaints --- xtask/src/main.rs | 4 ---- xtask/src/tools/lychee.rs | 44 ++++++++++++++------------------------- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 32fce561d..525963f5e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -8,10 +8,6 @@ pub(crate) mod target; pub(crate) mod tools; pub(crate) mod utils; -use anyhow::Result; -use clap::Parser; -use console::style; - #[tokio::main] async fn main() -> Result<()> { Xtask::parse().run().await diff --git a/xtask/src/tools/lychee.rs b/xtask/src/tools/lychee.rs index 751e39935..9bb0e30f2 100644 --- a/xtask/src/tools/lychee.rs +++ b/xtask/src/tools/lychee.rs @@ -2,12 +2,13 @@ use crate::utils::PKG_PROJECT_ROOT; use anyhow::{anyhow, Result}; use camino::Utf8PathBuf; -use futures::TryStreamExt; use http::StatusCode; -use lychee_lib::{Client, ClientBuilder, Collector, FileType, Input, InputSource, Request, Uri}; +use lychee_lib::{ + Client, ClientBuilder, Collector, FileType, Input, InputSource, Request, + Result as LycheeResult, Uri, +}; use std::{collections::HashSet, fs, path::PathBuf, time::Duration}; use tokio_stream::StreamExt; -use tokio::runtime::Runtime; pub(crate) struct LycheeRunner { client: Client, @@ -47,7 +48,6 @@ impl LycheeRunner { let links: Vec = Collector::new(None) .collect_links(inputs) - .await .collect::>>() .await?; @@ -68,31 +68,19 @@ impl LycheeRunner { crate::info!("{} links checked.", links_size); if !failed_checks.is_empty() { - for failed_check in failed_checks { - crate::info!("❌ {}", failed_check.as_str()); + for (uri, status_code) in failed_checks { + crate::info!( + "❌ [Status Code: {}]: {}", + status_code + .map(|status_code| status_code.to_string()) + .unwrap_or("unknown".to_string()), + uri + ); } - - crate::info!("{} links checked.", links_size); - - if !failed_checks.is_empty() { - for failed_check in failed_checks { - crate::info!( - "❌ [Status Code: {}] {}", - failed_check - .1 - .map(|status_code| status_code.to_string()) - .unwrap_or("unknown".to_string()), - failed_check.0.as_str() - ); - } - - Err(anyhow!("Some links in markdown documentation are down.")) - } else { - Ok(()) - } - })?; - - Ok(()) + Err(anyhow!("Some links in markdown documentation are down.")) + } else { + Ok(()) + } } } From 5940d368645f25b6812203cb20f7b52f0964b32e Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 14:58:30 -0400 Subject: [PATCH 34/68] fix(tests): asyncification of router runner test --- src/command/dev/router/runner.rs | 5 +++-- xtask/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/command/dev/router/runner.rs b/src/command/dev/router/runner.rs index 4516bbd0a..d8be96f7b 100644 --- a/src/command/dev/router/runner.rs +++ b/src/command/dev/router/runner.rs @@ -309,7 +309,8 @@ mod tests { use super::*; #[rstest] - fn test_wait_for_startup() { + #[tokio::test] + async fn test_wait_for_startup() { // GIVEN // * a mock health endpoint that returns 200 // * a RouterRunner @@ -343,7 +344,7 @@ mod tests { ); // WHEN waiting for router startup - let res = router_runner.wait_for_startup(Client::new()); + let res = router_runner.wait_for_startup(Client::new()).await; // THEN // * it succeeds diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 282812b28..b1ea7707f 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -31,7 +31,7 @@ serde = { workspace = true, features = ["derive"] } serde_json_traversal = { workspace = true } shell-candy = { workspace = true } tempfile = { workspace = true } -tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["rt", "macros"] } tokio-stream = { workspace = true } uuid = { workspace = true, features = ["v4"] } which = { workspace = true } From 0b3ecd78e1a347de2d1ada71b5dac9e73e034fb0 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 15:21:48 -0400 Subject: [PATCH 35/68] fix(asyncification): appease clippy, dev shutdown() async'd --- crates/sputnik/src/session.rs | 3 +++ src/command/dev/protocol/leader.rs | 14 +++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index 4e04d189a..eee35c27c 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -83,6 +83,9 @@ pub struct Command { /// should be reported to #[derive(Debug)] struct ReportingInfo { + // WARNING: wtf happened here? it's unread, but not on main; definitely used in Session::new(), + // and Session::new() gets called in src/cli.rs + #[allow(dead_code)] is_telemetry_enabled: bool, endpoint: Url, user_agent: String, diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index 8a388eaf6..6fcf11cfe 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -437,16 +437,12 @@ impl LeaderSession { pub async fn shutdown(&mut self) { let router_runner = self.router_runner.take(); - // WARNING: this used to be ipc_socket_addr, but that changted to raw_socket_name; we need - // to validate that that functionality is the same let raw_socket_name = self.raw_socket_name.clone(); - tokio::task::spawn(async move { - if let Some(mut runner) = router_runner { - let _ = runner.kill().await.map_err(log_err_and_continue); - } - let _ = std::fs::remove_file(&raw_socket_name); - std::process::exit(1) - }); + if let Some(mut runner) = router_runner { + let _ = runner.kill().await.map_err(log_err_and_continue); + } + let _ = std::fs::remove_file(&raw_socket_name); + std::process::exit(1) } /// Handles a follower message by updating the internal subgraph representation if needed, From f241400ae098c73fef980ffb6f9817a2d86c1b56 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 15:36:04 -0400 Subject: [PATCH 36/68] fix(xtask): runtime within a runtime within a runtime within a runtime --- xtask/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 525963f5e..3a313b3fa 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -71,9 +71,7 @@ impl Xtask { Command::Prep(command) => command.run().await, Command::Package(command) => command.run(), Command::SecurityChecks(command) => command.run(), - Command::GithubActions(command) => { - tokio::runtime::Runtime::new()?.block_on(command.run()) - } + Command::GithubActions(command) => command.run().await, Command::Smoke(command) => tokio::runtime::Runtime::new()?.block_on(command.run()), }?; eprintln!("{}", style("Success!").green().bold()); From b31c511418db4df3d75fa6f9049be3654573a58f Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 15:48:23 -0400 Subject: [PATCH 37/68] fix(xtask): asyncify tests --- Cargo.lock | 1 + crates/sputnik/Cargo.toml | 1 + crates/sputnik/src/session.rs | 7 ++++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8664404a8..e8eee95a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5135,6 +5135,7 @@ dependencies = [ "sha2", "speculoos", "thiserror", + "tokio", "tracing", "url", "uuid", diff --git a/crates/sputnik/Cargo.toml b/crates/sputnik/Cargo.toml index 23f0c5182..0c351da6b 100644 --- a/crates/sputnik/Cargo.toml +++ b/crates/sputnik/Cargo.toml @@ -22,6 +22,7 @@ tracing = { workspace = true } url = { workspace = true } uuid = { workspace = true, features = ["serde", "v4"] } wsl = { workspace = true } +tokio = { workspace = true, features = ["rt", "macros"] } [dev-dependencies] assert_fs = { workspace = true } diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index eee35c27c..92bc755d2 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -183,7 +183,7 @@ mod tests { use super::*; use httpmock::{Method::POST, MockServer}; - use reqwest::blocking::Client; + use reqwest::Client; use rstest::*; use speculoos::{assert_that, result::ResultAssertions}; @@ -236,7 +236,8 @@ mod tests { #[case::success(ReportCase::Success)] #[case::telemetry_disabled(ReportCase::TelemetryDisabled)] #[case::timedout(ReportCase::TimedOut)] - fn test_report( + #[tokio::test] + async fn test_report( #[case] case: ReportCase, mut session: Session, report_path: &'static str, @@ -276,7 +277,7 @@ mod tests { }; }); - let res = session.report(); + let res = session.report().await; if let ReportCase::TelemetryDisabled = case { // When telemetry is disabled, we should expect not outbound calls From e2ee38a9b5d6c884fd869c0015bc05f7c51dd5c8 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 15:53:06 -0400 Subject: [PATCH 38/68] fix(xtask): asyncify tests command running --- xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 3a313b3fa..cadb6901b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -72,7 +72,7 @@ impl Xtask { Command::Package(command) => command.run(), Command::SecurityChecks(command) => command.run(), Command::GithubActions(command) => command.run().await, - Command::Smoke(command) => tokio::runtime::Runtime::new()?.block_on(command.run()), + Command::Smoke(command) => command.run().await, }?; eprintln!("{}", style("Success!").green().bold()); Ok(()) From 2c49c5d995af320d517d269c52dd1ad8270fd793 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 15:59:18 -0400 Subject: [PATCH 39/68] fix(xtask): tokio fflag, rt-multi-thread --- xtask/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index b1ea7707f..282812b28 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -31,7 +31,7 @@ serde = { workspace = true, features = ["derive"] } serde_json_traversal = { workspace = true } shell-candy = { workspace = true } tempfile = { workspace = true } -tokio = { workspace = true, features = ["rt", "macros"] } +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } tokio-stream = { workspace = true } uuid = { workspace = true, features = ["v4"] } which = { workspace = true } From 2bd78c2f26020aef9eb6d22f7c7ff327a335ad3a Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 16:12:56 -0400 Subject: [PATCH 40/68] fix(sputnik): block if telemetry disabled --- crates/sputnik/src/session.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index 92bc755d2..c1dc16bc7 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -148,18 +148,19 @@ impl Session { if !cfg!(debug_assertions) && !cfg!(test) { return Ok(()); } - - let body = serde_json::to_string(&self)?; - tracing::debug!("POSTing to {}", &self.reporting_info.endpoint); - tracing::debug!("{}", body); - self.client - .post(self.reporting_info.endpoint.clone()) - .body(body) - .header("User-Agent", &self.reporting_info.user_agent) - .header("Content-Type", "application/json") - .timeout(REPORT_TIMEOUT) - .send() - .await?; + if self.reporting_info.is_telemetry_enabled { + let body = serde_json::to_string(&self)?; + tracing::debug!("POSTing to {}", &self.reporting_info.endpoint); + tracing::debug!("{}", body); + self.client + .post(self.reporting_info.endpoint.clone()) + .body(body) + .header("User-Agent", &self.reporting_info.user_agent) + .header("Content-Type", "application/json") + .timeout(REPORT_TIMEOUT) + .send() + .await?; + } Ok(()) } From fc5a6751368d3277c562df1cfbb29a1d5e92d791 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Thu, 18 Jul 2024 16:29:55 -0400 Subject: [PATCH 41/68] fix(sputnik): remove dead-code macro, inaccurate --- crates/sputnik/src/session.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index c1dc16bc7..920da0d6d 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -83,9 +83,6 @@ pub struct Command { /// should be reported to #[derive(Debug)] struct ReportingInfo { - // WARNING: wtf happened here? it's unread, but not on main; definitely used in Session::new(), - // and Session::new() gets called in src/cli.rs - #[allow(dead_code)] is_telemetry_enabled: bool, endpoint: Url, user_agent: String, From ecbd83f078f4046806cc865ee63b2e7350e96efb Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 19 Jul 2024 16:04:58 -0400 Subject: [PATCH 42/68] fix(supergraph compose): make faster! - collect futs, don't run sequentially - takes us from ~1min for 100 subs to 15s --- src/command/supergraph/resolve_config.rs | 247 +++++++++++++---------- 1 file changed, 135 insertions(+), 112 deletions(-) diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 5c92ef9d2..fb957330c 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -6,6 +6,7 @@ use apollo_federation_types::{ config::{FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig}, }; use apollo_parser::{cst, Parser}; +use futures::future::join_all; use rover_std::{Fs, Style}; use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; @@ -37,6 +38,12 @@ pub(crate) async fn resolve_supergraph_yaml( err.set_suggestion(RoverErrorSuggestion::ValidComposeRoutingUrl); err }; + let err_invalid_graph_ref = || { + let err = anyhow!("Invalid graph ref."); + let mut err = RoverError::new(err); + err.set_suggestion(RoverErrorSuggestion::CheckGraphNameAndAuth); + err + }; let supergraph_config = unresolved_supergraph_yaml .read_file_descriptor("supergraph config", &mut std::io::stdin()) .and_then(|contents| expand_supergraph_yaml(&contents))?; @@ -45,129 +52,145 @@ pub(crate) async fn resolve_supergraph_yaml( .into_iter() .collect::>(); - let mut subgraph_definition_results: Vec<(String, RoverResult)> = - Vec::new(); + let reqwest_client = client_config + .get_reqwest_client() + .map_err(RoverError::from)?; - for (subgraph_name, subgraph_data) in supergraph_config.into_iter() { - let cloned_subgraph_name = subgraph_name.to_string(); - let result = match &subgraph_data.schema { - SchemaSource::File { file } => { - let relative_schema_path = match unresolved_supergraph_yaml { - FileDescriptorType::File(config_path) => match config_path.parent() { - Some(parent) => { - let mut schema_path = parent.to_path_buf(); - schema_path.push(file); - schema_path - } - None => file.clone(), - }, - FileDescriptorType::Stdin => file.clone(), - }; + let authenticated_client = client_config + .get_authenticated_client(profile_opt) + .map_err(RoverError::from)?; - Fs::read_file(relative_schema_path) - .map_err(|e| { - let mut err = RoverError::new(e); - err.set_suggestion(RoverErrorSuggestion::ValidComposeFile); - err - }) - .and_then(|schema| { - subgraph_data - .routing_url - .clone() - .ok_or_else(err_no_routing_url) - .map(|url| SubgraphDefinition::new(subgraph_name, url, &schema)) - }) - } - SchemaSource::SubgraphIntrospection { - subgraph_url, - introspection_headers, - } => { - let reqwest_client = client_config - .get_reqwest_client() - .map_err(RoverError::from)?; - let client = GraphQLClient::new(subgraph_url.as_ref(), reqwest_client); + // WARNING: this is a departure from how both main and geal's branch work; by collecting the + // futs we're able to run them all at once rather than in parallel (even when async); takes + // resolution down from ~1min for 100 subgraphs to ~10s + let futs = supergraph_config + .iter() + .map(|(subgraph_name, subgraph_data)| async { + let cloned_subgraph_name = subgraph_name.to_string(); + let result = match &subgraph_data.schema { + SchemaSource::File { file } => { + let relative_schema_path = match unresolved_supergraph_yaml { + FileDescriptorType::File(config_path) => match config_path.parent() { + Some(parent) => { + let mut schema_path = parent.to_path_buf(); + schema_path.push(file); + schema_path + } + None => file.clone(), + }, + FileDescriptorType::Stdin => file.clone(), + }; + + Fs::read_file(relative_schema_path) + .map_err(|e| { + let mut err = RoverError::new(e); + err.set_suggestion(RoverErrorSuggestion::ValidComposeFile); + err + }) + .and_then(|schema| { + subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url) + .map(|url| { + SubgraphDefinition::new(subgraph_name.clone(), url, &schema) + }) + }) + } + SchemaSource::SubgraphIntrospection { + subgraph_url, + introspection_headers, + } => { + let client = GraphQLClient::new(subgraph_url.as_ref(), reqwest_client.clone()); - // given a federated introspection URL, use subgraph introspect to - // obtain SDL and add it to subgraph_definition. - introspect::run( - SubgraphIntrospectInput { - headers: introspection_headers.clone().unwrap_or_default(), - }, - &client, - false, - ) - .await - .map(|introspection_response| { - let schema = introspection_response.result; + // given a federated introspection URL, use subgraph introspect to + // obtain SDL and add it to subgraph_definition. + introspect::run( + SubgraphIntrospectInput { + headers: introspection_headers.clone().unwrap_or_default(), + }, + &client, + false, + ) + .await + .map(|introspection_response| { + let schema = introspection_response.result; - // We don't require a routing_url in config for this variant of a schema, - // if one isn't provided, just use the URL they passed for introspection. - let url = &subgraph_data - .routing_url - .clone() - .unwrap_or_else(|| subgraph_url.to_string()); - SubgraphDefinition::new(subgraph_name, url, schema) - }) - .map_err(RoverError::from) - } - SchemaSource::Subgraph { - graphref: graph_ref, - subgraph, - } => { - let authenticated_client = client_config - .get_authenticated_client(profile_opt) - .map_err(RoverError::from)?; - // given a graph_ref and subgraph, run subgraph fetch to - // obtain SDL and add it to subgraph_definition. - fetch::run( - SubgraphFetchInput { - graph_ref: GraphRef::from_str(graph_ref)?, - subgraph_name: subgraph.clone(), - }, - &authenticated_client, - ) - .await - .map_err(RoverError::from) - .and_then(|result| { - // We don't require a routing_url in config for this variant of a schema, - // if one isn't provided, just use the routing URL from the graph registry (if it exists). - if let rover_client::shared::SdlType::Subgraph { - routing_url: Some(graph_registry_routing_url), - } = result.sdl.r#type - { - let url = subgraph_data + // We don't require a routing_url in config for this variant of a schema, + // if one isn't provided, just use the URL they passed for introspection. + let url = &subgraph_data .routing_url .clone() - .unwrap_or(graph_registry_routing_url); - Ok(SubgraphDefinition::new( - subgraph_name, - url, - &result.sdl.contents, - )) - } else { - Err(err_no_routing_url()) - } - }) - } - SchemaSource::Sdl { sdl } => subgraph_data - .routing_url - .clone() - .ok_or_else(err_no_routing_url) - .map(|url| SubgraphDefinition::new(subgraph_name, url, sdl)), - }; + .unwrap_or_else(|| subgraph_url.to_string()); + SubgraphDefinition::new(subgraph_name.clone(), url, schema) + }) + .map_err(RoverError::from) + } + SchemaSource::Subgraph { + graphref: graph_ref, + subgraph, + } => { + // WARNING: here's where we're returning an error on invalid graph refs; before + // this would bubble up and, I _think_, early abort the resolving + let graph_ref = match GraphRef::from_str(graph_ref) { + Ok(graph_ref) => graph_ref, + Err(_err) => return Err(err_invalid_graph_ref()), + }; - subgraph_definition_results.push((cloned_subgraph_name, result)); - } + // given a graph_ref and subgraph, run subgraph fetch to + // obtain SDL and add it to subgraph_definition. + fetch::run( + SubgraphFetchInput { + graph_ref, + subgraph_name: subgraph.clone(), + }, + &authenticated_client, + ) + .await + .map_err(RoverError::from) + .and_then(|result| { + // We don't require a routing_url in config for this variant of a schema, + // if one isn't provided, just use the routing URL from the graph registry (if it exists). + if let rover_client::shared::SdlType::Subgraph { + routing_url: Some(graph_registry_routing_url), + } = result.sdl.r#type + { + let url = subgraph_data + .routing_url + .clone() + .unwrap_or(graph_registry_routing_url); + Ok(SubgraphDefinition::new( + subgraph_name.clone(), + url, + &result.sdl.contents, + )) + } else { + Err(err_no_routing_url()) + } + }) + } + SchemaSource::Sdl { sdl } => subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url) + .map(|url| SubgraphDefinition::new(subgraph_name.clone(), url, sdl)), + }; + Ok((cloned_subgraph_name, result)) + }); + + let subgraph_definition_results = join_all(futs).await.into_iter(); + let num_subgraphs = subgraph_definition_results.len(); let mut subgraph_definitions = Vec::new(); let mut subgraph_definition_errors = Vec::new(); - let num_subgraphs = subgraph_definition_results.len(); - - for (subgraph_name, subgraph_definition_result) in subgraph_definition_results { - match subgraph_definition_result { - Ok(subgraph_definition) => subgraph_definitions.push(subgraph_definition), - Err(e) => subgraph_definition_errors.push((subgraph_name, e)), + for res in subgraph_definition_results { + match res { + Ok((subgraph_name, subgraph_definition_result)) => match subgraph_definition_result { + Ok(subgraph_definition) => subgraph_definitions.push(subgraph_definition), + Err(e) => subgraph_definition_errors.push((subgraph_name, e)), + }, + Err(e) => subgraph_definition_errors.push(("malformed subgraph name".to_string(), e)), } } From 747584a8b0c8132dafcd50dcc2f4dd4cf32b6f35 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 19 Jul 2024 16:28:39 -0400 Subject: [PATCH 43/68] wip --- src/command/supergraph/resolve_config.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index fb957330c..e8a811af5 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -38,12 +38,12 @@ pub(crate) async fn resolve_supergraph_yaml( err.set_suggestion(RoverErrorSuggestion::ValidComposeRoutingUrl); err }; - let err_invalid_graph_ref = || { - let err = anyhow!("Invalid graph ref."); - let mut err = RoverError::new(err); - err.set_suggestion(RoverErrorSuggestion::CheckGraphNameAndAuth); - err - }; + //let err_invalid_graph_ref = || { + // let err = anyhow!("Invalid graph ref."); + // let mut err = RoverError::new(err); + // err.set_suggestion(RoverErrorSuggestion::CheckGraphNameAndAuth); + // err + //}; let supergraph_config = unresolved_supergraph_yaml .read_file_descriptor("supergraph config", &mut std::io::stdin()) .and_then(|contents| expand_supergraph_yaml(&contents))?; @@ -132,10 +132,12 @@ pub(crate) async fn resolve_supergraph_yaml( } => { // WARNING: here's where we're returning an error on invalid graph refs; before // this would bubble up and, I _think_, early abort the resolving - let graph_ref = match GraphRef::from_str(graph_ref) { - Ok(graph_ref) => graph_ref, - Err(_err) => return Err(err_invalid_graph_ref()), - }; + //let graph_ref = match GraphRef::from_str(graph_ref) { + // Ok(graph_ref) => graph_ref, + // Err(_err) => return Err(err_invalid_graph_ref()), + //}; + let graph_ref = GraphRef::from_str(graph_ref) + .expect(format!("Invalid graph ref: {graph_ref}").as_str()); // given a graph_ref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. From 46b70088e78437800ee479465ac2ce7400cd7d00 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 19 Jul 2024 16:38:14 -0400 Subject: [PATCH 44/68] rebase me: tests failing --- src/command/supergraph/resolve_config.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index e8a811af5..fb957330c 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -38,12 +38,12 @@ pub(crate) async fn resolve_supergraph_yaml( err.set_suggestion(RoverErrorSuggestion::ValidComposeRoutingUrl); err }; - //let err_invalid_graph_ref = || { - // let err = anyhow!("Invalid graph ref."); - // let mut err = RoverError::new(err); - // err.set_suggestion(RoverErrorSuggestion::CheckGraphNameAndAuth); - // err - //}; + let err_invalid_graph_ref = || { + let err = anyhow!("Invalid graph ref."); + let mut err = RoverError::new(err); + err.set_suggestion(RoverErrorSuggestion::CheckGraphNameAndAuth); + err + }; let supergraph_config = unresolved_supergraph_yaml .read_file_descriptor("supergraph config", &mut std::io::stdin()) .and_then(|contents| expand_supergraph_yaml(&contents))?; @@ -132,12 +132,10 @@ pub(crate) async fn resolve_supergraph_yaml( } => { // WARNING: here's where we're returning an error on invalid graph refs; before // this would bubble up and, I _think_, early abort the resolving - //let graph_ref = match GraphRef::from_str(graph_ref) { - // Ok(graph_ref) => graph_ref, - // Err(_err) => return Err(err_invalid_graph_ref()), - //}; - let graph_ref = GraphRef::from_str(graph_ref) - .expect(format!("Invalid graph ref: {graph_ref}").as_str()); + let graph_ref = match GraphRef::from_str(graph_ref) { + Ok(graph_ref) => graph_ref, + Err(_err) => return Err(err_invalid_graph_ref()), + }; // given a graph_ref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. From f2006926fda964f02066095e384b88745e03037d Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 22 Jul 2024 16:24:00 -0400 Subject: [PATCH 45/68] rebase --- src/command/supergraph/compose/do_compose.rs | 10 ++++---- src/command/supergraph/resolve_config.rs | 25 ++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 11252aad0..bdd49515a 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -306,10 +306,12 @@ mod tests { profile_name: "profile".to_string(), }, ) - .await - .unwrap() - .get_subgraph_definitions() - .unwrap(); + .await; + + let subgraph_definitions = subgraph_definitions + .unwrap() + .get_subgraph_definitions() + .unwrap(); let film_subgraph = subgraph_definitions.first().unwrap(); let people_subgraph = subgraph_definitions.get(1).unwrap(); diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index fb957330c..780e3173b 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -132,10 +132,12 @@ pub(crate) async fn resolve_supergraph_yaml( } => { // WARNING: here's where we're returning an error on invalid graph refs; before // this would bubble up and, I _think_, early abort the resolving - let graph_ref = match GraphRef::from_str(graph_ref) { - Ok(graph_ref) => graph_ref, - Err(_err) => return Err(err_invalid_graph_ref()), - }; + //let graph_ref = match GraphRef::from_str(graph_ref) { + // Ok(graph_ref) => graph_ref, + // Err(_err) => return Err(err_invalid_graph_ref()), + //}; + + let graph_ref = GraphRef::from_str(graph_ref).unwrap(); // given a graph_ref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. @@ -165,7 +167,7 @@ pub(crate) async fn resolve_supergraph_yaml( &result.sdl.contents, )) } else { - Err(err_no_routing_url()) + panic!("whoops: rebase me"); } }) } @@ -175,7 +177,7 @@ pub(crate) async fn resolve_supergraph_yaml( .ok_or_else(err_no_routing_url) .map(|url| SubgraphDefinition::new(subgraph_name.clone(), url, sdl)), }; - Ok((cloned_subgraph_name, result)) + (cloned_subgraph_name, result) }); let subgraph_definition_results = join_all(futs).await.into_iter(); @@ -184,13 +186,10 @@ pub(crate) async fn resolve_supergraph_yaml( let mut subgraph_definitions = Vec::new(); let mut subgraph_definition_errors = Vec::new(); - for res in subgraph_definition_results { - match res { - Ok((subgraph_name, subgraph_definition_result)) => match subgraph_definition_result { - Ok(subgraph_definition) => subgraph_definitions.push(subgraph_definition), - Err(e) => subgraph_definition_errors.push((subgraph_name, e)), - }, - Err(e) => subgraph_definition_errors.push(("malformed subgraph name".to_string(), e)), + for (subgraph_name, subgraph_definition_result) in subgraph_definition_results { + match subgraph_definition_result { + Ok(subgraph_definition) => subgraph_definitions.push(subgraph_definition), + Err(e) => subgraph_definition_errors.push((subgraph_name, e)), } } From b0d02c4696882a585021e54ddadd488d7dd71e43 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 22 Jul 2024 16:39:53 -0400 Subject: [PATCH 46/68] rebase --- src/command/supergraph/resolve_config.rs | 26 ++++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 780e3173b..5700b451f 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -132,13 +132,12 @@ pub(crate) async fn resolve_supergraph_yaml( } => { // WARNING: here's where we're returning an error on invalid graph refs; before // this would bubble up and, I _think_, early abort the resolving - //let graph_ref = match GraphRef::from_str(graph_ref) { - // Ok(graph_ref) => graph_ref, - // Err(_err) => return Err(err_invalid_graph_ref()), - //}; - - let graph_ref = GraphRef::from_str(graph_ref).unwrap(); + let graph_ref = match GraphRef::from_str(graph_ref) { + Ok(graph_ref) => graph_ref, + Err(_err) => return Err(err_invalid_graph_ref()), + }; + //let graph_ref = GraphRef::from_str(graph_ref).unwrap(); // given a graph_ref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. fetch::run( @@ -177,7 +176,7 @@ pub(crate) async fn resolve_supergraph_yaml( .ok_or_else(err_no_routing_url) .map(|url| SubgraphDefinition::new(subgraph_name.clone(), url, sdl)), }; - (cloned_subgraph_name, result) + Ok((cloned_subgraph_name, result)) }); let subgraph_definition_results = join_all(futs).await.into_iter(); @@ -186,10 +185,15 @@ pub(crate) async fn resolve_supergraph_yaml( let mut subgraph_definitions = Vec::new(); let mut subgraph_definition_errors = Vec::new(); - for (subgraph_name, subgraph_definition_result) in subgraph_definition_results { - match subgraph_definition_result { - Ok(subgraph_definition) => subgraph_definitions.push(subgraph_definition), - Err(e) => subgraph_definition_errors.push((subgraph_name, e)), + for res in subgraph_definition_results { + match res { + Ok((subgraph_name, subgraph_definition_result)) => match subgraph_definition_result { + Ok(subgraph_definition) => subgraph_definitions.push(subgraph_definition), + Err(e) => subgraph_definition_errors.push((subgraph_name, e)), + }, + Err(err) => { + eprintln!("err: {err}"); + } } } From f7efa703244cb0bf1fcfd857a826f67bd4469592 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 22 Jul 2024 18:03:20 -0400 Subject: [PATCH 47/68] rebase --- crates/houston/src/profile/mod.rs | 1 + src/command/supergraph/resolve_config.rs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/houston/src/profile/mod.rs b/crates/houston/src/profile/mod.rs index 7823da537..5b5e04d6c 100644 --- a/crates/houston/src/profile/mod.rs +++ b/crates/houston/src/profile/mod.rs @@ -70,6 +70,7 @@ impl Profile { /// /// Takes an optional `profile` argument. Defaults to `"default"`. pub fn get_credential(name: &str, config: &Config) -> Result { + println!("h"); let credential = match &config.override_api_key { Some(api_key) => Credential { api_key: api_key.to_string(), diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 5700b451f..1ed416731 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -52,14 +52,6 @@ pub(crate) async fn resolve_supergraph_yaml( .into_iter() .collect::>(); - let reqwest_client = client_config - .get_reqwest_client() - .map_err(RoverError::from)?; - - let authenticated_client = client_config - .get_authenticated_client(profile_opt) - .map_err(RoverError::from)?; - // WARNING: this is a departure from how both main and geal's branch work; by collecting the // futs we're able to run them all at once rather than in parallel (even when async); takes // resolution down from ~1min for 100 subgraphs to ~10s @@ -101,7 +93,10 @@ pub(crate) async fn resolve_supergraph_yaml( subgraph_url, introspection_headers, } => { - let client = GraphQLClient::new(subgraph_url.as_ref(), reqwest_client.clone()); + let client = client_config + .get_reqwest_client() + .map_err(RoverError::from)?; + let client = GraphQLClient::new(subgraph_url.as_ref(), client); // given a federated introspection URL, use subgraph introspect to // obtain SDL and add it to subgraph_definition. @@ -137,6 +132,10 @@ pub(crate) async fn resolve_supergraph_yaml( Err(_err) => return Err(err_invalid_graph_ref()), }; + let authenticated_client = client_config + .get_authenticated_client(profile_opt) + .map_err(RoverError::from)?; + //let graph_ref = GraphRef::from_str(graph_ref).unwrap(); // given a graph_ref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. From 9e864fd42f120222c159c4bf4e98c9a6431daf06 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 22 Jul 2024 18:14:42 -0400 Subject: [PATCH 48/68] rebase --- crates/houston/src/profile/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/houston/src/profile/mod.rs b/crates/houston/src/profile/mod.rs index 5b5e04d6c..7823da537 100644 --- a/crates/houston/src/profile/mod.rs +++ b/crates/houston/src/profile/mod.rs @@ -70,7 +70,6 @@ impl Profile { /// /// Takes an optional `profile` argument. Defaults to `"default"`. pub fn get_credential(name: &str, config: &Config) -> Result { - println!("h"); let credential = match &config.override_api_key { Some(api_key) => Credential { api_key: api_key.to_string(), From 5e12ef21dfe3b09b76543483bbd16d1b53c8c24e Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 22 Jul 2024 18:18:10 -0400 Subject: [PATCH 49/68] rebase --- src/command/supergraph/resolve_config.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/command/supergraph/resolve_config.rs b/src/command/supergraph/resolve_config.rs index 1ed416731..d279d81ee 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/command/supergraph/resolve_config.rs @@ -148,7 +148,7 @@ pub(crate) async fn resolve_supergraph_yaml( ) .await .map_err(RoverError::from) - .and_then(|result| { + .map(|result| { // We don't require a routing_url in config for this variant of a schema, // if one isn't provided, just use the routing URL from the graph registry (if it exists). if let rover_client::shared::SdlType::Subgraph { @@ -159,11 +159,11 @@ pub(crate) async fn resolve_supergraph_yaml( .routing_url .clone() .unwrap_or(graph_registry_routing_url); - Ok(SubgraphDefinition::new( + SubgraphDefinition::new( subgraph_name.clone(), url, &result.sdl.contents, - )) + ) } else { panic!("whoops: rebase me"); } From ac69e938f00d9cb3e824ea3392c739986edb7f85 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 29 Jul 2024 16:52:35 -0400 Subject: [PATCH 50/68] docs(rover client): add readme --- Cargo.toml | 2 +- crates/rover-client/readme.md | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 crates/rover-client/readme.md diff --git a/Cargo.toml b/Cargo.toml index 5b1823abb..6ed953e28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ assert_cmd = "2" assert-json-diff = "2" anyhow = "1" backtrace = "0.3" -backoff = "0.4" +backoff = { version = "0.4", features = [ "tokio" ]} base64 = "0.22" billboard = "0.2" cargo_metadata = "0.18" diff --git a/crates/rover-client/readme.md b/crates/rover-client/readme.md new file mode 100644 index 000000000..7c0bb3ad6 --- /dev/null +++ b/crates/rover-client/readme.md @@ -0,0 +1,42 @@ +# Rover Client + +This is the client used by rover to make network requests. This README covers aspects of the client that are useful to know when developing and using it. + +The rover client uses [Reqwest](https://docs.rs/reqwest/latest/reqwest/) and some familiarity with that crate is useful in both developing and using it. + +# Development :: WIP + +We're in the midst of undergoing a transition from a synchronous, blocking client used by threads to an asynchronous one used by an event loop (a tokio runtime that also uses threads, but is non-blocking). Because of that, some of the naming and ergonmics might feel weird. + +# Using the client + +## Timeouts + +By default, the timeout is 10s. This is set _not_ by the `MAX_ELAPSED_TIME` const in the `rover-client/src/blocking/client.rs` file, but in the `Default` implementation for `ClientTimeout` in `rover/src/utils/client.rs`. Users can pass the flag `--client-timeout` with an integer representing seconds to control the overall client timeout. + +## Retries + +### Overview +Retries can happen for two broad reasons: either the client failed or the server failed. Retries are also enabled by default, but can be disabled by passing an argument to the client's `execute` method called `should_retry` (a boolean). + +Retries are also only part of the story. The interval of time you place between retries matters. If you retry all at once, you might get rate-limited or otherwise fail. It's best to spread them out exponentially, adding big chunks of time so that the server can complete its work and get ready for more work. Spreading out retries is a good idea, but if you have multiple calls happening at the same time with the same spread, they might fail if the server is overloaded. It's better to spread them out with some added noise, meaning that you spread them out with some randomly generated bit of time added or subtracted so that calls are received by the server in a somewhat distributed fashion. + +#### `backoff` crate + +We use the [backoff](https://docs.rs/backoff/latest/backoff/) crate for retries. It builds in both the spreading-out of retries in an exponential way, but also the little bit of jitter that helps the server handle many requests. + +The crate is interesting in handling retries not by a total amount of retries, but total amount of time. The `MAX_ELAPSED_TIME` in the client file sets this value and defaults to 10s. + +#### Client failures + +Retries happen when either the client times out (there's a flag for setting the timeout, but by default it's 10s), when there's a connection error, or when incomplete messages are received. Errors about the request or response body, decoding, building the client, or redirecting the network call aren't retried. + +#### Server failures + +Retries happen for general server errors (noteably, _all_ statuses between 500-99), but not when the request is ill-formed as identified by the server (that is, a 400). + + + + + + From 18143aa56f98ef9fbd83891c2a98f1b99105f3e1 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Wed, 24 Jul 2024 14:08:32 +0100 Subject: [PATCH 51/68] ROVER-69 Unify supergraph_config parsing We do this in both supergraph commands and dev and having it in two places is not helping. --- src/command/dev/do_dev.rs | 53 +--- src/command/dev/mod.rs | 9 +- src/command/dev/remote_subgraphs.rs | 55 ---- src/command/supergraph/compose/do_compose.rs | 173 +++--------- src/command/supergraph/mod.rs | 11 +- src/utils/mod.rs | 2 + src/utils/parsers.rs | 24 +- .../supergraph_config.rs} | 255 +++++++++++++++++- 8 files changed, 321 insertions(+), 261 deletions(-) delete mode 100644 src/command/dev/remote_subgraphs.rs rename src/{command/supergraph/resolve_config.rs => utils/supergraph_config.rs} (60%) diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 9d08c6bcd..c2fa58538 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -3,15 +3,14 @@ use apollo_federation_types::config::FederationVersion; use camino::Utf8PathBuf; use crossbeam_channel::bounded as sync_channel; -use rover_std::{Emoji, Fs}; +use rover_std::Emoji; use crate::command::dev::protocol::FollowerMessage; -use crate::command::supergraph::expand_supergraph_yaml; use crate::utils::client::StudioClientConfig; +use crate::utils::supergraph_config::get_supergraph_config; use crate::{RoverError, RoverOutput, RoverResult}; use super::protocol::{FollowerChannel, FollowerMessenger, LeaderChannel, LeaderSession}; -use super::remote_subgraphs::RemoteSubgraphs; use super::router::RouterConfigHandler; use super::Dev; @@ -36,42 +35,18 @@ impl Dev { let leader_channel = LeaderChannel::new(); let follower_channel = FollowerChannel::new(); - // Read in Remote subgraphs - let remote_subgraphs = match &self.opts.supergraph_opts.graph_ref { - Some(graph_ref) => Some(RemoteSubgraphs::fetch( - &client_config.get_authenticated_client(&self.opts.plugin_opts.profile)?, - &self - .opts - .supergraph_opts - .federation_version - .clone() - .unwrap_or(FederationVersion::LatestFedTwo), - graph_ref, - )?), - None => None, - }; - - // Read in Local Supergraph Config - let supergraph_config = - if let Some(config_path) = &self.opts.supergraph_opts.supergraph_config_path { - let config_content = Fs::read_file(config_path)?; - Some(expand_supergraph_yaml(&config_content)?) - } else { - None - }; - - // Merge Remote and Local Supergraph Configs - let supergraph_config = match remote_subgraphs { - Some(remote_subgraphs) => match supergraph_config { - Some(supergraph_config) => { - let mut merged_supergraph_config = remote_subgraphs.inner().clone(); - merged_supergraph_config.merge_subgraphs(&supergraph_config); - Some(merged_supergraph_config) - } - None => Some(remote_subgraphs.inner().clone()), - }, - None => supergraph_config, - }; + let supergraph_config = get_supergraph_config( + &self.opts.supergraph_opts.graph_ref, + &self.opts.supergraph_opts.supergraph_config_path, + &self + .opts + .supergraph_opts + .federation_version + .clone() + .unwrap_or(FederationVersion::LatestFedTwo), + client_config.clone(), + &self.opts.plugin_opts.profile, + )?; // Build a Rayon Thread pool let tp = rayon::ThreadPoolBuilder::new() diff --git a/src/command/dev/mod.rs b/src/command/dev/mod.rs index b057dc9ca..ecd129ea1 100644 --- a/src/command/dev/mod.rs +++ b/src/command/dev/mod.rs @@ -3,10 +3,12 @@ use std::net::IpAddr; use apollo_federation_types::config::FederationVersion; use camino::Utf8PathBuf; use clap::Parser; -use rover_client::shared::GraphRef; use serde::Serialize; +use rover_client::shared::GraphRef; + use crate::options::{OptionalSubgraphOpts, PluginOpts}; +use crate::utils::parsers::FileDescriptorType; #[cfg(feature = "composition-js")] mod compose; @@ -20,9 +22,6 @@ mod introspect; #[cfg(feature = "composition-js")] mod protocol; -#[cfg(feature = "composition-js")] -mod remote_subgraphs; - #[cfg(feature = "composition-js")] mod router; @@ -91,7 +90,7 @@ pub struct SupergraphOpts { long = "supergraph-config", conflicts_with_all = ["subgraph_name", "subgraph_url", "subgraph_schema_path"] )] - supergraph_config_path: Option, + supergraph_config_path: Option, /// A [`GraphRef`] that is accessible in Apollo Studio. /// This is used to initialize your supergraph with the values contained in this variant. diff --git a/src/command/dev/remote_subgraphs.rs b/src/command/dev/remote_subgraphs.rs deleted file mode 100644 index 1e585db74..000000000 --- a/src/command/dev/remote_subgraphs.rs +++ /dev/null @@ -1,55 +0,0 @@ -use apollo_federation_types::config::{ - FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig, -}; -use rover_client::{ - blocking::StudioClient, - operations::subgraph::{self, list::SubgraphListInput}, - shared::GraphRef, -}; - -use crate::RoverResult; - -/// Nominal type that captures the behavior of collecting remote subgraphs into a -/// [`SupergraphConfig`] representation -#[derive(Clone, Debug)] -pub struct RemoteSubgraphs(SupergraphConfig); - -impl RemoteSubgraphs { - /// Fetches [`RemoteSubgraphs`] from Studio - pub fn fetch( - client: &StudioClient, - federation_version: &FederationVersion, - graph_ref: &GraphRef, - ) -> RoverResult { - let subgraphs = subgraph::list::run( - SubgraphListInput { - graph_ref: graph_ref.clone(), - }, - client, - )?; - let subgraphs = subgraphs - .subgraphs - .iter() - .map(|subgraph| { - ( - subgraph.name.clone(), - SubgraphConfig { - routing_url: subgraph.url.clone(), - schema: SchemaSource::Subgraph { - graphref: graph_ref.clone().to_string(), - subgraph: subgraph.name.clone(), - }, - }, - ) - }) - .collect(); - let supergraph_config = SupergraphConfig::new(subgraphs, Some(federation_version.clone())); - let remote_subgraphs = RemoteSubgraphs(supergraph_config); - Ok(remote_subgraphs) - } - - /// Provides a reference to the inner value of this representation - pub fn inner(&self) -> &SupergraphConfig { - &self.0 - } -} diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index ac1da583f..e8de81524 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -1,6 +1,7 @@ use std::{fs::File, io::Write, process::Command, str}; use anyhow::{anyhow, Context}; +use apollo_federation_types::config::FederationVersion::LatestFedTwo; use apollo_federation_types::config::SupergraphConfig; use apollo_federation_types::{ build::BuildResult, @@ -10,10 +11,11 @@ use camino::Utf8PathBuf; use clap::Parser; use serde::Serialize; +use rover_client::shared::GraphRef; use rover_client::RoverClientError; use rover_std::{Emoji, Style}; -use crate::command::supergraph::resolve_supergraph_yaml; +use crate::utils::supergraph_config::get_supergraph_config; use crate::utils::{client::StudioClientConfig, parsers::FileDescriptorType}; use crate::{ command::{ @@ -24,22 +26,45 @@ use crate::{ RoverError, RoverErrorSuggestion, RoverOutput, RoverResult, }; -#[derive(Debug, Clone, Serialize, Parser)] +#[derive(Debug, Serialize, Parser)] pub struct Compose { + #[clap(flatten)] + opts: SupergraphComposeOpts, +} + +#[derive(Debug, Serialize, Parser)] +pub struct SupergraphComposeOpts { + #[clap(flatten)] + pub plugin_opts: PluginOpts, + /// The relative path to the supergraph configuration file. You can pass `-` to use stdin instead of a file. #[serde(skip_serializing)] #[arg(long = "config")] supergraph_yaml: FileDescriptorType, - #[clap(flatten)] - opts: PluginOpts, + /// A [`GraphRef`] that is accessible in Apollo Studio. + /// This is used to initialize your supergraph with the values contained in this variant. + /// + /// This is analogous to providing a supergraph.yaml file with references to your graph variant in studio. + /// + /// If used in conjunction with `--supergraph-config`, the values presented in the supergraph.yaml will take precedence over these values. + #[arg(long = "graph-ref")] + graph_ref: Option, + + /// The version of Apollo Federation to use for composition + #[arg(long = "federation-version")] + federation_version: Option, } impl Compose { pub fn new(compose_opts: PluginOpts) -> Self { Self { - supergraph_yaml: FileDescriptorType::File("RAM".into()), - opts: compose_opts, + opts: SupergraphComposeOpts { + plugin_opts: compose_opts, + supergraph_yaml: FileDescriptorType::File("RAM".into()), + graph_ref: None, + federation_version: Some(LatestFedTwo), + }, } } @@ -52,6 +77,7 @@ impl Compose { let plugin = Plugin::Supergraph(federation_version.clone()); if federation_version.is_fed_two() { self.opts + .plugin_opts .elv2_license_accepter .require_elv2_license(&client_config)?; } @@ -60,14 +86,14 @@ impl Compose { let install_command = Install { force: false, plugin: Some(plugin), - elv2_license_accepter: self.opts.elv2_license_accepter, + elv2_license_accepter: self.opts.plugin_opts.elv2_license_accepter, }; // maybe do the install, maybe find a pre-existing installation, maybe fail let plugin_exe = install_command.get_versioned_plugin( override_install_path, client_config, - self.opts.skip_update, + self.opts.plugin_opts.skip_update, )?; Ok(plugin_exe) } @@ -80,13 +106,16 @@ impl Compose { eprintln!( "{}resolving SDL for subgraphs defined in {}", Emoji::Hourglass, - Style::Path.paint(self.supergraph_yaml.to_string()) + Style::Path.paint(self.opts.supergraph_yaml.to_string()) ); - let mut supergraph_config = resolve_supergraph_yaml( - &self.supergraph_yaml, + let mut supergraph_config = get_supergraph_config( + &self.opts.graph_ref, + &Some(self.opts.supergraph_yaml.clone()), + &self.opts.federation_version.clone().unwrap_or(LatestFedTwo), client_config.clone(), - &self.opts.profile, - )?; + &self.opts.plugin_opts.profile, + )? + .unwrap(); self.compose(override_install_path, client_config, &mut supergraph_config) } @@ -187,130 +216,12 @@ impl Compose { #[cfg(test)] mod tests { use std::convert::TryFrom; - use std::fs; - use assert_fs::TempDir; use rstest::rstest; use speculoos::assert_that; - use houston as houston_config; - use houston_config::Config; - - use crate::options::ProfileOpt; - use crate::utils::client::ClientBuilder; - use super::*; - fn get_studio_config() -> StudioClientConfig { - let tmp_home = TempDir::new().unwrap(); - let tmp_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - StudioClientConfig::new( - None, - Config::new(Some(&tmp_path), None).unwrap(), - false, - ClientBuilder::default(), - ) - } - - #[rstest] - fn it_errs_on_invalid_subgraph_path() { - let raw_good_yaml = r#"subgraphs: - films: - routing_url: https://films.example.com - schema: - file: ./films-do-not-exist.graphql - people: - routing_url: https://people.example.com - schema: - file: ./people-do-not-exist.graphql"#; - let tmp_home = TempDir::new().unwrap(); - let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - config_path.push("config.yaml"); - fs::write(&config_path, raw_good_yaml).unwrap(); - assert!(resolve_supergraph_yaml( - &FileDescriptorType::File(config_path), - get_studio_config(), - &ProfileOpt { - profile_name: "profile".to_string() - } - ) - .is_err()) - } - - #[rstest] - fn it_can_get_subgraph_definitions_from_fs() { - let raw_good_yaml = r#"subgraphs: - films: - routing_url: https://films.example.com - schema: - file: ./films.graphql - people: - routing_url: https://people.example.com - schema: - file: ./people.graphql"#; - let tmp_home = TempDir::new().unwrap(); - let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - config_path.push("config.yaml"); - fs::write(&config_path, raw_good_yaml).unwrap(); - let tmp_dir = config_path.parent().unwrap().to_path_buf(); - let films_path = tmp_dir.join("films.graphql"); - let people_path = tmp_dir.join("people.graphql"); - fs::write(films_path, "there is something here").unwrap(); - fs::write(people_path, "there is also something here").unwrap(); - assert!(resolve_supergraph_yaml( - &FileDescriptorType::File(config_path), - get_studio_config(), - &ProfileOpt { - profile_name: "profile".to_string() - } - ) - .is_ok()) - } - - #[rstest] - fn it_can_compute_relative_schema_paths() { - let raw_good_yaml = r#"subgraphs: - films: - routing_url: https://films.example.com - schema: - file: ../../films.graphql - people: - routing_url: https://people.example.com - schema: - file: ../../people.graphql"#; - let tmp_home = TempDir::new().unwrap(); - let tmp_dir = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - let mut config_path = tmp_dir.clone(); - config_path.push("layer"); - config_path.push("layer"); - fs::create_dir_all(&config_path).unwrap(); - config_path.push("config.yaml"); - fs::write(&config_path, raw_good_yaml).unwrap(); - let films_path = tmp_dir.join("films.graphql"); - let people_path = tmp_dir.join("people.graphql"); - fs::write(films_path, "there is something here").unwrap(); - fs::write(people_path, "there is also something here").unwrap(); - let subgraph_definitions = resolve_supergraph_yaml( - &FileDescriptorType::File(config_path), - get_studio_config(), - &ProfileOpt { - profile_name: "profile".to_string(), - }, - ) - .unwrap() - .get_subgraph_definitions() - .unwrap(); - let film_subgraph = subgraph_definitions.first().unwrap(); - let people_subgraph = subgraph_definitions.get(1).unwrap(); - - assert_eq!(film_subgraph.name, "films"); - assert_eq!(film_subgraph.url, "https://films.example.com"); - assert_eq!(film_subgraph.sdl, "there is something here"); - assert_eq!(people_subgraph.name, "people"); - assert_eq!(people_subgraph.url, "https://people.example.com"); - assert_eq!(people_subgraph.sdl, "there is also something here"); - } - #[rstest] #[case::simple_binary("a/b/c/d/supergraph-v2.8.5", "v2.8.5")] #[case::simple_windows_binary("a/b/supergraph-v2.9.1.exe", "v2.9.1")] diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index decf897de..9488b0998 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -1,11 +1,3 @@ -pub(crate) mod compose; -mod fetch; - -#[cfg(feature = "composition-js")] -mod resolve_config; -#[cfg(feature = "composition-js")] -pub(crate) use resolve_config::{expand_supergraph_yaml, resolve_supergraph_yaml}; - use camino::Utf8PathBuf; use clap::Parser; use serde::Serialize; @@ -13,6 +5,9 @@ use serde::Serialize; use crate::utils::client::StudioClientConfig; use crate::{RoverOutput, RoverResult}; +pub(crate) mod compose; +mod fetch; + #[derive(Debug, Serialize, Parser)] pub struct Supergraph { #[clap(subcommand)] diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 18cc386a7..9242cd376 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,6 +3,8 @@ pub mod env; pub mod parsers; pub mod pkg; pub mod stringify; +#[cfg(feature = "composition-js")] +pub mod supergraph_config; pub mod table; pub mod telemetry; pub mod version; diff --git a/src/utils/parsers.rs b/src/utils/parsers.rs index 7cfd3358f..309cd91d3 100644 --- a/src/utils/parsers.rs +++ b/src/utils/parsers.rs @@ -1,16 +1,18 @@ -use anyhow::{anyhow, Context}; -use camino::{Utf8Path, Utf8PathBuf}; -use rover_std::Fs; - -use crate::{RoverError, RoverErrorSuggestion, RoverResult}; - use std::{ fmt, io::{self, Read}, str::FromStr, }; -#[derive(Debug, Clone, Eq, PartialEq)] +use anyhow::{anyhow, Context}; +use camino::{Utf8Path, Utf8PathBuf}; +use serde::Serialize; + +use rover_std::Fs; + +use crate::{RoverError, RoverErrorSuggestion, RoverResult}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub enum FileDescriptorType { Stdin, File(Utf8PathBuf), @@ -111,12 +113,14 @@ pub fn parse_header(header: &str) -> std::result::Result<(String, String), io::E #[cfg(test)] mod tests { - use super::FileDescriptorType; - use assert_fs::prelude::*; - use camino::Utf8PathBuf; use std::convert::TryFrom; use std::str::FromStr; + use assert_fs::prelude::*; + use camino::Utf8PathBuf; + + use super::FileDescriptorType; + #[test] fn it_correctly_parses_stdin_flag() { let fd = FileDescriptorType::from_str("-").unwrap(); diff --git a/src/command/supergraph/resolve_config.rs b/src/utils/supergraph_config.rs similarity index 60% rename from src/command/supergraph/resolve_config.rs rename to src/utils/supergraph_config.rs index 3aa660755..fced29c92 100644 --- a/src/command/supergraph/resolve_config.rs +++ b/src/utils/supergraph_config.rs @@ -1,26 +1,122 @@ use std::str::FromStr; use anyhow::anyhow; -use apollo_federation_types::{ - build::{BuildError, BuildErrors, SubgraphDefinition}, - config::{FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig}, +use apollo_federation_types::build::{BuildError, BuildErrors, SubgraphDefinition}; +use apollo_federation_types::config::{ + FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig, }; use apollo_parser::{cst, Parser}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; -use rover_client::operations::subgraph::introspect::{self, SubgraphIntrospectInput}; +use rover_client::blocking::GraphQLClient; +use rover_client::operations::subgraph; +use rover_client::operations::subgraph::fetch::SubgraphFetchInput; +use rover_client::operations::subgraph::introspect::SubgraphIntrospectInput; +use rover_client::operations::subgraph::list::SubgraphListInput; +use rover_client::operations::subgraph::{fetch, introspect}; use rover_client::shared::GraphRef; -use rover_client::{blocking::GraphQLClient, RoverClientError}; +use rover_client::RoverClientError; use rover_std::{Fs, Style}; -use crate::{ - options::ProfileOpt, - utils::{client::StudioClientConfig, expansion::expand, parsers::FileDescriptorType}, -}; +use crate::options::ProfileOpt; +use crate::utils::client::StudioClientConfig; +use crate::utils::expansion::expand; +use crate::utils::parsers::FileDescriptorType; use crate::{RoverError, RoverErrorSuggestion, RoverResult}; -pub(crate) fn expand_supergraph_yaml(content: &str) -> RoverResult { +/// Nominal type that captures the behavior of collecting remote subgraphs into a +/// [`SupergraphConfig`] representation +#[derive(Clone, Debug)] +pub struct RemoteSubgraphs(SupergraphConfig); + +impl RemoteSubgraphs { + /// Fetches [`RemoteSubgraphs`] from Studio + pub fn fetch( + client_config: &StudioClientConfig, + profile_opt: &ProfileOpt, + federation_version: &FederationVersion, + graph_ref: &GraphRef, + ) -> RoverResult { + let client = &client_config.get_authenticated_client(profile_opt)?; + + let subgraphs = subgraph::list::run( + SubgraphListInput { + graph_ref: graph_ref.clone(), + }, + client, + )?; + let subgraphs = subgraphs + .subgraphs + .iter() + .map(|subgraph| { + ( + subgraph.name.clone(), + SubgraphConfig { + routing_url: subgraph.url.clone(), + schema: SchemaSource::Subgraph { + graphref: graph_ref.clone().to_string(), + subgraph: subgraph.name.clone(), + }, + }, + ) + }) + .collect(); + let supergraph_config = SupergraphConfig::new(subgraphs, Some(federation_version.clone())); + let remote_subgraphs = RemoteSubgraphs(supergraph_config); + Ok(remote_subgraphs) + } + + /// Provides a reference to the inner value of this representation + pub fn inner(&self) -> &SupergraphConfig { + &self.0 + } +} + +pub fn get_supergraph_config( + graph_ref: &Option, + supergraph_config_path: &Option, + federation_version: &FederationVersion, + client_config: StudioClientConfig, + profile_opt: &ProfileOpt, +) -> Result, RoverError> { + // Read in Remote subgraphs + let remote_subgraphs = match graph_ref { + Some(graph_ref) => Some(RemoteSubgraphs::fetch( + &client_config, + profile_opt, + federation_version, + graph_ref, + )?), + None => None, + }; + + // Read in Local Supergraph Config + let supergraph_config = if let Some(file_descriptor) = &supergraph_config_path { + Some(resolve_supergraph_yaml( + file_descriptor, + client_config, + profile_opt, + )?) + } else { + None + }; + + // Merge Remote and Local Supergraph Configs + let supergraph_config = match remote_subgraphs { + Some(remote_subgraphs) => match supergraph_config { + Some(supergraph_config) => { + let mut merged_supergraph_config = remote_subgraphs.inner().clone(); + merged_supergraph_config.merge_subgraphs(&supergraph_config); + Some(merged_supergraph_config) + } + None => Some(remote_subgraphs.inner().clone()), + }, + None => supergraph_config, + }; + Ok(supergraph_config) +} + +fn expand_supergraph_yaml(content: &str) -> RoverResult { serde_yaml::from_str(content) .map_err(RoverError::from) .and_then(expand) @@ -270,14 +366,26 @@ pub(crate) fn resolve_supergraph_yaml( } #[cfg(test)] -mod test_expand_supergraph_yaml { +mod test { + use std::fs; + use apollo_federation_types::config::FederationVersion; + use assert_fs::TempDir; + use camino::Utf8PathBuf; + use rstest::{fixture, rstest}; + + use houston::Config; + + use crate::options::ProfileOpt; + use crate::utils::client::{ClientBuilder, StudioClientConfig}; + use crate::utils::parsers::FileDescriptorType; + use crate::utils::supergraph_config::resolve_supergraph_yaml; #[test] fn test_supergraph_yaml_int_version() { let yaml = r#" federation_version: 1 -subgraphs: +subgraphs: "#; let config = super::expand_supergraph_yaml(yaml).unwrap(); assert_eq!( @@ -285,4 +393,125 @@ subgraphs: Some(FederationVersion::LatestFedOne) ); } + + #[fixture] + fn client_config() -> StudioClientConfig { + let tmp_home = TempDir::new().unwrap(); + let tmp_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); + StudioClientConfig::new( + None, + Config::new(Some(&tmp_path), None).unwrap(), + false, + ClientBuilder::default(), + ) + } + + #[fixture] + fn profile_opt() -> ProfileOpt { + ProfileOpt { + profile_name: "profile".to_string(), + } + } + + #[rstest] + fn it_errs_on_invalid_subgraph_path( + client_config: StudioClientConfig, + profile_opt: ProfileOpt, + ) { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./films-do-not-exist.graphql + people: + routing_url: https://people.example.com + schema: + file: ./people-do-not-exist.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + assert!(resolve_supergraph_yaml( + &FileDescriptorType::File(config_path), + client_config, + &profile_opt + ) + .is_err()) + } + + #[rstest] + fn it_can_get_subgraph_definitions_from_fs( + client_config: StudioClientConfig, + profile_opt: ProfileOpt, + ) { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./films.graphql + people: + routing_url: https://people.example.com + schema: + file: ./people.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + let tmp_dir = config_path.parent().unwrap().to_path_buf(); + let films_path = tmp_dir.join("films.graphql"); + let people_path = tmp_dir.join("people.graphql"); + fs::write(films_path, "there is something here").unwrap(); + fs::write(people_path, "there is also something here").unwrap(); + assert!(resolve_supergraph_yaml( + &FileDescriptorType::File(config_path), + client_config, + &profile_opt + ) + .is_ok()) + } + + #[rstest] + fn it_can_compute_relative_schema_paths( + client_config: StudioClientConfig, + profile_opt: ProfileOpt, + ) { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ../../films.graphql + people: + routing_url: https://people.example.com + schema: + file: ../../people.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let tmp_dir = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); + let mut config_path = tmp_dir.clone(); + config_path.push("layer"); + config_path.push("layer"); + fs::create_dir_all(&config_path).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + let films_path = tmp_dir.join("films.graphql"); + let people_path = tmp_dir.join("people.graphql"); + fs::write(films_path, "there is something here").unwrap(); + fs::write(people_path, "there is also something here").unwrap(); + let subgraph_definitions = resolve_supergraph_yaml( + &FileDescriptorType::File(config_path), + client_config, + &profile_opt, + ) + .unwrap() + .get_subgraph_definitions() + .unwrap(); + let film_subgraph = subgraph_definitions.first().unwrap(); + let people_subgraph = subgraph_definitions.get(1).unwrap(); + + assert_eq!(film_subgraph.name, "films"); + assert_eq!(film_subgraph.url, "https://films.example.com"); + assert_eq!(film_subgraph.sdl, "there is something here"); + assert_eq!(people_subgraph.name, "people"); + assert_eq!(people_subgraph.url, "https://people.example.com"); + assert_eq!(people_subgraph.sdl, "there is also something here"); + } } From 4cb408accf72996ad46411191d5bd26371e25701 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 25 Jul 2024 14:03:55 +0100 Subject: [PATCH 52/68] ROVER-69 Migrate tests from #1977 --- Cargo.lock | 1 + Cargo.toml | 1 + src/command/supergraph/compose/do_compose.rs | 2 - src/utils/supergraph_config.rs | 415 ++++++++++++++++++- 4 files changed, 411 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66821291e..b691c3422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4474,6 +4474,7 @@ dependencies = [ "heck 0.5.0", "houston", "httpmock", + "indoc", "interprocess", "lazy_static", "lazycell", diff --git a/Cargo.toml b/Cargo.toml index 299b20feb..26c2ce15c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,6 +202,7 @@ dircpy = "0.3.18" duct = "0.13.7" git2 = { workspace = true, features = ["https"]} httpmock = { workspace = true } +indoc = { workspace = true } mime = "0.3.17" portpicker = "0.1.1" predicates = { workspace = true } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index e8de81524..a2681a59f 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -215,8 +215,6 @@ impl Compose { #[cfg(test)] mod tests { - use std::convert::TryFrom; - use rstest::rstest; use speculoos::assert_that; diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index fced29c92..4db7236ac 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -368,25 +368,35 @@ pub(crate) fn resolve_supergraph_yaml( #[cfg(test)] mod test { use std::fs; + use std::io::Write; + use std::string::ToString; - use apollo_federation_types::config::FederationVersion; + use anyhow::Result; + use apollo_federation_types::config::{FederationVersion, SchemaSource, SubgraphConfig}; use assert_fs::TempDir; use camino::Utf8PathBuf; + use httpmock::MockServer; + use indoc::indoc; use rstest::{fixture, rstest}; + use serde_json::json; + use speculoos::assert_that; + use speculoos::prelude::{ResultAssertions, VecAssertions}; use houston::Config; use crate::options::ProfileOpt; use crate::utils::client::{ClientBuilder, StudioClientConfig}; use crate::utils::parsers::FileDescriptorType; - use crate::utils::supergraph_config::resolve_supergraph_yaml; + + use super::*; #[test] fn test_supergraph_yaml_int_version() { - let yaml = r#" -federation_version: 1 -subgraphs: -"#; + let yaml = indoc! {r#" + federation_version: 1 + subgraphs: +"# + }; let config = super::expand_supergraph_yaml(yaml).unwrap(); assert_eq!( config.get_federation_version(), @@ -514,4 +524,397 @@ subgraphs: assert_eq!(people_subgraph.url, "https://people.example.com"); assert_eq!(people_subgraph.sdl, "there is also something here"); } + + const INTROSPECTION_SDL: &str = r#"directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @requires(fields: _FieldSet!) on FIELD_DEFINITION + +directive @provides(fields: _FieldSet!) on FIELD_DEFINITION + +directive @external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @extends on OBJECT | INTERFACE + +type Query {\n test: String!\n _service: _Service!\n} + +scalar _FieldSet + +scalar _Any + +type _Service {\n sdl: String\n}"#; + + #[fixture] + fn schema() -> String { + String::from(indoc! {r#" + type Query { + test: String! + } + "# + }) + } + + #[fixture] + #[once] + fn home_dir() -> Utf8PathBuf { + tempfile::tempdir() + .unwrap() + .path() + .to_path_buf() + .try_into() + .unwrap() + } + + #[fixture] + #[once] + fn api_key() -> String { + uuid::Uuid::new_v4().as_simple().to_string() + } + + #[fixture] + fn config(home_dir: &Utf8PathBuf, api_key: &String) -> Config { + Config::new(Some(home_dir), Some(api_key.to_string())).unwrap() + } + + #[fixture] + fn studio_client_config(config: Config) -> StudioClientConfig { + StudioClientConfig::new(None, config, false, ClientBuilder::default()) + } + + #[rstest] + fn test_subgraph_file_resolution( + schema: String, + profile_opt: ProfileOpt, + studio_client_config: StudioClientConfig, + ) -> Result<()> { + let mut schema_path = tempfile::NamedTempFile::new()?; + schema_path + .as_file_mut() + .write_all(&schema.clone().into_bytes())?; + let supergraph_config = format!( + indoc! {r#" + federation_version: 2 + subgraphs: + products: + routing_url: http://localhost:8000/ + schema: + file: {} +"# + }, + schema_path.path().to_str().unwrap() + ); + + let mut supergraph_config_path = tempfile::NamedTempFile::new()?; + supergraph_config_path + .as_file_mut() + .write_all(&supergraph_config.into_bytes())?; + + let unresolved_supergraph_config = + FileDescriptorType::File(supergraph_config_path.path().to_path_buf().try_into()?); + + let resolved_config = super::resolve_supergraph_yaml( + &unresolved_supergraph_config, + studio_client_config, + &profile_opt, + ); + + assert_that!(resolved_config).is_ok(); + let resolved_config = resolved_config.unwrap(); + + let subgraphs = resolved_config.into_iter().collect::>(); + assert_that!(subgraphs).has_length(1); + let subgraph = &subgraphs[0]; + assert_that!(subgraph).is_equal_to(&( + "products".to_string(), + SubgraphConfig { + routing_url: Some("http://localhost:8000/".to_string()), + schema: SchemaSource::Sdl { + sdl: schema.to_string(), + }, + }, + )); + + Ok(()) + } + + #[rstest] + fn test_subgraph_introspection_resolution( + profile_opt: ProfileOpt, + studio_client_config: StudioClientConfig, + ) -> Result<()> { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + let body = json!({ + "data": { + "_service": { + "sdl": INTROSPECTION_SDL + } + } + }); + when.method(httpmock::Method::POST).path("/"); + then.status(200) + .header("content-type", "application/json") + .json_body(body); + }); + + let supergraph_config = format!( + indoc! {r#" + federation_version: 2 + subgraphs: + products: + routing_url: {} + schema: + subgraph_url: {} +"# + }, + server.base_url(), + server.base_url() + ); + + let mut supergraph_config_path = tempfile::NamedTempFile::new()?; + supergraph_config_path + .as_file_mut() + .write_all(&supergraph_config.into_bytes())?; + + let unresolved_supergraph_config = + FileDescriptorType::File(supergraph_config_path.path().to_path_buf().try_into()?); + + let resolved_config = super::resolve_supergraph_yaml( + &unresolved_supergraph_config, + studio_client_config, + &profile_opt, + ); + + mock.assert_hits(1); + + assert_that!(resolved_config).is_ok(); + let resolved_config = resolved_config.unwrap(); + + let subgraphs = resolved_config.into_iter().collect::>(); + assert_that!(subgraphs).has_length(1); + let subgraph = &subgraphs[0]; + assert_that!(subgraph).is_equal_to(&( + "products".to_string(), + SubgraphConfig { + routing_url: Some(server.base_url()), + schema: SchemaSource::Sdl { + sdl: INTROSPECTION_SDL.to_string(), + }, + }, + )); + + Ok(()) + } + + #[rstest] + fn test_subgraph_studio_resolution(profile_opt: ProfileOpt, config: Config) -> Result<()> { + let graph_id = "testgraph"; + let variant = "current"; + let graphref = format!("{}@{}", graph_id, variant); + let server = MockServer::start(); + + let subgraph_fetch_mock = server.mock(|when, then| { + let body = json!({ + "data": { + "variant": { + "__typename": "GraphVariant", + "subgraph": { + "url": server.base_url(), + "activePartialSchema": { + "sdl": INTROSPECTION_SDL + } + }, + "subgraphs": [ + { + "name": "products" + } + ] + } + } + }); + when.method(httpmock::Method::POST) + .path("/") + .json_body_obj(&json!({ + "query": indoc!{ + r#" + query SubgraphFetchQuery($graph_ref: ID!, $subgraph_name: ID!) { + variant(ref: $graph_ref) { + __typename + ... on GraphVariant { + subgraph(name: $subgraph_name) { + url, + activePartialSchema { + sdl + } + } + subgraphs { + name + } + } + } + } + "# + }, + "variables": { + "graph_ref": graphref, + "subgraph_name": "products" + }, + "operationName": "SubgraphFetchQuery" + })); + then.status(200) + .header("content-type", "application/json") + .json_body(body); + }); + + let is_federated_mock = server.mock(|when, then| { + let body = json!({ + "data": { + "graph": { + "variant": { + "subgraphs": [ + { + "name": "products" + } + ] + } + } + } + }); + when.method(httpmock::Method::POST) + .path("/") + .json_body_obj(&json!({ + "query": indoc!{ + r#" + query IsFederatedGraph($graph_id: ID!, $variant: String!) { + graph(id: $graph_id) { + variant(name: $variant) { + subgraphs { + name + } + } + } + } + "# + }, + "variables": { + "graph_id": graph_id, + "variant": variant + }, + "operationName": "IsFederatedGraph" + })); + then.status(200) + .header("content-type", "application/json") + .json_body(body); + }); + + let supergraph_config = format!( + indoc! {r#" + federation_version: 2 + subgraphs: + products: + schema: + graphref: {} + subgraph: products +"# + }, + graphref + ); + + let studio_client_config = StudioClientConfig::new( + Some(server.base_url()), + config, + false, + ClientBuilder::default(), + ); + + let mut supergraph_config_path = tempfile::NamedTempFile::new()?; + supergraph_config_path + .as_file_mut() + .write_all(&supergraph_config.into_bytes())?; + + let unresolved_supergraph_config = + FileDescriptorType::File(supergraph_config_path.path().to_path_buf().try_into()?); + + let resolved_config = super::resolve_supergraph_yaml( + &unresolved_supergraph_config, + studio_client_config, + &profile_opt, + ); + + assert_that!(resolved_config).is_ok(); + let resolved_config = resolved_config.unwrap(); + + is_federated_mock.assert_hits(1); + subgraph_fetch_mock.assert_hits(1); + + let subgraphs = resolved_config.into_iter().collect::>(); + assert_that!(subgraphs).has_length(1); + let subgraph = &subgraphs[0]; + assert_that!(subgraph).is_equal_to(&( + "products".to_string(), + SubgraphConfig { + routing_url: Some(server.base_url()), + schema: SchemaSource::Sdl { + sdl: INTROSPECTION_SDL.to_string(), + }, + }, + )); + + Ok(()) + } + + #[rstest] + fn test_subgraph_sdl_resolution( + schema: String, + profile_opt: ProfileOpt, + studio_client_config: StudioClientConfig, + ) -> Result<()> { + let supergraph_config = format!( + indoc! { + r#" + federation_version: 2 + subgraphs: + products: + routing_url: http://localhost:8000/ + schema: + sdl: "{}" + "# + }, + schema.escape_default() + ); + + let mut supergraph_config_path = tempfile::NamedTempFile::new()?; + supergraph_config_path + .as_file_mut() + .write_all(&supergraph_config.into_bytes())?; + + let unresolved_supergraph_config = + FileDescriptorType::File(supergraph_config_path.path().to_path_buf().try_into()?); + + let resolved_config = super::resolve_supergraph_yaml( + &unresolved_supergraph_config, + studio_client_config, + &profile_opt, + ); + + assert_that!(resolved_config).is_ok(); + let resolved_config = resolved_config.unwrap(); + + let subgraphs = resolved_config.into_iter().collect::>(); + assert_that!(subgraphs).has_length(1); + let subgraph = &subgraphs[0]; + assert_that!(subgraph).is_equal_to(&( + "products".to_string(), + SubgraphConfig { + routing_url: Some("http://localhost:8000/".to_string()), + schema: SchemaSource::Sdl { + sdl: schema.to_string(), + }, + }, + )); + + Ok(()) + } } From 3b9aff7f64a5ff5043d18de3ee9d841eefcf6fea Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 25 Jul 2024 07:29:03 +0100 Subject: [PATCH 53/68] ROVER-69 Fix Federation Versions to remove WARNs --- src/utils/supergraph_config.rs | 164 +++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 68 deletions(-) diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index 4db7236ac..2f8e1f35a 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -116,13 +116,6 @@ pub fn get_supergraph_config( Ok(supergraph_config) } -fn expand_supergraph_yaml(content: &str) -> RoverResult { - serde_yaml::from_str(content) - .map_err(RoverError::from) - .and_then(expand) - .and_then(|v| serde_yaml::from_value(v).map_err(RoverError::from)) -} - pub(crate) fn resolve_supergraph_yaml( unresolved_supergraph_yaml: &FileDescriptorType, client_config: StudioClientConfig, @@ -365,10 +358,19 @@ pub(crate) fn resolve_supergraph_yaml( Ok(resolved_supergraph_config) } +fn expand_supergraph_yaml(content: &str) -> RoverResult { + serde_yaml::from_str(content) + .map_err(RoverError::from) + .and_then(expand) + .and_then(|v| serde_yaml::from_value(v).map_err(RoverError::from)) +} + #[cfg(test)] -mod test { +mod test_resolve_supergraph_yaml { use std::fs; + use std::fs::File; use std::io::Write; + use std::path::PathBuf; use std::string::ToString; use anyhow::Result; @@ -378,7 +380,8 @@ mod test { use httpmock::MockServer; use indoc::indoc; use rstest::{fixture, rstest}; - use serde_json::json; + use semver::Version; + use serde_json::{json, Value}; use speculoos::assert_that; use speculoos::prelude::{ResultAssertions, VecAssertions}; @@ -390,6 +393,59 @@ mod test { use super::*; + #[fixture] + fn profile_opt() -> ProfileOpt { + ProfileOpt { + profile_name: "profile".to_string(), + } + } + + #[fixture] + #[once] + fn home_dir() -> Utf8PathBuf { + tempfile::tempdir() + .unwrap() + .path() + .to_path_buf() + .try_into() + .unwrap() + } + + #[fixture] + #[once] + fn api_key() -> String { + uuid::Uuid::new_v4().as_simple().to_string() + } + + #[fixture] + fn config(home_dir: &Utf8PathBuf, api_key: &String) -> Config { + Config::new(Some(home_dir), Some(api_key.to_string())).unwrap() + } + + #[fixture] + fn client_config(config: Config) -> StudioClientConfig { + StudioClientConfig::new(None, config, false, ClientBuilder::default()) + } + + #[fixture] + #[once] + fn latest_fed2_version() -> FederationVersion { + let d = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("latest_plugin_versions.json"); + let fp = File::open(d).expect("could not open version file"); + let raw_version_file: Value = serde_json::from_reader(fp).expect("malformed JSON"); + let raw_version = raw_version_file + .get("supergraph") + .unwrap() + .get("versions") + .unwrap() + .get("latest-2") + .unwrap() + .as_str() + .unwrap(); + let version = Version::from_str(&raw_version.replace("v", "")).unwrap(); + FederationVersion::ExactFedTwo(version) + } + #[test] fn test_supergraph_yaml_int_version() { let yaml = indoc! {r#" @@ -404,25 +460,6 @@ mod test { ); } - #[fixture] - fn client_config() -> StudioClientConfig { - let tmp_home = TempDir::new().unwrap(); - let tmp_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - StudioClientConfig::new( - None, - Config::new(Some(&tmp_path), None).unwrap(), - false, - ClientBuilder::default(), - ) - } - - #[fixture] - fn profile_opt() -> ProfileOpt { - ProfileOpt { - profile_name: "profile".to_string(), - } - } - #[rstest] fn it_errs_on_invalid_subgraph_path( client_config: StudioClientConfig, @@ -453,8 +490,12 @@ mod test { fn it_can_get_subgraph_definitions_from_fs( client_config: StudioClientConfig, profile_opt: ProfileOpt, + latest_fed2_version: &FederationVersion, ) { - let raw_good_yaml = r#"subgraphs: + let raw_good_yaml = format!( + r#" +federation_version: {} +subgraphs: films: routing_url: https://films.example.com schema: @@ -462,7 +503,9 @@ mod test { people: routing_url: https://people.example.com schema: - file: ./people.graphql"#; + file: ./people.graphql"#, + latest_fed2_version.to_string() + ); let tmp_home = TempDir::new().unwrap(); let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); config_path.push("config.yaml"); @@ -484,8 +527,12 @@ mod test { fn it_can_compute_relative_schema_paths( client_config: StudioClientConfig, profile_opt: ProfileOpt, + latest_fed2_version: &FederationVersion, ) { - let raw_good_yaml = r#"subgraphs: + let raw_good_yaml = format!( + r#" +federation_version: {} +subgraphs: films: routing_url: https://films.example.com schema: @@ -493,7 +540,9 @@ mod test { people: routing_url: https://people.example.com schema: - file: ../../people.graphql"#; + file: ../../people.graphql"#, + latest_fed2_version.to_string() + ); let tmp_home = TempDir::new().unwrap(); let tmp_dir = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); let mut config_path = tmp_dir.clone(); @@ -555,38 +604,12 @@ type _Service {\n sdl: String\n}"#; }) } - #[fixture] - #[once] - fn home_dir() -> Utf8PathBuf { - tempfile::tempdir() - .unwrap() - .path() - .to_path_buf() - .try_into() - .unwrap() - } - - #[fixture] - #[once] - fn api_key() -> String { - uuid::Uuid::new_v4().as_simple().to_string() - } - - #[fixture] - fn config(home_dir: &Utf8PathBuf, api_key: &String) -> Config { - Config::new(Some(home_dir), Some(api_key.to_string())).unwrap() - } - - #[fixture] - fn studio_client_config(config: Config) -> StudioClientConfig { - StudioClientConfig::new(None, config, false, ClientBuilder::default()) - } - #[rstest] fn test_subgraph_file_resolution( schema: String, profile_opt: ProfileOpt, - studio_client_config: StudioClientConfig, + client_config: StudioClientConfig, + latest_fed2_version: &FederationVersion, ) -> Result<()> { let mut schema_path = tempfile::NamedTempFile::new()?; schema_path @@ -594,7 +617,7 @@ type _Service {\n sdl: String\n}"#; .write_all(&schema.clone().into_bytes())?; let supergraph_config = format!( indoc! {r#" - federation_version: 2 + federation_version: {} subgraphs: products: routing_url: http://localhost:8000/ @@ -602,6 +625,7 @@ type _Service {\n sdl: String\n}"#; file: {} "# }, + latest_fed2_version.to_string(), schema_path.path().to_str().unwrap() ); @@ -615,7 +639,7 @@ type _Service {\n sdl: String\n}"#; let resolved_config = super::resolve_supergraph_yaml( &unresolved_supergraph_config, - studio_client_config, + client_config, &profile_opt, ); @@ -641,7 +665,8 @@ type _Service {\n sdl: String\n}"#; #[rstest] fn test_subgraph_introspection_resolution( profile_opt: ProfileOpt, - studio_client_config: StudioClientConfig, + client_config: StudioClientConfig, + latest_fed2_version: &FederationVersion, ) -> Result<()> { let server = MockServer::start(); @@ -661,7 +686,7 @@ type _Service {\n sdl: String\n}"#; let supergraph_config = format!( indoc! {r#" - federation_version: 2 + federation_version: {} subgraphs: products: routing_url: {} @@ -669,6 +694,7 @@ type _Service {\n sdl: String\n}"#; subgraph_url: {} "# }, + latest_fed2_version.to_string(), server.base_url(), server.base_url() ); @@ -683,7 +709,7 @@ type _Service {\n sdl: String\n}"#; let resolved_config = super::resolve_supergraph_yaml( &unresolved_supergraph_config, - studio_client_config, + client_config, &profile_opt, ); @@ -869,12 +895,13 @@ type _Service {\n sdl: String\n}"#; fn test_subgraph_sdl_resolution( schema: String, profile_opt: ProfileOpt, - studio_client_config: StudioClientConfig, + client_config: StudioClientConfig, + latest_fed2_version: &FederationVersion, ) -> Result<()> { let supergraph_config = format!( indoc! { r#" - federation_version: 2 + federation_version: {} subgraphs: products: routing_url: http://localhost:8000/ @@ -882,6 +909,7 @@ type _Service {\n sdl: String\n}"#; sdl: "{}" "# }, + latest_fed2_version.to_string(), schema.escape_default() ); @@ -895,7 +923,7 @@ type _Service {\n sdl: String\n}"#; let resolved_config = super::resolve_supergraph_yaml( &unresolved_supergraph_config, - studio_client_config, + client_config, &profile_opt, ); From 1792645a5d88c048551cf8b325cc53b4a7b7352e Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 25 Jul 2024 07:30:58 +0100 Subject: [PATCH 54/68] ROVER-69 Fix docs --- src/command/supergraph/compose/do_compose.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index a2681a59f..a85f846da 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -47,7 +47,7 @@ pub struct SupergraphComposeOpts { /// /// This is analogous to providing a supergraph.yaml file with references to your graph variant in studio. /// - /// If used in conjunction with `--supergraph-config`, the values presented in the supergraph.yaml will take precedence over these values. + /// If used in conjunction with `--config`, the values presented in the supergraph.yaml will take precedence over these values. #[arg(long = "graph-ref")] graph_ref: Option, From 82ef6d5a0f55b5254e14ec43ddbb6d50577f9504 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 25 Jul 2024 07:39:30 +0100 Subject: [PATCH 55/68] ROVER-69 Optimise --graph-ref uses Rather than fetching each subgraph one at a time, make a single GraphQL request to get them all at once. This combines the work from #1985 --- Cargo.lock | 58 ++++ Cargo.toml | 2 + crates/rover-client/Cargo.toml | 2 + .../fetch_all/fetch_all_query.graphql | 14 + .../src/operations/subgraph/fetch_all/mod.rs | 5 + .../operations/subgraph/fetch_all/runner.rs | 123 +++++++ .../operations/subgraph/fetch_all/types.rs | 31 ++ .../src/operations/subgraph/mod.rs | 3 + src/utils/supergraph_config.rs | 311 ++++++++++++++++-- 9 files changed, 525 insertions(+), 24 deletions(-) create mode 100644 crates/rover-client/src/operations/subgraph/fetch_all/fetch_all_query.graphql create mode 100644 crates/rover-client/src/operations/subgraph/fetch_all/mod.rs create mode 100644 crates/rover-client/src/operations/subgraph/fetch_all/runner.rs create mode 100644 crates/rover-client/src/operations/subgraph/fetch_all/types.rs diff --git a/Cargo.lock b/Cargo.lock index b691c3422..3235246fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -758,6 +758,21 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" +[[package]] +name = "buildstructor" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3907aac66c65520545ae3cb3c195306e20d5ed5c90bfbb992e061cf12a104d0" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "str_inflector", + "syn 2.0.72", + "thiserror", + "try_match", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1565,6 +1580,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive-getters" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6433aac097572ea8ccc60b3f2e756c661c9aeed9225cdd4d0cb119cb7ff6ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -4525,8 +4551,10 @@ dependencies = [ "apollo-parser", "ariadne", "backoff", + "buildstructor", "camino", "chrono", + "derive-getters", "git-url-parse", "git2", "graphql_client", @@ -5236,6 +5264,16 @@ dependencies = [ "wsl", ] +[[package]] +name = "str_inflector" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0b848d5a7695b33ad1be00f84a3c079fe85c9278a325ff9159e6c99cef4ef7" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "strict" version = "0.2.0" @@ -5904,6 +5942,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "try_match" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b065c869a3f832418e279aa4c1d7088f9d5d323bde15a60a08e20c2cd4549082" +dependencies = [ + "try_match_inner", +] + +[[package]] +name = "try_match_inner" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c81686f7ab4065ccac3df7a910c4249f8c0f3fb70421d6ddec19b9311f63f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "typed-builder" version = "0.18.2" diff --git a/Cargo.toml b/Cargo.toml index 26c2ce15c..6408d020d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ backtrace = "0.3" backoff = "0.4" base64 = "0.22" billboard = "0.2" +buildstructor = "0.5.4" cargo_metadata = "0.18" calm_io = "0.1" camino = "1" @@ -84,6 +85,7 @@ ci_info = "0.14" console = "0.15" crossbeam-channel = "0.5" ctrlc = "3" +derive-getters = "0.4.0" dialoguer = "0.11" directories-next = "2.0" flate2 = "1" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 8be0b2f55..a69a5ccd3 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -13,7 +13,9 @@ apollo-federation-types = { workspace = true } apollo-parser = { workspace = true } apollo-encoder = { workspace = true } backoff = { workspace = true } +buildstructor = { workspace = true } chrono = { workspace = true, features = ["serde"] } +derive-getters = { workspace = true } git-url-parse = { workspace = true } git2 = { workspace = true, features = [ "vendored-openssl", diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/fetch_all_query.graphql b/crates/rover-client/src/operations/subgraph/fetch_all/fetch_all_query.graphql new file mode 100644 index 000000000..6233fde6d --- /dev/null +++ b/crates/rover-client/src/operations/subgraph/fetch_all/fetch_all_query.graphql @@ -0,0 +1,14 @@ +query SubgraphFetchAllQuery($graph_ref: ID!) { + variant(ref: $graph_ref) { + __typename + ... on GraphVariant { + subgraphs { + name + url + activePartialSchema { + sdl + } + } + } + } +} diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/mod.rs b/crates/rover-client/src/operations/subgraph/fetch_all/mod.rs new file mode 100644 index 000000000..3c7d12e25 --- /dev/null +++ b/crates/rover-client/src/operations/subgraph/fetch_all/mod.rs @@ -0,0 +1,5 @@ +mod runner; +mod types; + +pub use runner::run; +pub use types::SubgraphFetchAllInput; diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs b/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs new file mode 100644 index 000000000..33af7da27 --- /dev/null +++ b/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs @@ -0,0 +1,123 @@ +use graphql_client::*; + +use crate::blocking::StudioClient; +use crate::operations::config::is_federated::{self, IsFederatedInput}; +use crate::RoverClientError; + +use super::types::*; + +#[derive(GraphQLQuery)] +// The paths are relative to the directory where your `Cargo.toml` is located. +// Both json and the GraphQL schema language are supported as sources for the schema +#[graphql( + query_path = "src/operations/subgraph/fetch_all/fetch_all_query.graphql", + schema_path = ".schema/schema.graphql", + response_derives = "Eq, PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. subgraph_fetch_all_query +pub(crate) struct SubgraphFetchAllQuery; + +/// For a given graph return all of its subgraphs as a list +pub fn run( + input: SubgraphFetchAllInput, + client: &StudioClient, +) -> Result, RoverClientError> { + let variables = input.clone().into(); + let response_data = client.post::(variables)?; + get_subgraphs_from_response_data(input, response_data) +} + +fn get_subgraphs_from_response_data( + input: SubgraphFetchAllInput, + response_data: SubgraphFetchAllResponseData, +) -> Result, RoverClientError> { + if let Some(maybe_variant) = response_data.variant { + match maybe_variant { + SubgraphFetchAllGraphVariant::GraphVariant(variant) => { + if let Some(subgraphs) = variant.subgraphs { + Ok(subgraphs + .into_iter() + .map(|subgraph| { + Subgraph::builder() + .name(subgraph.name.clone()) + .and_url(subgraph.url) + .sdl(subgraph.active_partial_schema.sdl) + .build() + }) + .collect()) + } else { + Err(RoverClientError::ExpectedFederatedGraph { + graph_ref: input.graph_ref, + can_operation_convert: true, + }) + } + } + _ => Err(RoverClientError::InvalidGraphRef), + } + } else { + Err(RoverClientError::GraphNotFound { + graph_ref: input.graph_ref, + }) + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::shared::GraphRef; + + use super::*; + + #[test] + fn get_services_from_response_data_works() { + let sdl = "extend type User @key(fields: \"id\") {\n id: ID! @external\n age: Int\n}\n" + .to_string(); + let url = "http://my.subgraph.com".to_string(); + let input = mock_input(); + let json_response = json!({ + "variant": { + "__typename": "GraphVariant", + "subgraphs": [ + { + "name": "accounts", + "url": &url, + "activePartialSchema": { + "sdl": &sdl + } + }, + ] + } + }); + let data: SubgraphFetchAllResponseData = serde_json::from_value(json_response).unwrap(); + let expected_subgraph = Subgraph::builder() + .url(url) + .sdl(sdl) + .name("accounts".to_string()) + .build(); + let output = get_subgraphs_from_response_data(input, data); + + assert!(output.is_ok()); + assert_eq!(output.unwrap(), vec![expected_subgraph]); + } + + #[test] + fn get_services_from_response_data_errs_with_no_variant() { + let json_response = json!({ "variant": null }); + let data: SubgraphFetchAllResponseData = serde_json::from_value(json_response).unwrap(); + let output = get_subgraphs_from_response_data(mock_input(), data); + assert!(output.is_err()); + } + + fn mock_input() -> SubgraphFetchAllInput { + let graph_ref = GraphRef { + name: "mygraph".to_string(), + variant: "current".to_string(), + }; + + SubgraphFetchAllInput { graph_ref } + } +} diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/types.rs b/crates/rover-client/src/operations/subgraph/fetch_all/types.rs new file mode 100644 index 000000000..7462dd6ee --- /dev/null +++ b/crates/rover-client/src/operations/subgraph/fetch_all/types.rs @@ -0,0 +1,31 @@ +use buildstructor::Builder; +use derive_getters::Getters; + +use crate::shared::GraphRef; + +use super::runner::subgraph_fetch_all_query; + +pub(crate) type SubgraphFetchAllResponseData = subgraph_fetch_all_query::ResponseData; +pub(crate) type SubgraphFetchAllGraphVariant = + subgraph_fetch_all_query::SubgraphFetchAllQueryVariant; +pub(crate) type QueryVariables = subgraph_fetch_all_query::Variables; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SubgraphFetchAllInput { + pub graph_ref: GraphRef, +} + +impl From for QueryVariables { + fn from(input: SubgraphFetchAllInput) -> Self { + Self { + graph_ref: input.graph_ref.to_string(), + } + } +} + +#[derive(Clone, Builder, Debug, Eq, Getters, PartialEq)] +pub struct Subgraph { + name: String, + url: Option, + sdl: String, +} diff --git a/crates/rover-client/src/operations/subgraph/mod.rs b/crates/rover-client/src/operations/subgraph/mod.rs index a9f057c56..61c110f2b 100644 --- a/crates/rover-client/src/operations/subgraph/mod.rs +++ b/crates/rover-client/src/operations/subgraph/mod.rs @@ -10,6 +10,9 @@ pub mod check; /// "subgraph fetch" command execution pub mod fetch; +/// "subgraph fetch_all" command execution +pub mod fetch_all; + /// "subgraph publish" command execution pub mod publish; diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index 2f8e1f35a..5fce3bfd5 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -8,11 +8,11 @@ use apollo_federation_types::config::{ use apollo_parser::{cst, Parser}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use rover_client::blocking::GraphQLClient; +use rover_client::blocking::{GraphQLClient, StudioClient}; use rover_client::operations::subgraph; use rover_client::operations::subgraph::fetch::SubgraphFetchInput; +use rover_client::operations::subgraph::fetch_all::SubgraphFetchAllInput; use rover_client::operations::subgraph::introspect::SubgraphIntrospectInput; -use rover_client::operations::subgraph::list::SubgraphListInput; use rover_client::operations::subgraph::{fetch, introspect}; use rover_client::shared::GraphRef; use rover_client::RoverClientError; @@ -32,30 +32,25 @@ pub struct RemoteSubgraphs(SupergraphConfig); impl RemoteSubgraphs { /// Fetches [`RemoteSubgraphs`] from Studio pub fn fetch( - client_config: &StudioClientConfig, - profile_opt: &ProfileOpt, + client: &StudioClient, federation_version: &FederationVersion, graph_ref: &GraphRef, ) -> RoverResult { - let client = &client_config.get_authenticated_client(profile_opt)?; - - let subgraphs = subgraph::list::run( - SubgraphListInput { + let subgraphs = subgraph::fetch_all::run( + SubgraphFetchAllInput { graph_ref: graph_ref.clone(), }, client, )?; let subgraphs = subgraphs - .subgraphs .iter() .map(|subgraph| { ( - subgraph.name.clone(), + subgraph.name().clone(), SubgraphConfig { - routing_url: subgraph.url.clone(), - schema: SchemaSource::Subgraph { - graphref: graph_ref.clone().to_string(), - subgraph: subgraph.name.clone(), + routing_url: subgraph.url().clone(), + schema: SchemaSource::Sdl { + sdl: subgraph.sdl().clone(), }, }, ) @@ -81,12 +76,14 @@ pub fn get_supergraph_config( ) -> Result, RoverError> { // Read in Remote subgraphs let remote_subgraphs = match graph_ref { - Some(graph_ref) => Some(RemoteSubgraphs::fetch( - &client_config, - profile_opt, - federation_version, - graph_ref, - )?), + Some(graph_ref) => { + let studio_client = client_config.get_authenticated_client(profile_opt)?; + Some(RemoteSubgraphs::fetch( + &studio_client, + federation_version, + graph_ref, + )?) + } None => None, }; @@ -116,6 +113,268 @@ pub fn get_supergraph_config( Ok(supergraph_config) } +#[cfg(test)] +mod test_get_supergraph_config { + use std::fs::File; + use std::io::Write; + use std::path::PathBuf; + use std::str::FromStr; + + use apollo_federation_types::config::FederationVersion; + use camino::Utf8PathBuf; + use httpmock::MockServer; + use indoc::indoc; + use rstest::{fixture, rstest}; + use semver::Version; + use serde_json::{json, Value}; + use speculoos::assert_that; + use speculoos::prelude::OptionAssertions; + + use houston::Config; + use rover_client::shared::GraphRef; + + use crate::options::ProfileOpt; + use crate::utils::client::{ClientBuilder, StudioClientConfig}; + use crate::utils::parsers::FileDescriptorType; + use crate::utils::supergraph_config::get_supergraph_config; + + #[fixture] + #[once] + fn home_dir() -> Utf8PathBuf { + tempfile::tempdir() + .unwrap() + .path() + .to_path_buf() + .try_into() + .unwrap() + } + + #[fixture] + #[once] + fn api_key() -> String { + uuid::Uuid::new_v4().as_simple().to_string() + } + + #[fixture] + fn config(home_dir: &Utf8PathBuf, api_key: &String) -> Config { + Config::new(Some(home_dir), Some(api_key.to_string())).unwrap() + } + + #[fixture] + fn profile_opt() -> ProfileOpt { + ProfileOpt { + profile_name: "profile".to_string(), + } + } + + #[fixture] + #[once] + fn latest_fed2_version() -> FederationVersion { + let d = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("latest_plugin_versions.json"); + let fp = File::open(d).expect("could not open version file"); + let raw_version_file: Value = serde_json::from_reader(fp).expect("malformed JSON"); + let raw_version = raw_version_file + .get("supergraph") + .unwrap() + .get("versions") + .unwrap() + .get("latest-2") + .unwrap() + .as_str() + .unwrap(); + let version = Version::from_str(&raw_version.replace("v", "")).unwrap(); + FederationVersion::ExactFedTwo(version) + } + + #[rstest] + #[case::no_subgraphs_at_all(None, None, None)] + #[case::only_remote_subgraphs(Some(String::from("products")), None, Some(vec![(String::from("products"), String::from("remote"))]))] + #[case::only_local_subgraphs(None, Some(String::from("pandas")), Some(vec![(String::from("pandas"), String::from("local"))]))] + #[case::both_local_and_remote_subgraphs(Some(String::from("products")), Some(String::from("pandas")), Some(vec![(String::from("pandas"), String::from("local")), (String::from("products"), String::from("remote"))]))] + #[case::local_takes_precedence(Some(String::from("pandas")), Some(String::from("pandas")), Some(vec![(String::from("pandas"), String::from("local"))]))] + fn test_get_supergraph_config( + config: Config, + profile_opt: ProfileOpt, + latest_fed2_version: &FederationVersion, + #[case] remote_subgraph: Option, + #[case] local_subgraph: Option, + #[case] expected: Option>, + ) { + let server = MockServer::start(); + let sdl = "extend type User @key(fields: \"id\") {\n id: ID! @external\n age: Int\n}\n" + .to_string(); + let graphref = if let Some(name) = remote_subgraph { + let variant = String::from("current"); + let graphref_raw = format!("{name}@{variant}"); + let url = format!("http://{}.remote.com", name); + server.mock(|when, then| { + let body = json!({ + "data": { + "variant": { + "__typename": "GraphVariant", + "subgraphs": [ + { + "name": name, + "url": url, + "activePartialSchema": { + "sdl": sdl + } + } + ] + } + } + }); + when.method(httpmock::Method::POST) + .path("/") + .json_body_obj(&json!({ + "query": indoc!{ + r#" + query SubgraphFetchAllQuery($graph_ref: ID!) { + variant(ref: $graph_ref) { + __typename + ... on GraphVariant { + subgraphs { + name + url + activePartialSchema { + sdl + } + } + } + } + } + "# + }, + "variables": { + "graph_ref": graphref_raw, + }, + "operationName": "SubgraphFetchAllQuery" + })); + then.status(200) + .header("content-type", "application/json") + .json_body(body); + }); + + server.mock(|when, then| { + let body = json!({ + "data": { + "graph": { + "variant": { + "subgraphs": [ + { + "name": name + } + ] + } + } + } + }); + when.method(httpmock::Method::POST) + .path("/") + .json_body_obj(&json!({ + "query": indoc!{ + r#" + query IsFederatedGraph($graph_id: ID!, $variant: String!) { + graph(id: $graph_id) { + variant(name: $variant) { + subgraphs { + name + } + } + } + } + "# + }, + "variables": { + "graph_id": name, + "variant": "current" + }, + "operationName": "IsFederatedGraph" + })); + then.status(200) + .header("content-type", "application/json") + .json_body(body); + }); + Some(GraphRef::new(name, Some(variant)).unwrap()) + } else { + None + }; + + let studio_client_config = StudioClientConfig::new( + Some(server.base_url()), + config, + false, + ClientBuilder::default(), + ); + + let actual_result = if let Some(name) = local_subgraph { + let supergraph_config = format!( + indoc! { + r#" + federation_version: {} + subgraphs: + {}: + routing_url: http://{}.local.com + schema: + sdl: "{}" + "# + }, + latest_fed2_version.to_string(), + name, + name, + sdl.escape_default() + ); + let mut supergraph_config_path = + tempfile::NamedTempFile::new().expect("Could not create temporary file"); + supergraph_config_path + .as_file_mut() + .write_all(&supergraph_config.into_bytes()) + .expect("Could not write to temporary file"); + + get_supergraph_config( + &graphref, + &Some(FileDescriptorType::File( + Utf8PathBuf::from_path_buf(supergraph_config_path.path().to_path_buf()) + .unwrap(), + )), + latest_fed2_version, + studio_client_config, + &profile_opt, + ) + .expect("Could not construct SupergraphConfig") + } else { + get_supergraph_config( + &graphref, + &None, + latest_fed2_version, + studio_client_config, + &profile_opt, + ) + .expect("Could not construct SupergraphConfig") + }; + + if expected.is_none() { + assert_that!(actual_result).is_none() + } else { + assert_that(&actual_result).is_some(); + for (idx, subgraph) in actual_result + .unwrap() + .get_subgraph_definitions() + .unwrap() + .iter() + .enumerate() + { + let expected_result = expected.as_ref().unwrap(); + assert_that!(subgraph.name).is_equal_to(&expected_result[idx].0); + assert_that!(subgraph.url).is_equal_to(format!( + "http://{}.{}.com", + expected_result[idx].0, expected_result[idx].1 + )) + } + } + } +} + pub(crate) fn resolve_supergraph_yaml( unresolved_supergraph_yaml: &FileDescriptorType, client_config: StudioClientConfig, @@ -735,7 +994,11 @@ type _Service {\n sdl: String\n}"#; } #[rstest] - fn test_subgraph_studio_resolution(profile_opt: ProfileOpt, config: Config) -> Result<()> { + fn test_subgraph_studio_resolution( + profile_opt: ProfileOpt, + config: Config, + latest_fed2_version: &FederationVersion, + ) -> Result<()> { let graph_id = "testgraph"; let variant = "current"; let graphref = format!("{}@{}", graph_id, variant); @@ -837,7 +1100,7 @@ type _Service {\n sdl: String\n}"#; let supergraph_config = format!( indoc! {r#" - federation_version: 2 + federation_version: {} subgraphs: products: schema: @@ -845,7 +1108,7 @@ type _Service {\n sdl: String\n}"#; subgraph: products "# }, - graphref + latest_fed2_version, graphref ); let studio_client_config = StudioClientConfig::new( @@ -863,7 +1126,7 @@ type _Service {\n sdl: String\n}"#; let unresolved_supergraph_config = FileDescriptorType::File(supergraph_config_path.path().to_path_buf().try_into()?); - let resolved_config = super::resolve_supergraph_yaml( + let resolved_config = resolve_supergraph_yaml( &unresolved_supergraph_config, studio_client_config, &profile_opt, From 95dd8d6069e5e236ec73a2238b50bc580b8c48b5 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Tue, 30 Jul 2024 16:45:39 +0100 Subject: [PATCH 56/68] ROVER-69 Update to account for #2004 --- src/utils/supergraph_config.rs | 42 ---------------------------------- 1 file changed, 42 deletions(-) diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index 5fce3bfd5..34fb48196 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -1057,47 +1057,6 @@ type _Service {\n sdl: String\n}"#; .json_body(body); }); - let is_federated_mock = server.mock(|when, then| { - let body = json!({ - "data": { - "graph": { - "variant": { - "subgraphs": [ - { - "name": "products" - } - ] - } - } - } - }); - when.method(httpmock::Method::POST) - .path("/") - .json_body_obj(&json!({ - "query": indoc!{ - r#" - query IsFederatedGraph($graph_id: ID!, $variant: String!) { - graph(id: $graph_id) { - variant(name: $variant) { - subgraphs { - name - } - } - } - } - "# - }, - "variables": { - "graph_id": graph_id, - "variant": variant - }, - "operationName": "IsFederatedGraph" - })); - then.status(200) - .header("content-type", "application/json") - .json_body(body); - }); - let supergraph_config = format!( indoc! {r#" federation_version: {} @@ -1135,7 +1094,6 @@ type _Service {\n sdl: String\n}"#; assert_that!(resolved_config).is_ok(); let resolved_config = resolved_config.unwrap(); - is_federated_mock.assert_hits(1); subgraph_fetch_mock.assert_hits(1); let subgraphs = resolved_config.into_iter().collect::>(); From f33a29a4b4d40fcb705280e478e57dc6782d7b9c Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Wed, 31 Jul 2024 08:47:24 +0100 Subject: [PATCH 57/68] ROVER-69 Fix argument parsing --- crates/rover-std/src/emoji.rs | 6 ++- src/command/supergraph/compose/do_compose.rs | 42 +++++++++++--------- src/utils/supergraph_config.rs | 18 +++++++-- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/crates/rover-std/src/emoji.rs b/crates/rover-std/src/emoji.rs index 4e131bb87..49920bf0b 100644 --- a/crates/rover-std/src/emoji.rs +++ b/crates/rover-std/src/emoji.rs @@ -9,6 +9,7 @@ pub enum Emoji { Hourglass, Listen, Memo, + Merge, New, Note, Person, @@ -30,9 +31,10 @@ impl Emoji { match self { Action => "🎬 ", Compose => "🎢 ", - Hourglass => "βŒ› ", + Hourglass => "βŒ› ", Listen => "πŸ‘‚ ", Memo => "πŸ“ ", + Merge => "β›™ ", New => "🐀 ", Note => "πŸ—’οΈ ", Person => "πŸ§‘ ", @@ -42,7 +44,7 @@ impl Emoji { Sparkle => "✨ ", Start => "πŸ›« ", Stop => "βœ‹ ", - Success => "βœ… ", + Success => "βœ… ", Warn => "⚠️ ", Watch => "πŸ‘€ ", Web => "πŸ•ΈοΈ ", diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index a85f846da..0aac71456 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -8,12 +8,12 @@ use apollo_federation_types::{ config::{FederationVersion, PluginVersion}, }; use camino::Utf8PathBuf; -use clap::Parser; +use clap::{Args, Parser}; use serde::Serialize; use rover_client::shared::GraphRef; use rover_client::RoverClientError; -use rover_std::{Emoji, Style}; +use rover_std::Emoji; use crate::utils::supergraph_config::get_supergraph_config; use crate::utils::{client::StudioClientConfig, parsers::FileDescriptorType}; @@ -32,15 +32,13 @@ pub struct Compose { opts: SupergraphComposeOpts, } -#[derive(Debug, Serialize, Parser)] -pub struct SupergraphComposeOpts { - #[clap(flatten)] - pub plugin_opts: PluginOpts, - +#[derive(Args, Debug, Serialize)] +#[group(required = true)] +pub struct SupergraphConfigSource { /// The relative path to the supergraph configuration file. You can pass `-` to use stdin instead of a file. #[serde(skip_serializing)] - #[arg(long = "config")] - supergraph_yaml: FileDescriptorType, + #[arg(long = "config", conflicts_with = "graph_ref")] + supergraph_yaml: Option, /// A [`GraphRef`] that is accessible in Apollo Studio. /// This is used to initialize your supergraph with the values contained in this variant. @@ -48,8 +46,17 @@ pub struct SupergraphComposeOpts { /// This is analogous to providing a supergraph.yaml file with references to your graph variant in studio. /// /// If used in conjunction with `--config`, the values presented in the supergraph.yaml will take precedence over these values. - #[arg(long = "graph-ref")] + #[arg(long = "graph-ref", conflicts_with = "supergraph_yaml")] graph_ref: Option, +} + +#[derive(Debug, Serialize, Parser)] +pub struct SupergraphComposeOpts { + #[clap(flatten)] + pub plugin_opts: PluginOpts, + + #[clap(flatten)] + pub supergraph_config_source: SupergraphConfigSource, /// The version of Apollo Federation to use for composition #[arg(long = "federation-version")] @@ -61,9 +68,11 @@ impl Compose { Self { opts: SupergraphComposeOpts { plugin_opts: compose_opts, - supergraph_yaml: FileDescriptorType::File("RAM".into()), - graph_ref: None, federation_version: Some(LatestFedTwo), + supergraph_config_source: SupergraphConfigSource { + supergraph_yaml: Some(FileDescriptorType::File("RAM".into())), + graph_ref: None, + }, }, } } @@ -103,14 +112,9 @@ impl Compose { override_install_path: Option, client_config: StudioClientConfig, ) -> RoverResult { - eprintln!( - "{}resolving SDL for subgraphs defined in {}", - Emoji::Hourglass, - Style::Path.paint(self.opts.supergraph_yaml.to_string()) - ); let mut supergraph_config = get_supergraph_config( - &self.opts.graph_ref, - &Some(self.opts.supergraph_yaml.clone()), + &self.opts.supergraph_config_source.graph_ref, + &self.opts.supergraph_config_source.supergraph_yaml.clone(), &self.opts.federation_version.clone().unwrap_or(LatestFedTwo), client_config.clone(), &self.opts.plugin_opts.profile, diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index 34fb48196..ea929d3e2 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -16,7 +16,7 @@ use rover_client::operations::subgraph::introspect::SubgraphIntrospectInput; use rover_client::operations::subgraph::{fetch, introspect}; use rover_client::shared::GraphRef; use rover_client::RoverClientError; -use rover_std::{Fs, Style}; +use rover_std::{Emoji, Fs, Style}; use crate::options::ProfileOpt; use crate::utils::client::StudioClientConfig; @@ -78,17 +78,27 @@ pub fn get_supergraph_config( let remote_subgraphs = match graph_ref { Some(graph_ref) => { let studio_client = client_config.get_authenticated_client(profile_opt)?; - Some(RemoteSubgraphs::fetch( + let remote_subgraphs = Some(RemoteSubgraphs::fetch( &studio_client, federation_version, graph_ref, - )?) + )?); + eprintln!( + "{}retrieving subgraphs remotely from {}", + Emoji::Hourglass, + graph_ref + ); + remote_subgraphs } None => None, }; // Read in Local Supergraph Config let supergraph_config = if let Some(file_descriptor) = &supergraph_config_path { + eprintln!( + "{}resolving SDL for subgraphs defined in supergraph schema file", + Emoji::Hourglass, + ); Some(resolve_supergraph_yaml( file_descriptor, client_config, @@ -104,12 +114,14 @@ pub fn get_supergraph_config( Some(supergraph_config) => { let mut merged_supergraph_config = remote_subgraphs.inner().clone(); merged_supergraph_config.merge_subgraphs(&supergraph_config); + eprintln!("{}merging supergraph schema files", Emoji::Merge,); Some(merged_supergraph_config) } None => Some(remote_subgraphs.inner().clone()), }, None => supergraph_config, }; + eprintln!("{}supergraph config loaded successfully", Emoji::Success,); Ok(supergraph_config) } From 05276fd4c9d25cbeffb263513b93705447f6b7fb Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Wed, 31 Jul 2024 10:14:22 +0100 Subject: [PATCH 58/68] ROVER-69 Respond to PR comments --- Cargo.lock | 1 + crates/rover-client/Cargo.toml | 1 + .../operations/subgraph/fetch_all/runner.rs | 64 +++++++++---------- .../operations/subgraph/fetch_all/types.rs | 10 +++ src/utils/supergraph_config.rs | 37 ++++------- 5 files changed, 56 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3235246fc..19250bb4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4568,6 +4568,7 @@ dependencies = [ "regex", "reqwest 0.12.5", "rover-std", + "rstest", "semver", "serde", "serde_json", diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index a69a5ccd3..ca0f8b8ee 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -57,3 +57,4 @@ indoc = { workspace = true} httpmock = { workspace = true } pretty_assertions = { workspace = true } strip-ansi-escapes = { workspace = true } +rstest = { workspace = true } diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs b/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs index 33af7da27..64cab5f8c 100644 --- a/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs @@ -1,7 +1,6 @@ use graphql_client::*; use crate::blocking::StudioClient; -use crate::operations::config::is_federated::{self, IsFederatedInput}; use crate::RoverClientError; use super::types::*; @@ -34,50 +33,48 @@ fn get_subgraphs_from_response_data( input: SubgraphFetchAllInput, response_data: SubgraphFetchAllResponseData, ) -> Result, RoverClientError> { - if let Some(maybe_variant) = response_data.variant { - match maybe_variant { - SubgraphFetchAllGraphVariant::GraphVariant(variant) => { - if let Some(subgraphs) = variant.subgraphs { - Ok(subgraphs - .into_iter() - .map(|subgraph| { - Subgraph::builder() - .name(subgraph.name.clone()) - .and_url(subgraph.url) - .sdl(subgraph.active_partial_schema.sdl) - .build() - }) - .collect()) - } else { - Err(RoverClientError::ExpectedFederatedGraph { - graph_ref: input.graph_ref, - can_operation_convert: true, - }) - } - } - _ => Err(RoverClientError::InvalidGraphRef), - } - } else { - Err(RoverClientError::GraphNotFound { + match response_data.variant { + None => Err(RoverClientError::GraphNotFound { graph_ref: input.graph_ref, - }) + }), + Some(SubgraphFetchAllGraphVariant::GraphVariant(variant)) => variant.subgraphs.map_or_else( + || { + Err(RoverClientError::ExpectedFederatedGraph { + graph_ref: input.graph_ref, + can_operation_convert: true, + }) + }, + |subgraphs| { + Ok(subgraphs + .into_iter() + .map(|subgraph| { + Subgraph::builder() + .name(subgraph.name.clone()) + .and_url(subgraph.url) + .sdl(subgraph.active_partial_schema.sdl) + .build() + }) + .collect()) + }, + ), + _ => Err(RoverClientError::InvalidGraphRef), } } #[cfg(test)] mod tests { + use rstest::{fixture, rstest}; use serde_json::json; use crate::shared::GraphRef; use super::*; - #[test] - fn get_services_from_response_data_works() { + #[rstest] + fn get_services_from_response_data_works(#[from(mock_input)] input: SubgraphFetchAllInput) { let sdl = "extend type User @key(fields: \"id\") {\n id: ID! @external\n age: Int\n}\n" .to_string(); let url = "http://my.subgraph.com".to_string(); - let input = mock_input(); let json_response = json!({ "variant": { "__typename": "GraphVariant", @@ -104,14 +101,15 @@ mod tests { assert_eq!(output.unwrap(), vec![expected_subgraph]); } - #[test] - fn get_services_from_response_data_errs_with_no_variant() { + #[rstest] + fn get_services_from_response_data_errs_with_no_variant(mock_input: SubgraphFetchAllInput) { let json_response = json!({ "variant": null }); let data: SubgraphFetchAllResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_subgraphs_from_response_data(mock_input(), data); + let output = get_subgraphs_from_response_data(mock_input, data); assert!(output.is_err()); } + #[fixture] fn mock_input() -> SubgraphFetchAllInput { let graph_ref = GraphRef { name: "mygraph".to_string(), diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/types.rs b/crates/rover-client/src/operations/subgraph/fetch_all/types.rs index 7462dd6ee..67b511a58 100644 --- a/crates/rover-client/src/operations/subgraph/fetch_all/types.rs +++ b/crates/rover-client/src/operations/subgraph/fetch_all/types.rs @@ -1,3 +1,4 @@ +use apollo_federation_types::config::{SchemaSource, SubgraphConfig}; use buildstructor::Builder; use derive_getters::Getters; @@ -29,3 +30,12 @@ pub struct Subgraph { url: Option, sdl: String, } + +impl From for SubgraphConfig { + fn from(value: Subgraph) -> Self { + Self { + routing_url: value.url, + schema: SchemaSource::Sdl { sdl: value.sdl }, + } + } +} diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index ea929d3e2..69702dfca 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -43,18 +43,8 @@ impl RemoteSubgraphs { client, )?; let subgraphs = subgraphs - .iter() - .map(|subgraph| { - ( - subgraph.name().clone(), - SubgraphConfig { - routing_url: subgraph.url().clone(), - schema: SchemaSource::Sdl { - sdl: subgraph.sdl().clone(), - }, - }, - ) - }) + .into_iter() + .map(|subgraph| (subgraph.name().clone(), subgraph.into())) .collect(); let supergraph_config = SupergraphConfig::new(subgraphs, Some(federation_version.clone())); let remote_subgraphs = RemoteSubgraphs(supergraph_config); @@ -97,7 +87,7 @@ pub fn get_supergraph_config( let supergraph_config = if let Some(file_descriptor) = &supergraph_config_path { eprintln!( "{}resolving SDL for subgraphs defined in supergraph schema file", - Emoji::Hourglass, + Emoji::Hourglass ); Some(resolve_supergraph_yaml( file_descriptor, @@ -109,17 +99,16 @@ pub fn get_supergraph_config( }; // Merge Remote and Local Supergraph Configs - let supergraph_config = match remote_subgraphs { - Some(remote_subgraphs) => match supergraph_config { - Some(supergraph_config) => { - let mut merged_supergraph_config = remote_subgraphs.inner().clone(); - merged_supergraph_config.merge_subgraphs(&supergraph_config); - eprintln!("{}merging supergraph schema files", Emoji::Merge,); - Some(merged_supergraph_config) - } - None => Some(remote_subgraphs.inner().clone()), - }, - None => supergraph_config, + let supergraph_config = match (remote_subgraphs, supergraph_config) { + (Some(remote_subgraphs), Some(supergraph_config)) => { + let mut merged_supergraph_config = remote_subgraphs.inner().clone(); + merged_supergraph_config.merge_subgraphs(&supergraph_config); + eprintln!("{}merging supergraph schema files", Emoji::Merge); + Some(merged_supergraph_config) + } + (Some(remote_subgraphs), None) => Some(remote_subgraphs.inner().clone()), + (None, Some(supergraph_config)) => Some(supergraph_config), + (None, None) => None, }; eprintln!("{}supergraph config loaded successfully", Emoji::Success,); Ok(supergraph_config) From ddda4900051b596e98a1e5b6ef5b987ed4f277ad Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Wed, 31 Jul 2024 16:24:31 -0400 Subject: [PATCH 59/68] fix(async): missed asyncifications during merge --- .../operations/subgraph/fetch_all/runner.rs | 4 +-- src/command/dev/do_dev.rs | 2 +- src/command/supergraph/compose/do_compose.rs | 4 ++- src/utils/supergraph_config.rs | 27 ++++++++----------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs b/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs index 64cab5f8c..cdb5a956b 100644 --- a/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs +++ b/crates/rover-client/src/operations/subgraph/fetch_all/runner.rs @@ -20,12 +20,12 @@ use super::types::*; pub(crate) struct SubgraphFetchAllQuery; /// For a given graph return all of its subgraphs as a list -pub fn run( +pub async fn run( input: SubgraphFetchAllInput, client: &StudioClient, ) -> Result, RoverClientError> { let variables = input.clone().into(); - let response_data = client.post::(variables)?; + let response_data = client.post::(variables).await?; get_subgraphs_from_response_data(input, response_data) } diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index af92041a3..3e4f932c8 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -47,7 +47,7 @@ impl Dev { .unwrap_or(FederationVersion::LatestFedTwo), client_config.clone(), &self.opts.plugin_opts.profile, - )?; + ).await?; if let Some(mut leader_session) = LeaderSession::new( override_install_path, diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index f231089c5..3131a9a8f 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -121,7 +121,9 @@ impl Compose { client_config.clone(), &self.opts.plugin_opts.profile, ) - .await?; + .await? + // WARNING: remove this unwrap + .unwrap(); self.compose(override_install_path, client_config, &mut supergraph_config) .await } diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index 206633d6f..5683b9959 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -6,7 +6,7 @@ use apollo_federation_types::config::{ FederationVersion, SchemaSource, SubgraphConfig, SupergraphConfig, }; use apollo_parser::{cst, Parser}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use futures::future::join_all; use rover_client::blocking::{GraphQLClient, StudioClient}; use rover_client::operations::subgraph; @@ -31,7 +31,7 @@ pub struct RemoteSubgraphs(SupergraphConfig); impl RemoteSubgraphs { /// Fetches [`RemoteSubgraphs`] from Studio - pub fn fetch( + pub async fn fetch( client: &StudioClient, federation_version: &FederationVersion, graph_ref: &GraphRef, @@ -41,7 +41,8 @@ impl RemoteSubgraphs { graph_ref: graph_ref.clone(), }, client, - )?; + ) + .await?; let subgraphs = subgraphs .into_iter() .map(|subgraph| (subgraph.name().clone(), subgraph.into())) @@ -57,7 +58,7 @@ impl RemoteSubgraphs { } } -pub fn get_supergraph_config( +pub async fn get_supergraph_config( graph_ref: &Option, supergraph_config_path: &Option, federation_version: &FederationVersion, @@ -68,11 +69,8 @@ pub fn get_supergraph_config( let remote_subgraphs = match graph_ref { Some(graph_ref) => { let studio_client = client_config.get_authenticated_client(profile_opt)?; - let remote_subgraphs = Some(RemoteSubgraphs::fetch( - &studio_client, - federation_version, - graph_ref, - )?); + let remote_subgraphs = + Some(RemoteSubgraphs::fetch(&studio_client, federation_version, graph_ref).await?); eprintln!( "{}retrieving subgraphs remotely from {}", Emoji::Hourglass, @@ -89,11 +87,7 @@ pub fn get_supergraph_config( "{}resolving SDL for subgraphs defined in supergraph schema file", Emoji::Hourglass ); - Some(resolve_supergraph_yaml( - file_descriptor, - client_config, - profile_opt, - )?) + Some(resolve_supergraph_yaml(file_descriptor, client_config, profile_opt).await?) } else { None }; @@ -188,6 +182,7 @@ mod test_get_supergraph_config { } #[rstest] + #[tokio::test] #[case::no_subgraphs_at_all(None, None, None)] #[case::only_remote_subgraphs(Some(String::from("products")), None, Some(vec![(String::from("products"), String::from("remote"))]))] #[case::only_local_subgraphs(None, Some(String::from("pandas")), Some(vec![(String::from("pandas"), String::from("local"))]))] @@ -341,7 +336,7 @@ mod test_get_supergraph_config { latest_fed2_version, studio_client_config, &profile_opt, - ) + ).await .expect("Could not construct SupergraphConfig") } else { get_supergraph_config( @@ -376,7 +371,7 @@ mod test_get_supergraph_config { } } -pub(crate) fn resolve_supergraph_yaml( +pub(crate) async fn resolve_supergraph_yaml( unresolved_supergraph_yaml: &FileDescriptorType, client_config: StudioClientConfig, profile_opt: &ProfileOpt, From a447870b52265ec90c704cc4503b3b3b9ca448a2 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Wed, 7 Aug 2024 15:27:24 -0400 Subject: [PATCH 60/68] fix(merge): bad merge resolutions --- src/command/graph/mod.rs | 2 +- src/command/install/plugin.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 0f36740af..1de45ba45 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -10,7 +10,7 @@ pub use introspect::Introspect; use serde::Serialize; use crate::options::OutputOpts; -use crate::utils::client::{self, StudioClientConfig}; +use crate::utils::client::StudioClientConfig; use crate::{RoverOutput, RoverResult}; use rover_client::shared::GitContext; diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs index 41f53fc07..d91ed5f84 100644 --- a/src/command/install/plugin.rs +++ b/src/command/install/plugin.rs @@ -384,13 +384,11 @@ impl PluginInstaller { ) -> RoverResult> { if let Ok(Some(exe)) = self.find_existing_exact(plugin, version) { if !self.force { - if let Ok(Some(exe)) = self.find_existing_exact(plugin, version) { - tracing::debug!("{} exists, skipping install", &exe); - return Ok(Some(exe)); - } + tracing::debug!("{} exists, skipping install", &exe); + return Ok(Some(exe)); } - self.do_install(plugin, false).await } + self.do_install(plugin, false).await } async fn do_install( From 8e650db46a38dc9d8c4ec49cd394de4064a18de5 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Wed, 7 Aug 2024 15:28:40 -0400 Subject: [PATCH 61/68] appease clippy --- xtask/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1f8874074..d90f8b970 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -69,8 +69,6 @@ impl Xtask { Command::Package(command) => command.run(), Command::SecurityChecks(command) => command.run(), Command::GithubActions(command) => command.run().await, - Command::Smoke(command) => command.run().await, - }?; eprintln!("{}", style("Success!").green().bold()); Ok(()) From 1bfc4496eeb5e8bec53012e83486415de5eba91b Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Wed, 7 Aug 2024 15:34:35 -0400 Subject: [PATCH 62/68] fix(tests): kill println --- tests/e2e/subgraph/lint.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/subgraph/lint.rs b/tests/e2e/subgraph/lint.rs index da71142f8..66fe6c567 100644 --- a/tests/e2e/subgraph/lint.rs +++ b/tests/e2e/subgraph/lint.rs @@ -33,7 +33,6 @@ async fn e2e_test_rover_subgraph_lint( &remote_supergraph_graphref, ]); let output = cmd.output().expect("Could not run command"); - println!("output: {output:?}"); if !output.status.success() { error!("{}", String::from_utf8(output.stderr).unwrap()); From a5b662f1776a8c5b724b27078fea2aa95060d6d3 Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 9 Aug 2024 10:12:42 -0400 Subject: [PATCH 63/68] Update Cargo.toml Co-authored-by: Jonathan Rainer --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 42395461e..f54d054ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -196,7 +196,7 @@ which = { workspace = true } uuid = { workspace = true } url = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } -futures.workspace = true +futures = { workspace = true } [dev-dependencies] assert_cmd = { workspace = true } From 655809e7733c4d08f44dc2c18104e11175474ec1 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 9 Aug 2024 13:09:54 -0400 Subject: [PATCH 64/68] fix(bin/main): make async --- src/bin/rover.rs | 7 +++---- src/command/supergraph/compose/do_compose.rs | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/rover.rs b/src/bin/rover.rs index 6ce9673ab..c25557a0f 100644 --- a/src/bin/rover.rs +++ b/src/bin/rover.rs @@ -1,9 +1,9 @@ use robot_panic::setup_panic; use rover::cli::Rover; -use tokio::runtime::Runtime; +#[tokio::main] #[calm_io::pipefail] -fn main() -> Result<_, std::io::Error> { +async fn main() -> Result<_, std::io::Error> { setup_panic!(Metadata { name: rover::PKG_NAME.into(), version: rover::PKG_VERSION.into(), @@ -12,6 +12,5 @@ fn main() -> Result<_, std::io::Error> { repository: rover::PKG_REPOSITORY.into() }); - let rt = Runtime::new().expect("failed to start asynchronous runtime"); - Ok(rt.block_on(Rover::run_from_args())) + Ok(Rover::run_from_args().await) } diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 24cff80c2..3035fa4e5 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -124,6 +124,7 @@ impl Compose { .await? // WARNING: remove this unwrap .unwrap(); + self.compose(override_install_path, client_config, &mut supergraph_config) .await } From fe13a6410350360e833697d0e1c47859c020f5f2 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 9 Aug 2024 13:13:47 -0400 Subject: [PATCH 65/68] fix(introspect): warnings invalidated; toggling booleans --- src/command/dev/introspect.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/command/dev/introspect.rs b/src/command/dev/introspect.rs index e2db95954..9d6586002 100644 --- a/src/command/dev/introspect.rs +++ b/src/command/dev/introspect.rs @@ -124,9 +124,7 @@ impl SubgraphIntrospectRunner { watch: false, }, } - // WARNING: this changed on `main` to `true`, which I kept as `false` (async branch), but it needs to - // be validated - .exec(&self.client, false, self.retry_period) + .exec(&self.client, true, self.retry_period) .await } } @@ -152,9 +150,7 @@ impl GraphIntrospectRunner { watch: false, }, } - // WARNING: this changed on `main` to `true`, which I kept as `false` (async branch), but it needs to - // be validated - .exec(&self.client, false, self.retry_period) + .exec(&self.client, true, self.retry_period) .await } } From c96b715761876d8c631fa131dfc7a020ce0b5e94 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 9 Aug 2024 13:15:06 -0400 Subject: [PATCH 66/68] fix(leader): remove unnecessary warning --- src/command/dev/protocol/leader.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/command/dev/protocol/leader.rs b/src/command/dev/protocol/leader.rs index 6fcf11cfe..075313424 100644 --- a/src/command/dev/protocol/leader.rs +++ b/src/command/dev/protocol/leader.rs @@ -475,9 +475,6 @@ impl LeaderSession { impl Drop for LeaderSession { fn drop(&mut self) { - // WARNING: geal's branch had this as an awaited future for shutdown(), but we can't get - // that without marrking it as async, which isn't allowed, Drop can only be sync; not sure - // how to DRY this up let router_runner = self.router_runner.take(); let socket_addr = self.raw_socket_name.clone(); tokio::task::spawn(async move { From cb3f6c9863aa373b86c53e93b4844302fb3020a5 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Fri, 9 Aug 2024 13:17:46 -0400 Subject: [PATCH 67/68] fix(deps): remove reqwest blocking feature --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f54d054ec..c4a128f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ notify = { workspace = true } opener = { workspace = true } os_info = { workspace = true } rayon = { workspace = true } -reqwest = { workspace = true, features = ["blocking", "json"] } +reqwest = { workspace = true, features = ["json"] } robot-panic = { workspace = true } rover-client = { workspace = true } rover-std = { workspace = true } @@ -214,7 +214,7 @@ predicates = { workspace = true } rand = "0.8.5" rand_regex = "0.17.0" regex = { workspace = true } -reqwest = { workspace = true, features = ["blocking", "native-tls-vendored"] } +reqwest = { workspace = true, features = ["native-tls-vendored"] } rstest = { workspace = true } serial_test = { workspace = true } speculoos = { workspace = true } From 224046d08ea78a6c0501a23b50095246e24db366 Mon Sep 17 00:00:00 2001 From: Aaron Arinder Date: Mon, 12 Aug 2024 13:19:13 -0400 Subject: [PATCH 68/68] fix(log): bad merge --- src/command/dev/do_dev.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs index 6fb0e366d..a36cdb806 100644 --- a/src/command/dev/do_dev.rs +++ b/src/command/dev/do_dev.rs @@ -60,7 +60,7 @@ impl Dev { .await? { eprintln!( - "Do not run this command in production! {0}It is intended for local development." + "Do not run this command in production! It is intended for local development." ); let (ready_sender, mut ready_receiver) = channel(1); let follower_messenger = FollowerMessenger::from_main_session(