Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor config whoami #633

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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