From aa1b50f548094fb79a476dc565e28cd452d57d9f Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Sat, 28 Feb 2026 23:45:59 -0500 Subject: [PATCH] feat(kubectl): add --context, --kubeconfig, -n, -l, --as global flags kubectl subcommands (pods, services, logs) now accept common global flags that are forwarded to the underlying kubectl binary. Previously only -n was supported on pods/services, and logs had no namespace support at all. --- src/main.rs | 206 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 190 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7125464..77b4804 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,7 +50,7 @@ mod wc_cmd; mod wget_cmd; use anyhow::{Context, Result}; -use clap::{Parser, Subcommand}; +use clap::{Args, Parser, Subcommand}; use std::ffi::OsString; use std::path::{Path, PathBuf}; @@ -710,20 +710,66 @@ enum ComposeCommands { Other(Vec), } +#[derive(Args, Clone, Debug)] +struct KubectlGlobalArgs { + /// The name of the kubeconfig context to use + #[arg(long)] + context: Option, + /// Path to the kubeconfig file + #[arg(long)] + kubeconfig: Option, + /// Namespace scope for this request + #[arg(short, long)] + namespace: Option, + /// Label selector (e.g. -l app=web) + #[arg(short = 'l', long)] + selector: Option, + /// Username to impersonate + #[arg(long = "as")] + impersonate: Option, +} + +impl KubectlGlobalArgs { + fn to_args(&self) -> Vec { + let mut args = Vec::new(); + if let Some(ref ctx) = self.context { + args.push("--context".to_string()); + args.push(ctx.clone()); + } + if let Some(ref kc) = self.kubeconfig { + args.push("--kubeconfig".to_string()); + args.push(kc.clone()); + } + if let Some(ref ns) = self.namespace { + args.push("-n".to_string()); + args.push(ns.clone()); + } + if let Some(ref sel) = self.selector { + args.push("-l".to_string()); + args.push(sel.clone()); + } + if let Some(ref imp) = self.impersonate { + args.push("--as".to_string()); + args.push(imp.clone()); + } + args + } +} + #[derive(Subcommand)] enum KubectlCommands { /// List pods Pods { - #[arg(short, long)] - namespace: Option, + #[command(flatten)] + global: KubectlGlobalArgs, /// All namespaces #[arg(short = 'A', long)] all: bool, }, /// List services Services { - #[arg(short, long)] - namespace: Option, + #[command(flatten)] + global: KubectlGlobalArgs, /// All namespaces #[arg(short = 'A', long)] all: bool, @@ -731,6 +777,8 @@ enum KubectlCommands { /// Show pod logs (deduplicated) Logs { pod: String, + #[command(flatten)] + global: KubectlGlobalArgs, #[arg(short, long)] container: Option, }, @@ -1058,28 +1106,27 @@ fn main() -> Result<()> { }, Commands::Kubectl { command } => match command { - KubectlCommands::Pods { namespace, all } => { - let mut args: Vec = Vec::new(); + KubectlCommands::Pods { global, all } => { + let mut args = global.to_args(); if all { args.push("-A".to_string()); - } else if let Some(n) = namespace { - args.push("-n".to_string()); - args.push(n); } container::run(container::ContainerCmd::KubectlPods, &args, cli.verbose)?; } - KubectlCommands::Services { namespace, all } => { - let mut args: Vec = Vec::new(); + KubectlCommands::Services { global, all } => { + let mut args = global.to_args(); if all { args.push("-A".to_string()); - } else if let Some(n) = namespace { - args.push("-n".to_string()); - args.push(n); } container::run(container::ContainerCmd::KubectlServices, &args, cli.verbose)?; } - KubectlCommands::Logs { pod, container: c } => { + KubectlCommands::Logs { + pod, + global, + container: c, + } => { let mut args = vec![pod]; + args.extend(global.to_args()); if let Some(cont) = c { args.push("-c".to_string()); args.push(cont); @@ -1563,4 +1610,131 @@ mod tests { _ => panic!("Expected Git Commit command"), } } + + #[test] + fn test_kubectl_pods_context() { + let cli = Cli::try_parse_from([ + "rtk", + "kubectl", + "pods", + "--context", + "my-cluster", + ]) + .unwrap(); + match cli.command { + Commands::Kubectl { + command: KubectlCommands::Pods { global, all }, + } => { + assert_eq!(global.context.as_deref(), Some("my-cluster")); + assert!(!all); + let args = global.to_args(); + assert_eq!(args, vec!["--context", "my-cluster"]); + } + _ => panic!("Expected Kubectl Pods command"), + } + } + + #[test] + fn test_kubectl_pods_all_global_args() { + let cli = Cli::try_parse_from([ + "rtk", + "kubectl", + "pods", + "--context", + "prod", + "--kubeconfig", + "/tmp/kube.conf", + "-n", + "default", + "-l", + "app=web", + "--as", + "admin", + "-A", + ]) + .unwrap(); + match cli.command { + Commands::Kubectl { + command: KubectlCommands::Pods { global, all }, + } => { + assert_eq!(global.context.as_deref(), Some("prod")); + assert_eq!(global.kubeconfig.as_deref(), Some("/tmp/kube.conf")); + assert_eq!(global.namespace.as_deref(), Some("default")); + assert_eq!(global.selector.as_deref(), Some("app=web")); + assert_eq!(global.impersonate.as_deref(), Some("admin")); + assert!(all); + let args = global.to_args(); + assert!(args.contains(&"--context".to_string())); + assert!(args.contains(&"--kubeconfig".to_string())); + assert!(args.contains(&"-n".to_string())); + assert!(args.contains(&"-l".to_string())); + assert!(args.contains(&"--as".to_string())); + } + _ => panic!("Expected Kubectl Pods command"), + } + } + + #[test] + fn test_kubectl_logs_with_namespace() { + let cli = Cli::try_parse_from([ + "rtk", + "kubectl", + "logs", + "my-pod", + "-n", + "staging", + "--context", + "dev", + ]) + .unwrap(); + match cli.command { + Commands::Kubectl { + command: + KubectlCommands::Logs { + pod, + global, + container, + }, + } => { + assert_eq!(pod, "my-pod"); + assert_eq!(global.namespace.as_deref(), Some("staging")); + assert_eq!(global.context.as_deref(), Some("dev")); + assert!(container.is_none()); + } + _ => panic!("Expected Kubectl Logs command"), + } + } + + #[test] + fn test_kubectl_services_selector() { + let cli = Cli::try_parse_from([ + "rtk", + "kubectl", + "services", + "-l", + "tier=frontend", + ]) + .unwrap(); + match cli.command { + Commands::Kubectl { + command: KubectlCommands::Services { global, all }, + } => { + assert_eq!(global.selector.as_deref(), Some("tier=frontend")); + assert!(!all); + } + _ => panic!("Expected Kubectl Services command"), + } + } + + #[test] + fn test_kubectl_global_args_to_args_empty() { + let global = KubectlGlobalArgs { + context: None, + kubeconfig: None, + namespace: None, + selector: None, + impersonate: None, + }; + assert!(global.to_args().is_empty()); + } }