objectstore_server/
cli.rs

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