diff --git a/README.md b/README.md index 2f39d74..4f66f4a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ kubectl ditto deployment -n my-namespace my-app --full # Suppress description comments kubectl ditto deployment -n my-namespace my-app --no-comments + +# Use a specific kubeconfig or context +kubectl ditto deployment my-app --kubeconfig ~/.kube/prod-config --context production ``` ## Install @@ -65,7 +68,7 @@ The binary `kubectl-ditto` is placed on your PATH. kubectl automatically discove ## Requirements -- A running Kubernetes cluster (uses your current kubeconfig context) +- A running Kubernetes cluster (uses your current kubeconfig context unless `--kubeconfig` or `--context` is provided) - CRDs must be installed for custom resources ## Release diff --git a/src/cli.rs b/src/cli.rs index 0868ef5..11f5276 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use clap::Parser; /// Generate YAML for any Kubernetes resource or CRD using cluster schema and smart defaults. @@ -14,6 +16,14 @@ pub struct Args { #[arg(short, long)] pub namespace: Option, + /// Path to the kubeconfig file to use + #[arg(long)] + pub kubeconfig: Option, + + /// Name of the kubeconfig context to use + #[arg(long)] + pub context: Option, + /// Output only required fields (skip optional fields with defaults) #[arg(long)] pub minimal: bool, @@ -34,3 +44,37 @@ pub struct Args { #[arg(long)] pub dump_schema: bool, } + +#[cfg(test)] +mod tests { + use super::Args; + use clap::Parser; + use std::path::PathBuf; + + #[test] + fn test_parses_kubeconfig_and_context_flags() { + let args = Args::parse_from([ + "kubectl-ditto", + "--kubeconfig", + "/tmp/kubeconfig", + "--context", + "production", + "deployment", + "my-app", + ]); + + assert_eq!(args.kubeconfig, Some(PathBuf::from("/tmp/kubeconfig"))); + assert_eq!(args.context.as_deref(), Some("production")); + assert_eq!(args.resource.as_deref(), Some("deployment")); + assert_eq!(args.name.as_deref(), Some("my-app")); + } + + #[test] + fn test_parses_context_without_custom_kubeconfig() { + let args = Args::parse_from(["kubectl-ditto", "--context", "staging", "deployment"]); + + assert_eq!(args.kubeconfig, None); + assert_eq!(args.context.as_deref(), Some("staging")); + assert_eq!(args.resource.as_deref(), Some("deployment")); + } +} diff --git a/src/main.rs b/src/main.rs index d34ab8e..3bb232b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,27 @@ mod schema; use anyhow::Result; use clap::Parser; +use kube::config::{KubeConfigOptions, Kubeconfig}; + +async fn client_for_args(args: &cli::Args) -> Result { + if args.kubeconfig.is_none() && args.context.is_none() { + return Ok(kube::Client::try_default().await?); + } + + let options = KubeConfigOptions { + context: args.context.clone(), + ..Default::default() + }; + + let config = if let Some(path) = &args.kubeconfig { + let kubeconfig = Kubeconfig::read_from(path)?; + kube::Config::from_custom_kubeconfig(kubeconfig, &options).await? + } else { + kube::Config::from_kubeconfig(&options).await? + }; + + Ok(kube::Client::try_from(config)?) +} #[tokio::main] async fn main() -> Result<()> { @@ -19,7 +40,7 @@ async fn main() -> Result<()> { } }; - let client = kube::Client::try_default().await?; + let client = client_for_args(&args).await?; // 1. Resolve the resource type (dynamic short names from API server) let resolved = discovery::resolve_resource(&client, &resource).await?;