Skip to main content

bucketwarden_cli/
lib.rs

1mod operator_cli;
2mod operator_tooling;
3mod s3_listener;
4
5use operator_cli::{parse_operator_command, OperatorCommand, S3ServeStorage, HELP};
6
7#[derive(Debug)]
8pub enum CliError {
9    Usage(String),
10    Runtime(anyhow::Error),
11}
12
13impl std::fmt::Display for CliError {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        match self {
16            Self::Usage(message) => write!(f, "{message}"),
17            Self::Runtime(error) => write!(f, "{error}"),
18        }
19    }
20}
21
22impl std::error::Error for CliError {}
23
24impl From<anyhow::Error> for CliError {
25    fn from(error: anyhow::Error) -> Self {
26        Self::Runtime(error)
27    }
28}
29
30pub fn run_env() -> Result<(), CliError> {
31    run(std::env::args().collect())
32}
33
34pub fn run(args: Vec<String>) -> Result<(), CliError> {
35    let mut command = parse_operator_command(&args).map_err(CliError::Usage)?;
36    let mut quiet = false;
37    let mut verbosity = 0;
38    if let OperatorCommand::Global {
39        options,
40        command: inner,
41    } = command
42    {
43        quiet = options.quiet;
44        verbosity = options.verbose;
45        command = *inner;
46    }
47
48    if quiet
49        && matches!(
50            command,
51            OperatorCommand::Help | OperatorCommand::DefaultStatus | OperatorCommand::Version
52        )
53    {
54        return Ok(());
55    }
56
57    match command {
58        OperatorCommand::Help => {
59            print!("{HELP}");
60            return Ok(());
61        }
62        OperatorCommand::Version => {
63            print!("{}", operator_cli::version_text());
64            return Ok(());
65        }
66        OperatorCommand::Global { .. } => {
67            unreachable!("global command wrappers are unwrapped before dispatch")
68        }
69        OperatorCommand::Health { config_path } => {
70            println!("{}", operator_cli::health_json(config_path.as_deref())?);
71            return Ok(());
72        }
73        OperatorCommand::Readiness { config_path } => {
74            println!("{}", operator_cli::readiness_json(config_path.as_deref())?);
75            return Ok(());
76        }
77        OperatorCommand::Diagnostics { config_path } => {
78            let diagnostics = if verbosity > 0 {
79                operator_cli::diagnostics_json_with_cli_metadata(config_path.as_deref(), verbosity)?
80            } else {
81                operator_cli::diagnostics_json(config_path.as_deref())?
82            };
83            println!("{diagnostics}");
84            return Ok(());
85        }
86        OperatorCommand::Metrics { config_path } => {
87            print!("{}", operator_cli::metrics_text(config_path.as_deref())?);
88            return Ok(());
89        }
90        OperatorCommand::OpsReportHealth {
91            config_path,
92            bucket,
93        } => {
94            println!(
95                "{}",
96                operator_cli::ops_report_health_json(config_path.as_deref(), bucket.as_deref(),)?
97            );
98            return Ok(());
99        }
100        OperatorCommand::OpsReportConfig {
101            config_path,
102            bucket,
103        } => {
104            println!(
105                "{}",
106                operator_cli::ops_report_config_json(config_path.as_deref(), bucket.as_deref(),)?
107            );
108            return Ok(());
109        }
110        OperatorCommand::OpsReportAdminSurfaces {
111            config_path,
112            bucket,
113        } => {
114            println!(
115                "{}",
116                operator_cli::ops_report_admin_surfaces_json(
117                    config_path.as_deref(),
118                    bucket.as_deref(),
119                )?
120            );
121            return Ok(());
122        }
123        OperatorCommand::OpsReportIncident {
124            config_path,
125            bucket,
126            incident_type,
127        } => {
128            println!(
129                "{}",
130                operator_cli::ops_report_incident_json(
131                    config_path.as_deref(),
132                    bucket.as_deref(),
133                    &incident_type,
134                )?
135            );
136            return Ok(());
137        }
138        OperatorCommand::OpsReportEvidenceExport {
139            config_path,
140            bucket,
141        } => {
142            println!(
143                "{}",
144                operator_cli::ops_report_evidence_export_json(
145                    config_path.as_deref(),
146                    bucket.as_deref(),
147                )?
148            );
149            return Ok(());
150        }
151        OperatorCommand::UiBrowserManifest => {
152            println!("{}", operator_cli::ui_browser_manifest_json());
153            return Ok(());
154        }
155        OperatorCommand::UiBrowserHtml => {
156            print!("{}", operator_cli::ui_browser_html());
157            return Ok(());
158        }
159        OperatorCommand::UiBrowserCss => {
160            print!("{}", operator_cli::ui_browser_css());
161            return Ok(());
162        }
163        OperatorCommand::UiBrowserJs => {
164            print!("{}", operator_cli::ui_browser_js());
165            return Ok(());
166        }
167        OperatorCommand::ConfigValidate { config_path } => {
168            println!("{}", operator_cli::validate_config_path(&config_path)?);
169            return Ok(());
170        }
171        OperatorCommand::AuditExport { config_path } => {
172            print!("{}", operator_cli::audit_export(config_path.as_deref())?);
173            return Ok(());
174        }
175        OperatorCommand::ReplicationStatus { config_path } => {
176            println!(
177                "{}",
178                operator_cli::replication_status_json(config_path.as_deref())?
179            );
180            return Ok(());
181        }
182        OperatorCommand::S3Serve {
183            bind,
184            console_bind,
185            config_path,
186            principal,
187            access_key_id,
188            secret_access_key,
189            storage,
190        } => {
191            let storage = match storage {
192                S3ServeStorage::Filesystem { data_dir } => s3_listener::S3StorageMode::Filesystem {
193                    data_dir: data_dir.into(),
194                },
195                S3ServeStorage::InMemory => s3_listener::S3StorageMode::InMemory,
196            };
197            return Ok(s3_listener::serve(s3_listener::S3ServeOptions {
198                bind,
199                console_bind,
200                config: operator_cli::load_runtime_config(config_path.as_deref())?,
201                principal,
202                access_key_id,
203                secret_access_key,
204                storage,
205            })?);
206        }
207        OperatorCommand::PolicyExplain(args) => {
208            println!("{}", operator_tooling::policy_explain_json(&args)?);
209            return Ok(());
210        }
211        OperatorCommand::PolicySimulate(args) => {
212            println!("{}", operator_tooling::policy_simulate_json(&args)?);
213            return Ok(());
214        }
215        OperatorCommand::PolicyAnalyze => {
216            println!("{}", operator_tooling::policy_analyze_json()?);
217            return Ok(());
218        }
219        OperatorCommand::AuthReportIdentityProviders => {
220            println!(
221                "{}",
222                operator_tooling::auth_report_identity_providers_json()?
223            );
224            return Ok(());
225        }
226        OperatorCommand::AuthReportCredentials => {
227            println!("{}", operator_tooling::auth_report_credentials_json()?);
228            return Ok(());
229        }
230        OperatorCommand::AuthReportTemporaryCredentials => {
231            println!(
232                "{}",
233                operator_tooling::auth_report_temporary_credentials_json()?
234            );
235            return Ok(());
236        }
237        OperatorCommand::AuthRoleAssign {
238            principal,
239            role,
240            scope,
241        } => {
242            println!(
243                "{}",
244                operator_tooling::auth_role_assign_json(&principal, role, &scope)?
245            );
246            return Ok(());
247        }
248        OperatorCommand::AuthRoleList { principal } => {
249            println!("{}", operator_tooling::auth_role_list_json(&principal)?);
250            return Ok(());
251        }
252        OperatorCommand::AuthKeyRotate {
253            principal,
254            old_access_key_id,
255            new_access_key_id,
256            secret_access_key,
257        } => {
258            println!(
259                "{}",
260                operator_tooling::auth_key_rotate_json(
261                    &principal,
262                    &old_access_key_id,
263                    &new_access_key_id,
264                    &secret_access_key,
265                )?
266            );
267            return Ok(());
268        }
269        OperatorCommand::AuthKeyRevoke {
270            principal,
271            access_key_id,
272        } => {
273            println!(
274                "{}",
275                operator_tooling::auth_key_revoke_json(&principal, &access_key_id)?
276            );
277            return Ok(());
278        }
279        OperatorCommand::AuthKeyReportLeaked {
280            principal,
281            access_key_id,
282        } => {
283            println!(
284                "{}",
285                operator_tooling::auth_key_report_leaked_json(&principal, &access_key_id)?
286            );
287            return Ok(());
288        }
289        OperatorCommand::Conformance { target } => {
290            bucketwarden_demo::print_conformance(target.as_deref())?;
291            return Ok(());
292        }
293        OperatorCommand::Demo(command) => {
294            let runtime =
295                bucketwarden_server::BucketWarden::new(operator_cli::load_runtime_config(None)?)
296                    .map_err(anyhow::Error::from)?;
297            return Ok(bucketwarden_demo::run_demo_command(&command, &runtime)?);
298        }
299        OperatorCommand::DefaultStatus => {
300            print_default_status();
301            return Ok(());
302        }
303    }
304
305    unreachable!("all operator commands should be fully handled by match arms");
306}
307
308fn print_default_status() {
309    println!(
310        "{}",
311        serde_json::json!({
312            "name": "bucketwarden",
313            "status": "ready",
314            "certified_runtime_slice": "in-memory-0.1.0"
315        })
316    );
317}