Skip to main content

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        .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
191// Mock functions that would normally query the Kubernetes API
192fn 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    // Generate random suffix based on current time
225    let timestamp = SystemTime::now()
226        .duration_since(UNIX_EPOCH)
227        .unwrap()
228        .as_secs();
229
230    // Simple pseudo-random generation
231    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    // Generate random suffix based on current time
281    let timestamp = SystemTime::now()
282        .duration_since(UNIX_EPOCH)
283        .unwrap()
284        .as_secs();
285
286    // Simple pseudo-random generation
287    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            // In a real app, you'd get the root command from a shared reference
366            // For this example, we'll recreate it
367            let root = build_kubectl();
368            println!("{}", root.generate_completion(shell));
369
370            Ok(())
371        })
372        .build()
373}