xianyun_cli/commands/
tool.rs1use anyhow::Context as _;
8use clap::Args;
9use thiserror::Error;
10use xianyun_cluster::clustertool::{Runner, Tool};
11use xianyun_core::config::caproni::load_from_context;
12use xianyun_core::config::global::Context;
13
14#[derive(Args, Debug)]
15pub struct ToolArgs {
16 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
18 pub args: Vec<String>,
19}
20
21#[derive(Debug, Error, PartialEq, Eq)]
23#[error("please specify a tool to run")]
24pub struct NoToolSpecifiedError;
25
26fn known_tools() -> Vec<Tool> {
28 vec![
29 Tool {
30 name: "k9s".to_string(),
31 install_hint: "install it with `mise use -g k9s`, or see https://k9scli.io".to_string(),
32 require_terminal: true,
33 },
34 Tool {
35 name: "kubectl".to_string(),
36 install_hint: "install it with `mise use -g kubectl`, or see https://kubernetes.io"
37 .to_string(),
38 require_terminal: false,
39 },
40 Tool {
41 name: "helm".to_string(),
42 install_hint: "install it with `mise use -g helm`, or see https://helm.sh".to_string(),
43 require_terminal: false,
44 },
45 Tool {
46 name: "stern".to_string(),
47 install_hint:
48 "install it with `mise use -g stern`, or see https://github.com/stern/stern"
49 .to_string(),
50 require_terminal: false,
51 },
52 ]
53}
54
55fn find_tool(name: &str) -> Tool {
60 known_tools()
61 .into_iter()
62 .find(|t| t.name == name)
63 .unwrap_or(Tool {
64 name: name.to_string(),
65 install_hint: String::new(),
66 require_terminal: false,
67 })
68}
69
70pub fn run(args: &ToolArgs, ctx: &Context) -> anyhow::Result<()> {
71 run_tool(ctx, &args.args)
72}
73
74pub fn run_tool(ctx: &Context, args: &[String]) -> anyhow::Result<()> {
76 let Some((name, rest)) = args.split_first() else {
77 return Err(NoToolSpecifiedError.into());
78 };
79
80 crate::drivers::ensure_registered();
81
82 let container = load_from_context(ctx).context("loading configuration")?;
83
84 let manager = xianyun_cluster::registry::new_manager(
85 container.caproni.cluster.clone(),
86 container.global.profile_name.clone(),
87 )
88 .context("creating cluster manager")?;
89
90 let tool = find_tool(name);
91
92 let code = Runner::new()
93 .run(
94 manager.as_ref(),
95 &container.global.profile_name,
96 &tool,
97 rest,
98 )
99 .with_context(|| format!("running {name}"))?;
100
101 if code != 0 {
102 return Err(crate::exitcode::CliExit::new(code).into());
105 }
106
107 Ok(())
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
117 fn run_tool_requires_a_tool_name() {
118 let err = run_tool(&Context::default(), &[]).unwrap_err();
119 assert!(err.downcast_ref::<NoToolSpecifiedError>().is_some());
120 }
121
122 #[test]
123 fn find_tool_returns_known_tool_metadata() {
124 let tool = find_tool("k9s");
125 assert_eq!(tool.name, "k9s");
126 assert!(tool.require_terminal);
127 assert!(tool.install_hint.contains("k9scli.io"));
128
129 let tool = find_tool("kubectl");
130 assert!(!tool.require_terminal);
131 assert!(tool.install_hint.contains("kubernetes.io"));
132
133 let tool = find_tool("helm");
134 assert!(tool.install_hint.contains("helm.sh"));
135
136 let tool = find_tool("stern");
137 assert!(tool.install_hint.contains("stern"));
138 }
139
140 #[test]
141 fn find_tool_falls_back_to_a_bare_tool_for_unknown_names() {
142 let tool = find_tool("some-custom-script");
143 assert_eq!(tool.name, "some-custom-script");
144 assert!(tool.install_hint.is_empty());
145 assert!(!tool.require_terminal);
146 }
147}