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}