kubectl/
kubectl.rs

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            // In a real kubectl, this would query the API server
31            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            // This is the key feature - dynamic completion based on runtime state!
85            let namespace = ctx
86                .flag("namespace")
87                .map(String::as_str)
88                .unwrap_or("default");
89
90            // In real kubectl, this would query the K8s API
91            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
185// Mock functions that would normally query the Kubernetes API
186fn 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    // Generate random suffix based on current time
219    let timestamp = SystemTime::now()
220        .duration_since(UNIX_EPOCH)
221        .unwrap()
222        .as_secs();
223
224    // Simple pseudo-random generation
225    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    // Generate random suffix based on current time
275    let timestamp = SystemTime::now()
276        .duration_since(UNIX_EPOCH)
277        .unwrap()
278        .as_secs();
279
280    // Simple pseudo-random generation
281    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            // In a real app, you'd get the root command from a shared reference
360            // For this example, we'll recreate it
361            let root = build_kubectl();
362            println!("{}", root.generate_completion(shell));
363
364            Ok(())
365        })
366        .build()
367}