From 69c38b9ff96ef6d3e71ff18d195293d20783e2c4 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 28 Jun 2021 11:34:17 -0500 Subject: [PATCH] chore: refactor config whoami (#633) --- crates/rover-client/src/blocking/client.rs | 29 ++++++++- crates/rover-client/src/blocking/mod.rs | 2 + .../src/blocking/studio_client.rs | 49 +++++++++++++-- crates/rover-client/src/headers.rs | 61 ------------------- crates/rover-client/src/lib.rs | 3 - crates/rover-client/src/query/config/mod.rs | 2 +- .../src/query/config/who_am_i/mod.rs | 4 ++ .../{whoami.rs => who_am_i/query_runner.rs} | 46 ++++++-------- .../src/query/config/who_am_i/types.rs | 31 ++++++++++ .../who_am_i_query.graphql} | 2 +- src/command/config/whoami.rs | 10 +-- 11 files changed, 134 insertions(+), 105 deletions(-) delete mode 100644 crates/rover-client/src/headers.rs create mode 100644 crates/rover-client/src/query/config/who_am_i/mod.rs rename crates/rover-client/src/query/config/{whoami.rs => who_am_i/query_runner.rs} (76%) create mode 100644 crates/rover-client/src/query/config/who_am_i/types.rs rename crates/rover-client/src/query/config/{whoami.graphql => who_am_i/who_am_i_query.graphql} (80%) diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index 8cde1bb23..f9a472a03 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -1,13 +1,16 @@ -use crate::{headers, RoverClientError}; +use crate::RoverClientError; use graphql_client::{Error as GraphQLError, GraphQLQuery, Response as GraphQLResponse}; use reqwest::{ blocking::{Client as ReqwestClient, Response}, - header::HeaderMap, + header::{HeaderMap, HeaderName, HeaderValue}, Error as ReqwestError, StatusCode, }; use std::collections::HashMap; +pub(crate) const JSON_CONTENT_TYPE: &str = "application/json"; +pub(crate) const CLIENT_NAME: &str = "rover-client"; + /// Represents a generic GraphQL client for making http requests. pub struct GraphQLClient { client: ReqwestClient, @@ -35,7 +38,7 @@ impl GraphQLClient { variables: Q::Variables, header_map: &HashMap, ) -> Result { - let header_map = headers::build(header_map)?; + let header_map = build_headers(header_map)?; let response = self.execute::(variables, header_map)?; GraphQLClient::handle_response::(response) } @@ -125,6 +128,26 @@ fn handle_graphql_body_errors(errors: Vec) -> Result<(), RoverClie } } +/// Function for building a [HeaderMap] for making http requests. Use for +/// Generic requests to any graphql endpoint. +/// +/// Takes a single argument, list of header key/value pairs +fn build_headers(header_map: &HashMap) -> Result { + let mut headers = HeaderMap::new(); + + // this should be consistent for any graphql requests + let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; + headers.append("Content-Type", content_type); + + for (key, value) in header_map { + let header_key = HeaderName::from_bytes(key.as_bytes())?; + let header_value = HeaderValue::from_str(&value)?; + headers.append(header_key, header_value); + } + + Ok(headers) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rover-client/src/blocking/mod.rs b/crates/rover-client/src/blocking/mod.rs index e51e31143..d89a42510 100644 --- a/crates/rover-client/src/blocking/mod.rs +++ b/crates/rover-client/src/blocking/mod.rs @@ -3,3 +3,5 @@ mod studio_client; pub use client::GraphQLClient; pub use studio_client::StudioClient; + +pub(crate) use client::{CLIENT_NAME, JSON_CONTENT_TYPE}; diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index 22b2d15b1..12f23011e 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -1,12 +1,17 @@ -use crate::{blocking::GraphQLClient, headers, RoverClientError}; -use houston::Credential; +use crate::{ + blocking::{GraphQLClient, CLIENT_NAME, JSON_CONTENT_TYPE}, + RoverClientError, +}; + +use houston::{Credential, CredentialOrigin}; use graphql_client::GraphQLQuery; +use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::Error as ReqwestError; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { - pub credential: Credential, + credential: Credential, client: GraphQLClient, version: String, } @@ -33,8 +38,44 @@ impl StudioClient { &self, variables: Q::Variables, ) -> Result { - let header_map = headers::build_studio_headers(&self.credential.api_key, &self.version)?; + let header_map = self.build_studio_headers()?; let response = self.client.execute::(variables, header_map)?; GraphQLClient::handle_response::(response) } + + /// Function for building a [HeaderMap] for making http requests. Use for making + /// requests to Apollo Studio. We're leaving this separate from `build` since we + /// need to be able to mark the api_key as sensitive (at the bottom) + /// + /// Takes an `api_key` and a `client_version`, and returns a [HeaderMap]. + pub fn build_studio_headers(&self) -> Result { + let mut headers = HeaderMap::new(); + + let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; + headers.insert("Content-Type", content_type); + + // The headers "apollographql-client-name" and "apollographql-client-version" + // are used for client identification in Apollo Studio. + + // This provides metrics in Studio that help keep track of what parts of the schema + // Rover uses, which ensures future changes to the API do not break Rover users. + // more info here: + // https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client + + let client_name = HeaderValue::from_str(CLIENT_NAME)?; + headers.insert("apollographql-client-name", client_name); + tracing::debug!(?self.version); + let client_version = HeaderValue::from_str(&self.version)?; + headers.insert("apollographql-client-version", client_version); + + let mut api_key = HeaderValue::from_str(&self.credential.api_key)?; + api_key.set_sensitive(true); + headers.insert("x-api-key", api_key); + + Ok(headers) + } + + pub fn get_credential_origin(&self) -> CredentialOrigin { + self.credential.origin.clone() + } } diff --git a/crates/rover-client/src/headers.rs b/crates/rover-client/src/headers.rs deleted file mode 100644 index 6bcf47762..000000000 --- a/crates/rover-client/src/headers.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::RoverClientError; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -use std::collections::HashMap; - -const JSON_CONTENT_TYPE: &str = "application/json"; -const CLIENT_NAME: &str = "rover-client"; - -/// Function for building a [HeaderMap] for making http requests. Use for -/// Generic requests to any graphql endpoint. -/// -/// Takes a single argument, list of header key/value pairs -pub fn build(header_map: &HashMap) -> Result { - let mut headers = HeaderMap::new(); - - // this should be consistent for any graphql requests - let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; - headers.append("Content-Type", content_type); - - for (key, value) in header_map { - let header_key = HeaderName::from_bytes(key.as_bytes())?; - let header_value = HeaderValue::from_str(&value)?; - headers.append(header_key, header_value); - } - - Ok(headers) -} - -/// Function for building a [HeaderMap] for making http requests. Use for making -/// requests to Apollo Studio. We're leaving this separate from `build` since we -/// need to be able to mark the api_key as sensitive (at the bottom) -/// -/// Takes an `api_key` and a `client_version`, and returns a [HeaderMap]. -pub fn build_studio_headers( - api_key: &str, - client_version: &str, -) -> Result { - let mut headers = HeaderMap::new(); - - let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; - headers.insert("Content-Type", content_type); - - // The headers "apollographql-client-name" and "apollographql-client-version" - // are used for client identification in Apollo Studio. - - // This provides metrics in Studio that help keep track of what parts of the schema - // Rover uses, which ensures future changes to the API do not break Rover users. - // more info here: - // https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client - - let client_name = HeaderValue::from_str(CLIENT_NAME)?; - headers.insert("apollographql-client-name", client_name); - tracing::debug!(?client_version); - let client_version = HeaderValue::from_str(&client_version)?; - headers.insert("apollographql-client-version", client_version); - - let mut api_key = HeaderValue::from_str(api_key)?; - api_key.set_sensitive(true); - headers.insert("x-api-key", api_key); - - Ok(headers) -} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index f206d4c0d..d650fc35d 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -6,9 +6,6 @@ pub mod blocking; mod error; -/// Module related to constructing request headers. -pub mod headers; - /// Module related to building an SDL from an introspection response. pub mod introspection; diff --git a/crates/rover-client/src/query/config/mod.rs b/crates/rover-client/src/query/config/mod.rs index ab814a569..e5e9f78ca 100644 --- a/crates/rover-client/src/query/config/mod.rs +++ b/crates/rover-client/src/query/config/mod.rs @@ -1,5 +1,5 @@ /// runner for rover config whoami -pub mod whoami; +pub mod who_am_i; /// runner is_federated check pub mod is_federated; diff --git a/crates/rover-client/src/query/config/who_am_i/mod.rs b/crates/rover-client/src/query/config/who_am_i/mod.rs new file mode 100644 index 000000000..30535495a --- /dev/null +++ b/crates/rover-client/src/query/config/who_am_i/mod.rs @@ -0,0 +1,4 @@ +mod types; + +pub mod query_runner; +pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity}; diff --git a/crates/rover-client/src/query/config/whoami.rs b/crates/rover-client/src/query/config/who_am_i/query_runner.rs similarity index 76% rename from crates/rover-client/src/query/config/whoami.rs rename to crates/rover-client/src/query/config/who_am_i/query_runner.rs index 238440eb1..0bc7eba7e 100644 --- a/crates/rover-client/src/query/config/whoami.rs +++ b/crates/rover-client/src/query/config/who_am_i/query_runner.rs @@ -1,5 +1,10 @@ use crate::blocking::StudioClient; +use crate::query::config::who_am_i::{ + types::{QueryActorType, QueryResponseData, RegistryIdentity}, + Actor, ConfigWhoAmIInput, +}; use crate::RoverClientError; + use houston::CredentialOrigin; use graphql_client::*; @@ -8,43 +13,28 @@ use graphql_client::*; // 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/query/config/whoami.graphql", + query_path = "src/query/config/who_am_i/who_am_i_query.graphql", schema_path = ".schema/schema.graphql", response_derives = "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. who_am_i_query -pub struct WhoAmIQuery; - -#[derive(Debug, PartialEq)] -pub struct RegistryIdentity { - pub id: String, - pub graph_title: Option, - pub key_actor_type: Actor, - pub credential_origin: CredentialOrigin, -} - -#[derive(Debug, PartialEq)] -pub enum Actor { - GRAPH, - USER, - OTHER, -} +/// Snake case of this name is the mod name. i.e. config_who_am_i_query +pub 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( - variables: who_am_i_query::Variables, + input: ConfigWhoAmIInput, client: &StudioClient, ) -> Result { - let response_data = client.post::(variables)?; - get_identity_from_response_data(response_data, client.credential.origin.clone()) + let response_data = client.post::(input.into())?; + get_identity_from_response_data(response_data, client.get_credential_origin()) } fn get_identity_from_response_data( - response_data: who_am_i_query::ResponseData, + response_data: QueryResponseData, credential_origin: CredentialOrigin, ) -> Result { if let Some(me) = response_data.me { @@ -54,13 +44,13 @@ fn get_identity_from_response_data( // more here: https://studio-staging.apollographql.com/graph/engine/schema/reference/enums/ActorType?variant=prod let key_actor_type = match me.as_actor.type_ { - who_am_i_query::ActorType::GRAPH => Actor::GRAPH, - who_am_i_query::ActorType::USER => Actor::USER, + QueryActorType::GRAPH => Actor::GRAPH, + QueryActorType::USER => Actor::USER, _ => Actor::OTHER, }; let graph_title = match me.on { - who_am_i_query::WhoAmIQueryMeOn::Service(s) => Some(s.title), + config_who_am_i_query::ConfigWhoAmIQueryMeOn::Service(s) => Some(s.title), _ => None, }; @@ -91,7 +81,8 @@ mod tests { }, } }); - let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let data: config_who_am_i_query::ResponseData = + serde_json::from_value(json_response).unwrap(); let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { @@ -116,7 +107,8 @@ mod tests { }, } }); - let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let data: config_who_am_i_query::ResponseData = + serde_json::from_value(json_response).unwrap(); let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { diff --git a/crates/rover-client/src/query/config/who_am_i/types.rs b/crates/rover-client/src/query/config/who_am_i/types.rs new file mode 100644 index 000000000..c9cfeafa2 --- /dev/null +++ b/crates/rover-client/src/query/config/who_am_i/types.rs @@ -0,0 +1,31 @@ +use super::query_runner::config_who_am_i_query; + +use houston::CredentialOrigin; + +pub(crate) type QueryResponseData = config_who_am_i_query::ResponseData; +pub(crate) type QueryVariables = config_who_am_i_query::Variables; +pub(crate) type QueryActorType = config_who_am_i_query::ActorType; + +#[derive(Debug, PartialEq)] +pub struct RegistryIdentity { + pub id: String, + pub graph_title: Option, + pub key_actor_type: Actor, + pub credential_origin: CredentialOrigin, +} + +#[derive(Debug, PartialEq)] +pub enum Actor { + GRAPH, + USER, + OTHER, +} + +#[derive(Debug, PartialEq)] +pub struct ConfigWhoAmIInput {} + +impl From for QueryVariables { + fn from(_input: ConfigWhoAmIInput) -> Self { + Self {} + } +} diff --git a/crates/rover-client/src/query/config/whoami.graphql b/crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql similarity index 80% rename from crates/rover-client/src/query/config/whoami.graphql rename to crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql index 9bf0e18f6..4f131f2fa 100644 --- a/crates/rover-client/src/query/config/whoami.graphql +++ b/crates/rover-client/src/query/config/who_am_i/who_am_i_query.graphql @@ -1,4 +1,4 @@ -query WhoAmIQuery { +query ConfigWhoAmIQuery { me { __typename ... on Service { diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 2c343c16e..1c77dea5a 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -1,9 +1,9 @@ use ansi_term::Colour::Green; +use rover_client::query::config::who_am_i::{query_runner, Actor, ConfigWhoAmIInput}; use serde::Serialize; use structopt::StructOpt; use houston::CredentialOrigin; -use rover_client::query::config::whoami; use crate::anyhow; use crate::command::RoverStdout; @@ -26,7 +26,7 @@ impl WhoAmI { let client = client_config.get_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)?; + let identity = query_runner::run(ConfigWhoAmIInput {}, &client)?; let mut message = format!( "{}: {:?}\n", @@ -35,7 +35,7 @@ impl WhoAmI { ); match identity.key_actor_type { - whoami::Actor::GRAPH => { + Actor::GRAPH => { if let Some(graph_title) = identity.graph_title { message.push_str(&format!( "{}: {}\n", @@ -50,7 +50,7 @@ impl WhoAmI { )); Ok(()) } - whoami::Actor::USER => { + Actor::USER => { message.push_str(&format!( "{}: {}\n", Green.normal().paint("User ID"), @@ -63,7 +63,7 @@ impl WhoAmI { )), }?; - let origin = match client.credential.origin { + let origin = match client.get_credential_origin() { CredentialOrigin::ConfigFile(path) => format!("--profile {}", &path), CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key), };