objectstore_server/
cli.rs

1use std::path::PathBuf;
2use std::thread;
3use std::time::Duration;
4
5use anyhow::Result;
6use argh::FromArgs;
7
8use crate::config::Config;
9use crate::{healthcheck, observability, web};
10
11/// Objectstore API webserver.
12#[derive(Debug, FromArgs)]
13struct Args {
14    /// path to the YAML configuration file
15    #[argh(option, short = 'c')]
16    pub config: Option<PathBuf>,
17
18    #[argh(subcommand)]
19    pub command: Command,
20}
21
22#[derive(Debug, FromArgs)]
23#[argh(subcommand)]
24enum Command {
25    Run(RunCommand),
26    Healthcheck(HealthcheckCommand),
27    Version(VersionCommand),
28    Sleep(SleepCommand),
29}
30
31/// run the objectstore web server
32#[derive(Debug, FromArgs)]
33#[argh(subcommand, name = "run")]
34struct RunCommand {}
35
36/// perform a healthcheck against the running objectstore web server
37///
38/// This command checks if the objectstore server is available on the configured host and port. This
39/// is used for Docker healthchecks.
40#[derive(Debug, FromArgs)]
41#[argh(subcommand, name = "healthcheck")]
42struct HealthcheckCommand {}
43
44/// print the objectstore server version
45#[derive(Default, Debug, FromArgs)]
46#[argh(subcommand, name = "version")]
47struct VersionCommand {}
48
49/// sleep for the specified number of seconds
50#[derive(Debug, FromArgs)]
51#[argh(subcommand, name = "sleep")]
52struct SleepCommand {
53    #[argh(positional)]
54    seconds: u64,
55}
56
57/// Bootstrap the runtime and execute the CLI command.
58pub fn execute() -> Result<()> {
59    let args: Args = argh::from_env();
60
61    // Special switch to just print the version and exit.
62    if let Command::Version(_) = args.command {
63        println!("{}", env!("OBJECTSTORE_RELEASE"));
64        return Ok(());
65    }
66
67    if let Command::Sleep(SleepCommand { seconds }) = args.command {
68        thread::sleep(Duration::from_secs(seconds));
69        return Ok(());
70    }
71
72    let config = Config::load(args.config.as_deref())?;
73
74    // Ensure a rustls crypto provider is installed, required on distroless.
75    rustls::crypto::ring::default_provider()
76        .install_default()
77        .map_err(|_| anyhow::anyhow!("Failed to install rustls crypto provider"))?;
78
79    // Sentry should be initialized before creating the async runtime.
80    let _sentry_guard = observability::init_sentry(&config);
81
82    let runtime = tokio::runtime::Builder::new_multi_thread()
83        .thread_name("main-rt")
84        .enable_all()
85        .worker_threads(config.runtime.worker_threads)
86        .build()?;
87    let _runtime_guard = runtime.enter();
88
89    observability::init_tracing(&config);
90    tracing::debug!(?config);
91
92    let metrics_guard = observability::init_metrics(&config)?;
93
94    let result = runtime.block_on(async move {
95        match args.command {
96            Command::Run(RunCommand {}) => web::server(config).await,
97            Command::Healthcheck(HealthcheckCommand {}) => healthcheck::healthcheck(config).await,
98            Command::Version(VersionCommand {}) | Command::Sleep(SleepCommand { .. }) => {
99                unreachable!()
100            }
101        }
102    });
103
104    // Flush metrics unconditionally before shutdown, even on error.
105    runtime.block_on(async {
106        if let Some(metrics_guard) = metrics_guard {
107            metrics_guard.flush(None).await.ok();
108        }
109    });
110
111    result
112}