1use std::fmt::Debug;
4use std::net::SocketAddr;
5#[cfg(unix)]
6use std::os::unix::fs::MetadataExt;
7use std::path::PathBuf;
8use std::sync::atomic::AtomicBool;
9use std::sync::atomic::Ordering;
10use std::time::Duration;
11
12use anyhow::Result;
13use anyhow::anyhow;
14use clap::ArgAction;
15use clap::Args;
16use clap::Parser;
17use clap::Subcommand;
18use clap::builder::FalseyValueParser;
19use parking_lot::Mutex;
20use regex::Captures;
21use regex::Regex;
22use url::ParseError;
23use url::Url;
24
25use crate::LicenseSource;
26use crate::configuration::Discussed;
27use crate::configuration::expansion::Expansion;
28use crate::configuration::generate_config_schema;
29use crate::configuration::generate_upgrade;
30use crate::configuration::schema::Mode;
31use crate::configuration::validate_yaml_configuration;
32use crate::metrics::meter_provider_internal;
33use crate::plugin::plugins;
34use crate::plugins::telemetry::reload::init_telemetry;
35use crate::registry::OciConfig;
36use crate::router::ConfigurationSource;
37use crate::router::RouterHttpServer;
38use crate::router::SchemaSource;
39use crate::router::ShutdownSource;
40use crate::uplink::Endpoints;
41use crate::uplink::UplinkConfig;
42
43#[cfg(all(feature = "global-allocator", not(feature = "dhat-heap"), unix))]
44#[global_allocator]
45static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
46
47#[cfg(feature = "dhat-heap")]
51#[global_allocator]
52pub(crate) static ALLOC: dhat::Alloc = dhat::Alloc;
53
54#[cfg(feature = "dhat-heap")]
55pub(crate) static DHAT_HEAP_PROFILER: Mutex<Option<dhat::Profiler>> = Mutex::new(None);
56
57#[cfg(feature = "dhat-ad-hoc")]
58pub(crate) static DHAT_AD_HOC_PROFILER: Mutex<Option<dhat::Profiler>> = Mutex::new(None);
59
60pub(crate) static APOLLO_ROUTER_DEV_MODE: AtomicBool = AtomicBool::new(false);
61pub(crate) static APOLLO_ROUTER_SUPERGRAPH_PATH_IS_SET: AtomicBool = AtomicBool::new(false);
62pub(crate) static APOLLO_ROUTER_SUPERGRAPH_URLS_IS_SET: AtomicBool = AtomicBool::new(false);
63pub(crate) static APOLLO_ROUTER_LICENCE_IS_SET: AtomicBool = AtomicBool::new(false);
64pub(crate) static APOLLO_ROUTER_LICENCE_PATH_IS_SET: AtomicBool = AtomicBool::new(false);
65pub(crate) static APOLLO_TELEMETRY_DISABLED: AtomicBool = AtomicBool::new(false);
66pub(crate) static APOLLO_ROUTER_LISTEN_ADDRESS: Mutex<Option<SocketAddr>> = Mutex::new(None);
67
68const INITIAL_UPLINK_POLL_INTERVAL: Duration = Duration::from_secs(10);
69
70#[cfg(feature = "dhat-heap")]
73fn create_heap_profiler() {
74 *DHAT_HEAP_PROFILER.lock() = Some(dhat::Profiler::new_heap());
75 println!("heap profiler installed");
76 unsafe { libc::atexit(drop_heap_profiler) };
77}
78
79#[cfg(feature = "dhat-heap")]
80#[unsafe(no_mangle)]
81extern "C" fn drop_heap_profiler() {
82 if let Some(p) = DHAT_HEAP_PROFILER.lock().take() {
83 drop(p);
84 }
85}
86
87#[cfg(feature = "dhat-ad-hoc")]
88fn create_ad_hoc_profiler() {
89 *DHAT_AD_HOC_PROFILER.lock() = Some(dhat::Profiler::new_heap());
90 println!("ad-hoc profiler installed");
91 unsafe { libc::atexit(drop_ad_hoc_profiler) };
92}
93
94#[cfg(feature = "dhat-ad-hoc")]
95#[unsafe(no_mangle)]
96extern "C" fn drop_ad_hoc_profiler() {
97 if let Some(p) = DHAT_AD_HOC_PROFILER.lock().take() {
98 drop(p);
99 }
100}
101
102#[derive(Subcommand, Debug)]
104enum Commands {
105 Config(ConfigSubcommandArgs),
107}
108
109#[derive(Args, Debug)]
110struct ConfigSubcommandArgs {
111 #[clap(subcommand)]
113 command: ConfigSubcommand,
114}
115
116#[derive(Subcommand, Debug)]
117enum ConfigSubcommand {
118 Schema,
120
121 Upgrade {
123 #[clap(value_parser, env = "APOLLO_ROUTER_CONFIG_PATH")]
125 config_path: PathBuf,
126
127 #[clap(action = ArgAction::SetTrue, long)]
129 diff: bool,
130 },
131 Validate {
133 #[clap(value_parser, env = "APOLLO_ROUTER_CONFIG_PATH")]
135 config_path: PathBuf,
136 },
137 Experimental,
139 Preview,
141}
142
143#[derive(Parser, Debug)]
145#[clap(name = "router", about = "Apollo federation router")]
146#[command(disable_version_flag(true))]
147pub struct Opt {
148 #[clap(
150 long = "log",
151 default_value = "info",
152 alias = "log-level",
153 value_parser = add_log_filter,
154 env = "APOLLO_ROUTER_LOG"
155 )]
156 log_level: String,
158
159 #[clap(
161 alias = "hr",
162 long = "hot-reload",
163 env = "APOLLO_ROUTER_HOT_RELOAD",
164 action(ArgAction::SetTrue)
165 )]
166 hot_reload: bool,
167
168 #[clap(
170 short,
171 long = "config",
172 value_parser,
173 env = "APOLLO_ROUTER_CONFIG_PATH"
174 )]
175 config_path: Option<PathBuf>,
176
177 #[clap(env = "APOLLO_ROUTER_DEV", long = "dev", action(ArgAction::SetTrue))]
179 dev: bool,
180
181 #[clap(
183 short,
184 long = "supergraph",
185 value_parser,
186 env = "APOLLO_ROUTER_SUPERGRAPH_PATH"
187 )]
188 supergraph_path: Option<PathBuf>,
189
190 #[clap(env = "APOLLO_ROUTER_SUPERGRAPH_URLS", value_delimiter = ',')]
192 supergraph_urls: Option<Vec<Url>>,
193
194 #[clap(subcommand)]
196 command: Option<Commands>,
197
198 #[clap(skip = std::env::var("APOLLO_KEY").ok())]
200 apollo_key: Option<String>,
201
202 #[cfg(unix)]
204 #[clap(long = "apollo-key-path", env = "APOLLO_KEY_PATH")]
205 apollo_key_path: Option<PathBuf>,
206
207 #[clap(skip = std::env::var("APOLLO_GRAPH_REF").ok())]
209 apollo_graph_ref: Option<String>,
210
211 #[clap(skip = std::env::var("APOLLO_ROUTER_LICENSE").ok())]
213 apollo_router_license: Option<String>,
214
215 #[clap(long = "license", env = "APOLLO_ROUTER_LICENSE_PATH")]
217 apollo_router_license_path: Option<PathBuf>,
218
219 #[clap(long, env, action = ArgAction::Append)]
221 apollo_uplink_endpoints: Option<String>,
223
224 #[clap(long, env = "APOLLO_GRAPH_ARTIFACT_REFERENCE", action = ArgAction::Append)]
226 graph_artifact_reference: Option<String>,
227
228 #[clap(long, env = "APOLLO_TELEMETRY_DISABLED", value_parser = FalseyValueParser::new())]
230 anonymous_telemetry_disabled: bool,
231
232 #[clap(long, default_value = "30s", value_parser = humantime::parse_duration, env)]
234 apollo_uplink_timeout: Duration,
235
236 #[clap(long = "listen", env = "APOLLO_ROUTER_LISTEN_ADDRESS")]
238 listen_address: Option<SocketAddr>,
239
240 #[clap(action = ArgAction::SetTrue, long, short = 'V')]
242 pub(crate) version: bool,
243}
244
245fn add_log_filter(raw: &str) -> Result<String, String> {
249 match std::env::var("RUST_LOG") {
250 Ok(filter) => Ok(filter),
251 Err(_e) => {
252 let lowered = raw.to_lowercase();
254 let rgx =
256 Regex::new(r"(^|,)(off|error|warn|info|debug|trace)").expect("regex must be valid");
257 let res = rgx.replace_all(&lowered, |caps: &Captures| {
258 format!("{}apollo_router={}", &caps[1], &caps[2])
261 });
262 Ok(format!("info,{res}"))
263 }
264 }
265}
266
267impl Opt {
268 pub(crate) fn uplink_config(&self) -> Result<UplinkConfig, anyhow::Error> {
269 Ok(UplinkConfig {
270 apollo_key: self
271 .apollo_key
272 .clone()
273 .ok_or(Self::err_require_opt("APOLLO_KEY"))?,
274 apollo_graph_ref: self
275 .apollo_graph_ref
276 .clone()
277 .ok_or(Self::err_require_opt("APOLLO_GRAPH_REF"))?,
278 endpoints: self
279 .apollo_uplink_endpoints
280 .as_ref()
281 .map(|endpoints| Self::parse_endpoints(endpoints))
282 .transpose()?,
283 poll_interval: INITIAL_UPLINK_POLL_INTERVAL,
284 timeout: self.apollo_uplink_timeout,
285 })
286 }
287
288 pub(crate) fn oci_config(&self) -> Result<OciConfig, anyhow::Error> {
289 Ok(OciConfig {
290 apollo_key: self
291 .apollo_key
292 .clone()
293 .ok_or(Self::err_require_opt("APOLLO_KEY"))?,
294 reference: Self::validate_oci_reference(
295 &self
296 .graph_artifact_reference
297 .clone()
298 .ok_or(Self::err_require_opt("APOLLO_GRAPH_ARTIFACT_REFERENCE"))?,
299 )?,
300 })
301 }
302
303 pub fn validate_oci_reference(reference: &str) -> std::result::Result<String, anyhow::Error> {
304 let valid_regex = Regex::new(r"@sha256:[0-9a-fA-F]{64}$").unwrap();
307 if valid_regex.is_match(reference) {
308 tracing::debug!("validated OCI configuration");
309 Ok(reference.to_string())
310 } else {
311 Err(anyhow!("invalid graph artifact reference: {reference}"))
312 }
313 }
314
315 fn parse_endpoints(endpoints: &str) -> std::result::Result<Endpoints, anyhow::Error> {
316 Ok(Endpoints::fallback(
317 endpoints
318 .split(',')
319 .map(|endpoint| Url::parse(endpoint.trim()))
320 .collect::<Result<Vec<Url>, ParseError>>()
321 .map_err(|err| anyhow!("invalid Apollo Uplink endpoint, {}", err))?,
322 ))
323 }
324
325 fn err_require_opt(env_var: &str) -> anyhow::Error {
326 anyhow!("Use of Apollo Graph OS requires setting the {env_var} environment variable")
327 }
328}
329
330pub fn main() -> Result<()> {
337 #[cfg(feature = "dhat-heap")]
338 create_heap_profiler();
339
340 #[cfg(feature = "dhat-ad-hoc")]
341 create_ad_hoc_profiler();
342
343 let mut builder = tokio::runtime::Builder::new_multi_thread();
344 builder.enable_all();
345
346 if let Some(nb) = std::env::var("APOLLO_ROUTER_IO_THREADS")
349 .ok()
350 .and_then(|value| value.parse::<usize>().ok())
351 {
352 builder.worker_threads(nb);
353 }
354
355 let runtime = builder.build()?;
356 runtime.block_on(Executable::builder().start())
357}
358
359#[non_exhaustive]
361pub struct Executable {}
362
363#[buildstructor::buildstructor]
364impl Executable {
365 #[builder(entry = "builder", exit = "start", visibility = "pub")]
407 async fn start(
408 shutdown: Option<ShutdownSource>,
409 schema: Option<SchemaSource>,
410 license: Option<LicenseSource>,
411 config: Option<ConfigurationSource>,
412 cli_args: Option<Opt>,
413 ) -> Result<()> {
414 let opt = cli_args.unwrap_or_else(Opt::parse);
415
416 if opt.version {
417 println!("{}", std::env!("CARGO_PKG_VERSION"));
418 return Ok(());
419 }
420
421 *crate::services::APOLLO_KEY.lock() = opt.apollo_key.clone();
422 *crate::services::APOLLO_GRAPH_REF.lock() = opt.apollo_graph_ref.clone();
423 *APOLLO_ROUTER_LISTEN_ADDRESS.lock() = opt.listen_address;
424 APOLLO_ROUTER_DEV_MODE.store(opt.dev, Ordering::Relaxed);
425 APOLLO_ROUTER_SUPERGRAPH_PATH_IS_SET
426 .store(opt.supergraph_path.is_some(), Ordering::Relaxed);
427 APOLLO_ROUTER_SUPERGRAPH_URLS_IS_SET
428 .store(opt.supergraph_urls.is_some(), Ordering::Relaxed);
429 APOLLO_ROUTER_LICENCE_IS_SET.store(opt.apollo_router_license.is_some(), Ordering::Relaxed);
430 APOLLO_ROUTER_LICENCE_PATH_IS_SET
431 .store(opt.apollo_router_license_path.is_some(), Ordering::Relaxed);
432 APOLLO_TELEMETRY_DISABLED.store(opt.anonymous_telemetry_disabled, Ordering::Relaxed);
433
434 let apollo_telemetry_initialized = if graph_os() {
435 init_telemetry(&opt.log_level)?;
436 true
437 } else {
438 init_telemetry(&opt.log_level).is_ok()
440 };
441
442 setup_panic_handler();
443
444 let result = match opt.command.as_ref() {
445 Some(Commands::Config(ConfigSubcommandArgs {
446 command: ConfigSubcommand::Schema,
447 })) => {
448 let schema = generate_config_schema();
449 println!("{}", serde_json::to_string_pretty(&schema)?);
450 Ok(())
451 }
452 Some(Commands::Config(ConfigSubcommandArgs {
453 command: ConfigSubcommand::Validate { config_path },
454 })) => {
455 let config_string = std::fs::read_to_string(config_path)?;
456 validate_yaml_configuration(
457 &config_string,
458 Expansion::default()?,
459 Mode::NoUpgrade,
460 )?
461 .validate()?;
462
463 println!("Configuration at path {config_path:?} is valid!");
464
465 Ok(())
466 }
467 Some(Commands::Config(ConfigSubcommandArgs {
468 command: ConfigSubcommand::Upgrade { config_path, diff },
469 })) => {
470 let config_string = std::fs::read_to_string(config_path)?;
471 let output = generate_upgrade(&config_string, *diff)?;
472 println!("{output}");
473 Ok(())
474 }
475 Some(Commands::Config(ConfigSubcommandArgs {
476 command: ConfigSubcommand::Experimental,
477 })) => {
478 Discussed::new().print_experimental();
479 Ok(())
480 }
481 Some(Commands::Config(ConfigSubcommandArgs {
482 command: ConfigSubcommand::Preview,
483 })) => {
484 Discussed::new().print_preview();
485 Ok(())
486 }
487 None => Self::inner_start(shutdown, schema, config, license, opt).await,
488 };
489
490 if apollo_telemetry_initialized {
491 tokio::task::spawn_blocking(move || {
493 opentelemetry::global::shutdown_tracer_provider();
494 meter_provider_internal().shutdown();
495 })
496 .await?;
497 }
498 result
499 }
500
501 async fn inner_start(
502 shutdown: Option<ShutdownSource>,
503 schema: Option<SchemaSource>,
504 config: Option<ConfigurationSource>,
505 license: Option<LicenseSource>,
506 mut opt: Opt,
507 ) -> Result<()> {
508 let current_directory = std::env::current_dir()?;
509 opt.hot_reload = opt.hot_reload || opt.dev;
511
512 let configuration = match (config, opt.config_path.as_ref()) {
513 (Some(_), Some(_)) => {
514 return Err(anyhow!(
515 "--config and APOLLO_ROUTER_CONFIG_PATH cannot be used when a custom configuration source is in use"
516 ));
517 }
518 (Some(config), None) => config,
519 #[allow(clippy::blocks_in_conditions)]
520 _ => opt
521 .config_path
522 .as_ref()
523 .map(|path| {
524 let path = if path.is_relative() {
525 current_directory.join(path)
526 } else {
527 path.to_path_buf()
528 };
529
530 ConfigurationSource::File {
531 path,
532 watch: opt.hot_reload,
533 }
534 })
535 .unwrap_or_default(),
536 };
537
538 let apollo_telemetry_msg = if opt.anonymous_telemetry_disabled {
539 "Anonymous usage data collection is disabled.".to_string()
540 } else {
541 "Anonymous usage data is gathered to inform Apollo product development. See https://go.apollo.dev/o/privacy for details.".to_string()
542 };
543
544 let apollo_router_msg = format!(
545 "Apollo Router v{} // (c) Apollo Graph, Inc. // Licensed as ELv2 (https://go.apollo.dev/elv2)",
546 std::env!("CARGO_PKG_VERSION")
547 );
548
549 #[cfg(unix)]
556 let akp = &opt.apollo_key_path;
557 #[cfg(not(unix))]
558 let akp: &Option<PathBuf> = &None;
559
560 let schema_source = match (
561 schema,
562 &opt.supergraph_path,
563 &opt.supergraph_urls,
564 &opt.apollo_key,
565 akp,
566 ) {
567 (Some(_), Some(_), _, _, _) | (Some(_), _, Some(_), _, _) => {
568 return Err(anyhow!(
569 "--supergraph and APOLLO_ROUTER_SUPERGRAPH_PATH cannot be used when a custom schema source is in use"
570 ));
571 }
572 (Some(source), None, None, _, _) => source,
573 (_, Some(supergraph_path), _, _, _) => {
574 tracing::info!("{apollo_router_msg}");
575 tracing::info!("{apollo_telemetry_msg}");
576
577 let supergraph_path = if supergraph_path.is_relative() {
578 current_directory.join(supergraph_path)
579 } else {
580 supergraph_path.clone()
581 };
582 SchemaSource::File {
583 path: supergraph_path,
584 watch: opt.hot_reload,
585 }
586 }
587 (_, _, Some(supergraph_urls), _, _) => {
588 tracing::info!("{apollo_router_msg}");
589 tracing::info!("{apollo_telemetry_msg}");
590
591 if opt.hot_reload {
592 tracing::warn!(
593 "Schema hot reloading is disabled for --supergraph-urls / APOLLO_ROUTER_SUPERGRAPH_URLS."
594 );
595 }
596
597 SchemaSource::URLs {
598 urls: supergraph_urls.clone(),
599 }
600 }
601 (_, None, None, _, Some(apollo_key_path)) => {
602 let apollo_key_path = if apollo_key_path.is_relative() {
603 current_directory.join(apollo_key_path)
604 } else {
605 apollo_key_path.clone()
606 };
607
608 if !apollo_key_path.exists() {
609 tracing::error!(
610 "Apollo key at path '{}' does not exist.",
611 apollo_key_path.to_string_lossy()
612 );
613 return Err(anyhow!(
614 "Apollo key at path '{}' does not exist.",
615 apollo_key_path.to_string_lossy()
616 ));
617 } else {
618 #[cfg(unix)]
622 {
623 let meta = std::fs::metadata(apollo_key_path.clone())
624 .map_err(|err| anyhow!("Failed to read Apollo key file: {}", err))?;
625 let mode = meta.mode();
626 if mode & 0o077 != 0 {
629 return Err(anyhow!(
630 "Apollo key file permissions ({:#o}) are too permissive",
631 mode & 0o000777
632 ));
633 }
634 let euid = unsafe { libc::geteuid() };
635 let owner = meta.uid();
636 if euid != owner {
637 return Err(anyhow!(
638 "Apollo key file owner id ({owner}) does not match effective user id ({euid})"
639 ));
640 }
641 }
642 match std::fs::read_to_string(&apollo_key_path) {
644 Ok(apollo_key) => {
645 opt.apollo_key = Some(apollo_key.trim().to_string());
646 }
647 Err(err) => {
648 return Err(anyhow!("Failed to read Apollo key file: {}", err));
649 }
650 };
651 match opt.graph_artifact_reference {
652 None => SchemaSource::Registry(opt.uplink_config()?),
653 Some(_) => SchemaSource::OCI(opt.oci_config()?),
654 }
655 }
656 }
657 (_, None, None, Some(_apollo_key), None) => {
658 tracing::info!("{apollo_router_msg}");
659 tracing::info!("{apollo_telemetry_msg}");
660 match opt.graph_artifact_reference {
661 None => SchemaSource::Registry(opt.uplink_config()?),
662 Some(_) => SchemaSource::OCI(opt.oci_config()?),
663 }
664 }
665 _ => {
666 return Err(anyhow!(
667 r#"{apollo_router_msg}
668
669⚠️ The Apollo Router requires a composed supergraph schema at startup. ⚠️
670
671👉 DO ONE:
672
673 * Pass a local schema file with the '--supergraph' option:
674
675 $ ./router --supergraph <file_path>
676
677 * Fetch a registered schema from GraphOS by setting
678 these environment variables:
679
680 $ APOLLO_KEY="..." APOLLO_GRAPH_REF="..." ./router
681
682 For details, see the Apollo docs:
683 https://www.apollographql.com/docs/federation/managed-federation/setup
684
685🔬 TESTING THINGS OUT?
686
687 1. Download an example supergraph schema with Apollo-hosted subgraphs:
688
689 $ curl -L https://supergraph.demo.starstuff.dev/ > starstuff.graphql
690
691 2. Run the Apollo Router in development mode with the supergraph schema:
692
693 $ ./router --dev --supergraph starstuff.graphql
694
695 "#
696 ));
697 }
698 };
699
700 let license = if let Some(license) = license {
706 license
707 } else {
708 match (
709 &opt.apollo_router_license,
710 &opt.apollo_router_license_path,
711 &opt.apollo_key,
712 &opt.apollo_graph_ref,
713 ) {
714 (_, Some(license_path), _, _) => {
715 let license_path = if license_path.is_relative() {
716 current_directory.join(license_path)
717 } else {
718 license_path.clone()
719 };
720 LicenseSource::File {
721 path: license_path,
722 watch: opt.hot_reload,
723 }
724 }
725 (Some(_license), _, _, _) => LicenseSource::Env,
726 (_, _, Some(_apollo_key), Some(_apollo_graph_ref)) => {
727 LicenseSource::Registry(opt.uplink_config()?)
728 }
729
730 _ => LicenseSource::default(),
731 }
732 };
733
734 let user_plugins_present = plugins().filter(|p| !p.is_apollo()).count() > 0;
736 let rust_log_set = std::env::var("RUST_LOG").is_ok();
737 let apollo_router_log = std::env::var("APOLLO_ROUTER_LOG").unwrap_or_default();
738 if user_plugins_present
739 && !rust_log_set
740 && ["trace", "debug", "warn", "error", "info"].contains(&apollo_router_log.as_str())
741 {
742 tracing::info!(
743 "Custom plugins are present. To see log messages from your plugins you must configure `RUST_LOG` or `APOLLO_ROUTER_LOG` environment variables. See the Router logging documentation for more details"
744 );
745 }
746
747 let uplink_config = opt.uplink_config().ok();
748 if uplink_config
749 .clone()
750 .unwrap_or_default()
751 .endpoints
752 .unwrap_or_default()
753 .url_count()
754 == 1
755 {
756 tracing::warn!(
757 "Only a single uplink endpoint is configured. We recommend specifying at least two endpoints so that a fallback exists."
758 );
759 }
760
761 let router = RouterHttpServer::builder()
762 .is_telemetry_disabled(opt.anonymous_telemetry_disabled)
763 .configuration(configuration)
764 .and_uplink(uplink_config)
765 .schema(schema_source)
766 .license(license)
767 .shutdown(shutdown.unwrap_or(ShutdownSource::CtrlC))
768 .start();
769
770 if let Err(err) = router.await {
771 tracing::error!("{}", err);
772 return Err(err.into());
773 }
774 Ok(())
775 }
776}
777
778fn graph_os() -> bool {
779 crate::services::APOLLO_KEY.lock().is_some()
780 && crate::services::APOLLO_GRAPH_REF.lock().is_some()
781}
782
783fn setup_panic_handler() {
784 let backtrace_env = std::env::var("RUST_BACKTRACE");
786 let show_backtraces =
787 backtrace_env.as_deref() == Ok("1") || backtrace_env.as_deref() == Ok("full");
788 if show_backtraces {
789 tracing::warn!(
790 "RUST_BACKTRACE={} detected. This is useful for diagnostics but will have a performance impact and may leak sensitive information",
791 backtrace_env.as_ref().unwrap()
792 );
793 }
794 std::panic::set_hook(Box::new(move |e| {
795 if show_backtraces {
796 let backtrace = std::backtrace::Backtrace::capture();
797 tracing::error!("{}\n{}", e, backtrace)
798 } else {
799 tracing::error!("{}", e)
800 }
801
802 std::process::exit(1);
805 }));
806}
807
808#[cfg(test)]
809mod tests {
810 use crate::executable::add_log_filter;
811
812 #[test]
813 fn simplest_logging_modifications() {
814 for level in ["off", "error", "warn", "info", "debug", "trace"] {
815 assert_eq!(
816 add_log_filter(level).expect("conversion works"),
817 format!("info,apollo_router={level}")
818 );
819 }
820 }
821
822 #[test]
827 fn complex_logging_modifications() {
828 assert_eq!(add_log_filter("hello").unwrap(), "info,hello");
829 assert_eq!(add_log_filter("trace").unwrap(), "info,apollo_router=trace");
830 assert_eq!(add_log_filter("TRACE").unwrap(), "info,apollo_router=trace");
831 assert_eq!(add_log_filter("info").unwrap(), "info,apollo_router=info");
832 assert_eq!(add_log_filter("INFO").unwrap(), "info,apollo_router=info");
833 assert_eq!(add_log_filter("hello=debug").unwrap(), "info,hello=debug");
834 assert_eq!(add_log_filter("hello=DEBUG").unwrap(), "info,hello=debug");
835 assert_eq!(
836 add_log_filter("hello,std::option").unwrap(),
837 "info,hello,std::option"
838 );
839 assert_eq!(
840 add_log_filter("error,hello=warn").unwrap(),
841 "info,apollo_router=error,hello=warn"
842 );
843 assert_eq!(
844 add_log_filter("error,hello=off").unwrap(),
845 "info,apollo_router=error,hello=off"
846 );
847 assert_eq!(add_log_filter("off").unwrap(), "info,apollo_router=off");
848 assert_eq!(add_log_filter("OFF").unwrap(), "info,apollo_router=off");
849 assert_eq!(add_log_filter("hello/foo").unwrap(), "info,hello/foo");
850 assert_eq!(add_log_filter("hello/f.o").unwrap(), "info,hello/f.o");
851 assert_eq!(
852 add_log_filter("hello=debug/foo*foo").unwrap(),
853 "info,hello=debug/foo*foo"
854 );
855 assert_eq!(
856 add_log_filter("error,hello=warn/[0-9]scopes").unwrap(),
857 "info,apollo_router=error,hello=warn/[0-9]scopes"
858 );
859 assert_eq!(
861 add_log_filter("hyper=debug,warn,regex=warn,h2=off").unwrap(),
862 "info,hyper=debug,apollo_router=warn,regex=warn,h2=off"
863 );
864 assert_eq!(
865 add_log_filter("hyper=debug,apollo_router=off,regex=info,h2=off").unwrap(),
866 "info,hyper=debug,apollo_router=off,regex=info,h2=off"
867 );
868 assert_eq!(
869 add_log_filter("apollo_router::plugins=debug").unwrap(),
870 "info,apollo_router::plugins=debug"
871 );
872 }
873
874 #[test]
875 fn test_validate_oci_reference_valid_cases() {
876 let valid_hashes = vec![
878 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
879 "@sha256:ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
880 "@sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
881 "@sha256:0000000000000000000000000000000000000000000000000000000000000000",
882 "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
883 ];
884
885 for hash in valid_hashes {
886 let result = super::Opt::validate_oci_reference(hash);
887 assert!(result.is_ok(), "Hash '{}' should be valid", hash);
888 assert_eq!(result.unwrap(), hash);
889 }
890 }
891
892 #[test]
893 fn test_validate_oci_reference_invalid_cases() {
894 let invalid_references = vec![
895 "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
897 "@sha1:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
899 "@sha512:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
900 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcde",
902 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1",
904 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeg",
906 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcde!",
907 "",
909 "@sha256:",
911 "@sha256: 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
913 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ",
914 "@sha256:12345678-90abcdef-12345678-90abcdef-12345678-90abcdef-12345678-90abcdef",
916 "@sha256:12345678:90abcdef:12345678:90abcdef:12345678:90abcdef:12345678:90abcdef",
918 "@sha256",
920 "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
922 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef:latest",
924 "@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef@tag",
925 ];
926
927 for reference in invalid_references {
928 let result = super::Opt::validate_oci_reference(reference);
929 assert!(
930 result.is_err(),
931 "Reference '{}' should be invalid",
932 reference
933 );
934 let error_msg = result.unwrap_err().to_string();
935 assert!(
936 error_msg.contains("invalid graph artifact reference"),
937 "Error message should contain 'invalid graph artifact reference' for '{}'",
938 reference
939 );
940 assert!(
941 error_msg.contains(reference),
942 "Error message should contain the invalid reference '{}'",
943 reference
944 );
945 }
946 }
947}