Skip to content

Commit 31f3da5

Browse files
Read $KUBECONFIG env var in kubectl generator (#233)
Co-authored-by: Oz <oz-agent@warp.dev>
1 parent 10729ed commit 31f3da5

1 file changed

Lines changed: 107 additions & 2 deletions

File tree

command-signatures/src/generators/kubectl.rs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,29 @@ fn space_or_equals_delimited_option_value<'a>(
4646
})
4747
}
4848

49+
/// Returns the value of a given `key` from a list of environment variables formatted as
50+
/// `KEY=VALUE`.
51+
fn env_var_value<'a>(env_vars: &'a [String], key: &str) -> Option<&'a str> {
52+
let prefix = format!("{key}=");
53+
env_vars.iter().find_map(|env| env.strip_prefix(&prefix))
54+
}
55+
4956
/// Returns a command string to run the given `subcommand` string with the same `--namespace` and/or
5057
/// `--kubeconfig` values as specified in the incomplete command being entered (`tokens`), which
5158
/// scopes down suggestions to be more helpful based on the already-specified namespace or
52-
/// kubeconfig file.
59+
/// kubeconfig file. Also reads the `KUBECONFIG` environment variable if `--kubeconfig` is not
60+
/// explicitly specified in the tokens.
5361
fn kubectl_script(
5462
env_vars: &[String],
5563
tokens: &[&str],
5664
subcommand: CommandBuilder,
5765
) -> CommandBuilder {
5866
let kubeconfig_value = space_or_equals_delimited_option_value(tokens, "--kubeconfig")
67+
.or_else(|| env_var_value(env_vars, "KUBECONFIG"))
5968
.map(|value| format!("--kubeconfig={value} "))
60-
.unwrap_or_else(|| "".to_owned());
69+
// Fall back to the $KUBECONFIG shell variable, which is set when session environment
70+
// variables are forwarded to the child process.
71+
.unwrap_or_else(|| r#"${KUBECONFIG:+--kubeconfig="$KUBECONFIG"} "#.to_owned());
6172
let namespace_value = space_or_equals_delimited_option_value(tokens, "--namespace")
6273
.or(space_or_equals_delimited_option_value(tokens, "-n"))
6374
.map(|value| format!("--namespace={value} "))
@@ -254,3 +265,97 @@ pub fn generator() -> CommandSignatureGenerators {
254265
KUBECTL_BUILTIN_COMPLETION.clone(),
255266
)
256267
}
268+
269+
#[cfg(test)]
270+
mod tests {
271+
use super::*;
272+
use warp_completion_metadata::Shell;
273+
274+
#[test]
275+
fn test_kubeconfig_from_flag_in_tokens() {
276+
let env_vars = vec![];
277+
let tokens = vec![
278+
"kubectl",
279+
"--kubeconfig",
280+
"/path/to/config",
281+
"config",
282+
"use-context",
283+
];
284+
let cmd = kubectl_script(
285+
&env_vars,
286+
&tokens,
287+
CommandBuilder::single_command("config get-contexts -o name"),
288+
);
289+
let built = cmd.build(Shell::Posix);
290+
assert!(
291+
built.contains("--kubeconfig=/path/to/config"),
292+
"Expected --kubeconfig flag from tokens, got: {built}"
293+
);
294+
}
295+
296+
#[test]
297+
fn test_kubeconfig_from_env_vars() {
298+
let env_vars = vec!["KUBECONFIG=/tmp/kube-test/config".to_string()];
299+
let tokens = vec!["kubectl", "config", "use-context"];
300+
let cmd = kubectl_script(
301+
&env_vars,
302+
&tokens,
303+
CommandBuilder::single_command("config get-contexts -o name"),
304+
);
305+
let built = cmd.build(Shell::Posix);
306+
assert!(
307+
built.contains("--kubeconfig=/tmp/kube-test/config"),
308+
"Expected --kubeconfig from KUBECONFIG env var, got: {built}"
309+
);
310+
}
311+
312+
#[test]
313+
fn test_kubeconfig_flag_takes_precedence_over_env_var() {
314+
let env_vars = vec!["KUBECONFIG=/env/path/config".to_string()];
315+
let tokens = vec![
316+
"kubectl",
317+
"--kubeconfig",
318+
"/flag/path/config",
319+
"config",
320+
"use-context",
321+
];
322+
let cmd = kubectl_script(
323+
&env_vars,
324+
&tokens,
325+
CommandBuilder::single_command("config get-contexts -o name"),
326+
);
327+
let built = cmd.build(Shell::Posix);
328+
assert!(
329+
built.contains("--kubeconfig=/flag/path/config"),
330+
"Expected --kubeconfig from flag (not env var), got: {built}"
331+
);
332+
assert!(
333+
!built.contains("--kubeconfig=/env/path/config"),
334+
"Should not contain env var value when flag is present, got: {built}"
335+
);
336+
}
337+
338+
#[test]
339+
fn test_kubeconfig_fallback_to_shell_variable() {
340+
let env_vars: Vec<String> = vec![];
341+
let tokens = vec!["kubectl", "config", "use-context"];
342+
let cmd = kubectl_script(
343+
&env_vars,
344+
&tokens,
345+
CommandBuilder::single_command("config get-contexts -o name"),
346+
);
347+
let built = cmd.build(Shell::Posix);
348+
assert!(
349+
built.contains("${KUBECONFIG:+--kubeconfig="),
350+
"Expected $KUBECONFIG shell variable fallback, got: {built}"
351+
);
352+
}
353+
354+
#[test]
355+
fn test_env_var_value_finds_key() {
356+
let env_vars = vec!["FOO=bar".to_string(), "KUBECONFIG=/my/config".to_string()];
357+
assert_eq!(env_var_value(&env_vars, "KUBECONFIG"), Some("/my/config"));
358+
assert_eq!(env_var_value(&env_vars, "FOO"), Some("bar"));
359+
assert_eq!(env_var_value(&env_vars, "MISSING"), None);
360+
}
361+
}

0 commit comments

Comments
 (0)