Skip to main content

xianyun_deploy/diagnostics/
base.rs

1//! Shared types for diagnostics: the [`Diagnostic`] trait, the
2//! [`Meta`] context passed to every diagnostic, and the
3//! [`SimpleDiagnostic`] closure-backed implementation.
4//!
5//! Ported from `internal/diagnostics/base/{meta,diagnostic,simple_diagnostic}.go`.
6//!
7//! # Divergence from Go
8//!
9//! Go's `base.Meta` also carries `Cluster cluster.Manager`.
10//! `xianyun-cluster` does not yet expose a stable `Manager` trait (see
11//! that crate's driver-based design in progress), so that field is
12//! deferred here rather than coupling this crate to an interface that
13//! is still moving. Diagnostics that need it (`cluster_domain`) stay
14//! `#[ignore]`d; see `PORT_NOTES.md`. `Kubectl kubectl.Backend` *is*
15//! ported below, since `xianyun_backends::kubectl::Backend` is stable
16//! and `orphaned_mirror_copies` needs it.
17
18use std::sync::Arc;
19
20use xianyun_core::config::latest::CaproniConfig;
21
22/// Context passed to every diagnostic.
23#[derive(Clone, Default)]
24pub struct Meta {
25    /// The loaded caproni configuration, when available.
26    pub config: Option<CaproniConfig>,
27    /// A kubectl backend for diagnostics that query the cluster, when
28    /// available.
29    pub kubectl: Option<Arc<dyn xianyun_backends::kubectl::Backend + Send + Sync>>,
30}
31
32impl std::fmt::Debug for Meta {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.debug_struct("Meta")
35            .field("config", &self.config)
36            .field(
37                "kubectl",
38                &self.kubectl.as_ref().map(|_| "<kubectl backend>"),
39            )
40            .finish()
41    }
42}
43
44/// A single diagnostic check.
45pub trait Diagnostic: Send + Sync {
46    /// The diagnostic's display name.
47    fn name(&self) -> &str;
48
49    /// Runs the diagnostic, returning a warning message (empty when
50    /// healthy) or an error if the check itself failed to run.
51    fn run(&self, meta: &Meta) -> (String, Option<Box<dyn std::error::Error + Send + Sync>>);
52
53    /// Whether this diagnostic requires a running cluster.
54    fn needs_cluster(&self) -> bool;
55}
56
57type Action =
58    dyn Fn(&Meta) -> (String, Option<Box<dyn std::error::Error + Send + Sync>>) + Send + Sync;
59
60/// A [`Diagnostic`] backed by a plain closure.
61pub struct SimpleDiagnostic {
62    title: String,
63    needs_running_cluster: bool,
64    action: Box<Action>,
65}
66
67impl SimpleDiagnostic {
68    /// Creates a new [`SimpleDiagnostic`] with the given title,
69    /// cluster requirement, and action.
70    pub fn new<F>(title: impl Into<String>, needs_running_cluster: bool, action: F) -> Self
71    where
72        F: Fn(&Meta) -> (String, Option<Box<dyn std::error::Error + Send + Sync>>)
73            + Send
74            + Sync
75            + 'static,
76    {
77        Self {
78            title: title.into(),
79            needs_running_cluster,
80            action: Box::new(action),
81        }
82    }
83}
84
85impl Diagnostic for SimpleDiagnostic {
86    fn name(&self) -> &str {
87        &self.title
88    }
89
90    fn run(&self, meta: &Meta) -> (String, Option<Box<dyn std::error::Error + Send + Sync>>) {
91        (self.action)(meta)
92    }
93
94    fn needs_cluster(&self) -> bool {
95        self.needs_running_cluster
96    }
97}