From 4cc9e8d867fe65627d46c552d046d2ad2eddf337 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 1 Jul 2021 14:53:58 -0500 Subject: [PATCH] chore: only use one instance of reqwest::Client This change allows us to take advantage of reqwest's ability to re-use connections. Their [docs](https://docs.rs/reqwest/0.11.4/reqwest/blocking/struct.Client.html) explain that you should reuse a client by cloning it to take advantage of its internal connection pool. --- Cargo.lock | 110 ++++++------------ Cargo.toml | 2 +- crates/rover-client/Cargo.toml | 3 +- crates/rover-client/build.rs | 7 +- crates/rover-client/src/blocking/client.rs | 17 +-- crates/rover-client/src/blocking/mod.rs | 1 - .../src/blocking/studio_client.rs | 5 +- crates/rover-client/src/releases.rs | 7 +- crates/rover-client/tests/client.rs | 17 ++- crates/sputnik/Cargo.toml | 2 +- crates/sputnik/src/report.rs | 6 +- crates/sputnik/src/session.rs | 12 +- src/cli.rs | 22 +++- src/command/config/whoami.rs | 2 +- src/command/graph/check.rs | 2 +- src/command/graph/fetch.rs | 2 +- src/command/graph/introspect.rs | 5 +- src/command/graph/mod.rs | 2 +- src/command/graph/publish.rs | 2 +- src/command/subgraph/check.rs | 2 +- src/command/subgraph/delete.rs | 2 +- src/command/subgraph/fetch.rs | 2 +- src/command/subgraph/introspect.rs | 5 +- src/command/subgraph/list.rs | 2 +- src/command/subgraph/mod.rs | 2 +- src/command/subgraph/publish.rs | 2 +- src/command/supergraph/compose/do_compose.rs | 18 ++- src/command/supergraph/fetch.rs | 2 +- src/command/update/check.rs | 5 +- src/command/update/mod.rs | 5 +- src/utils/client.rs | 25 +++- src/utils/telemetry.rs | 5 + src/utils/version.rs | 15 ++- 33 files changed, 182 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90341230e..3a83d9564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192ec435945d87bc2f70992b4d818154b5feede43c09fb7592146374eac90a6" + +[[package]] +name = "alloc-stdlib" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -102,6 +117,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443ccbb270374a2b1055fc72da40e1f237809cd6bb0e97e66d264cd138473a6" dependencies = [ + "brotli", "flate2", "futures-core", "memchr", @@ -200,6 +216,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29919120f08613aadcd4383764e00526fc9f18b6c0895814faeed0dd78613e" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1052e1c3b8d4d80eb84a8b94f0a1498797b5fb96314c001156a1c761940ef4ec" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "0.2.16" @@ -674,21 +711,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -1120,19 +1142,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "idna" version = "0.2.3" @@ -1373,24 +1382,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "native-tls" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1472,20 +1463,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "openssl" -version = "0.10.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - [[package]] name = "openssl-probe" version = "0.1.4" @@ -1864,13 +1841,11 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "lazy_static", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", "rustls", @@ -1879,7 +1854,6 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "url", @@ -2592,16 +2566,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index 632cf8d48..b24c6bb4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ humantime = "2.1.0" opener = "0.5.0" os_info = "3.0" prettytable-rs = "0.8.0" +reqwest = {version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "rustls-tls-native-roots"]} regex = "1" semver = "1" serde = "1.0" @@ -75,7 +76,6 @@ url = { version = "2.2.2", features = ["serde"] } assert_cmd = "1.0.5" assert_fs = "1.0.0" predicates = "1.0.8" -reqwest = { version = "0.11.4", default-features = false, features = ["blocking", "rustls-tls-native-roots"] } rustversion = "1.0.5" serial_test = "0.5.0" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 830a8c9fd..603bb7457 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -18,7 +18,7 @@ graphql-parser = "0.3.0" graphql_client = "0.9" http = "0.2" regex = "1.5.4" -reqwest = {version = "0.11", default-features = false, features = ["blocking", "gzip", "json", "rustls-tls-native-roots"]} +reqwest = {version = "0.11", default-features = false, features = ["blocking", "json"]} sdl-encoder = {path = "../sdl-encoder"} serde = "1" serde_json = "1" @@ -32,5 +32,6 @@ reqwest = {version = "0.11", default-features = false, features = ["json", "bloc uuid = {version = "0.8", features = ["v4"]} [dev-dependencies] + indoc = "1.0.3" pretty_assertions = "0.7.1" diff --git a/crates/rover-client/build.rs b/crates/rover-client/build.rs index 569046724..863aee6fa 100644 --- a/crates/rover-client/build.rs +++ b/crates/rover-client/build.rs @@ -23,7 +23,12 @@ fn main() -> std::io::Result<()> { let schema_url = env::var("APOLLO_GPAPHQL_SCHEMA_URL") .unwrap_or_else(|_| "https://graphql.api.apollographql.com/api/schema".to_owned()); - let client = Client::new(); + let client = Client::builder() + .use_rustls_tls() + .tls_built_in_root_certs(true) + .build() + .expect("Could not create reqwest Client"); + let etag_path = PathBuf::from(".schema/etag.id"); let should_update_schema = !(etag_path.exists()) || online::sync::check(None).is_ok(); diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index bb6cf85fe..c734a63ad 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -8,27 +8,22 @@ use reqwest::{ use std::collections::HashMap; -pub(crate) fn get_client() -> Result { - ReqwestClient::builder() - .use_rustls_tls() - .tls_built_in_root_certs(true) - .gzip(true) - .build() -} - /// Represents a generic GraphQL client for making http requests. pub struct GraphQLClient { - client: ReqwestClient, graphql_endpoint: String, + client: ReqwestClient, } impl GraphQLClient { /// Construct a new [Client] from a `graphql_endpoint`. /// This client is used for generic GraphQL requests, such as introspection. - pub fn new(graphql_endpoint: &str) -> Result { + pub fn new( + graphql_endpoint: &str, + client: ReqwestClient, + ) -> Result { Ok(GraphQLClient { - client: get_client()?, graphql_endpoint: graphql_endpoint.to_string(), + client, }) } diff --git a/crates/rover-client/src/blocking/mod.rs b/crates/rover-client/src/blocking/mod.rs index 516e45a1d..e51e31143 100644 --- a/crates/rover-client/src/blocking/mod.rs +++ b/crates/rover-client/src/blocking/mod.rs @@ -1,6 +1,5 @@ mod client; mod studio_client; -pub(crate) use client::get_client; pub use client::GraphQLClient; pub use studio_client::StudioClient; diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index 22b2d15b1..a7a4d2fbc 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -2,7 +2,7 @@ use crate::{blocking::GraphQLClient, headers, RoverClientError}; use houston::Credential; use graphql_client::GraphQLQuery; -use reqwest::Error as ReqwestError; +use reqwest::{blocking::Client as ReqwestClient, Error as ReqwestError}; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { @@ -18,10 +18,11 @@ impl StudioClient { credential: Credential, graphql_endpoint: &str, version: &str, + client: ReqwestClient, ) -> Result { Ok(StudioClient { credential, - client: GraphQLClient::new(graphql_endpoint)?, + client: GraphQLClient::new(graphql_endpoint, client)?, version: version.to_string(), }) } diff --git a/crates/rover-client/src/releases.rs b/crates/rover-client/src/releases.rs index 6bca1f8a6..544636899 100644 --- a/crates/rover-client/src/releases.rs +++ b/crates/rover-client/src/releases.rs @@ -1,11 +1,12 @@ -use crate::{blocking::get_client, RoverClientError}; +use crate::RoverClientError; use regex::Regex; +use reqwest::blocking::Client; const LATEST_RELEASE_URL: &str = "https://github.com/apollographql/rover/releases/latest"; /// Looks up the latest release version, and returns it as a string -pub fn get_latest_release() -> Result { - let res = get_client()?.head(LATEST_RELEASE_URL).send()?; +pub fn get_latest_release(client: Client) -> Result { + let res = client.head(LATEST_RELEASE_URL).send()?; let release_url = res.url().to_string(); let release_url_parts: Vec<&str> = release_url.split('/').collect(); diff --git a/crates/rover-client/tests/client.rs b/crates/rover-client/tests/client.rs index 5a8486201..4f857051a 100644 --- a/crates/rover-client/tests/client.rs +++ b/crates/rover-client/tests/client.rs @@ -1,7 +1,19 @@ +use reqwest::blocking::Client; const STUDIO_PROD_API_ENDPOINT: &str = "https://graphql.api.apollographql.com/api/graphql"; +pub(crate) fn get_client() -> Client { + Client::builder() + .use_rustls_tls() + .tls_built_in_root_certs(true) + .gzip(true) + .brotli(true) + .build() + .expect("Could not create reqwest Client") +} + #[cfg(test)] mod tests { + use super::*; use houston::{Credential, CredentialOrigin}; use rover_client::blocking::{GraphQLClient, StudioClient}; @@ -9,7 +21,7 @@ mod tests { #[test] fn it_can_build_client() { - assert!(GraphQLClient::new(STUDIO_PROD_API_ENDPOINT).is_ok()); + assert!(GraphQLClient::new(STUDIO_PROD_API_ENDPOINT, get_client()).is_ok(),); } #[test] @@ -20,7 +32,8 @@ mod tests { origin: CredentialOrigin::EnvVar, }, "0.1.0", - STUDIO_PROD_API_ENDPOINT + STUDIO_PROD_API_ENDPOINT, + get_client() ) .is_ok()); } diff --git a/crates/sputnik/Cargo.toml b/crates/sputnik/Cargo.toml index 8be905949..ed782bb6d 100644 --- a/crates/sputnik/Cargo.toml +++ b/crates/sputnik/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" camino = "1.0" ci_info = { version = "0.14", features = ["serde-1"] } git2 = "0.13" -reqwest = { version = "0.11", features = ["blocking", "rustls-tls-native-roots"] } +reqwest = { version = "0.11", default-features = false, features = ["blocking"] } semver = { version = "1", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/crates/sputnik/src/report.rs b/crates/sputnik/src/report.rs index 34bd835e4..a15129f4d 100644 --- a/crates/sputnik/src/report.rs +++ b/crates/sputnik/src/report.rs @@ -1,4 +1,5 @@ use camino::{Utf8Path, Utf8PathBuf}; +use reqwest::blocking::Client; use url::Url; use uuid::Uuid; @@ -38,12 +39,15 @@ pub trait Report { /// returns the globally persistent machine identifier /// and writes it if it does not exist - /// the default implemenation uses self.machine_id_config() + /// the default implementation uses self.machine_id_config() /// as the location the machine identifier is written to. fn machine_id(&self) -> Result { let config_path = self.machine_id_config()?; get_or_write_machine_id(&config_path) } + + /// returns the Client to use when sending telemetry data + fn client(&self) -> Client; } fn get_or_write_machine_id(path: &Utf8PathBuf) -> Result { diff --git a/crates/sputnik/src/session.rs b/crates/sputnik/src/session.rs index ded167688..9b7a5f6a1 100644 --- a/crates/sputnik/src/session.rs +++ b/crates/sputnik/src/session.rs @@ -1,6 +1,7 @@ use camino::Utf8PathBuf; use ci_info::types::Vendor as CiVendor; use git2::Repository; +use reqwest::blocking::Client; use reqwest::Url; use semver::Version; use serde::Serialize; @@ -45,6 +46,10 @@ pub struct Session { /// Where the telemetry data is being reported to #[serde(skip_serializing)] reporting_info: ReportingInfo, + + /// The reqwest Client sputnik uses to send telemetry data + #[serde(skip_serializing)] + client: Client, } /// Platform represents the platform the CLI is being run from @@ -82,6 +87,7 @@ impl Session { pub fn new(app: &T) -> Result { let machine_id = app.machine_id()?; let command = app.serialize_command()?; + let client = app.client(); let reporting_info = ReportingInfo { is_telemetry_enabled: app.is_telemetry_enabled()?, endpoint: app.endpoint()?, @@ -114,6 +120,7 @@ impl Session { platform, cli_version, reporting_info, + client, }) } @@ -125,10 +132,7 @@ impl Session { let body = serde_json::to_string(&self)?; tracing::debug!("POSTing to {}", &self.reporting_info.endpoint); tracing::debug!("{}", body); - reqwest::blocking::Client::builder() - .use_rustls_tls() - .tls_built_in_root_certs(true) - .build()? + self.client .post(self.reporting_info.endpoint.clone()) .body(body) .header("User-Agent", &self.reporting_info.user_agent) diff --git a/src/cli.rs b/src/cli.rs index fa25d3dfd..4271bbec7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use reqwest::blocking::Client; use serde::Serialize; use structopt::{clap::AppSettings, StructOpt}; @@ -60,6 +61,10 @@ pub struct Rover { #[structopt(skip)] #[serde(skip_serializing)] pub env_store: RoverEnv, + + #[structopt(skip)] + #[serde(skip_serializing)] + client: Client, } impl Rover { @@ -75,7 +80,11 @@ impl Rover { pub(crate) fn get_client_config(&self) -> Result { let override_endpoint = self.env_store.get(RoverEnvKey::RegistryUrl)?; let config = self.get_rover_config()?; - Ok(StudioClientConfig::new(override_endpoint, config)) + Ok(StudioClientConfig::new( + override_endpoint, + config, + self.get_reqwest_client(), + )) } pub(crate) fn get_install_override_path(&self) -> Result> { @@ -91,6 +100,11 @@ impl Rover { tracing::debug!(?git_context); Ok(git_context) } + + pub(crate) fn get_reqwest_client(&self) -> Client { + // we can use clone here freely since `reqwest` uses an `Arc` under the hood + self.client.clone() + } } #[derive(Debug, Serialize, StructOpt)] @@ -136,7 +150,7 @@ impl Rover { } else { let config = self.get_rover_config(); if let Ok(config) = config { - let _ = version::check_for_update(config, false); + let _ = version::check_for_update(config, false, self.get_reqwest_client()); } } @@ -150,7 +164,9 @@ impl Rover { Command::Subgraph(command) => { command.run(self.get_client_config()?, self.get_git_context()?) } - Command::Update(command) => command.run(self.get_rover_config()?), + Command::Update(command) => { + command.run(self.get_rover_config()?, self.get_reqwest_client()) + } Command::Install(command) => command.run(self.get_install_override_path()?), Command::Info(command) => command.run(), Command::Explain(command) => command.run(), diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 2c343c16e..0a77288ec 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -23,7 +23,7 @@ pub struct WhoAmI { impl WhoAmI { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; eprintln!("Checking identity of your API key against the registry."); let identity = whoami::run(whoami::who_am_i_query::Variables {}, &client)?; diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index e3433a9c9..f4720436e 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -55,7 +55,7 @@ impl Check { client_config: StudioClientConfig, git_context: GitContext, ) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let sdl = load_schema_from_flag(&self.schema, std::io::stdin())?; let res = check::run( check::check_schema_query::Variables { diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index d3d4c1e38..c1c147d31 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -25,7 +25,7 @@ pub struct Fetch { impl Fetch { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( "Fetching SDL from {} using credentials from the {} profile.", diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index 25e270bef..a12ac60c9 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -1,4 +1,5 @@ use crate::Result; +use reqwest::blocking::Client; use serde::Serialize; use std::collections::HashMap; use structopt::StructOpt; @@ -27,8 +28,8 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self) -> Result { - let client = GraphQLClient::new(&self.endpoint.to_string())?; + pub fn run(&self, client: Client) -> Result { + let client = GraphQLClient::new(&self.endpoint.to_string(), client)?; // add the flag headers to a hashmap to pass along to rover-client let mut headers = HashMap::new(); diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 92e9a3c86..13744518e 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -42,7 +42,7 @@ impl Graph { Command::Check(command) => command.run(client_config, git_context), Command::Fetch(command) => command.run(client_config), Command::Publish(command) => command.run(client_config, git_context), - Command::Introspect(command) => command.run(), + Command::Introspect(command) => command.run(client_config.get_reqwest_client()), } } } diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index 06f66860c..053d3b953 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -37,7 +37,7 @@ impl Publish { client_config: StudioClientConfig, git_context: GitContext, ) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( "Publishing SDL to {} using credentials from the {} profile.", diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index d4e85770a..9fff514ec 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -61,7 +61,7 @@ impl Check { client_config: StudioClientConfig, git_context: GitContext, ) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let sdl = load_schema_from_flag(&self.schema, std::io::stdin())?; diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index f66a24485..030e6f45a 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -36,7 +36,7 @@ pub struct Delete { impl Delete { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( "Checking for composition errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index e51ae6bc1..020dad04a 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -30,7 +30,7 @@ pub struct Fetch { impl Fetch { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( "Fetching SDL from {} (subgraph: {}) using credentials from the {} profile.", diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index 4b69876f0..cae22c14c 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -1,3 +1,4 @@ +use reqwest::blocking::Client; use serde::Serialize; use std::collections::HashMap; use structopt::StructOpt; @@ -32,8 +33,8 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self) -> Result { - let client = GraphQLClient::new(&self.endpoint.to_string())?; + pub fn run(&self, client: Client) -> Result { + let client = GraphQLClient::new(&self.endpoint.to_string(), client)?; // add the flag headers to a hashmap to pass along to rover-client let mut headers = HashMap::new(); diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index 27843938d..e469b68db 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -25,7 +25,7 @@ pub struct List { impl List { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; eprintln!( "Listing subgraphs for {} using credentials from the {} profile.", diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index dd0173a44..9361fcf70 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -48,7 +48,7 @@ impl Subgraph { ) -> Result { match &self.command { Command::Publish(command) => command.run(client_config, git_context), - Command::Introspect(command) => command.run(), + Command::Introspect(command) => command.run(client_config.get_reqwest_client()), Command::Delete(command) => command.run(client_config), Command::Fetch(command) => command.run(client_config), Command::Check(command) => command.run(client_config, git_context), diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 6781d50c4..8e400488c 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -55,7 +55,7 @@ impl Publish { client_config: StudioClientConfig, git_context: GitContext, ) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = format!("{}:{}", &self.graph.name, &self.graph.variant); eprintln!( "Publishing SDL to {} (subgraph: {}) using credentials from the {} profile.", diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 781845afb..a54c2e3ff 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -102,7 +102,10 @@ pub(crate) fn get_subgraph_definitions( SchemaSource::SubgraphIntrospection { subgraph_url } => { // given a federated introspection URL, use subgraph introspect to // obtain SDL and add it to subgraph_definition. - let client = GraphQLClient::new(&subgraph_url.to_string())?; + let client = GraphQLClient::new( + &subgraph_url.to_string(), + client_config.get_reqwest_client(), + )?; let introspection_response = introspect::run(&client, &HashMap::new())?; let schema = introspection_response.result; @@ -120,7 +123,7 @@ pub(crate) fn get_subgraph_definitions( SchemaSource::Subgraph { graphref, subgraph } => { // given a graphref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. - let client = client_config.get_client(&profile_name)?; + let client = client_config.get_authenticated_client(&profile_name)?; let graphref = parse_graph_ref(graphref)?; let schema = fetch::run( fetch::fetch_subgraph_query::Variables { @@ -153,12 +156,21 @@ mod tests { use assert_fs::TempDir; use houston as houston_config; use houston_config::Config; + use reqwest::blocking::Client; use std::convert::TryFrom; 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()) + StudioClientConfig::new( + None, + Config::new(Some(&tmp_path), None).unwrap(), + Client::builder() + .use_rustls_tls() + .tls_built_in_root_certs(true) + .build() + .unwrap(), + ) } #[test] diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index 8176f0297..3bc84ded4 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -24,7 +24,7 @@ pub struct Fetch { impl Fetch { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let client = client_config.get_client(&self.profile_name)?; + let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( "Fetching supergraph SDL from {} using credentials from the {} profile.", diff --git a/src/command/update/check.rs b/src/command/update/check.rs index 13c745bc7..f4fe31ebd 100644 --- a/src/command/update/check.rs +++ b/src/command/update/check.rs @@ -1,3 +1,4 @@ +use reqwest::blocking::Client; use serde::Serialize; use structopt::StructOpt; @@ -12,8 +13,8 @@ pub struct Check { } impl Check { - pub fn run(&self, config: config::Config) -> Result { - version::check_for_update(config, true)?; + pub fn run(&self, config: config::Config, client: Client) -> Result { + version::check_for_update(config, true, client)?; Ok(RoverStdout::None) } } diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index 3b95446a1..9296ba172 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -1,5 +1,6 @@ mod check; +use reqwest::blocking::Client; use serde::Serialize; use structopt::StructOpt; @@ -21,9 +22,9 @@ pub enum Command { } impl Update { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config, client: Client) -> Result { match &self.command { - Command::Check(command) => command.run(config), + Command::Check(command) => command.run(config, client), } } } diff --git a/src/utils/client.rs b/src/utils/client.rs index e511c747d..96b7d2633 100644 --- a/src/utils/client.rs +++ b/src/utils/client.rs @@ -2,19 +2,25 @@ use crate::Result; use crate::PKG_VERSION; use houston as config; +use reqwest::blocking::Client; use rover_client::blocking::StudioClient; /// the Apollo graph registry's production API endpoint const STUDIO_PROD_API_ENDPOINT: &str = "https://graphql.api.apollographql.com/api/graphql"; pub struct StudioClientConfig { + pub(crate) config: config::Config, + client: Client, uri: String, - pub config: config::Config, version: String, } impl StudioClientConfig { - pub fn new(override_endpoint: Option, config: config::Config) -> StudioClientConfig { + pub fn new( + override_endpoint: Option, + config: config::Config, + client: Client, + ) -> StudioClientConfig { let version = if cfg!(debug_assertions) { format!("{} (dev)", PKG_VERSION) } else { @@ -25,11 +31,22 @@ impl StudioClientConfig { uri: override_endpoint.unwrap_or_else(|| STUDIO_PROD_API_ENDPOINT.to_string()), config, version, + client, } } - pub fn get_client(&self, profile_name: &str) -> Result { + pub(crate) fn get_reqwest_client(&self) -> Client { + // we can use clone here freely since `reqwest` uses an `Arc` under the hood + self.client.clone() + } + + pub fn get_authenticated_client(&self, profile_name: &str) -> Result { let credential = config::Profile::get_credential(profile_name, &self.config)?; - Ok(StudioClient::new(credential, &self.uri, &self.version)?) + Ok(StudioClient::new( + credential, + &self.uri, + &self.version, + self.get_reqwest_client(), + )?) } } diff --git a/src/utils/telemetry.rs b/src/utils/telemetry.rs index a03aef807..3882364d5 100644 --- a/src/utils/telemetry.rs +++ b/src/utils/telemetry.rs @@ -1,4 +1,5 @@ use camino::Utf8PathBuf; +use reqwest::blocking::Client; use url::Url; use crate::utils::env::RoverEnvKey; @@ -115,6 +116,10 @@ impl Report for Rover { .map_err(|_| SputnikError::ConfigError)?; Ok(config.home.join("machine.txt")) } + + fn client(&self) -> Client { + self.get_reqwest_client() + } } #[cfg(test)] diff --git a/src/utils/version.rs b/src/utils/version.rs index 9de822804..8ac32c7a8 100644 --- a/src/utils/version.rs +++ b/src/utils/version.rs @@ -3,6 +3,7 @@ use std::{fs, time::SystemTime}; use ansi_term::Colour::{Cyan, Yellow}; use billboard::{Alignment, Billboard}; use camino::Utf8PathBuf; +use reqwest::blocking::Client; use semver::Version; use crate::{Result, PKG_VERSION}; @@ -18,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) -> Result<()> { +pub 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 @@ -28,7 +29,7 @@ pub fn check_for_update(config: config::Config, force: bool) -> Result<()> { 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)?; + do_update_check(&mut checked, force, client)?; } 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!( @@ -37,7 +38,7 @@ pub fn check_for_update(config: config::Config, force: bool) -> Result<()> { ); if time_since_check > ONE_DAY { - do_update_check(&mut checked, force)?; + do_update_check(&mut checked, force, client)?; } } @@ -49,8 +50,12 @@ pub fn check_for_update(config: config::Config, force: bool) -> Result<()> { Ok(()) } -fn do_update_check(checked: &mut bool, should_output_if_updated: bool) -> Result<()> { - let latest = get_latest_release()?; +fn do_update_check( + checked: &mut bool, + should_output_if_updated: bool, + client: Client, +) -> Result<()> { + let latest = get_latest_release(client)?; let pretty_latest = Cyan.normal().paint(format!("v{}", latest)); let update_available = is_latest_newer(&latest, PKG_VERSION)?; if update_available {