Initial commit
This commit is contained in:
179
src/main.rs
Normal file
179
src/main.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use clap::Parser;
|
||||
use linefeed::{Completer, Completion, Interface, ReadResult, Terminal};
|
||||
use nix::sys::signal;
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
os::unix::process::CommandExt,
|
||||
process::Command,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Interact with a kubernetes cluster
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(bin_name="kube")]
|
||||
struct Args {
|
||||
env: String,
|
||||
|
||||
/// Run k9s (extra interactive)
|
||||
#[arg(short, long)]
|
||||
interactive: bool,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
if args.interactive {
|
||||
run_interactive(args.env)?;
|
||||
} else {
|
||||
run_non_interactive(args.env)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_interactive(context: String) -> anyhow::Result<()> {
|
||||
cmd_lib::spawn!(k9s --context $context --readonly)?.wait()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_non_interactive(context: String) -> anyhow::Result<()> {
|
||||
let mut context = Context::new(context);
|
||||
context.get_pods()?;
|
||||
|
||||
let term = Interface::new("kube")?;
|
||||
term.set_prompt(&format!("{}> ", context.name()))?;
|
||||
|
||||
term.set_completer(Arc::new(K8Completer::new(context.pod_names())));
|
||||
|
||||
while let ReadResult::Input(line) = term.read_line()? {
|
||||
let res = match line.trim() {
|
||||
"" => continue,
|
||||
"exit" => break,
|
||||
"get pods" => context.get_pods(),
|
||||
other => context.run_line(other),
|
||||
};
|
||||
if res.is_err() {
|
||||
eprintln!("Command failed");
|
||||
}
|
||||
term.add_history(line);
|
||||
term.set_completer(Arc::new(K8Completer::new(context.pod_names())));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Context {
|
||||
name: String,
|
||||
pod_names: Arc<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new(name: String) -> Self {
|
||||
Context {
|
||||
name,
|
||||
pod_names: Arc::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn run_line(&self, line: &str) -> anyhow::Result<()> {
|
||||
let args = shell_words::split(line)?;
|
||||
|
||||
let old_sig =
|
||||
unsafe { signal::signal(signal::Signal::SIGINT, signal::SigHandler::SigIgn).unwrap() };
|
||||
|
||||
let mut cmd = Command::new("kubectl");
|
||||
cmd.arg("--context");
|
||||
cmd.arg(&self.name);
|
||||
cmd.args(args);
|
||||
unsafe {
|
||||
cmd.pre_exec(|| {
|
||||
signal::signal(signal::Signal::SIGINT, signal::SigHandler::SigDfl).unwrap();
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
let status = cmd.status()?;
|
||||
|
||||
unsafe {
|
||||
signal::signal(signal::Signal::SIGINT, old_sig).unwrap();
|
||||
}
|
||||
|
||||
anyhow::ensure!(status.success(), "Command exited with error");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pods(&mut self) -> anyhow::Result<()> {
|
||||
let old_sig =
|
||||
unsafe { signal::signal(signal::Signal::SIGINT, signal::SigHandler::SigIgn).unwrap() };
|
||||
|
||||
let mut cmd = Command::new("kubectl");
|
||||
cmd.args(["--context", &self.name, "get", "pods"]);
|
||||
unsafe {
|
||||
cmd.pre_exec(|| {
|
||||
signal::signal(signal::Signal::SIGINT, signal::SigHandler::SigDfl).unwrap();
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
let out = cmd.output()?;
|
||||
|
||||
unsafe {
|
||||
signal::signal(signal::Signal::SIGINT, old_sig).unwrap();
|
||||
}
|
||||
|
||||
let out_str = String::from_utf8_lossy(&out.stdout);
|
||||
let err_str = String::from_utf8_lossy(&out.stderr);
|
||||
|
||||
print!("{}", out_str);
|
||||
eprint!("{}", err_str);
|
||||
|
||||
self.pod_names = out_str
|
||||
.lines()
|
||||
.filter_map(|line| line.find(' ').map(|end| line[0..end].to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
|
||||
anyhow::ensure!(out.status.success(), "Command exited with error");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pod_names(&self) -> Arc<Vec<String>> {
|
||||
self.pod_names.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct K8Completer<Term: Terminal> {
|
||||
pods: Arc<Vec<String>>,
|
||||
term: PhantomData<Term>,
|
||||
}
|
||||
|
||||
impl<Term: Terminal> K8Completer<Term> {
|
||||
fn new(pods: Arc<Vec<String>>) -> Self {
|
||||
K8Completer {
|
||||
pods,
|
||||
term: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Term: Terminal> Completer<Term> for K8Completer<Term> {
|
||||
fn complete(
|
||||
&self,
|
||||
word: &str,
|
||||
_prompter: &linefeed::Prompter<Term>,
|
||||
_start: usize,
|
||||
_end: usize,
|
||||
) -> Option<Vec<linefeed::Completion>> {
|
||||
Some(
|
||||
self.pods
|
||||
.iter()
|
||||
.filter(|pod| pod.starts_with(word))
|
||||
.map(|pod| Completion::simple(pod.clone()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user