use crate::operator_cli::config::{load_runtime_config, load_runtime_config_with_source};
use bucketwarden_server::{BucketWarden, OperatorRole};
use serde_json::json;
use std::path::Path;
const OPERATOR_PRINCIPAL: &str = "operator";
pub fn health_json(config_path: Option<&str>) -> anyhow::Result<String> {
let runtime = BucketWarden::new(load_runtime_config(config_path)?)?;
Ok(serde_json::to_string(&runtime.health())?)
}
pub fn readiness_json(config_path: Option<&str>) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
let report = runtime.ops_health_report(OPERATOR_PRINCIPAL, None)?;
let config = runtime.ops_config_report(OPERATOR_PRINCIPAL, None)?;
let evidence = runtime.ops_evidence_export_report(OPERATOR_PRINCIPAL, None)?;
Ok(serde_json::to_string(&json!({
"status": if report.ready { "ready" } else { "degraded" },
"ready": report.ready,
"checks": {
"runtime": report.status,
"audit_log": if report.last_audit_sequence.is_some() || report.audit_event_count == 0 { "ready" } else { "degraded" },
"replication_log": if report.last_replication_sequence.is_some() || report.replication_record_count == 0 { "ready" } else { "degraded" },
"kms": "ready",
"storage_backend": if config.supported_storage_backends.contains(&config.active_storage_backend) { "ready" } else { "degraded" },
"evidence_export": if evidence.snapshot_json.is_empty() { "degraded" } else { "ready" }
},
"issues": report.issues,
}))?)
}
pub fn diagnostics_json(config_path: Option<&str>) -> anyhow::Result<String> {
let selection = load_runtime_config_with_source(config_path)?;
let mut runtime = BucketWarden::new(selection.config)?;
seed_operator_identity(&mut runtime)?;
let health = runtime.ops_health_report(OPERATOR_PRINCIPAL, None)?;
let config = runtime.ops_config_report(OPERATOR_PRINCIPAL, None)?;
let evidence = runtime.ops_evidence_export_report(OPERATOR_PRINCIPAL, None)?;
Ok(serde_json::to_string(&json!({
"status": if health.ready { "ok" } else { "degraded" },
"runtime": health,
"audit": {
"summary": runtime.audit_summary(),
"first_sequence": evidence.first_audit_sequence,
"last_sequence": evidence.last_audit_sequence
},
"replication": {
"summary": runtime.replication_summary(),
"last_sequence": evidence.last_replication_sequence
},
"config_report": config,
"evidence_export": {
"audit_event_count": evidence.audit_event_count,
"notification_event_count": evidence.notification_event_count,
"replication_record_count": evidence.replication_record_count
},
"config": {
"source": selection.source,
"path": selection.source_path,
},
"certification": {
"full_repository_certified": false,
"claims_promoted": false
}
}))?)
}
pub fn metrics_text(config_path: Option<&str>) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
let health = runtime.ops_health_report(OPERATOR_PRINCIPAL, None)?;
let audit = runtime.audit_summary();
let replication = runtime.replication_summary();
Ok(format!(
"# HELP bucketwarden_bucket_count Number of buckets in the runtime.\n\
# TYPE bucketwarden_bucket_count gauge\n\
bucketwarden_bucket_count {}\n\
# HELP bucketwarden_object_version_count Number of object versions in the runtime.\n\
# TYPE bucketwarden_object_version_count gauge\n\
bucketwarden_object_version_count {}\n\
# HELP bucketwarden_multipart_upload_count Number of in-flight multipart uploads.\n\
# TYPE bucketwarden_multipart_upload_count gauge\n\
bucketwarden_multipart_upload_count {}\n\
# HELP bucketwarden_audit_events_total Audit events recorded by outcome.\n\
# TYPE bucketwarden_audit_events_total counter\n\
bucketwarden_audit_events_total{{outcome=\"allowed\"}} {}\n\
bucketwarden_audit_events_total{{outcome=\"denied\"}} {}\n\
bucketwarden_audit_events_total{{outcome=\"failed\"}} {}\n\
# HELP bucketwarden_runtime_ready Whether the runtime is ready for operator traffic.\n\
# TYPE bucketwarden_runtime_ready gauge\n\
bucketwarden_runtime_ready {}\n\
# HELP bucketwarden_notification_event_count Number of notification events retained in the runtime.\n\
# TYPE bucketwarden_notification_event_count gauge\n\
bucketwarden_notification_event_count {}\n\
# HELP bucketwarden_operator_issue_count Number of active operator-visible issues.\n\
# TYPE bucketwarden_operator_issue_count gauge\n\
bucketwarden_operator_issue_count {}\n\
# HELP bucketwarden_credential_count Number of scoped credentials in the runtime.\n\
# TYPE bucketwarden_credential_count gauge\n\
bucketwarden_credential_count {}\n\
# HELP bucketwarden_replication_records_total Replication records by action.\n\
# TYPE bucketwarden_replication_records_total counter\n\
bucketwarden_replication_records_total{{action=\"put_object\"}} {}\n\
bucketwarden_replication_records_total{{action=\"delete_object\"}} {}\n",
health.bucket_count,
health.object_version_count,
health.multipart_upload_count,
audit.allowed,
audit.denied,
audit.failed,
usize::from(health.ready),
health.notification_event_count,
health.issues.len(),
health.credential_count,
replication.put_objects,
replication.delete_objects
))
}
pub fn audit_export(config_path: Option<&str>) -> anyhow::Result<String> {
let runtime = seeded_operator_runtime(config_path)?;
runtime.audit_json_lines()
}
pub fn replication_status_json(config_path: Option<&str>) -> anyhow::Result<String> {
let runtime = BucketWarden::new(load_runtime_config(config_path)?)?;
Ok(serde_json::to_string(&json!({
"status": "ok",
"summary": runtime.replication_summary(),
"last_sequence": runtime.health().last_replication_sequence
}))?)
}
pub fn ops_report_health_json(
config_path: Option<&str>,
bucket: Option<&str>,
) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
seed_ops_bucket_scope(&mut runtime, bucket)?;
Ok(serde_json::to_string(
&runtime.ops_health_report(OPERATOR_PRINCIPAL, bucket)?,
)?)
}
pub fn ops_report_config_json(
config_path: Option<&str>,
bucket: Option<&str>,
) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
seed_ops_bucket_scope(&mut runtime, bucket)?;
Ok(serde_json::to_string(
&runtime.ops_config_report(OPERATOR_PRINCIPAL, bucket)?,
)?)
}
pub fn ops_report_admin_surfaces_json(
config_path: Option<&str>,
bucket: Option<&str>,
) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
seed_ops_bucket_scope(&mut runtime, bucket)?;
Ok(serde_json::to_string(
&runtime.ops_admin_surface_report(OPERATOR_PRINCIPAL, bucket)?,
)?)
}
pub fn ops_report_incident_json(
config_path: Option<&str>,
bucket: Option<&str>,
incident_type: &str,
) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
seed_ops_bucket_scope(&mut runtime, bucket)?;
Ok(serde_json::to_string(&runtime.ops_incident_report(
OPERATOR_PRINCIPAL,
bucket,
incident_type,
)?)?)
}
pub fn ops_report_evidence_export_json(
config_path: Option<&str>,
bucket: Option<&str>,
) -> anyhow::Result<String> {
let mut runtime = seeded_operator_runtime(config_path)?;
seed_ops_bucket_scope(&mut runtime, bucket)?;
Ok(serde_json::to_string(
&runtime.ops_evidence_export_report(OPERATOR_PRINCIPAL, bucket)?,
)?)
}
pub fn ui_browser_manifest_json() -> String {
bucketwarden_server::browser_ui_manifest_json()
}
pub fn ui_browser_html() -> String {
bucketwarden_server::browser_ui_html()
}
pub fn ui_browser_css() -> &'static str {
bucketwarden_server::browser_ui_css()
}
pub fn ui_browser_js() -> &'static str {
bucketwarden_server::browser_ui_js()
}
pub fn version_text() -> String {
format!(
"bucketwarden-cli {} ({})\n",
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_NAME")
)
}
pub fn diagnostics_json_with_cli_metadata(
config_path: Option<&str>,
verbosity: u8,
) -> anyhow::Result<String> {
if verbosity == 0 {
return diagnostics_json(config_path);
}
let mut value: serde_json::Value = serde_json::from_str(&diagnostics_json(config_path)?)?;
let selection = load_runtime_config_with_source(config_path)?;
value["cli"] = json!({
"verbosity": verbosity,
"config": {
"source": selection.source,
"path": selection.source_path,
},
"build": {
"package": env!("CARGO_PKG_NAME"),
"version": env!("CARGO_PKG_VERSION")
}
});
Ok(serde_json::to_string(&value)?)
}
pub fn validate_config_path(path: &str) -> anyhow::Result<serde_json::Value> {
let config = load_runtime_config_with_source(Some(path))?;
let runtime_config = config.config;
Ok(json!({
"status": "valid",
"config_path": Path::new(path).display().to_string(),
"key_id": runtime_config.key_id,
"key_material_bytes": runtime_config.key_material.len(),
"clock_epoch_seconds": runtime_config.clock_epoch_seconds,
"config_source": config.source,
"config_source_path": config.source_path,
}))
}
fn seeded_operator_runtime(config_path: Option<&str>) -> anyhow::Result<BucketWarden> {
let mut runtime = BucketWarden::new(load_runtime_config(config_path)?)?;
seed_operator_identity(&mut runtime)?;
Ok(runtime)
}
fn seed_operator_identity(runtime: &mut BucketWarden) -> anyhow::Result<()> {
runtime.create_local_user(OPERATOR_PRINCIPAL);
runtime.assign_operator_role(
OPERATOR_PRINCIPAL,
OPERATOR_PRINCIPAL,
OperatorRole::ClusterAdmin,
"*",
)?;
Ok(())
}
fn seed_ops_bucket_scope(runtime: &mut BucketWarden, bucket: Option<&str>) -> anyhow::Result<()> {
if let Some(bucket) = bucket {
runtime.allow(OPERATOR_PRINCIPAL, "s3:*", "*");
runtime.create_bucket(OPERATOR_PRINCIPAL, bucket)?;
}
Ok(())
}