Skip to content

Commit b4d285f

Browse files
feat: adds origin to config whoami command
`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
1 parent a8c8195 commit b4d285f

File tree

10 files changed

+108
-43
lines changed

10 files changed

+108
-43
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/houston/src/lib.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ pub use error::HoustonProblem;
1111

1212
pub use profile::mask_key;
1313
/// Utilites for saving, loading, and deleting configuration profiles.
14-
pub use profile::LoadOpts;
15-
pub use profile::Profile;
14+
pub use profile::{Credential, CredentialOrigin, LoadOpts, Profile};

crates/houston/src/profile/mod.rs

+31-8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ pub struct Opts {
2626
pub api_key: Option<String>,
2727
}
2828

29+
/// Struct containing info about an API Key
30+
pub struct Credential {
31+
/// Apollo API Key
32+
pub api_key: String,
33+
34+
/// The origin of the credential
35+
pub origin: CredentialOrigin,
36+
}
37+
38+
/// Info about where the API key was retrieved
39+
#[derive(Debug, Clone, PartialEq)]
40+
pub enum CredentialOrigin {
41+
/// The credential is from an environment variable
42+
EnvVar,
43+
44+
/// The credential is from a configuration file
45+
ConfigFile(PathBuf),
46+
}
47+
2948
impl Profile {
3049
fn base_dir(config: &Config) -> PathBuf {
3150
config.home.join("profiles")
@@ -50,21 +69,25 @@ impl Profile {
5069
/// if it finds it. Otherwise looks for credentials on the file system.
5170
///
5271
/// Takes an optional `profile` argument. Defaults to `"default"`.
53-
pub fn get_api_key(name: &str, config: &Config) -> Result<String, HoustonProblem> {
54-
let api_key: Result<String, HoustonProblem> = match &config.override_api_key {
55-
Some(api_key) => Ok(api_key.to_string()),
72+
pub fn get_credential(name: &str, config: &Config) -> Result<Credential, HoustonProblem> {
73+
let credential = match &config.override_api_key {
74+
Some(api_key) => Credential {
75+
api_key: api_key.to_string(),
76+
origin: CredentialOrigin::EnvVar,
77+
},
5678
None => {
5779
let opts = LoadOpts { sensitive: true };
5880
let profile = Profile::load(name, config, opts)?;
59-
Ok(profile.sensitive.api_key)
81+
Credential {
82+
api_key: profile.sensitive.api_key,
83+
origin: CredentialOrigin::ConfigFile(Profile::dir(name, config)),
84+
}
6085
}
6186
};
6287

63-
let api_key = api_key?;
64-
65-
tracing::debug!("using API key {}", mask_key(&api_key));
88+
tracing::debug!("using API key {}", mask_key(&credential.api_key));
6689

67-
Ok(api_key)
90+
Ok(credential)
6891
}
6992

7093
/// Saves configuration options for a specific profile to the file system,

crates/houston/tests/auth.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ fn it_can_set_and_get_an_api_key_via_creds_file() {
2020
assert!(sensitive_file.exists());
2121

2222
assert_eq!(
23-
config::Profile::get_api_key(profile, &config)
24-
.expect("retreiving api key for default profile failed"),
23+
config::Profile::get_credential(profile, &config)
24+
.expect("retreiving api key for default profile failed")
25+
.api_key,
2526
String::from(api_key)
2627
);
2728

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

4344
assert_eq!(
44-
config::Profile::get_api_key(profile, &config)
45-
.expect("retreiving api key for default profile failed"),
45+
config::Profile::get_credential(profile, &config)
46+
.expect("retreiving api key for default profile failed")
47+
.api_key,
4648
String::from(api_key)
4749
);
4850
}

crates/rover-client/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ authors = ["Apollo Developers <[email protected]>"]
66
edition = "2018"
77

88
[dependencies]
9+
10+
# workspace deps
11+
houston = { path = "../houston" }
12+
13+
# crates.io deps
914
anyhow = "1"
1015
graphql_client = "0.9"
1116
http = "0.2"

crates/rover-client/src/blocking/studio_client.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use crate::blocking::Client;
22
use crate::headers;
33
use crate::RoverClientError;
4+
use houston::Credential;
5+
46
use graphql_client::GraphQLQuery;
57

68
/// Represents a client for making GraphQL requests to Apollo Studio.
79
pub struct StudioClient {
8-
api_key: String,
10+
pub credential: Credential,
911
client: reqwest::blocking::Client,
1012
uri: String,
1113
version: String,
@@ -14,9 +16,9 @@ pub struct StudioClient {
1416
impl StudioClient {
1517
/// Construct a new [StudioClient] from an `api_key`, a `uri`, and a `version`.
1618
/// For use in Rover, the `uri` is usually going to be to Apollo Studio
17-
pub fn new(api_key: &str, uri: &str, version: &str) -> StudioClient {
19+
pub fn new(credential: Credential, uri: &str, version: &str) -> StudioClient {
1820
StudioClient {
19-
api_key: api_key.to_string(),
21+
credential,
2022
client: reqwest::blocking::Client::new(),
2123
uri: uri.to_string(),
2224
version: version.to_string(),
@@ -30,7 +32,7 @@ impl StudioClient {
3032
&self,
3133
variables: Q::Variables,
3234
) -> Result<Q::ResponseData, RoverClientError> {
33-
let h = headers::build_studio_headers(&self.api_key, &self.version)?;
35+
let h = headers::build_studio_headers(&self.credential.api_key, &self.version)?;
3436
let body = Q::build_query(variables);
3537
tracing::trace!(request_headers = ?h);
3638
tracing::trace!("Request Body: {}", serde_json::to_string(&body)?);

crates/rover-client/src/query/config/whoami.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::blocking::StudioClient;
22
use crate::RoverClientError;
3+
use houston::CredentialOrigin;
4+
35
use graphql_client::*;
46

57
#[derive(GraphQLQuery)]
@@ -21,6 +23,7 @@ pub struct RegistryIdentity {
2123
pub id: String,
2224
pub graph_title: Option<String>,
2325
pub key_actor_type: Actor,
26+
pub credential_origin: CredentialOrigin,
2427
}
2528

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

4346
fn get_identity_from_response_data(
4447
response_data: who_am_i_query::ResponseData,
48+
credential_origin: CredentialOrigin,
4549
) -> Result<RegistryIdentity, RoverClientError> {
4650
if let Some(me) = response_data.me {
4751
// I believe for the purposes of the CLI, we only care about users and
@@ -64,6 +68,7 @@ fn get_identity_from_response_data(
6468
id: me.id,
6569
graph_title,
6670
key_actor_type,
71+
credential_origin,
6772
})
6873
} else {
6974
Err(RoverClientError::InvalidKey)
@@ -87,12 +92,13 @@ mod tests {
8792
}
8893
});
8994
let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap();
90-
let output = get_identity_from_response_data(data);
95+
let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar);
9196

9297
let expected_identity = RegistryIdentity {
9398
id: "gh.nobodydefinitelyhasthisusernamelol".to_string(),
9499
graph_title: None,
95100
key_actor_type: Actor::USER,
101+
credential_origin: CredentialOrigin::EnvVar,
96102
};
97103
assert!(output.is_ok());
98104
assert_eq!(output.unwrap(), expected_identity);
@@ -111,12 +117,13 @@ mod tests {
111117
}
112118
});
113119
let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap();
114-
let output = get_identity_from_response_data(data);
120+
let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar);
115121

116122
let expected_identity = RegistryIdentity {
117123
id: "big-ol-graph-key-lolol".to_string(),
118124
graph_title: Some("GraphKeyService".to_string()),
119125
key_actor_type: Actor::GRAPH,
126+
credential_origin: CredentialOrigin::EnvVar,
120127
};
121128
assert!(output.is_ok());
122129
assert_eq!(output.unwrap(), expected_identity);

src/command/config/auth.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl Auth {
2929
pub fn run(&self, config: config::Config) -> Result<RoverStdout> {
3030
let api_key = api_key_prompt()?;
3131
Profile::set_api_key(&self.profile_name, &config, &api_key)?;
32-
Profile::get_api_key(&self.profile_name, &config).map(|_| {
32+
Profile::get_credential(&self.profile_name, &config).map(|_| {
3333
eprintln!("Successfully saved API key.");
3434
})?;
3535
Ok(RoverStdout::None)
@@ -78,7 +78,9 @@ mod tests {
7878
let config = get_config(None);
7979

8080
Profile::set_api_key(DEFAULT_PROFILE, &config, DEFAULT_KEY).unwrap();
81-
let result = Profile::get_api_key(DEFAULT_PROFILE, &config).unwrap();
81+
let result = Profile::get_credential(DEFAULT_PROFILE, &config)
82+
.unwrap()
83+
.api_key;
8284
assert_eq!(result, DEFAULT_KEY);
8385
}
8486

@@ -88,7 +90,9 @@ mod tests {
8890
let config = get_config(None);
8991

9092
Profile::set_api_key(CUSTOM_PROFILE, &config, CUSTOM_KEY).unwrap();
91-
let result = Profile::get_api_key(CUSTOM_PROFILE, &config).unwrap();
93+
let result = Profile::get_credential(CUSTOM_PROFILE, &config)
94+
.unwrap()
95+
.api_key;
9296
assert_eq!(result, CUSTOM_KEY);
9397
}
9498

src/command/config/whoami.rs

+39-17
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use ansi_term::Colour::Green;
22
use serde::Serialize;
33
use structopt::StructOpt;
44

5+
use houston::CredentialOrigin;
56
use rover_client::query::config::whoami;
67

78
use crate::anyhow;
89
use crate::command::RoverStdout;
910
use crate::utils::client::StudioClientConfig;
11+
use crate::utils::env::RoverEnvKey;
1012
use crate::Result;
1113

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

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

27-
let message = match identity.key_actor_type {
28-
whoami::Actor::GRAPH => Ok(format!(
29-
"Key Info\n{}: {}\n{}: {}\n{}: {:?}",
30-
Green.normal().paint("Graph Title"),
31-
identity.graph_title.unwrap(),
32-
Green.normal().paint("Unique Graph ID"),
33-
identity.id,
34-
Green.normal().paint("Key Type"),
35-
identity.key_actor_type
36-
)),
37-
whoami::Actor::USER => Ok(format!(
38-
"Key Info\n{}: {}\n{}: {:?}",
39-
Green.normal().paint("User ID"),
40-
identity.id,
41-
Green.normal().paint("Key Type"),
42-
identity.key_actor_type
43-
)),
29+
let mut message = format!(
30+
"{}: {:?}\n",
31+
Green.normal().paint("Key Type"),
32+
identity.key_actor_type
33+
);
34+
35+
match identity.key_actor_type {
36+
whoami::Actor::GRAPH => {
37+
if let Some(graph_title) = identity.graph_title {
38+
message.push_str(&format!(
39+
"{}: {}\n",
40+
Green.normal().paint("Graph Title"),
41+
&graph_title
42+
));
43+
}
44+
message.push_str(&format!(
45+
"{}: {}\n",
46+
Green.normal().paint("Unique Graph ID"),
47+
identity.id
48+
));
49+
Ok(())
50+
}
51+
whoami::Actor::USER => {
52+
message.push_str(&format!(
53+
"{}: {}\n",
54+
Green.normal().paint("User ID"),
55+
identity.id
56+
));
57+
Ok(())
58+
}
4459
_ => Err(anyhow!(
4560
"The key provided is invalid. Rover only accepts personal and graph API keys"
4661
)),
4762
}?;
4863

64+
let origin = match client.credential.origin {
65+
CredentialOrigin::ConfigFile(path) => path.to_string_lossy().to_string(),
66+
CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key),
67+
};
68+
69+
message.push_str(&format!("{}: {}", Green.normal().paint("Origin"), &origin));
70+
4971
eprintln!("{}", message);
5072

5173
Ok(RoverStdout::None)

src/utils/client.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl StudioClientConfig {
3131
}
3232

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

0 commit comments

Comments
 (0)