Skip to content

Commit

Permalink
feat: adds origin to config whoami command
Browse files Browse the repository at this point in the history
`rover config whoami` now shows the user the file path
that their credentials are stored in, OR if Rover is
reading their credentials from the $APOLLO_KEY environment
variable
  • Loading branch information
EverlastingBugstopper committed Feb 23, 2021
1 parent a8c8195 commit 6733ec9
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 41 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions crates/houston/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ pub use error::HoustonProblem;

pub use profile::mask_key;
/// Utilites for saving, loading, and deleting configuration profiles.
pub use profile::LoadOpts;
pub use profile::Profile;
pub use profile::{Credential, CredentialOrigin, LoadOpts, Profile};
39 changes: 31 additions & 8 deletions crates/houston/src/profile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ pub struct Opts {
pub api_key: Option<String>,
}

/// Struct containing info about an API Key
pub struct Credential {
/// Apollo API Key
pub api_key: String,

/// The origin of the credential
pub origin: CredentialOrigin,
}

/// Info about where the API key was retrieved
#[derive(Debug, Clone, PartialEq)]
pub enum CredentialOrigin {
/// The credential is from an environment variable
EnvVar,

/// The credential is from a configuration file
ConfigFile(PathBuf),
}

impl Profile {
fn base_dir(config: &Config) -> PathBuf {
config.home.join("profiles")
Expand All @@ -50,21 +69,25 @@ impl Profile {
/// if it finds it. Otherwise looks for credentials on the file system.
///
/// Takes an optional `profile` argument. Defaults to `"default"`.
pub fn get_api_key(name: &str, config: &Config) -> Result<String, HoustonProblem> {
let api_key: Result<String, HoustonProblem> = match &config.override_api_key {
Some(api_key) => Ok(api_key.to_string()),
pub fn get_credential(name: &str, config: &Config) -> Result<Credential, HoustonProblem> {
let credential = match &config.override_api_key {
Some(api_key) => Credential {
api_key: api_key.to_string(),
origin: CredentialOrigin::EnvVar,
},
None => {
let opts = LoadOpts { sensitive: true };
let profile = Profile::load(name, config, opts)?;
Ok(profile.sensitive.api_key)
Credential {
api_key: profile.sensitive.api_key,
origin: CredentialOrigin::ConfigFile(Profile::dir(name, config)),
}
}
};

let api_key = api_key?;

tracing::debug!("using API key {}", mask_key(&api_key));
tracing::debug!("using API key {}", mask_key(&credential.api_key));

Ok(api_key)
Ok(credential)
}

/// Saves configuration options for a specific profile to the file system,
Expand Down
10 changes: 6 additions & 4 deletions crates/houston/tests/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ fn it_can_set_and_get_an_api_key_via_creds_file() {
assert!(sensitive_file.exists());

assert_eq!(
config::Profile::get_api_key(profile, &config)
.expect("retreiving api key for default profile failed"),
config::Profile::get_credential(profile, &config)
.expect("retreiving api key for default profile failed")
.api_key,
String::from(api_key)
);

Expand All @@ -41,8 +42,9 @@ fn it_can_get_an_api_key_via_env_var() {
let config = get_config(Some(api_key.to_string()));

assert_eq!(
config::Profile::get_api_key(profile, &config)
.expect("retreiving api key for default profile failed"),
config::Profile::get_credential(profile, &config)
.expect("retreiving api key for default profile failed")
.api_key,
String::from(api_key)
);
}
Expand Down
5 changes: 5 additions & 0 deletions crates/rover-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ authors = ["Apollo Developers <[email protected]>"]
edition = "2018"

[dependencies]

# workspace deps
houston = { path = "../houston" }

# crates.io deps
anyhow = "1"
graphql_client = "0.9"
http = "0.2"
Expand Down
10 changes: 6 additions & 4 deletions crates/rover-client/src/blocking/studio_client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::blocking::Client;
use crate::headers;
use crate::RoverClientError;
use houston::Credential;

use graphql_client::GraphQLQuery;

/// Represents a client for making GraphQL requests to Apollo Studio.
pub struct StudioClient {
api_key: String,
pub credential: Credential,
client: reqwest::blocking::Client,
uri: String,
version: String,
Expand All @@ -14,9 +16,9 @@ pub struct StudioClient {
impl StudioClient {
/// Construct a new [StudioClient] from an `api_key`, a `uri`, and a `version`.
/// For use in Rover, the `uri` is usually going to be to Apollo Studio
pub fn new(api_key: &str, uri: &str, version: &str) -> StudioClient {
pub fn new(credential: Credential, uri: &str, version: &str) -> StudioClient {
StudioClient {
api_key: api_key.to_string(),
credential,
client: reqwest::blocking::Client::new(),
uri: uri.to_string(),
version: version.to_string(),
Expand All @@ -30,7 +32,7 @@ impl StudioClient {
&self,
variables: Q::Variables,
) -> Result<Q::ResponseData, RoverClientError> {
let h = headers::build_studio_headers(&self.api_key, &self.version)?;
let h = headers::build_studio_headers(&self.credential.api_key, &self.version)?;
let body = Q::build_query(variables);
tracing::trace!(request_headers = ?h);
tracing::trace!("Request Body: {}", serde_json::to_string(&body)?);
Expand Down
13 changes: 10 additions & 3 deletions crates/rover-client/src/query/config/whoami.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::blocking::StudioClient;
use crate::RoverClientError;
use houston::CredentialOrigin;

use graphql_client::*;

#[derive(GraphQLQuery)]
Expand All @@ -21,6 +23,7 @@ pub struct RegistryIdentity {
pub id: String,
pub graph_title: Option<String>,
pub key_actor_type: Actor,
pub credential_origin: CredentialOrigin,
}

#[derive(Debug, PartialEq)]
Expand All @@ -37,11 +40,12 @@ pub fn run(
client: &StudioClient,
) -> Result<RegistryIdentity, RoverClientError> {
let response_data = client.post::<WhoAmIQuery>(variables)?;
get_identity_from_response_data(response_data)
get_identity_from_response_data(response_data, client.credential.origin.clone())
}

fn get_identity_from_response_data(
response_data: who_am_i_query::ResponseData,
credential_origin: CredentialOrigin,
) -> Result<RegistryIdentity, RoverClientError> {
if let Some(me) = response_data.me {
// I believe for the purposes of the CLI, we only care about users and
Expand All @@ -64,6 +68,7 @@ fn get_identity_from_response_data(
id: me.id,
graph_title,
key_actor_type,
credential_origin,
})
} else {
Err(RoverClientError::InvalidKey)
Expand All @@ -87,12 +92,13 @@ mod tests {
}
});
let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap();
let output = get_identity_from_response_data(data);
let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar);

let expected_identity = RegistryIdentity {
id: "gh.nobodydefinitelyhasthisusernamelol".to_string(),
graph_title: None,
key_actor_type: Actor::USER,
credential_origin: CredentialOrigin::EnvVar,
};
assert!(output.is_ok());
assert_eq!(output.unwrap(), expected_identity);
Expand All @@ -111,12 +117,13 @@ mod tests {
}
});
let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap();
let output = get_identity_from_response_data(data);
let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar);

let expected_identity = RegistryIdentity {
id: "big-ol-graph-key-lolol".to_string(),
graph_title: Some("GraphKeyService".to_string()),
key_actor_type: Actor::GRAPH,
credential_origin: CredentialOrigin::EnvVar,
};
assert!(output.is_ok());
assert_eq!(output.unwrap(), expected_identity);
Expand Down
2 changes: 1 addition & 1 deletion src/command/config/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl Auth {
pub fn run(&self, config: config::Config) -> Result<RoverStdout> {
let api_key = api_key_prompt()?;
Profile::set_api_key(&self.profile_name, &config, &api_key)?;
Profile::get_api_key(&self.profile_name, &config).map(|_| {
Profile::get_credential(&self.profile_name, &config).map(|_| {
eprintln!("Successfully saved API key.");
})?;
Ok(RoverStdout::None)
Expand Down
56 changes: 39 additions & 17 deletions src/command/config/whoami.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use ansi_term::Colour::Green;
use serde::Serialize;
use structopt::StructOpt;

use houston::CredentialOrigin;
use rover_client::query::config::whoami;

use crate::anyhow;
use crate::command::RoverStdout;
use crate::utils::client::StudioClientConfig;
use crate::utils::env::RoverEnvKey;
use crate::Result;

#[derive(Debug, Serialize, StructOpt)]
Expand All @@ -24,28 +26,48 @@ impl WhoAmI {

let identity = whoami::run(whoami::who_am_i_query::Variables {}, &client)?;

let message = match identity.key_actor_type {
whoami::Actor::GRAPH => Ok(format!(
"Key Info\n{}: {}\n{}: {}\n{}: {:?}",
Green.normal().paint("Graph Title"),
identity.graph_title.unwrap(),
Green.normal().paint("Unique Graph ID"),
identity.id,
Green.normal().paint("Key Type"),
identity.key_actor_type
)),
whoami::Actor::USER => Ok(format!(
"Key Info\n{}: {}\n{}: {:?}",
Green.normal().paint("User ID"),
identity.id,
Green.normal().paint("Key Type"),
identity.key_actor_type
)),
let mut message = format!(
"{}: {:?}\n",
Green.normal().paint("Key Type"),
identity.key_actor_type
);

match identity.key_actor_type {
whoami::Actor::GRAPH => {
if let Some(graph_title) = identity.graph_title {
message.push_str(&format!(
"{}: {}\n",
Green.normal().paint("Graph Title"),
&graph_title
));
}
message.push_str(&format!(
"{}: {}\n",
Green.normal().paint("Unique Graph ID"),
identity.id
));
Ok(())
}
whoami::Actor::USER => {
message.push_str(&format!(
"{}: {}\n",
Green.normal().paint("User ID"),
identity.id
));
Ok(())
}
_ => Err(anyhow!(
"The key provided is invalid. Rover only accepts personal and graph API keys"
)),
}?;

let origin = match client.credential.origin {
CredentialOrigin::ConfigFile(path) => path.to_string_lossy().to_string(),
CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key),
};

message.push_str(&format!("{}: {}", Green.normal().paint("Origin"), &origin));

eprintln!("{}", message);

Ok(RoverStdout::None)
Expand Down
4 changes: 2 additions & 2 deletions src/utils/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl StudioClientConfig {
}

pub fn get_client(&self, profile_name: &str) -> Result<StudioClient> {
let api_key = config::Profile::get_api_key(profile_name, &self.config)?;
Ok(StudioClient::new(&api_key, &self.uri, &self.version))
let credential = config::Profile::get_credential(profile_name, &self.config)?;
Ok(StudioClient::new(credential, &self.uri, &self.version))
}
}

0 comments on commit 6733ec9

Please sign in to comment.