Skip to content

Commit

Permalink
chore: refactor config whoami (#633)
Browse files Browse the repository at this point in the history
  • Loading branch information
EverlastingBugstopper committed Jun 28, 2021
1 parent 4cb9d0d commit 69c38b9
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 105 deletions.
29 changes: 26 additions & 3 deletions crates/rover-client/src/blocking/client.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -35,7 +38,7 @@ impl GraphQLClient {
variables: Q::Variables,
header_map: &HashMap<String, String>,
) -> Result<Q::ResponseData, RoverClientError> {
let header_map = headers::build(header_map)?;
let header_map = build_headers(header_map)?;
let response = self.execute::<Q>(variables, header_map)?;
GraphQLClient::handle_response::<Q>(response)
}
Expand Down Expand Up @@ -125,6 +128,26 @@ fn handle_graphql_body_errors(errors: Vec<GraphQLError>) -> 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<String, String>) -> Result<HeaderMap, RoverClientError> {
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::*;
Expand Down
2 changes: 2 additions & 0 deletions crates/rover-client/src/blocking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
49 changes: 45 additions & 4 deletions crates/rover-client/src/blocking/studio_client.rs
Original file line number Diff line number Diff line change
@@ -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,
}
Expand All @@ -33,8 +38,44 @@ impl StudioClient {
&self,
variables: Q::Variables,
) -> Result<Q::ResponseData, RoverClientError> {
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::<Q>(variables, header_map)?;
GraphQLClient::handle_response::<Q>(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<HeaderMap, RoverClientError> {
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()
}
}
61 changes: 0 additions & 61 deletions crates/rover-client/src/headers.rs

This file was deleted.

3 changes: 0 additions & 3 deletions crates/rover-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion crates/rover-client/src/query/config/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions crates/rover-client/src/query/config/who_am_i/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod types;

pub mod query_runner;
pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity};
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -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<String>,
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<RegistryIdentity, RoverClientError> {
let response_data = client.post::<WhoAmIQuery>(variables)?;
get_identity_from_response_data(response_data, client.credential.origin.clone())
let response_data = client.post::<ConfigWhoAmIQuery>(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<RegistryIdentity, RoverClientError> {
if let Some(me) = response_data.me {
Expand All @@ -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,
};

Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
31 changes: 31 additions & 0 deletions crates/rover-client/src/query/config/who_am_i/types.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
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<ConfigWhoAmIInput> for QueryVariables {
fn from(_input: ConfigWhoAmIInput) -> Self {
Self {}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
query WhoAmIQuery {
query ConfigWhoAmIQuery {
me {
__typename
... on Service {
Expand Down
10 changes: 5 additions & 5 deletions src/command/config/whoami.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -50,7 +50,7 @@ impl WhoAmI {
));
Ok(())
}
whoami::Actor::USER => {
Actor::USER => {
message.push_str(&format!(
"{}: {}\n",
Green.normal().paint("User ID"),
Expand All @@ -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),
};
Expand Down

0 comments on commit 69c38b9

Please sign in to comment.