From cd6fc9cea0a410a588d09971cca07b2d815353ea Mon Sep 17 00:00:00 2001 From: David Herberth Date: Tue, 3 Aug 2021 23:56:28 +0200 Subject: [PATCH] feat(kubernetes): implements regex matching for context aliases (#2883) --- docs/config/README.md | 27 ++++++++++ src/modules/kubernetes.rs | 103 ++++++++++++++++++++++++++++++++------ 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index a9962b7d..e4fe6b1d 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -1723,6 +1723,33 @@ format = 'on [⛵ $context \($namespace\)](dimmed green) ' disabled = false [kubernetes.context_aliases] "dev.local.cluster.k8s" = "dev" +".*/openshift-cluster/.*" = "openshift" +"gke_.*_(?P[\\w-]+)" = "gke-$cluster" +``` + +#### Regex Matching + +Additional to simple aliasing, `context_aliases` also supports +extended matching and renaming using regular expressions. + +The regular expression must match on the entire kube context, +capture groups can be referenced using `$name` and `$N` in the replacement. +This is more explained in the [regex crate](https://docs.rs/regex/1.5.4/regex/struct.Regex.html#method.replace) documentation. + +Long and automatically generated cluster names can be identified +and shortened using regular expressions: + +```toml +[kubernetes.context_aliases] +# OpenShift contexts carry the namespace and user in the kube context: `namespace/name/user`: +".*/openshift-cluster/.*" = "openshift" +# Or better, to rename every OpenShift cluster at once: +".*/(?P[\\w-]+)/.*" = "$cluster" + +# Contexts from GKE, AWS and other cloud providers usually carry additional information, like the region/zone. +# The following entry matches on the GKE format (`gke_projectname_zone_cluster-name`) +# and renames every matching kube context into a more readable format (`gke-cluster-name`): +"gke_.*_(?P[\\w-]+)" = "gke-$cluster" ``` ## Line Break diff --git a/src/modules/kubernetes.rs b/src/modules/kubernetes.rs index a71cc1e1..13835065 100644 --- a/src/modules/kubernetes.rs +++ b/src/modules/kubernetes.rs @@ -1,5 +1,6 @@ use yaml_rust::YamlLoader; +use std::borrow::Cow; use std::env; use std::path; @@ -49,6 +50,24 @@ fn get_kube_ns(filename: path::PathBuf, current_ctx: String) -> Option { Some(ns.to_owned()) } +fn get_kube_context_name<'a>(config: &'a KubernetesConfig, kube_ctx: &'a str) -> Cow<'a, str> { + if let Some(val) = config.context_aliases.get(kube_ctx) { + return Cow::Borrowed(val); + } + + config + .context_aliases + .iter() + .find_map(|(k, v)| { + let re = regex::Regex::new(&format!("^{}$", k)).ok()?; + match re.replace(kube_ctx, *v) { + Cow::Owned(replaced) => Some(Cow::Owned(replaced)), + _ => None, + } + }) + .unwrap_or(Cow::Borrowed(kube_ctx)) +} + pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("kubernetes"); let config: KubernetesConfig = KubernetesConfig::try_load(module.config); @@ -81,11 +100,8 @@ pub fn module<'a>(context: &'a Context) -> Option> { _ => None, }) .map(|variable| match variable { - "context" => match config.context_aliases.get(&kube_ctx) { - None => Some(Ok(kube_ctx.as_str())), - Some(&alias) => Some(Ok(alias)), - }, - "namespace" => kube_ns.as_ref().map(|s| Ok(s.as_str())), + "context" => Some(Ok(get_kube_context_name(&config, &kube_ctx))), + "namespace" => kube_ns.as_ref().map(|s| Ok(Cow::Borrowed(s.as_str()))), _ => None, }) .parse(None) @@ -144,41 +160,96 @@ users: [] dir.close() } - #[test] - fn test_ctx_alias() -> io::Result<()> { + fn base_test_ctx_alias(ctx_name: &str, config: toml::Value, expected: &str) -> io::Result<()> { let dir = tempfile::tempdir()?; let filename = dir.path().join("config"); let mut file = File::create(&filename)?; file.write_all( - b" + format!( + " apiVersion: v1 clusters: [] contexts: [] -current-context: test_context +current-context: {} kind: Config -preferences: {} +preferences: {{}} users: [] ", + ctx_name + ) + .as_bytes(), )?; file.sync_all()?; let actual = ModuleRenderer::new("kubernetes") .path(dir.path()) .env("KUBECONFIG", filename.to_string_lossy().as_ref()) - .config(toml::toml! { + .config(config) + .collect(); + + let expected = Some(format!("{} in ", Color::Cyan.bold().paint(expected))); + assert_eq!(expected, actual); + + dir.close() + } + + #[test] + fn test_ctx_alias_simple() -> io::Result<()> { + base_test_ctx_alias( + "test_context", + toml::toml! { [kubernetes] disabled = false [kubernetes.context_aliases] "test_context" = "test_alias" - }) - .collect(); + ".*" = "literal match has precedence" + }, + "☸ test_alias", + ) + } - let expected = Some(format!("{} in ", Color::Cyan.bold().paint("☸ test_alias"))); - assert_eq!(expected, actual); + #[test] + fn test_ctx_alias_regex() -> io::Result<()> { + base_test_ctx_alias( + "namespace/openshift-cluster/user", + toml::toml! { + [kubernetes] + disabled = false + [kubernetes.context_aliases] + ".*/openshift-cluster/.*" = "test_alias" + }, + "☸ test_alias", + ) + } - dir.close() + #[test] + fn test_ctx_alias_regex_replace() -> io::Result<()> { + base_test_ctx_alias( + "gke_infra-cluster-28cccff6_europe-west4_cluster-1", + toml::toml! { + [kubernetes] + disabled = false + [kubernetes.context_aliases] + "gke_.*_(?P[\\w-]+)" = "example: $cluster" + }, + "☸ example: cluster-1", + ) + } + + #[test] + fn test_ctx_alias_broken_regex() -> io::Result<()> { + base_test_ctx_alias( + "input", + toml::toml! { + [kubernetes] + disabled = false + [kubernetes.context_aliases] + "input[.*" = "this does not match" + }, + "☸ input", + ) } #[test]