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 .flag(
169 Flag::new("output")
170 .short('o')
171 .usage("Output format")
172 .value_type(FlagType::String),
173 )
174 .run(|_ctx| {
175 println!("Describe command - add resource type subcommands");
176 Ok(())
177 })
178 .build()
179}
180
181fn build_delete_command() -> Command {
182 CommandBuilder::new("delete")
183 .short("Delete resources")
184 .run(|_ctx| {
185 println!("Delete command - add resource type subcommands");
186 Ok(())
187 })
188 .build()
189}
190
191fn get_namespaces_with_descriptions() -> Vec<(String, String)> {
193 vec![
194 (
195 "default".to_string(),
196 "Default namespace for user workloads".to_string(),
197 ),
198 (
199 "kube-system".to_string(),
200 "Kubernetes system components".to_string(),
201 ),
202 (
203 "kube-public".to_string(),
204 "Public resources accessible to all users".to_string(),
205 ),
206 (
207 "development".to_string(),
208 "Development environment".to_string(),
209 ),
210 (
211 "staging".to_string(),
212 "Staging environment for pre-production testing".to_string(),
213 ),
214 (
215 "production".to_string(),
216 "Production environment (CAUTION)".to_string(),
217 ),
218 ]
219}
220
221fn get_pods_with_status(namespace: &str) -> Vec<(String, String)> {
222 use std::time::{SystemTime, UNIX_EPOCH};
223
224 let timestamp = SystemTime::now()
226 .duration_since(UNIX_EPOCH)
227 .unwrap()
228 .as_secs();
229
230 let rand1 = ((timestamp * 1_103_515_245 + 12345) / 65536) % 100_000;
232 let rand2 = ((rand1 * 1_103_515_245 + 12345) / 65536) % 100_000;
233 let rand3 = ((rand2 * 1_103_515_245 + 12345) / 65536) % 100_000;
234
235 match namespace {
236 "default" => vec![
237 (
238 format!("nginx-deployment-7fb96c846b-{:05x}", rand1),
239 "Running (2/2 containers)".to_string(),
240 ),
241 (
242 format!("nginx-deployment-7fb96c846b-{:05x}", rand2),
243 "Running (2/2 containers)".to_string(),
244 ),
245 (
246 "redis-master-0".to_string(),
247 "Running (1/1 containers)".to_string(),
248 ),
249 (
250 "redis-slave-0".to_string(),
251 "Running (1/1 containers)".to_string(),
252 ),
253 ("redis-slave-1".to_string(), "Pending".to_string()),
254 ],
255 "kube-system" => vec![
256 (
257 format!("coredns-5d78c9869d-{:05x}", rand1),
258 "Running (1/1 containers)".to_string(),
259 ),
260 (
261 format!("coredns-5d78c9869d-{:05x}", rand2),
262 "Running (1/1 containers)".to_string(),
263 ),
264 ("etcd-minikube".to_string(), "Running".to_string()),
265 ("kube-apiserver-minikube".to_string(), "Running".to_string()),
266 (
267 "kube-controller-manager-minikube".to_string(),
268 "Running".to_string(),
269 ),
270 (format!("kube-proxy-{:05x}", rand3), "Running".to_string()),
271 ("kube-scheduler-minikube".to_string(), "Running".to_string()),
272 ],
273 _ => vec![],
274 }
275}
276
277fn get_pods_in_namespace(namespace: &str) -> Vec<String> {
278 use std::time::{SystemTime, UNIX_EPOCH};
279
280 let timestamp = SystemTime::now()
282 .duration_since(UNIX_EPOCH)
283 .unwrap()
284 .as_secs();
285
286 let rand1 = ((timestamp * 1_103_515_245 + 12345) / 65536) % 100_000;
288 let rand2 = ((rand1 * 1_103_515_245 + 12345) / 65536) % 100_000;
289 let rand3 = ((rand2 * 1_103_515_245 + 12345) / 65536) % 100_000;
290
291 match namespace {
292 "default" => vec![
293 format!("nginx-deployment-7fb96c846b-{:05x}", rand1),
294 format!("nginx-deployment-7fb96c846b-{:05x}", rand2),
295 format!("redis-master-0"),
296 format!("redis-slave-0"),
297 format!("redis-slave-1"),
298 ],
299 "kube-system" => vec![
300 format!("coredns-5d78c9869d-{:05x}", rand1),
301 format!("coredns-5d78c9869d-{:05x}", rand2),
302 format!("etcd-minikube"),
303 format!("kube-apiserver-minikube"),
304 format!("kube-controller-manager-minikube"),
305 format!("kube-proxy-{:05x}", rand3),
306 format!("kube-scheduler-minikube"),
307 ],
308 _ => vec![],
309 }
310}
311
312fn get_services_in_namespace(namespace: &str) -> Vec<String> {
313 match namespace {
314 "default" => vec![
315 "kubernetes".to_string(),
316 "nginx-service".to_string(),
317 "redis-master".to_string(),
318 "redis-slave".to_string(),
319 ],
320 "kube-system" => vec!["kube-dns".to_string()],
321 _ => vec![],
322 }
323}
324
325fn build_completion_command() -> Command {
326 CommandBuilder::new("completion")
327 .short("Generate shell completion scripts")
328 .long("Generate shell completion scripts for kubectl")
329 .arg_completion(|_ctx, prefix| {
330 let shells = vec![
331 ("bash", "Bash shell completion"),
332 ("zsh", "Zsh shell completion"),
333 ("fish", "Fish shell completion"),
334 ];
335
336 let mut result = CompletionResult::new();
337 for (shell, description) in shells {
338 if shell.starts_with(prefix) {
339 result =
340 result.add_with_description(shell.to_string(), description.to_string());
341 }
342 }
343
344 Ok(result)
345 })
346 .run(|ctx| {
347 let shell_name = ctx.args().first().ok_or_else(|| {
348 flag_rs::Error::ArgumentParsing(
349 "shell name required (bash, zsh, or fish)".to_string(),
350 )
351 })?;
352
353 let shell = match shell_name.as_str() {
354 "bash" => Shell::Bash,
355 "zsh" => Shell::Zsh,
356 "fish" => Shell::Fish,
357 _ => {
358 return Err(flag_rs::Error::ArgumentParsing(format!(
359 "unsupported shell: {}",
360 shell_name
361 )));
362 }
363 };
364
365 let root = build_kubectl();
368 println!("{}", root.generate_completion(shell));
369
370 Ok(())
371 })
372 .build()
373}