Skip to main content

objectstore_server/
cli.rs

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