summaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
authorJan Katins <jasc@gmx.net>2023-09-02 09:19:33 +0200
committerGitHub <noreply@github.com>2023-09-02 09:19:33 +0200
commit6b444e05c688f9b871d0fe4624cd5559eba1f95c (patch)
tree4f779a10841bac2e973e0e76a26971b0a3083976 /src/modules
parente867cda1eb90ba452768bd2e0738afc2fd0db613 (diff)
feat(kubernetes): Add styling based on current context (#4550)
* feat(kubernetes): Add styling based on current context Add an ability to customize the configuration of the kubernetes module style, based on the current context. A new variable is added to the config section, called environments, which is a list of possible customizations. Each such customization is an object with a context_pattern regex, which matches context name, and an optional style and icon that will override the global configuration, if the currently used context matched the context_pattern. Based on multiple attempts to add per-context styling and symbols to the kubernetes module. - https://github.com/starship/starship/pull/1568 by @lht https://github.com/lht -> base - https://github.com/starship/starship/pull/614 by @nomaed https://github.com/nomaed -> naming, symbol, some tests Rebased and combined by @jankatins Contains the following squasched commits - Rename to contexts and move aliases into contexts - Move deprecated functions to a submodule - Cleanup: ignore None-valued KubeCtxComponents - Add regex func + clean up matching-context search - Placate paper clip Closes: https://github.com/starship/starship/issues/570 Co-authored-by: =?UTF-8?q?Boris=20Aranovic=CC=8C?= <nomaed@gmail.com> Co-authored-by: Jan Katins <jasc@gmx.net> Co-authored-by: Kevin Song <chips@ksong.dev> * refactor(kubernetes): Remove options and use clearer names * test(kubernetes): Handle duplicated contexts right * refactor(kubernetes): Cleaner user matching * fix(kubernetes): Only show warning in case of problems * feat(kubernetes): Add back alias replacements * refactor(kubernetes): Cleanup rust usage --------- Co-authored-by: Haitao Li <lihaitao@gmail.com> Co-authored-by: =?UTF-8?q?Boris=20Aranovic=CC=8C?= <nomaed@gmail.com> Co-authored-by: Kevin Song <chips@ksong.dev> Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/kubernetes.rs723
1 files changed, 618 insertions, 105 deletions
diff --git a/src/modules/kubernetes.rs b/src/modules/kubernetes.rs
index 28e125c55..c56514ba8 100644
--- a/src/modules/kubernetes.rs
+++ b/src/modules/kubernetes.rs
@@ -1,7 +1,6 @@
use yaml_rust::YamlLoader;
use std::borrow::Cow;
-use std::collections::HashMap;
use std::env;
use std::path;
@@ -11,99 +10,91 @@ use crate::configs::kubernetes::KubernetesConfig;
use crate::formatter::StringFormatter;
use crate::utils;
+#[derive(Default)]
struct KubeCtxComponents {
user: Option<String>,
namespace: Option<String>,
cluster: Option<String>,
}
-fn get_kube_context(filename: path::PathBuf) -> Option<String> {
+fn get_current_kube_context_name(filename: path::PathBuf) -> Option<String> {
let contents = utils::read_file(filename).ok()?;
let yaml_docs = YamlLoader::load_from_str(&contents).ok()?;
- if yaml_docs.is_empty() {
- return None;
- }
- let conf = &yaml_docs[0];
-
- let current_ctx = conf["current-context"].as_str()?;
-
- if current_ctx.is_empty() {
- return None;
- }
- Some(current_ctx.to_string())
+ let conf = yaml_docs.get(0)?;
+ conf["current-context"]
+ .as_str()
+ .filter(|s| !s.is_empty())
+ .map(String::from)
}
-fn get_kube_ctx_component(filename: path::PathBuf, current_ctx: &str) -> Option<KubeCtxComponents> {
+fn get_kube_ctx_components(
+ filename: path::PathBuf,
+ current_ctx_name: &str,
+) -> Option<KubeCtxComponents> {
let contents = utils::read_file(filename).ok()?;
let yaml_docs = YamlLoader::load_from_str(&contents).ok()?;
- if yaml_docs.is_empty() {
- return None;
- }
- let conf = &yaml_docs[0];
+ let conf = yaml_docs.get(0)?;
+ let contexts = conf["contexts"].as_vec()?;
- let ctx_yaml = conf["contexts"].as_vec().and_then(|contexts| {
- contexts
- .iter()
- .filter_map(|ctx| Some((ctx, ctx["name"].as_str()?)))
- .find(|(_, name)| *name == current_ctx)
- });
+ // Find the context with the name we're looking for
+ // or return None if we can't find it
+ let (ctx_yaml, _) = contexts
+ .iter()
+ .filter_map(|ctx| Some((ctx, ctx["name"].as_str()?)))
+ .find(|(_, name)| name == &current_ctx_name)?;
let ctx_components = KubeCtxComponents {
- user: ctx_yaml
- .and_then(|(ctx, _)| ctx["context"]["user"].as_str())
- .and_then(|s| {
- if s.is_empty() {
- return None;
- }
- Some(s.to_owned())
- }),
- namespace: ctx_yaml
- .and_then(|(ctx, _)| ctx["context"]["namespace"].as_str())
- .and_then(|s| {
- if s.is_empty() {
- return None;
- }
- Some(s.to_owned())
- }),
- cluster: ctx_yaml
- .and_then(|(ctx, _)| ctx["context"]["cluster"].as_str())
- .and_then(|s| {
- if s.is_empty() {
- return None;
- }
- Some(s.to_owned())
- }),
+ user: ctx_yaml["context"]["user"]
+ .as_str()
+ .filter(|s| !s.is_empty())
+ .map(String::from),
+ namespace: ctx_yaml["context"]["namespace"]
+ .as_str()
+ .filter(|s| !s.is_empty())
+ .map(String::from),
+ cluster: ctx_yaml["context"]["cluster"]
+ .as_str()
+ .filter(|s| !s.is_empty())
+ .map(String::from),
};
Some(ctx_components)
}
-fn get_kube_user<'a>(config: &'a KubernetesConfig, kube_user: &'a str) -> Cow<'a, str> {
- return get_alias(&config.user_aliases, kube_user).unwrap_or(Cow::Borrowed(kube_user));
-}
-
-fn get_kube_context_name<'a>(config: &'a KubernetesConfig, kube_ctx: &'a str) -> Cow<'a, str> {
- return get_alias(&config.context_aliases, kube_ctx).unwrap_or(Cow::Borrowed(kube_ctx));
-}
-
-fn get_alias<'a>(
- aliases: &'a HashMap<String, &'a str>,
- alias_candidate: &'a str,
-) -> Option<Cow<'a, str>> {
- if let Some(val) = aliases.get(alias_candidate) {
- return Some(Cow::Borrowed(val));
+fn get_aliased_name<'a>(
+ pattern: Option<&'a str>,
+ current_value: Option<&str>,
+ alias: Option<&'a str>,
+) -> Option<String> {
+ let replacement = alias.or(current_value)?.to_string();
+ let Some(pattern) = pattern else {
+ // If user pattern not set, treat it as a match-all pattern
+ return Some(replacement);
+ };
+ // If a pattern is set, but we have no value, there is no match
+ let value = current_value?;
+ if value == pattern {
+ return Some(replacement);
}
-
- return aliases.iter().find_map(|(k, v)| {
- let re = regex::Regex::new(&format!("^{k}$")).ok()?;
- let replaced = re.replace(alias_candidate, *v);
- match replaced {
- Cow::Owned(replaced) => Some(Cow::Owned(replaced)),
- _ => None,
+ let re = match regex::Regex::new(&format!("^{pattern}$")) {
+ Ok(re) => re,
+ Err(error) => {
+ log::warn!(
+ "Could not compile regular expression `{}`:\n{}",
+ &format!("^{pattern}$"),
+ error
+ );
+ return None;
}
- });
+ };
+ let replaced = re.replace(value, replacement.as_str());
+ match replaced {
+ Cow::Owned(replaced) => Some(replaced),
+ // It didn't match...
+ _ => None,
+ }
}
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
@@ -118,18 +109,28 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// If we have some config for doing the directory scan then we use it but if we don't then we
// assume we should treat it like the module is enabled to preserve backward compatibility.
- let have_scan_config = !(config.detect_files.is_empty()
- && config.detect_folders.is_empty()
- && config.detect_extensions.is_empty());
-
- let is_kube_project = context
- .try_begin_scan()?
- .set_files(&config.detect_files)
- .set_folders(&config.detect_folders)
- .set_extensions(&config.detect_extensions)
- .is_match();
-
- if have_scan_config && !is_kube_project {
+ let have_scan_config = [
+ &config.detect_files,
+ &config.detect_folders,
+ &config.detect_extensions,
+ ]
+ .into_iter()
+ .any(|v| !v.is_empty());
+
+ let is_kube_project = have_scan_config.then(|| {
+ context
+ .try_begin_scan()
+ .map(|scanner| {
+ scanner
+ .set_files(&config.detect_files)
+ .set_folders(&config.detect_folders)
+ .set_extensions(&config.detect_extensions)
+ .is_match()
+ })
+ .unwrap_or(false)
+ });
+
+ if !is_kube_project.unwrap_or(true) {
return None;
}
@@ -139,39 +140,89 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.get_env("KUBECONFIG")
.unwrap_or(default_config_file.to_str()?.to_string());
- let kube_ctx = env::split_paths(&kube_cfg).find_map(get_kube_context)?;
-
- let ctx_components: Vec<KubeCtxComponents> = env::split_paths(&kube_cfg)
- .filter_map(|filename| get_kube_ctx_component(filename, &kube_ctx))
- .collect();
+ let current_kube_ctx_name =
+ env::split_paths(&kube_cfg).find_map(get_current_kube_context_name)?;
+
+ // Even if we have multiple config files, the first key wins
+ // https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
+ // > Never change the value or map key. ... Example: If two files specify a red-user,
+ // > use only values from the first file's red-user. Even if the second file has
+ // > non-conflicting entries under red-user, discard them.
+ // for that reason, we can pick the first context with that name
+ let ctx_components: KubeCtxComponents = env::split_paths(&kube_cfg)
+ .find_map(|filename| get_kube_ctx_components(filename, &current_kube_ctx_name))
+ .unwrap_or_else(|| {
+ // TODO: figure out if returning is more sensible. But currently we have tests depending on this
+ log::warn!(
+ "Invalid KUBECONFIG: identified current-context `{}`, but couldn't find the context in any config file(s): `{}`.\n",
+ &current_kube_ctx_name,
+ &kube_cfg
+ );
+ KubeCtxComponents::default()
+ });
+
+ // Select the first style that matches the context_pattern and,
+ // if it is defined, the user_pattern
+ let (matched_context_config, display_context, display_user) = config
+ .contexts
+ .iter()
+ .find_map(|context_config| {
+ let context_alias = get_aliased_name(
+ Some(context_config.context_pattern),
+ Some(&current_kube_ctx_name),
+ context_config.context_alias,
+ )?;
+
+ let user_alias = get_aliased_name(
+ context_config.user_pattern,
+ ctx_components.user.as_deref(),
+ context_config.user_alias,
+ );
+ if matches!((context_config.user_pattern, &user_alias), (Some(_), None)) {
+ // defined pattern, but it didn't match
+ return None;
+ }
+
+ Some((Some(context_config), context_alias, user_alias))
+ })
+ .unwrap_or_else(|| (None, current_kube_ctx_name.clone(), ctx_components.user));
+
+ // TODO: remove deprecated aliases after starship 2.0
+ let display_context =
+ deprecated::get_alias(display_context, &config.context_aliases, "context").unwrap();
+ let display_user =
+ display_user.and_then(|user| deprecated::get_alias(user, &config.user_aliases, "user"));
+
+ let display_style = matched_context_config
+ .and_then(|ctx_cfg| ctx_cfg.style)
+ .unwrap_or(config.style);
+ let display_symbol = matched_context_config
+ .and_then(|ctx_cfg| ctx_cfg.symbol)
+ .unwrap_or(config.symbol);
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
- "symbol" => Some(config.symbol),
+ "symbol" => Some(display_symbol),
_ => None,
})
.map_style(|variable| match variable {
- "style" => Some(Ok(config.style)),
+ "style" => Some(Ok(display_style)),
_ => None,
})
.map(|variable| match variable {
- "context" => Some(Ok(get_kube_context_name(&config, &kube_ctx))),
-
+ "context" => Some(Ok(Cow::Borrowed(display_context.as_str()))),
"namespace" => ctx_components
- .iter()
- .find_map(|kube| kube.namespace.as_deref())
- .map(|namespace| Ok(Cow::Borrowed(namespace))),
-
- "user" => ctx_components
- .iter()
- .find_map(|kube| kube.user.as_deref())
- .map(|user| Ok(get_kube_user(&config, user))),
-
+ .namespace
+ .as_ref()
+ .map(|kube_ns| Ok(Cow::Borrowed(kube_ns.as_str()))),
"cluster" => ctx_components
- .iter()
- .find_map(|kube| kube.cluster.as_deref())
- .map(|cluster| Ok(Cow::Borrowed(cluster))),
+ .cluster
+ .as_ref()
+ .map(|kube_cluster| Ok(Cow::Borrowed(kube_cluster.as_str()))),
+ "user" => display_user
+ .as_ref()
+ .map(|kube_user| Ok(Cow::Borrowed(kube_user.as_str()))),
_ => None,
})
.parse(None, Some(context))
@@ -188,6 +239,47 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(module)
}
+mod deprecated {
+ use std::borrow::Cow;
+ use std::collections::HashMap;
+
+ pub fn get_alias<'a>(
+ current_value: String,
+ aliases: &'a HashMap<String, &'a str>,
+ name: &'a str,
+ ) -> Option<String> {
+ let alias = if let Some(val) = aliases.get(current_value.as_str()) {
+ // simple match without regex
+ Some((*val).to_string())
+ } else {
+ // regex match
+ aliases.iter().find_map(|(k, v)| {
+ let re = regex::Regex::new(&format!("^{k}$")).ok()?;
+ let replaced = re.replace(current_value.as_str(), *v);
+ match replaced {
+ // We have a match if the replaced string is different from the original
+ Cow::Owned(replaced) => Some(replaced),
+ _ => None,
+ }
+ })
+ };
+
+ match alias {
+ Some(alias) => {
+ log::warn!(
+ "Usage of '{}_aliases' is deprecated and will be removed in 2.0; Use 'contexts' with '{}_alias' instead. (`{}` -> `{}`)",
+ &name,
+ &name,
+ &current_value,
+ &alias
+ );
+ Some(alias)
+ }
+ None => Some(current_value),
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::test::ModuleRenderer;
@@ -430,6 +522,21 @@ users: []
}
#[test]
+ fn test_config_context_ctx_alias_regex_replace() -> io::Result<()> {
+ base_test_ctx_alias(
+ "gke_infra-cluster-28cccff6_europe-west4_cluster-1",
+ toml::toml! {
+ [kubernetes]
+ disabled = false
+ [[kubernetes.contexts]]
+ context_pattern = "gke_.*_(?P<cluster>[\\w-]+)"
+ context_alias = "example: $cluster"
+ },
+ "☸ example: cluster-1",
+ )
+ }
+
+ #[test]
fn test_ctx_alias_broken_regex() -> io::Result<()> {
base_test_ctx_alias(
"input",
@@ -577,7 +684,9 @@ users: []
}
#[test]
- fn test_multiple_config_files_with_ns() -> io::Result<()> {
+ fn test_multiple_config_files_with_context_defined_once() -> io::Result<()> {
+ // test that we get the current context from the first config file in the KUBECONFIG,
+ // no matter if it is only defined in the latter
let dir = tempfile::tempdir()?;
let filename_cc = dir.path().join("config_cc");
@@ -630,7 +739,7 @@ users: []
})
.collect();
- // And tes with context and namespace first
+ // And test with context and namespace first
let actual_ctx_first = ModuleRenderer::new("kubernetes")
.path(dir.path())
.env(
@@ -655,6 +764,87 @@ users: []
dir.close()
}
+ #[test]
+ fn test_multiple_config_files_with_context_defined_twice() -> io::Result<()> {
+ // tests that, if two files contain the same context,
+ // only the context config from the first is used.
+ let dir = tempfile::tempdir()?;
+
+ let config1 = dir.path().join("config1");
+
+ let mut file1 = File::create(&config1)?;
+ file1.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ cluster: test_cluster1
+ namespace: test_namespace1
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file1.sync_all()?;
+
+ let config2 = dir.path().join("config2");
+
+ let mut file2 = File::create(&config2)?;
+ file2.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ cluster: test_cluster2
+ user: test_user2
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file2.sync_all()?;
+
+ let paths1 = [config1.clone(), config2.clone()];
+ let kubeconfig_content1 = env::join_paths(paths1.iter()).unwrap();
+
+ let actual1 = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", kubeconfig_content1.to_string_lossy())
+ .config(toml::toml! {
+ [kubernetes]
+ format = "($user )($cluster )($namespace )"
+ disabled = false
+ })
+ .collect();
+
+ let expected1 = Some("test_cluster1 test_namespace1 ".to_string());
+ assert_eq!(expected1, actual1);
+
+ let paths2 = [config2, config1];
+ let kubeconfig_content2 = env::join_paths(paths2.iter()).unwrap();
+
+ let actual2 = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", kubeconfig_content2.to_string_lossy())
+ .config(toml::toml! {
+ [kubernetes]
+ format = "($user )($cluster )($namespace )"
+ disabled = false
+ })
+ .collect();
+
+ let expected2 = Some("test_user2 test_cluster2 ".to_string());
+ assert_eq!(expected2, actual2);
+
+ dir.close()
+ }
+
fn base_test_user_alias(
user_name: &str,
config: toml::Table,
@@ -745,6 +935,23 @@ users: []
}
#[test]
+ fn test_config_context_user_alias_regex_replace() -> io::Result<()> {
+ base_test_user_alias(
+ "gke_infra-user-28cccff6_europe-west4_cluster-1",
+ toml::toml! {
+ [kubernetes]
+ disabled = false
+ format = "[$symbol$context( \\($user\\))]($style) in "
+ [[kubernetes.contexts]]
+ context_pattern = ".*"
+ user_pattern = "gke_.*_(?P<cluster>[\\w-]+)"
+ user_alias = "example: $cluster"
+ },
+ "☸ test_context (example: cluster-1)",
+ )
+ }
+
+ #[test]
fn test_user_alias_broken_regex() -> io::Result<()> {
base_test_user_alias(
"input",
@@ -932,4 +1139,310 @@ users: []
assert_eq!(expected, actual);
dir.close()
}
+
+ #[test]
+ fn test_config_context_overwrites_defaults() -> std::io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ namespace: test_namespace
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ style = "bold red"
+
+ [[kubernetes.contexts]]
+ context_pattern = "test.*"
+ style = "bold green"
+ symbol = "§ "
+ })
+ .collect();
+
+ let expected = Some(format!(
+ "{} in ",
+ Color::Green.bold().paint("§ test_context (test_namespace)")
+ ));
+ assert_eq!(expected, actual);
+ dir.close()
+ }
+
+ #[test]
+ fn test_config_context_both_pattern_must_match() -> io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ format = "$symbol$context ($user )"
+
+ [[kubernetes.contexts]]
+ context_pattern = "test.*"
+ user_pattern = "test.*"
+ context_alias = "yy"
+ user_alias = "xx"
+ symbol = "§ "
+ })
+ .collect();
+
+ let expected = Some("§ yy xx ".to_string());
+ assert_eq!(expected, actual);
+ dir.close()
+ }
+
+ #[test]
+ fn test_config_context_only_one_pattern_matches() -> io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ format = "$symbol$context ($user )"
+
+ [[kubernetes.contexts]]
+ context_pattern = "test.*"
+ user_pattern = "test_BAD.*"
+ context_alias = "yy"
+ user_alias = "xx"
+ symbol = "§ "
+ })
+ .collect();
+
+ let expected = Some("☸ test_context test_user ".to_string());
+ assert_eq!(expected, actual);
+ dir.close()
+ }
+
+ #[test]
+ fn test_config_context_uses_aliases() -> std::io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ namespace: test_namespace
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ style = "bold red"
+ format = "$symbol($user )($context )($cluster )($namespace)"
+
+ [[kubernetes.contexts]]
+ context_pattern = "test.*"
+ context_alias = "xyz"
+ user_alias = "abc"
+ symbol = "§ "
+ })
+ .collect();
+
+ let expected = Some("§ abc xyz test_namespace".to_string());
+ assert_eq!(expected, actual);
+ dir.close()
+ }
+
+ #[test]
+ fn test_config_context_user_pattern_does_not_match() -> std::io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ namespace: test_namespace
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ style = "bold red"
+ format = "$symbol($user )($context )($cluster )($namespace)"
+
+ [[kubernetes.contexts]]
+ context_pattern = "test"
+ user_pattern = "not_matching"
+ context_alias = "xyz"
+ user_alias = "abc"
+ symbol = "§ "
+ })
+ .collect();
+
+ let expected = Some("☸ test_user test_context test_namespace".to_string());
+ assert_eq!(expected, actual);
+ dir.close()
+ }
+
+ #[test]
+ fn test_config_contexts_does_not_match() -> std::io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ namespace: test_namespace
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ style = "bold red"
+ contexts = [
+ {context_pattern = "tests_.*", style = "bold green", symbol = "§ "},
+ ]
+ })
+ .collect();
+
+ let expected = Some(format!(
+ "{} in ",
+ Color::Red.bold().paint("☸ test_context (test_namespace)")
+ ));
+ assert_eq!(expected, actual);
+ dir.close()
+ }
+
+ #[test]
+ fn test_config_context_bad_regex_should_not_panic() -> std::io::Result<()> {
+ let dir = tempfile::tempdir()?;
+ let filename = dir.path().join("config");
+ let mut file = File::create(&filename)?;
+ file.write_all(
+ b"
+apiVersion: v1
+clusters: []
+contexts:
+ - context:
+ user: test_user
+ namespace: test_namespace
+ name: test_context
+current-context: test_context
+kind: Config
+preferences: {}
+users: []
+",
+ )?;
+ file.sync_all()?;
+
+ let actual = ModuleRenderer::new("kubernetes")
+ .path(dir.path())
+ .env("KUBECONFIG", filename.to_string_lossy().as_ref())
+ .config(toml::toml! {
+ [kubernetes]
+ disabled = false
+ style = "bold red"
+ contexts = [
+ {context_pattern = "tests_(.*", style = "bold green", symbol = "§ "},
+ ]
+ })
+ .collect();
+
+ let expected = Some(format!(
+ "{} in ",
+ Color::Red.bold().paint("☸ test_context (test_namespace)")
+ ));
+ assert_eq!(expected, actual);
+ dir.close()
+ }
}