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::otel::init_telemetry;
35use crate::registry::OciConfig;
36use crate::registry::validate_oci_reference;
37use crate::router::ConfigurationSource;
38use crate::router::RouterHttpServer;
39use crate::router::SchemaSource;
40use crate::router::ShutdownSource;
41use crate::uplink::Endpoints;
42use crate::uplink::UplinkConfig;
43
44pub(crate) static APOLLO_ROUTER_DEV_MODE: AtomicBool = AtomicBool::new(false);
45pub(crate) static APOLLO_ROUTER_SUPERGRAPH_PATH_IS_SET: AtomicBool = AtomicBool::new(false);
46pub(crate) static APOLLO_ROUTER_SUPERGRAPH_URLS_IS_SET: AtomicBool = AtomicBool::new(false);
47pub(crate) static APOLLO_ROUTER_LICENCE_IS_SET: AtomicBool = AtomicBool::new(false);
48pub(crate) static APOLLO_ROUTER_LICENCE_PATH_IS_SET: AtomicBool = AtomicBool::new(false);
49pub(crate) static APOLLO_TELEMETRY_DISABLED: AtomicBool = AtomicBool::new(false);
50pub(crate) static APOLLO_ROUTER_LISTEN_ADDRESS: Mutex<Option<SocketAddr>> = Mutex::new(None);
51pub(crate) static APOLLO_ROUTER_GRAPH_ARTIFACT_REFERENCE: Mutex<Option<String>> = Mutex::new(None);
52pub(crate) static APOLLO_ROUTER_HOT_RELOAD_CLI: AtomicBool = AtomicBool::new(false);
53
54const INITIAL_UPLINK_POLL_INTERVAL: Duration = Duration::from_secs(10);
55const INITIAL_OCI_POLL_INTERVAL: Duration = Duration::from_secs(30);
56
57#[derive(Subcommand, Debug)]
59enum Commands {
60 Config(ConfigSubcommandArgs),
62}
63
64#[derive(Args, Debug)]
65struct ConfigSubcommandArgs {
66 #[clap(subcommand)]
68 command: ConfigSubcommand,
69}
70
71#[derive(Subcommand, Debug)]
72enum ConfigSubcommand {
73 Schema,
75
76 Upgrade {
78 #[clap(value_parser, env = "APOLLO_ROUTER_CONFIG_PATH")]
80 config_path: PathBuf,
81
82 #[clap(action = ArgAction::SetTrue, long)]
84 diff: bool,
85 },
86 Validate {
88 #[clap(value_parser, env = "APOLLO_ROUTER_CONFIG_PATH")]
90 config_path: PathBuf,
91 },
92 Experimental,
94 Preview,
96}
97
98#[derive(Parser, Debug)]
100#[clap(name = "router", about = "Apollo federation router")]
101#[command(disable_version_flag(true))]
102pub struct Opt {
103 #[clap(
105 long = "log",
106 default_value = "info",
107 alias = "log-level",
108 value_parser = add_log_filter,
109 env = "APOLLO_ROUTER_LOG"
110 )]
111 log_level: String,
113
114 #[clap(
116 alias = "hr",
117 long = "hot-reload",
118 env = "APOLLO_ROUTER_HOT_RELOAD",
119 action(ArgAction::SetTrue)
120 )]
121 hot_reload: bool,
122
123 #[clap(
125 short,
126 long = "config",
127 value_parser,
128 env = "APOLLO_ROUTER_CONFIG_PATH"
129 )]
130 config_path: Option<PathBuf>,
131
132 #[clap(env = "APOLLO_ROUTER_DEV", long = "dev", action(ArgAction::SetTrue))]
134 dev: bool,
135
136 #[clap(
138 short,
139 long = "supergraph",
140 value_parser,
141 env = "APOLLO_ROUTER_SUPERGRAPH_PATH"
142 )]
143 supergraph_path: Option<PathBuf>,
144
145 #[clap(env = "APOLLO_ROUTER_SUPERGRAPH_URLS", value_delimiter = ',')]
147 supergraph_urls: Option<Vec<Url>>,
148
149 #[clap(subcommand)]
151 command: Option<Commands>,
152
153 #[clap(skip = std::env::var("APOLLO_KEY").ok())]
155 apollo_key: Option<String>,
156
157 #[cfg(unix)]
159 #[clap(long = "apollo-key-path", env = "APOLLO_KEY_PATH")]
160 apollo_key_path: Option<PathBuf>,
161
162 #[clap(skip = std::env::var("APOLLO_GRAPH_REF").ok())]
164 apollo_graph_ref: Option<String>,
165
166 #[clap(skip = std::env::var("APOLLO_ROUTER_LICENSE").ok())]
168 apollo_router_license: Option<String>,
169
170 #[clap(long = "license", env = "APOLLO_ROUTER_LICENSE_PATH")]
172 apollo_router_license_path: Option<PathBuf>,
173
174 #[clap(long, env, action = ArgAction::Append)]
176 apollo_uplink_endpoints: Option<String>,
178
179 #[clap(long, env = "APOLLO_GRAPH_ARTIFACT_REFERENCE", action = ArgAction::Append)]
181 graph_artifact_reference: Option<String>,
182
183 #[clap(long, env = "APOLLO_TELEMETRY_DISABLED", value_parser = FalseyValueParser::new())]
185 anonymous_telemetry_disabled: bool,
186
187 #[clap(long, default_value = "30s", value_parser = humantime::parse_duration, env)]
189 apollo_uplink_timeout: Duration,
190
191 #[clap(long = "listen", env = "APOLLO_ROUTER_LISTEN_ADDRESS")]
193 listen_address: Option<SocketAddr>,
194
195 #[clap(action = ArgAction::SetTrue, long, short = 'V')]
197 pub(crate) version: bool,
198}
199
200fn add_log_filter(raw: &str) -> Result<String, String> {
204 match std::env::var("RUST_LOG") {
205 Ok(filter) => Ok(filter),
206 Err(_e) => {
207 let lowered = raw.to_lowercase();
209 let rgx =
211 Regex::new(r"(^|,)(off|error|warn|info|debug|trace)").expect("regex must be valid");
212 let res = rgx.replace_all(&lowered, |caps: &Captures| {
213 format!("{}apollo_router={}", &caps[1], &caps[2])
216 });
217 Ok(format!("info,{res}"))
218 }
219 }
220}
221
222impl Opt {
223 pub(crate) fn uplink_config(&self) -> Result<UplinkConfig, anyhow::Error> {
224 Ok(UplinkConfig {
225 apollo_key: self
226 .apollo_key
227 .clone()
228 .ok_or(Self::err_require_opt("APOLLO_KEY"))?,
229 apollo_graph_ref: self
230 .apollo_graph_ref
231 .clone()
232 .ok_or(Self::err_require_opt("APOLLO_GRAPH_REF"))?,
233 endpoints: self
234 .apollo_uplink_endpoints
235 .as_ref()
236 .map(|endpoints| Self::parse_endpoints(endpoints))
237 .transpose()?,
238 poll_interval: INITIAL_UPLINK_POLL_INTERVAL,
239 timeout: self.apollo_uplink_timeout,
240 })
241 }
242
243 pub(crate) fn oci_config(&self) -> Result<OciConfig, anyhow::Error> {
244 let graph_artifact_reference = self
245 .graph_artifact_reference
246 .clone()
247 .ok_or(Self::err_require_opt("APOLLO_GRAPH_ARTIFACT_REFERENCE"))?;
248 let (validated_reference, _) = validate_oci_reference(&graph_artifact_reference)?;
249
250 let poll_interval = std::env::var("TEST_APOLLO_OCI_POLL_INTERVAL")
252 .ok()
253 .and_then(|s| {
254 s.parse::<u64>()
255 .ok()
256 .filter(|&val| (1..=60).contains(&val))
257 .map(Duration::from_secs)
258 })
259 .unwrap_or(INITIAL_OCI_POLL_INTERVAL);
260
261 Ok(OciConfig {
262 apollo_key: self
263 .apollo_key
264 .clone()
265 .ok_or(Self::err_require_opt("APOLLO_KEY"))?,
266 reference: validated_reference,
267 hot_reload: self.hot_reload,
268 poll_interval,
269 })
270 }
271
272 fn parse_endpoints(endpoints: &str) -> std::result::Result<Endpoints, anyhow::Error> {
273 Ok(Endpoints::fallback(
274 endpoints
275 .split(',')
276 .map(|endpoint| Url::parse(endpoint.trim()))
277 .collect::<Result<Vec<Url>, ParseError>>()
278 .map_err(|err| anyhow!("invalid Apollo Uplink endpoint, {}", err))?,
279 ))
280 }
281
282 fn err_require_opt(env_var: &str) -> anyhow::Error {
283 anyhow!("Use of Apollo Graph OS requires setting the {env_var} environment variable")
284 }
285}
286
287pub fn main() -> Result<()> {
294 #[cfg(feature = "dhat-heap")]
295 crate::allocator::create_heap_profiler();
296
297 #[cfg(feature = "dhat-ad-hoc")]
298 crate::allocator::create_ad_hoc_profiler();
299
300 let mut builder = tokio::runtime::Builder::new_multi_thread();
301 builder.enable_all();
302
303 if let Some(nb) = std::env::var("APOLLO_ROUTER_IO_THREADS")
306 .ok()
307 .and_then(|value| value.parse::<usize>().ok())
308 {
309 builder.worker_threads(nb);
310 }
311
312 let runtime = builder.build()?;
313 runtime.block_on(Executable::builder().start())
314}
315
316#[non_exhaustive]
318pub struct Executable {}
319
320#[buildstructor::buildstructor]
321impl Executable {
322 #[builder(entry = "builder", exit = "start", visibility = "pub")]
364 async fn start(
365 shutdown: Option<ShutdownSource>,
366 schema: Option<SchemaSource>,
367 license: Option<LicenseSource>,
368 config: Option<ConfigurationSource>,
369 cli_args: Option<Opt>,
370 ) -> Result<()> {
371 let opt = cli_args.unwrap_or_else(Opt::parse);
372
373 if opt.version {
374 println!("{}", std::env!("CARGO_PKG_VERSION"));
375 return Ok(());
376 }
377
378 *crate::services::APOLLO_KEY.lock() = opt.apollo_key.clone();
379 *crate::services::APOLLO_GRAPH_REF.lock() = opt.apollo_graph_ref.clone();
380 *APOLLO_ROUTER_LISTEN_ADDRESS.lock() = opt.listen_address;
381 *APOLLO_ROUTER_GRAPH_ARTIFACT_REFERENCE.lock() = opt.graph_artifact_reference.clone();
382 if opt.hot_reload {
384 APOLLO_ROUTER_HOT_RELOAD_CLI.store(true, Ordering::Relaxed);
385 }
386 APOLLO_ROUTER_DEV_MODE.store(opt.dev, Ordering::Relaxed);
387 APOLLO_ROUTER_SUPERGRAPH_PATH_IS_SET
388 .store(opt.supergraph_path.is_some(), Ordering::Relaxed);
389 APOLLO_ROUTER_SUPERGRAPH_URLS_IS_SET
390 .store(opt.supergraph_urls.is_some(), Ordering::Relaxed);
391 APOLLO_ROUTER_LICENCE_IS_SET.store(opt.apollo_router_license.is_some(), Ordering::Relaxed);
392 APOLLO_ROUTER_LICENCE_PATH_IS_SET
393 .store(opt.apollo_router_license_path.is_some(), Ordering::Relaxed);
394 APOLLO_TELEMETRY_DISABLED.store(opt.anonymous_telemetry_disabled, Ordering::Relaxed);
395
396 let apollo_telemetry_initialized = if graph_os() {
397 init_telemetry(&opt.log_level)?;
398 true
399 } else {
400 init_telemetry(&opt.log_level).is_ok()
402 };
403
404 setup_panic_handler();
405
406 let result = match opt.command.as_ref() {
407 Some(Commands::Config(ConfigSubcommandArgs {
408 command: ConfigSubcommand::Schema,
409 })) => {
410 let schema = generate_config_schema();
411 println!("{}", serde_json::to_string_pretty(&schema)?);
412 Ok(())
413 }
414 Some(Commands::Config(ConfigSubcommandArgs {
415 command: ConfigSubcommand::Validate { config_path },
416 })) => {
417 let config_string = std::fs::read_to_string(config_path)?;
418 validate_yaml_configuration(
419 &config_string,
420 Expansion::default()?,
421 Mode::NoUpgrade,
422 )?
423 .validate()?;
424
425 println!("Configuration at path {config_path:?} is valid!");
426
427 Ok(())
428 }
429 Some(Commands::Config(ConfigSubcommandArgs {
430 command: ConfigSubcommand::Upgrade { config_path, diff },
431 })) => {
432 let config_string = std::fs::read_to_string(config_path)?;
433 let output = generate_upgrade(&config_string, *diff)?;
434 println!("{output}");
435 Ok(())
436 }
437 Some(Commands::Config(ConfigSubcommandArgs {
438 command: ConfigSubcommand::Experimental,
439 })) => {
440 Discussed::new().print_experimental();
441 Ok(())
442 }
443 Some(Commands::Config(ConfigSubcommandArgs {
444 command: ConfigSubcommand::Preview,
445 })) => {
446 Discussed::new().print_preview();
447 Ok(())
448 }
449 None => Self::inner_start(shutdown, schema, config, license, opt).await,
450 };
451
452 if apollo_telemetry_initialized {
453 tokio::task::spawn_blocking(move || {
455 opentelemetry::global::shutdown_tracer_provider();
456 meter_provider_internal().shutdown();
457 })
458 .await?;
459 }
460 result
461 }
462
463 async fn inner_start(
464 shutdown: Option<ShutdownSource>,
465 schema: Option<SchemaSource>,
466 config: Option<ConfigurationSource>,
467 license: Option<LicenseSource>,
468 mut opt: Opt,
469 ) -> Result<()> {
470 let current_directory = std::env::current_dir()?;
471 opt.hot_reload = opt.hot_reload || opt.dev;
473
474 let configuration = match (config, opt.config_path.as_ref()) {
475 (Some(_), Some(_)) => {
476 return Err(anyhow!(
477 "--config and APOLLO_ROUTER_CONFIG_PATH cannot be used when a custom configuration source is in use"
478 ));
479 }
480 (Some(config), None) => config,
481 #[allow(clippy::blocks_in_conditions)]
482 _ => opt
483 .config_path
484 .as_ref()
485 .map(|path| {
486 let path = if path.is_relative() {
487 current_directory.join(path)
488 } else {
489 path.to_path_buf()
490 };
491
492 ConfigurationSource::File {
493 path,
494 watch: opt.hot_reload,
495 }
496 })
497 .unwrap_or_default(),
498 };
499
500 let apollo_telemetry_msg = if opt.anonymous_telemetry_disabled {
501 "Anonymous usage data collection is disabled.".to_string()
502 } else {
503 "Anonymous usage data is gathered to inform Apollo product development. See https://go.apollo.dev/o/privacy for details.".to_string()
504 };
505
506 let apollo_router_msg = format!(
507 "Apollo Router v{} // (c) Apollo Graph, Inc. // Licensed as ELv2 (https://go.apollo.dev/elv2)",
508 std::env!("CARGO_PKG_VERSION")
509 );
510
511 #[cfg(unix)]
518 let akp = &opt.apollo_key_path;
519 #[cfg(not(unix))]
520 let akp: &Option<PathBuf> = &None;
521
522 let has_supergraph_file = schema.is_some() || opt.supergraph_path.is_some();
525 if has_supergraph_file && opt.graph_artifact_reference.is_some() {
526 return Err(anyhow!(
527 "--supergraph (-s) and --graph-artifact-reference cannot be used together. Please specify only one schema source."
528 ));
529 }
530 if opt.supergraph_urls.is_some() && opt.graph_artifact_reference.is_some() {
531 return Err(anyhow!(
532 "APOLLO_ROUTER_SUPERGRAPH_URLS and --graph-artifact-reference cannot be used together. Please specify only one schema source."
533 ));
534 }
535
536 let schema_source = match (
537 schema,
538 &opt.supergraph_path,
539 &opt.supergraph_urls,
540 &opt.apollo_key,
541 akp,
542 ) {
543 (Some(_), Some(_), _, _, _) | (Some(_), _, Some(_), _, _) => {
544 return Err(anyhow!(
545 "--supergraph and APOLLO_ROUTER_SUPERGRAPH_PATH cannot be used when a custom schema source is in use"
546 ));
547 }
548 (Some(source), None, None, _, _) => source,
549 (_, Some(supergraph_path), _, _, _) => {
550 tracing::info!("{apollo_router_msg}");
551 tracing::info!("{apollo_telemetry_msg}");
552
553 let supergraph_path = if supergraph_path.is_relative() {
554 current_directory.join(supergraph_path)
555 } else {
556 supergraph_path.clone()
557 };
558 SchemaSource::File {
559 path: supergraph_path,
560 watch: opt.hot_reload,
561 }
562 }
563 (_, _, Some(supergraph_urls), _, _) => {
564 tracing::info!("{apollo_router_msg}");
565 tracing::info!("{apollo_telemetry_msg}");
566
567 if opt.hot_reload {
568 tracing::warn!(
569 "Schema hot reloading is disabled for --supergraph-urls / APOLLO_ROUTER_SUPERGRAPH_URLS."
570 );
571 }
572
573 SchemaSource::URLs {
574 urls: supergraph_urls.clone(),
575 }
576 }
577 (_, None, None, _, Some(apollo_key_path)) => {
578 let apollo_key_path = if apollo_key_path.is_relative() {
579 current_directory.join(apollo_key_path)
580 } else {
581 apollo_key_path.clone()
582 };
583
584 if !apollo_key_path.exists() {
585 tracing::error!(
586 "Apollo key at path '{}' does not exist.",
587 apollo_key_path.to_string_lossy()
588 );
589 return Err(anyhow!(
590 "Apollo key at path '{}' does not exist.",
591 apollo_key_path.to_string_lossy()
592 ));
593 } else {
594 #[cfg(unix)]
598 {
599 let meta = std::fs::metadata(apollo_key_path.clone())
600 .map_err(|err| anyhow!("Failed to read Apollo key file: {}", err))?;
601 let mode = meta.mode();
602 if mode & 0o077 != 0 {
605 return Err(anyhow!(
606 "Apollo key file permissions ({:#o}) are too permissive",
607 mode & 0o000777
608 ));
609 }
610 let euid = unsafe { libc::geteuid() };
611 let owner = meta.uid();
612 if euid != owner {
613 return Err(anyhow!(
614 "Apollo key file owner id ({owner}) does not match effective user id ({euid})"
615 ));
616 }
617 }
618 match std::fs::read_to_string(&apollo_key_path) {
620 Ok(apollo_key) => {
621 opt.apollo_key = Some(apollo_key.trim().to_string());
622 }
623 Err(err) => {
624 return Err(anyhow!("Failed to read Apollo key file: {}", err));
625 }
626 };
627 match opt.graph_artifact_reference {
628 None => SchemaSource::Registry(opt.uplink_config()?),
629 Some(_) => SchemaSource::OCI(opt.oci_config()?),
630 }
631 }
632 }
633 (_, None, None, Some(_apollo_key), None) => {
634 tracing::info!("{apollo_router_msg}");
635 tracing::info!("{apollo_telemetry_msg}");
636 match opt.graph_artifact_reference {
637 None => SchemaSource::Registry(opt.uplink_config()?),
638 Some(_) => SchemaSource::OCI(opt.oci_config()?),
639 }
640 }
641 _ => {
642 return Err(anyhow!(
643 r#"{apollo_router_msg}
644
645⚠️ The Apollo Router requires a composed supergraph schema at startup. ⚠️
646
647👉 DO ONE:
648
649 * Pass a local schema file with the '--supergraph' option:
650
651 $ ./router --supergraph <file_path>
652
653 * Fetch a registered schema from GraphOS by setting
654 these environment variables:
655
656 $ APOLLO_KEY="..." APOLLO_GRAPH_REF="..." ./router
657
658 For details, see the Apollo docs:
659 https://www.apollographql.com/docs/federation/managed-federation/setup
660
661🔬 TESTING THINGS OUT?
662
663 1. Download an example supergraph schema with Apollo-hosted subgraphs:
664
665 $ curl -L https://supergraph.demo.starstuff.dev/ > starstuff.graphql
666
667 2. Run the Apollo Router in development mode with the supergraph schema:
668
669 $ ./router --dev --supergraph starstuff.graphql
670
671 "#
672 ));
673 }
674 };
675
676 let license = if let Some(license) = license {
682 license
683 } else {
684 match (
685 &opt.apollo_router_license,
686 &opt.apollo_router_license_path,
687 &opt.apollo_key,
688 &opt.apollo_graph_ref,
689 ) {
690 (_, Some(license_path), _, _) => {
691 let license_path = if license_path.is_relative() {
692 current_directory.join(license_path)
693 } else {
694 license_path.clone()
695 };
696 LicenseSource::File {
697 path: license_path,
698 watch: opt.hot_reload,
699 }
700 }
701 (Some(_license), _, _, _) => LicenseSource::Env,
702 (_, _, Some(_apollo_key), Some(_apollo_graph_ref)) => {
703 LicenseSource::Registry(opt.uplink_config()?)
704 }
705
706 _ => LicenseSource::default(),
707 }
708 };
709
710 let user_plugins_present = plugins().filter(|p| !p.is_apollo()).count() > 0;
712 let rust_log_set = std::env::var("RUST_LOG").is_ok();
713 let apollo_router_log = std::env::var("APOLLO_ROUTER_LOG").unwrap_or_default();
714 if user_plugins_present
715 && !rust_log_set
716 && ["trace", "debug", "warn", "error", "info"].contains(&apollo_router_log.as_str())
717 {
718 tracing::info!(
719 "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"
720 );
721 }
722
723 let uplink_config = opt.uplink_config().ok();
724 if uplink_config
725 .clone()
726 .unwrap_or_default()
727 .endpoints
728 .unwrap_or_default()
729 .url_count()
730 == 1
731 {
732 tracing::warn!(
733 "Only a single uplink endpoint is configured. We recommend specifying at least two endpoints so that a fallback exists."
734 );
735 }
736
737 if std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_ok() {
740 tracing::warn!(
741 "The OTEL_EXPORTER_OTLP_ENDPOINT environment variable is set. This takes precedence over default configurations and may override trace export to Apollo Studio."
742 );
743 }
744
745 let router = RouterHttpServer::builder()
746 .is_telemetry_disabled(opt.anonymous_telemetry_disabled)
747 .configuration(configuration)
748 .and_uplink(uplink_config)
749 .schema(schema_source)
750 .license(license)
751 .shutdown(shutdown.unwrap_or(ShutdownSource::CtrlC))
752 .start();
753
754 if let Err(err) = router.await {
755 tracing::error!("{}", err);
756 return Err(err.into());
757 }
758 Ok(())
759 }
760}
761
762fn graph_os() -> bool {
763 crate::services::APOLLO_KEY.lock().is_some()
764 && crate::services::APOLLO_GRAPH_REF.lock().is_some()
765}
766
767fn setup_panic_handler() {
768 let backtrace_env = std::env::var("RUST_BACKTRACE");
770 let show_backtraces =
771 backtrace_env.as_deref() == Ok("1") || backtrace_env.as_deref() == Ok("full");
772 if show_backtraces {
773 tracing::warn!(
774 "RUST_BACKTRACE={} detected. This is useful for diagnostics but will have a performance impact and may leak sensitive information",
775 backtrace_env.as_ref().unwrap()
776 );
777 }
778 std::panic::set_hook(Box::new(move |e| {
779 if show_backtraces {
780 let backtrace = std::backtrace::Backtrace::capture();
781 tracing::error!("{}\n{}", e, backtrace)
782 } else {
783 tracing::error!("{}", e)
784 }
785
786 std::process::exit(1);
789 }));
790}
791
792#[cfg(test)]
793mod tests {
794 use crate::executable::add_log_filter;
795
796 #[test]
797 fn simplest_logging_modifications() {
798 for level in ["off", "error", "warn", "info", "debug", "trace"] {
799 assert_eq!(
800 add_log_filter(level).expect("conversion works"),
801 format!("info,apollo_router={level}")
802 );
803 }
804 }
805
806 #[test]
811 fn complex_logging_modifications() {
812 assert_eq!(add_log_filter("hello").unwrap(), "info,hello");
813 assert_eq!(add_log_filter("trace").unwrap(), "info,apollo_router=trace");
814 assert_eq!(add_log_filter("TRACE").unwrap(), "info,apollo_router=trace");
815 assert_eq!(add_log_filter("info").unwrap(), "info,apollo_router=info");
816 assert_eq!(add_log_filter("INFO").unwrap(), "info,apollo_router=info");
817 assert_eq!(add_log_filter("hello=debug").unwrap(), "info,hello=debug");
818 assert_eq!(add_log_filter("hello=DEBUG").unwrap(), "info,hello=debug");
819 assert_eq!(
820 add_log_filter("hello,std::option").unwrap(),
821 "info,hello,std::option"
822 );
823 assert_eq!(
824 add_log_filter("error,hello=warn").unwrap(),
825 "info,apollo_router=error,hello=warn"
826 );
827 assert_eq!(
828 add_log_filter("error,hello=off").unwrap(),
829 "info,apollo_router=error,hello=off"
830 );
831 assert_eq!(add_log_filter("off").unwrap(), "info,apollo_router=off");
832 assert_eq!(add_log_filter("OFF").unwrap(), "info,apollo_router=off");
833 assert_eq!(add_log_filter("hello/foo").unwrap(), "info,hello/foo");
834 assert_eq!(add_log_filter("hello/f.o").unwrap(), "info,hello/f.o");
835 assert_eq!(
836 add_log_filter("hello=debug/foo*foo").unwrap(),
837 "info,hello=debug/foo*foo"
838 );
839 assert_eq!(
840 add_log_filter("error,hello=warn/[0-9]scopes").unwrap(),
841 "info,apollo_router=error,hello=warn/[0-9]scopes"
842 );
843 assert_eq!(
845 add_log_filter("hyper=debug,warn,regex=warn,h2=off").unwrap(),
846 "info,hyper=debug,apollo_router=warn,regex=warn,h2=off"
847 );
848 assert_eq!(
849 add_log_filter("hyper=debug,apollo_router=off,regex=info,h2=off").unwrap(),
850 "info,hyper=debug,apollo_router=off,regex=info,h2=off"
851 );
852 assert_eq!(
853 add_log_filter("apollo_router::plugins=debug").unwrap(),
854 "info,apollo_router::plugins=debug"
855 );
856 }
857
858 mod validation_tests {
859 use tokio::time::Duration;
860
861 use super::super::Executable;
862 use super::super::Opt;
863 use crate::router::SchemaSource;
864
865 #[tokio::test]
866 async fn test_conflicting_supergraph_file_and_graph_artifact_reference() {
867 let temp_dir = tempfile::tempdir().unwrap();
869 let supergraph_path = temp_dir.path().join("supergraph.graphql");
870 std::fs::File::create(&supergraph_path).unwrap();
871
872 let schema = Some(SchemaSource::File {
873 path: supergraph_path,
874 watch: false,
875 });
876
877 let opt = Opt {
878 log_level: "error".to_string(),
879 hot_reload: false,
880 config_path: None,
881 dev: false,
882 supergraph_path: None,
883 supergraph_urls: None,
884 command: None,
885 apollo_key: Some("test-key".to_string()),
886 #[cfg(unix)]
887 apollo_key_path: None,
888 apollo_graph_ref: None,
889 apollo_router_license: None,
890 apollo_router_license_path: None,
891 apollo_uplink_endpoints: None,
892 graph_artifact_reference: Some(
893 "registry.apollographql.com/my-graph@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
894 ),
895 anonymous_telemetry_disabled: true,
896 apollo_uplink_timeout: Duration::from_secs(30),
897 listen_address: None,
898 version: false,
899 };
900
901 use crate::router::ConfigurationSource;
903 let supergraph = crate::configuration::Supergraph::builder().build();
904 let config = ConfigurationSource::Static(Box::new(crate::Configuration {
905 supergraph,
906 health_check: Default::default(),
907 sandbox: Default::default(),
908 homepage: Default::default(),
909 server: Default::default(),
910 cors: Default::default(),
911 tls: Default::default(),
912 apq: Default::default(),
913 persisted_queries: Default::default(),
914 limits: Default::default(),
915 experimental_chaos: Default::default(),
916 batching: Default::default(),
917 experimental_type_conditioned_fetching: false,
918 plugins: Default::default(),
919 apollo_plugins: Default::default(),
920 notify: Default::default(),
921 uplink: None,
922 validated_yaml: None,
923 raw_yaml: None,
924 }));
925
926 let result = Executable::inner_start(
927 None,
928 schema,
929 Some(config),
930 Some(crate::router::LicenseSource::default()),
931 opt,
932 )
933 .await;
934
935 assert!(result.is_err(), "Should fail with conflicting options");
936 let error_msg = result.unwrap_err().to_string();
937 assert!(
938 error_msg.contains("cannot be used together"),
939 "Error should mention conflicting options, got: {}",
940 error_msg
941 );
942 assert!(
943 error_msg.contains("--supergraph")
944 || error_msg.contains("--graph-artifact-reference"),
945 "Error should mention the conflicting options"
946 );
947 }
948
949 #[tokio::test]
950 async fn test_conflicting_supergraph_urls_and_graph_artifact_reference() {
951 use url::Url;
953 let test_url = Url::parse("https://example.com/schema.graphql").unwrap();
954 let schema = Some(SchemaSource::URLs {
955 urls: vec![test_url],
956 });
957
958 let opt = Opt {
959 log_level: "error".to_string(),
960 hot_reload: false,
961 config_path: None,
962 dev: false,
963 supergraph_path: None,
964 supergraph_urls: Some(vec![Url::parse("https://example.com/schema.graphql").unwrap()]),
965 command: None,
966 apollo_key: Some("test-key".to_string()),
967 #[cfg(unix)]
968 apollo_key_path: None,
969 apollo_graph_ref: None,
970 apollo_router_license: None,
971 apollo_router_license_path: None,
972 apollo_uplink_endpoints: None,
973 graph_artifact_reference: Some(
974 "registry.apollographql.com/my-graph@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
975 ),
976 anonymous_telemetry_disabled: true,
977 apollo_uplink_timeout: Duration::from_secs(30),
978 listen_address: None,
979 version: false,
980 };
981
982 use crate::router::ConfigurationSource;
984 let supergraph = crate::configuration::Supergraph::builder().build();
985 let config = ConfigurationSource::Static(Box::new(crate::Configuration {
986 supergraph,
987 health_check: Default::default(),
988 sandbox: Default::default(),
989 homepage: Default::default(),
990 server: Default::default(),
991 cors: Default::default(),
992 tls: Default::default(),
993 apq: Default::default(),
994 persisted_queries: Default::default(),
995 limits: Default::default(),
996 experimental_chaos: Default::default(),
997 batching: Default::default(),
998 experimental_type_conditioned_fetching: false,
999 plugins: Default::default(),
1000 apollo_plugins: Default::default(),
1001 notify: Default::default(),
1002 uplink: None,
1003 validated_yaml: None,
1004 raw_yaml: None,
1005 }));
1006
1007 let result = Executable::inner_start(
1008 None,
1009 schema,
1010 Some(config),
1011 Some(crate::router::LicenseSource::default()),
1012 opt,
1013 )
1014 .await;
1015
1016 assert!(result.is_err(), "Should fail with conflicting options");
1017 let error_msg = result.unwrap_err().to_string();
1018 assert!(
1019 error_msg.contains("cannot be used together"),
1020 "Error should mention conflicting options, got: {}",
1021 error_msg
1022 );
1023 assert!(
1024 error_msg.contains("APOLLO_ROUTER_SUPERGRAPH_URLS")
1025 || error_msg.contains("--graph-artifact-reference"),
1026 "Error should mention the conflicting options"
1027 );
1028 }
1029 }
1030}