bucketwarden-cli 0.1.0

BucketWarden CLI command parsing, demos, and listener runtime.
Documentation
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(())
}