diff --git a/components/common/src/error.rs b/components/common/src/error.rs index 3cc3408ab83..9a870c6b820 100644 --- a/components/common/src/error.rs +++ b/components/common/src/error.rs @@ -55,6 +55,7 @@ pub enum Error { IO(io::Error), /// Errors when joining paths :) JoinPathsError(env::JoinPathsError), + MissingCLIInputError(String), NetParseError(net::AddrParseError), OfflineArtifactNotFound(PackageIdent), OfflineOriginKeyNotFound(String), @@ -119,6 +120,9 @@ impl fmt::Display for Error { s) } Error::HabitatCore(ref e) => format!("{}", e), + Error::MissingCLIInputError(ref arg) => { + format!("Missing required CLI argument!: {}", arg) + } Error::InstallHookFailed(ref ident) => { format!("Install hook exited unsuccessfully: {}", ident) } @@ -193,6 +197,7 @@ impl error::Error for Error { Error::InvalidInstallHookMode(_) => "Invalid InstallHookMode", Error::IO(ref err) => err.description(), Error::JoinPathsError(ref err) => err.description(), + Error::MissingCLIInputError(_) => "Missing required CLI argument!", Error::NetParseError(_) => "Can't parse IP:port", Error::OfflineArtifactNotFound(_) => "Cached artifact not found in offline mode", Error::OfflineOriginKeyNotFound(_) => "Cached origin key not found in offline mode", diff --git a/components/common/src/types/mod.rs b/components/common/src/types/mod.rs index 132c2bf1ddf..860c0dc7e64 100644 --- a/components/common/src/types/mod.rs +++ b/components/common/src/types/mod.rs @@ -109,6 +109,8 @@ impl EventStreamMetadata { pub struct AutomateAuthToken(String); impl AutomateAuthToken { + /// The name of the Clap argument we'll use for arguments of this type. + pub const ARG_NAME: &'static str = "EVENT_STREAM_TOKEN"; // Ideally, we'd like to take advantage of // `habitat_core::env::Config` trait, but that currently requires // a `Default` implementation, and there isn't really a legitimate @@ -125,6 +127,24 @@ impl AutomateAuthToken { println!("getting automate auth token from env..."); Ok(env::var(AutomateAuthToken::ENVVAR)?.parse().unwrap()) } + + /// Ensure that user input from Clap can be converted an instance + /// of a token. + #[allow(clippy::needless_pass_by_value)] // Signature required by CLAP + pub fn validate(value: String) -> result::Result<(), String> { + value.parse::() + .map(|_| ()) + .map_err(|_| "This should be impossible".to_string()) + } + + /// Create an instance of `AutomateAuthToken` from validated + /// user input. + pub fn from_matches(m: &ArgMatches) -> result::Result { + m.value_of(Self::ARG_NAME) + // This should be a required argument + .ok_or_else(|| Error::MissingCLIInputError(Self::ARG_NAME.to_string()))? + .parse() + } } impl FromStr for AutomateAuthToken { diff --git a/components/hab/src/cli.rs b/components/hab/src/cli.rs index e482dee2b66..5c927e33783 100644 --- a/components/hab/src/cli.rs +++ b/components/hab/src/cli.rs @@ -26,7 +26,8 @@ use habitat_common::{cli::{BINLINK_DIR_ENVVAR, PACKAGE_TARGET_ENVVAR, RING_ENVVAR, RING_KEY_ENVVAR}, - types::{EventStreamMetadata, + types::{AutomateAuthToken, + EventStreamMetadata, ListenCtlAddr}, FeatureFlag}; use habitat_core::{crypto::{keys::PairType, @@ -1200,6 +1201,14 @@ fn maybe_add_event_stream_options(mut app: App<'static, 'static>, .required(true) .takes_value(true) .validator(non_empty)); + app = app.arg(Arg::with_name(AutomateAuthToken::ARG_NAME).help("An authentication token for \ + streaming events to an \ + Automate server.") + .long("event-stream-token") + .required(true) + .takes_value(true) + .validator(AutomateAuthToken::validate) + .env(AutomateAuthToken::ENVVAR)); app = app.arg(Arg::with_name(EventStreamMetadata::ARG_NAME).help("An arbitrary key-value pair \ to add to each event \ @@ -1421,7 +1430,7 @@ mod tests { } #[test] - fn run_requries_app_and_env() { + fn run_requries_app_and_env_and_token() { let matches = sub_sup_run(event_stream_enabled()).get_matches_from_safe(vec!["run"]); assert!(matches.is_err()); assert_eq!(matches.unwrap_err().kind, @@ -1433,18 +1442,22 @@ mod tests { "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_ok()); } #[test] - fn app_and_env_options_require_event_stream_feature() { + fn app_and_env_and_token_options_require_event_stream_feature() { let matches = sub_sup_run(no_feature_flags()).get_matches_from_safe(vec![ "run", "--event-stream-application", "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); let error = matches.unwrap_err(); @@ -1460,6 +1473,8 @@ mod tests { "--event-stream-application", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); let error = matches.unwrap_err(); @@ -1476,6 +1491,8 @@ mod tests { "", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); let error = matches.unwrap_err(); @@ -1489,6 +1506,8 @@ mod tests { "--event-stream-application", "MY_APP", "--event-stream-environment", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); let error = matches.unwrap_err(); @@ -1505,6 +1524,8 @@ mod tests { "MY_APP", "--event-stream-environment", "", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); let error = matches.unwrap_err(); @@ -1519,6 +1540,8 @@ mod tests { "foo=bar", "--event-stream-application", "MY_APP", + "--event-stream-token", + "MY_TOKEN", "--event-stream-environment", "MY_ENV", ]); @@ -1542,6 +1565,8 @@ mod tests { "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_ok()); let matches = matches.unwrap(); @@ -1560,6 +1585,8 @@ mod tests { "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::EmptyValue); @@ -1575,6 +1602,8 @@ mod tests { "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::ValueValidation); @@ -1590,6 +1619,8 @@ mod tests { "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::ValueValidation); @@ -1605,10 +1636,45 @@ mod tests { "MY_APP", "--event-stream-environment", "MY_ENV", + "--event-stream-token", + "MY_TOKEN", ]); assert!(matches.is_err()); assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::ValueValidation); } + #[test] + fn token_option_must_take_a_value() { + let matches = sub_sup_run(event_stream_enabled()).get_matches_from_safe(vec![ + "run", + "--event-stream-application", + "MY_APP", + "--event-stream-environment", + "MY_ENV", + "--event-stream-token", + ]); + assert!(matches.is_err()); + let error = matches.unwrap_err(); + assert_eq!(error.kind, clap::ErrorKind::EmptyValue); + assert_eq!(error.info, + Some(vec![AutomateAuthToken::ARG_NAME.to_string()])); + } + + #[test] + fn token_option_cannot_be_empty() { + let matches = sub_sup_run(event_stream_enabled()).get_matches_from_safe(vec![ + "run", + "--event-stream-application", + "MY_APP", + "--event-stream-environment", + "MY_ENV", + "--event-stream-token", + "", + ]); + assert!(matches.is_err()); + let error = matches.unwrap_err(); + assert_eq!(error.kind, clap::ErrorKind::ValueValidation); + } + } } diff --git a/components/sup/src/event.rs b/components/sup/src/event.rs index e75a5cd8fa7..cfa9acb1d27 100644 --- a/components/sup/src/event.rs +++ b/components/sup/src/event.rs @@ -71,9 +71,9 @@ lazy_static! { /// server. Stashes the handle to the stream, as well as the core /// event information that will be a part of all events, in a global /// static reference for access later. -pub fn init_stream(conn_info: EventConnectionInfo, event_core: EventCore) { +pub fn init_stream(config: EventStreamConfig, event_core: EventCore) { INIT.call_once(|| { - println!("automate auth token is {}", conn_info.auth_token); + let conn_info = EventConnectionInfo::new(config.token); let event_stream = init_nats_stream(conn_info).expect("Could not start NATS thread"); EVENT_STREAM.set(event_stream); EVENT_CORE.set(event_core); @@ -87,6 +87,7 @@ pub struct EventStreamConfig { environment: String, application: String, meta: EventStreamMetadata, + token: AutomateAuthToken, } impl EventStreamConfig { @@ -98,7 +99,8 @@ impl EventStreamConfig { application: m.value_of("EVENT_STREAM_APPLICATION") .map(str::to_string) .expect("Required option for EventStream feature"), - meta: EventStreamMetadata::from_matches(m)?, }) + meta: EventStreamMetadata::from_matches(m)?, + token: AutomateAuthToken::from_matches(m)?, }) } } @@ -146,12 +148,12 @@ pub struct EventCore { } impl EventCore { - pub fn new(config: EventStreamConfig, sys: &Sys) -> Self { + pub fn new(config: &EventStreamConfig, sys: &Sys) -> Self { EventCore { supervisor_id: sys.member_id.clone(), ip_address: sys.gossip_listen(), - environment: config.environment, - application: config.application, - meta: config.meta, } + environment: config.environment.clone(), + application: config.application.clone(), + meta: config.meta.clone(), } } } diff --git a/components/sup/src/manager/mod.rs b/components/sup/src/manager/mod.rs index 3a1fed42b83..57f155c91f5 100644 --- a/components/sup/src/manager/mod.rs +++ b/components/sup/src/manager/mod.rs @@ -50,7 +50,6 @@ use crate::{census::{CensusRing, Result, SupError}, event::{self, - EventConnectionInfo, EventCore, EventStreamConfig}, http_gateway, @@ -66,8 +65,7 @@ use habitat_butterfly::{member::Member, Suitability}, trace::Trace}; use habitat_common::{outputln, - types::{AutomateAuthToken, - ListenCtlAddr}, + types::ListenCtlAddr, FeatureFlag}; use habitat_core::{crypto::SymKey, env::{self, @@ -488,11 +486,10 @@ impl Manager { let es_config = cfg.event_stream_config .expect("Config should be present if the EventStream feature is enabled"); - let ec = EventCore::new(es_config, &sys); + let ec = EventCore::new(&es_config, &sys); // unwrap won't fail here; if there were an issue, from_env() // would have already propagated an error up the stack. - event::init_stream(EventConnectionInfo::new(AutomateAuthToken::from_env().unwrap()), - ec); + event::init_stream(es_config, ec); } Ok(Manager { state: Arc::new(ManagerState { cfg: cfg_static,