Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -14,6 +16,14 @@ pub struct Args {
#[arg(short, long)]
pub namespace: Option<String>,

/// Path to the kubeconfig file to use
#[arg(long)]
pub kubeconfig: Option<PathBuf>,

/// Name of the kubeconfig context to use
#[arg(long)]
pub context: Option<String>,

/// Output only required fields (skip optional fields with defaults)
#[arg(long)]
pub minimal: bool,
Expand All @@ -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"));
}
}
23 changes: 22 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<kube::Client> {
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<()> {
Expand All @@ -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?;
Expand Down