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}