1use flag_rs::{Command, CommandBuilder, CompletionResult, Flag, FlagType, FlagValue, Shell};
2use std::env;
3
4fn main() {
5 let app = build_kubectl();
6
7 let args: Vec<String> = env::args().skip(1).collect();
8 if let Err(e) = app.execute(args) {
9 eprintln!("Error: {e:?}");
10 std::process::exit(1);
11 }
12}
13
14fn build_kubectl() -> Command {
15 CommandBuilder::new("kubectl")
16 .short("Kubernetes command-line tool")
17 .long("kubectl controls the Kubernetes cluster manager")
18 .subcommand(build_get_command())
19 .subcommand(build_describe_command())
20 .subcommand(build_delete_command())
21 .subcommand(build_completion_command())
22 .flag(
23 Flag::new("namespace")
24 .short('n')
25 .usage("Kubernetes namespace")
26 .value_type(FlagType::String)
27 .default(FlagValue::String("default".to_string())),
28 )
29 .flag_completion("namespace", |_ctx, prefix| {
30 let namespaces = get_namespaces_with_descriptions();
32 let mut result = CompletionResult::new();
33
34 for (ns_name, description) in namespaces {
35 if ns_name.starts_with(prefix) {
36 result = result.add_with_description(ns_name, description);
37 }
38 }
39
40 Ok(result)
41 })
42 .build()
43}
44
45fn build_get_command() -> Command {
46 CommandBuilder::new("get")
47 .short("Display one or many resources")
48 .long("Display one or many resources. Prints a table of the most important information about the specified resources.")
49 .flag(
50 Flag::new("limit")
51 .short('l')
52 .usage("Maximum number of resources to display")
53 .value_type(FlagType::String)
54 )
55 .flag_completion("limit", |_ctx, prefix| {
56 let common_limits = vec![
57 ("10", "Display 10 items"),
58 ("20", "Display 20 items"),
59 ("50", "Display 50 items"),
60 ("100", "Display 100 items"),
61 ("all", "Display all items (no limit)"),
62 ];
63
64 let mut result = CompletionResult::new();
65 for (limit, description) in common_limits {
66 if limit.starts_with(prefix) {
67 result = result.add_with_description(limit.to_string(), description.to_string());
68 }
69 }
70
71 Ok(result)
72 })
73 .subcommand(build_get_pods())
74 .subcommand(build_get_services())
75 .subcommand(build_get_deployments())
76 .build()
77}
78
79fn build_get_pods() -> Command {
80 CommandBuilder::new("pods")
81 .aliases(vec!["po", "pod"])
82 .short("List pods")
83 .arg_completion(|ctx, prefix| {
84 let namespace = ctx
86 .flag("namespace")
87 .map(String::as_str)
88 .unwrap_or("default");
89
90 let pods = get_pods_with_status(namespace);
92 let mut result = CompletionResult::new();
93
94 for (pod_name, status) in pods {
95 if pod_name.starts_with(prefix) {
96 result = result.add_with_description(pod_name, status);
97 }
98 }
99
100 Ok(result)
101 })
102 .run(|ctx| {
103 let namespace = ctx
104 .flag("namespace")
105 .map(String::as_str)
106 .unwrap_or("default");
107
108 println!("Listing pods in namespace: {namespace}");
109
110 if let Some(pod_name) = ctx.args().first() {
111 println!("Getting specific pod: {pod_name}");
112 } else {
113 for pod in get_pods_in_namespace(namespace) {
114 println!("pod/{}", pod);
115 }
116 }
117
118 Ok(())
119 })
120 .build()
121}
122
123fn build_get_services() -> Command {
124 CommandBuilder::new("services")
125 .aliases(vec!["svc", "service"])
126 .short("List services")
127 .arg_completion(|ctx, prefix| {
128 let namespace = ctx
129 .flag("namespace")
130 .map(String::as_str)
131 .unwrap_or("default");
132
133 let services = get_services_in_namespace(namespace);
134 Ok(CompletionResult::new()
135 .extend(services.into_iter().filter(|svc| svc.starts_with(prefix))))
136 })
137 .run(|ctx| {
138 let namespace = ctx
139 .flag("namespace")
140 .map(String::as_str)
141 .unwrap_or("default");
142
143 println!("Listing services in namespace: {namespace}");
144 Ok(())
145 })
146 .build()
147}
148
149fn build_get_deployments() -> Command {
150 CommandBuilder::new("deployments")
151 .aliases(vec!["deploy", "deployment"])
152 .short("List deployments")
153 .run(|ctx| {
154 let namespace = ctx
155 .flag("namespace")
156 .map(String::as_str)
157 .unwrap_or("default");
158
159 println!("Listing deployments in namespace: {namespace}");
160 Ok(())
161 })
162 .build()
163}
164
165fn build_describe_command() -> Command {
166 CommandBuilder::new("describe")
167 .short("Show details of a specific resource")
168 .run(|_ctx| {
169 println!("Describe command - add resource type subcommands");
170 Ok(())
171 })
172 .build()
173}
174
175fn build_delete_command() -> Command {
176 CommandBuilder::new("delete")
177 .short("Delete resources")
178 .run(|_ctx| {
179 println!("Delete command - add resource type subcommands");
180 Ok(())
181 })
182 .build()
183}
184
185fn get_namespaces_with_descriptions() -> Vec<(String, String)> {
187 vec![
188 (
189 "default".to_string(),
190 "Default namespace for user workloads".to_string(),
191 ),
192 (
193 "kube-system".to_string(),
194 "Kubernetes system components".to_string(),
195 ),
196 (
197 "kube-public".to_string(),
198 "Public resources accessible to all users".to_string(),
199 ),
200 (
201 "development".to_string(),
202 "Development environment".to_string(),
203 ),
204 (
205 "staging".to_string(),
206 "Staging environment for pre-production testing".to_string(),
207 ),
208 (
209 "production".to_string(),
210 "Production environment (CAUTION)".to_string(),
211 ),
212 ]
213}
214
215fn get_pods_with_status(namespace: &str) -> Vec<(String, String)> {
216 use std::time::{SystemTime, UNIX_EPOCH};
217
218 let timestamp = SystemTime::now()
220 .duration_since(UNIX_EPOCH)
221 .unwrap()
222 .as_secs();
223
224 let rand1 = ((timestamp * 1_103_515_245 + 12345) / 65536) % 100_000;
226 let rand2 = ((rand1 * 1_103_515_245 + 12345) / 65536) % 100_000;
227 let rand3 = ((rand2 * 1_103_515_245 + 12345) / 65536) % 100_000;
228
229 match namespace {
230 "default" => vec![
231 (
232 format!("nginx-deployment-7fb96c846b-{:05x}", rand1),
233 "Running (2/2 containers)".to_string(),
234 ),
235 (
236 format!("nginx-deployment-7fb96c846b-{:05x}", rand2),
237 "Running (2/2 containers)".to_string(),
238 ),
239 (
240 "redis-master-0".to_string(),
241 "Running (1/1 containers)".to_string(),
242 ),
243 (
244 "redis-slave-0".to_string(),
245 "Running (1/1 containers)".to_string(),
246 ),
247 ("redis-slave-1".to_string(), "Pending".to_string()),
248 ],
249 "kube-system" => vec![
250 (
251 format!("coredns-5d78c9869d-{:05x}", rand1),
252 "Running (1/1 containers)".to_string(),
253 ),
254 (
255 format!("coredns-5d78c9869d-{:05x}", rand2),
256 "Running (1/1 containers)".to_string(),
257 ),
258 ("etcd-minikube".to_string(), "Running".to_string()),
259 ("kube-apiserver-minikube".to_string(), "Running".to_string()),
260 (
261 "kube-controller-manager-minikube".to_string(),
262 "Running".to_string(),
263 ),
264 (format!("kube-proxy-{:05x}", rand3), "Running".to_string()),
265 ("kube-scheduler-minikube".to_string(), "Running".to_string()),
266 ],
267 _ => vec![],
268 }
269}
270
271fn get_pods_in_namespace(namespace: &str) -> Vec<String> {
272 use std::time::{SystemTime, UNIX_EPOCH};
273
274 let timestamp = SystemTime::now()
276 .duration_since(UNIX_EPOCH)
277 .unwrap()
278 .as_secs();
279
280 let rand1 = ((timestamp * 1_103_515_245 + 12345) / 65536) % 100_000;
282 let rand2 = ((rand1 * 1_103_515_245 + 12345) / 65536) % 100_000;
283 let rand3 = ((rand2 * 1_103_515_245 + 12345) / 65536) % 100_000;
284
285 match namespace {
286 "default" => vec![
287 format!("nginx-deployment-7fb96c846b-{:05x}", rand1),
288 format!("nginx-deployment-7fb96c846b-{:05x}", rand2),
289 format!("redis-master-0"),
290 format!("redis-slave-0"),
291 format!("redis-slave-1"),
292 ],
293 "kube-system" => vec![
294 format!("coredns-5d78c9869d-{:05x}", rand1),
295 format!("coredns-5d78c9869d-{:05x}", rand2),
296 format!("etcd-minikube"),
297 format!("kube-apiserver-minikube"),
298 format!("kube-controller-manager-minikube"),
299 format!("kube-proxy-{:05x}", rand3),
300 format!("kube-scheduler-minikube"),
301 ],
302 _ => vec![],
303 }
304}
305
306fn get_services_in_namespace(namespace: &str) -> Vec<String> {
307 match namespace {
308 "default" => vec![
309 "kubernetes".to_string(),
310 "nginx-service".to_string(),
311 "redis-master".to_string(),
312 "redis-slave".to_string(),
313 ],
314 "kube-system" => vec!["kube-dns".to_string()],
315 _ => vec![],
316 }
317}
318
319fn build_completion_command() -> Command {
320 CommandBuilder::new("completion")
321 .short("Generate shell completion scripts")
322 .long("Generate shell completion scripts for kubectl")
323 .arg_completion(|_ctx, prefix| {
324 let shells = vec![
325 ("bash", "Bash shell completion"),
326 ("zsh", "Zsh shell completion"),
327 ("fish", "Fish shell completion"),
328 ];
329
330 let mut result = CompletionResult::new();
331 for (shell, description) in shells {
332 if shell.starts_with(prefix) {
333 result =
334 result.add_with_description(shell.to_string(), description.to_string());
335 }
336 }
337
338 Ok(result)
339 })
340 .run(|ctx| {
341 let shell_name = ctx.args().first().ok_or_else(|| {
342 flag_rs::Error::ArgumentParsing(
343 "shell name required (bash, zsh, or fish)".to_string(),
344 )
345 })?;
346
347 let shell = match shell_name.as_str() {
348 "bash" => Shell::Bash,
349 "zsh" => Shell::Zsh,
350 "fish" => Shell::Fish,
351 _ => {
352 return Err(flag_rs::Error::ArgumentParsing(format!(
353 "unsupported shell: {}",
354 shell_name
355 )));
356 }
357 };
358
359 let root = build_kubectl();
362 println!("{}", root.generate_completion(shell));
363
364 Ok(())
365 })
366 .build()
367}