diff --git a/server/src/builder.rs b/server/src/builder.rs index 6209e93f..220f2c88 100644 --- a/server/src/builder.rs +++ b/server/src/builder.rs @@ -13,7 +13,7 @@ use unleash_yggdrasil::EngineState; use crate::cli::RedisMode; use crate::feature_cache::FeatureCache; use crate::http::feature_refresher::{FeatureRefreshConfig, FeatureRefresherMode}; -use crate::http::unleash_client::new_reqwest_client; +use crate::http::unleash_client::{new_reqwest_client, ClientMetaInformation}; use crate::offline::offline_hotload::{load_bootstrap, load_offline_engine_cache}; use crate::persistence::file::FilePersister; use crate::persistence::redis::RedisPersister; @@ -218,7 +218,10 @@ async fn get_data_source(args: &EdgeArgs) -> Option> { None } -async fn build_edge(args: &EdgeArgs, app_name: &str) -> EdgeResult { +async fn build_edge( + args: &EdgeArgs, + client_meta_information: ClientMetaInformation, +) -> EdgeResult { if !args.strict { if !args.dynamic { error!("You should explicitly opt into either strict or dynamic behavior. Edge has defaulted to dynamic to preserve legacy behavior, however we recommend using strict from now on. Not explicitly opting into a behavior will return an error on startup in a future release"); @@ -237,13 +240,12 @@ async fn build_edge(args: &EdgeArgs, app_name: &str) -> EdgeResult { let persistence = get_data_source(args).await; let http_client = new_reqwest_client( - "unleash_edge".into(), args.skip_ssl_verification, args.client_identity.clone(), args.upstream_certificate_file.clone(), Duration::seconds(args.upstream_request_timeout), Duration::seconds(args.upstream_socket_timeout), - app_name.into(), + client_meta_information.clone(), )?; let unleash_client = Url::parse(&args.upstream_url.clone()) @@ -267,7 +269,7 @@ async fn build_edge(args: &EdgeArgs, app_name: &str) -> EdgeResult { let feature_config = FeatureRefreshConfig::new( Duration::seconds(args.features_refresh_interval_seconds as i64), refresher_mode, - app_name.to_string(), + client_meta_information, ); let feature_refresher = Arc::new(FeatureRefresher::new( unleash_client, @@ -315,7 +317,16 @@ pub async fn build_caches_and_refreshers(args: CliArgs) -> EdgeResult EdgeMode::Offline(offline_args) => { build_offline(offline_args).map(|cache| (cache, None, None, None)) } - EdgeMode::Edge(edge_args) => build_edge(&edge_args, &args.app_name).await, + EdgeMode::Edge(edge_args) => { + build_edge( + &edge_args, + ClientMetaInformation { + app_name: args.app_name, + instance_id: args.instance_id, + }, + ) + .await + } _ => unreachable!(), } } @@ -325,6 +336,7 @@ mod tests { use crate::{ builder::{build_edge, build_offline}, cli::{EdgeArgs, OfflineArgs, TokenHeader}, + http::unleash_client::ClientMetaInformation, }; #[test] @@ -375,7 +387,14 @@ mod tests { streaming: false, }; - let result = build_edge(&args, "test-app").await; + let result = build_edge( + &args, + ClientMetaInformation { + app_name: "test-app".into(), + instance_id: "test-instance-id".into(), + }, + ) + .await; assert!(result.is_err()); assert_eq!( result.err().unwrap().to_string(), diff --git a/server/src/cli.rs b/server/src/cli.rs index 41c80a35..ee9aeb40 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -349,7 +349,7 @@ pub struct CliArgs { pub mode: EdgeMode, /// Instance id. Used for metrics reporting. - #[clap(long, env, default_value_t = ulid::Ulid::new().to_string())] + #[clap(long, env, default_value_t = format!("unleash-edge@{}", ulid::Ulid::new()))] pub instance_id: String, /// App name. Used for metrics reporting. diff --git a/server/src/client_api.rs b/server/src/client_api.rs index 059b9a92..daa24013 100644 --- a/server/src/client_api.rs +++ b/server/src/client_api.rs @@ -299,7 +299,7 @@ mod tests { use crate::auth::token_validator::TokenValidator; use crate::cli::{OfflineArgs, TokenHeader}; - use crate::http::unleash_client::UnleashClient; + use crate::http::unleash_client::{UnleashClient, ClientMetaInformation}; use crate::middleware; use crate::tests::{features_from_disk, upstream_server}; use actix_http::{Request, StatusCode}; @@ -1011,7 +1011,7 @@ mod tests { persistence: None, strict: false, streaming: false, - app_name: "test-app".into(), + client_meta_information: ClientMetaInformation::test_config(), }); let token_validator = Arc::new(TokenValidator { unleash_client: unleash_client.clone(), diff --git a/server/src/http/feature_refresher.rs b/server/src/http/feature_refresher.rs index 1468915e..5aba48f1 100644 --- a/server/src/http/feature_refresher.rs +++ b/server/src/http/feature_refresher.rs @@ -25,7 +25,7 @@ use crate::{ types::{ClientFeaturesRequest, ClientFeaturesResponse, EdgeToken, TokenRefresh}, }; -use super::unleash_client::UnleashClient; +use super::unleash_client::{ClientMetaInformation, UnleashClient}; fn frontend_token_is_covered_by_tokens( frontend_token: &EdgeToken, @@ -48,7 +48,7 @@ pub struct FeatureRefresher { pub persistence: Option>, pub strict: bool, pub streaming: bool, - pub app_name: String, + pub client_meta_information: ClientMetaInformation, } impl Default for FeatureRefresher { @@ -62,7 +62,7 @@ impl Default for FeatureRefresher { persistence: None, strict: true, streaming: false, - app_name: "unleash_edge".into(), + client_meta_information: Default::default(), } } } @@ -70,13 +70,13 @@ impl Default for FeatureRefresher { fn client_application_from_token_and_name( token: EdgeToken, refresh_interval: i64, - app_name: &str, + client_meta_information: ClientMetaInformation, ) -> ClientApplication { ClientApplication { - app_name: app_name.into(), + app_name: client_meta_information.app_name, connect_via: None, environment: token.environment, - instance_id: None, + instance_id: Some(client_meta_information.instance_id), interval: refresh_interval as u32, started: Utc::now(), strategies: vec![], @@ -99,19 +99,19 @@ pub enum FeatureRefresherMode { pub struct FeatureRefreshConfig { features_refresh_interval: chrono::Duration, mode: FeatureRefresherMode, - app_name: String, + client_meta_information: ClientMetaInformation, } impl FeatureRefreshConfig { pub fn new( features_refresh_interval: chrono::Duration, mode: FeatureRefresherMode, - app_name: String, + client_meta_information: ClientMetaInformation, ) -> Self { Self { features_refresh_interval, mode, - app_name, + client_meta_information, } } } @@ -133,7 +133,7 @@ impl FeatureRefresher { persistence, strict: config.mode != FeatureRefresherMode::Dynamic, streaming: config.mode == FeatureRefresherMode::Streaming, - app_name: config.app_name, + client_meta_information: config.client_meta_information, } } @@ -254,7 +254,7 @@ impl FeatureRefresher { client_application_from_token_and_name( token.clone(), self.refresh_interval.num_seconds(), - &self.app_name, + self.client_meta_information.clone(), ), ) .await @@ -276,7 +276,7 @@ impl FeatureRefresher { /// This is where we set up a listener per token. pub async fn start_streaming_features_background_task( &self, - app_name: String, + client_meta_information: ClientMetaInformation, custom_headers: Vec<(String, String)>, ) -> anyhow::Result<()> { use anyhow::Context; @@ -289,8 +289,11 @@ impl FeatureRefresher { let mut es_client_builder = eventsource_client::ClientBuilder::for_url(streaming_url) .context("Failed to create EventSource client for streaming")? .header("Authorization", &token.token)? - .header(UNLEASH_APPNAME_HEADER, &app_name)? - .header(UNLEASH_INSTANCE_ID_HEADER, "unleash_edge")? + .header(UNLEASH_APPNAME_HEADER, &client_meta_information.app_name)? + .header( + UNLEASH_INSTANCE_ID_HEADER, + &client_meta_information.instance_id, + )? .header( UNLEASH_CLIENT_SPEC_HEADER, unleash_yggdrasil::SUPPORTED_SPEC_VERSION, @@ -557,7 +560,7 @@ mod tests { use crate::feature_cache::{update_projects_from_feature_update, FeatureCache}; use crate::filters::{project_filter, FeatureFilterSet}; - use crate::http::unleash_client::new_reqwest_client; + use crate::http::unleash_client::{new_reqwest_client, ClientMetaInformation}; use crate::tests::features_from_disk; use crate::tokens::cache_key; use crate::types::TokenValidationStatus::Validated; @@ -580,13 +583,12 @@ mod tests { fn create_test_client() -> UnleashClient { let http_client = new_reqwest_client( - "unleash_edge".into(), false, None, None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + ClientMetaInformation::test_config(), ) .expect("Failed to create client"); diff --git a/server/src/http/unleash_client.rs b/server/src/http/unleash_client.rs index ae80b2be..5f4562e5 100644 --- a/server/src/http/unleash_client.rs +++ b/server/src/http/unleash_client.rs @@ -73,6 +73,30 @@ lazy_static! { .unwrap(); } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ClientMetaInformation { + pub app_name: String, + pub instance_id: String, +} + +impl Default for ClientMetaInformation { + fn default() -> Self { + Self { + app_name: "unleash-edge".into(), + instance_id: format!("unleash-edge@{}", ulid::Ulid::new().to_string()), + } + } +} + +impl ClientMetaInformation { + pub fn test_config() -> Self { + Self { + app_name: "test-app-name".into(), + instance_id: "test-instance-id".into(), + } + } +} + #[derive(Clone, Debug, Default)] pub struct UnleashClient { pub urls: UnleashUrls, @@ -130,13 +154,12 @@ fn build_identity(tls: Option) -> EdgeResult { } pub fn new_reqwest_client( - instance_id: String, skip_ssl_verification: bool, client_identity: Option, upstream_certificate_file: Option, connect_timeout: Duration, socket_timeout: Duration, - app_name: String, + client_meta_information: ClientMetaInformation, ) -> EdgeResult { build_identity(client_identity) .and_then(|builder| { @@ -149,12 +172,12 @@ pub fn new_reqwest_client( let mut header_map = HeaderMap::new(); header_map.insert( UNLEASH_APPNAME_HEADER, - header::HeaderValue::from_str(app_name.as_str()) + header::HeaderValue::from_str(&client_meta_information.app_name) .expect("Could not add app name as a header"), ); header_map.insert( UNLEASH_INSTANCE_ID_HEADER, - header::HeaderValue::from_bytes(instance_id.as_bytes()).unwrap(), + header::HeaderValue::from_str(&client_meta_information.instance_id).unwrap(), ); header_map.insert( UNLEASH_CLIENT_SPEC_HEADER, @@ -195,13 +218,15 @@ impl UnleashClient { Ok(Self { urls: UnleashUrls::from_str(server_url)?, backing_client: new_reqwest_client( - instance_id, false, None, None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + ClientMetaInformation { + instance_id, + app_name: "test-client".into(), + }, ) .unwrap(), custom_headers: Default::default(), @@ -211,18 +236,16 @@ impl UnleashClient { #[cfg(test)] pub fn new_insecure(server_url: &str) -> Result { - use ulid::Ulid; Ok(Self { urls: UnleashUrls::from_str(server_url)?, backing_client: new_reqwest_client( - Ulid::new().to_string(), true, None, None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + ClientMetaInformation::test_config(), ) .unwrap(), custom_headers: Default::default(), @@ -514,7 +537,7 @@ mod tests { }, }; - use super::{EdgeTokens, UnleashClient}; + use super::{EdgeTokens, UnleashClient, ClientMetaInformation}; impl ClientFeaturesRequest { pub(crate) fn new(api_key: String, etag: Option) -> Self { @@ -801,13 +824,15 @@ mod tests { pkcs12_passphrase: Some(passphrase.into()), }; let client = new_reqwest_client( - "test_pkcs12".into(), false, Some(identity), None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + ClientMetaInformation { + app_name: "test-client".into(), + instance_id: "test-pkcs12".into(), + }, ); assert!(client.is_ok()); } @@ -823,13 +848,15 @@ mod tests { pkcs12_passphrase: Some(passphrase.into()), }; let client = new_reqwest_client( - "test_pkcs12".into(), false, Some(identity), None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + ClientMetaInformation { + app_name: "test-client".into(), + instance_id: "test-pkcs12".into(), + }, ); assert!(client.is_err()); } @@ -845,13 +872,15 @@ mod tests { pkcs12_passphrase: None, }; let client = new_reqwest_client( - "test_pkcs8".into(), false, Some(identity), None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + ClientMetaInformation { + app_name: "test-client".into(), + instance_id: "test-pkcs8".into(), + }, ); assert!(client.is_ok()); } diff --git a/server/src/main.rs b/server/src/main.rs index 273d79c5..641f8e0f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -29,7 +29,10 @@ use unleash_edge::{internal_backstage, tls}; #[cfg(not(tarpaulin_include))] #[actix_web::main] async fn main() -> Result<(), anyhow::Error> { - use unleash_edge::{http::broadcaster::Broadcaster, metrics::metrics_pusher}; + use unleash_edge::{ + http::{broadcaster::Broadcaster, unleash_client::ClientMetaInformation}, + metrics::metrics_pusher, + }; let args = CliArgs::parse(); let disable_all_endpoint = args.disable_all_endpoint; @@ -58,6 +61,7 @@ async fn main() -> Result<(), anyhow::Error> { instance_id: args.clone().instance_id, }; let app_name = args.app_name.clone(); + let instance_id = args.instance_id.clone(); let custom_headers = match args.mode { cli::EdgeMode::Edge(ref edge) => edge.custom_client_headers.clone(), _ => vec![], @@ -167,7 +171,13 @@ async fn main() -> Result<(), anyhow::Error> { let custom_headers = custom_headers.clone(); tokio::spawn(async move { let _ = refresher_for_background - .start_streaming_features_background_task(app_name, custom_headers) + .start_streaming_features_background_task( + ClientMetaInformation { + app_name, + instance_id, + }, + custom_headers, + ) .await; }); } diff --git a/server/src/middleware/client_token_from_frontend_token.rs b/server/src/middleware/client_token_from_frontend_token.rs index b33eb17c..f3f2a9c9 100644 --- a/server/src/middleware/client_token_from_frontend_token.rs +++ b/server/src/middleware/client_token_from_frontend_token.rs @@ -132,13 +132,12 @@ mod tests { .await; let http_client = new_reqwest_client( - "unleash_edge".into(), false, None, None, Duration::seconds(5), Duration::seconds(5), - "test-client".into(), + crate::http::unleash_client::ClientMetaInformation::test_config(), ) .expect("Failed to create client");