feat: Add Kubernetes Module (#404)

Adds a Kubernetes module, which works by parsing kubeconfig.
This commit is contained in:
Thomas O'Donnell 2019-10-01 20:58:24 +02:00 committed by Kevin Song
parent 6888f3619a
commit 9fc5a43355
7 changed files with 233 additions and 0 deletions

16
Cargo.lock generated
View File

@ -402,6 +402,11 @@ dependencies = [
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "linked-hash-map"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.8"
@ -772,6 +777,7 @@ dependencies = [
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1002,6 +1008,14 @@ dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "yaml-rust"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
@ -1051,6 +1065,7 @@ dependencies = [
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum libgit2-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a30f8637eb59616ee3b8a00f6adff781ee4ddd8343a615b8238de756060cc1b3"
"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
@ -1126,3 +1141,4 @@ dependencies = [
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9"
"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"

View File

@ -85,6 +85,7 @@ The prompt shows information you need while you're working, while staying sleek
- `✘` — deleted files
- Execution time of the last command if it exceeds the set threshold
- Indicator for jobs in the background (`✦`)
- Current Kubernetes Cluster and Namespace (`☸`)
## 🚀 Installation

View File

@ -85,6 +85,7 @@ The default `prompt_order` is used to define the order in which modules are show
prompt_order = [
"username",
"hostname",
"kubernetes",
"directory",
"git_branch",
"git_state",
@ -528,6 +529,35 @@ symbol = "+ "
threshold = 4
```
## Kubernetes
Displays the current Kubernetes context name and, if set, the namespace from
the kubeconfig file. The namespace needs to be set in the kubeconfig file, this
can be done via `kubectl config set-context starship-cluster --namespace
astronaut`. If the `$KUBECONFIG` env var is set the module will use that if
not it will use the `~/.kube/config`.
### Options
| Variable | Default | Description |
| ---------- | ------------- | --------------------------------------------------- |
| `symbol` | `"☸ "` | The symbol used before displaying the Cluster info. |
| `style` | `"bold blue"` | The style for the module. |
| `disabled` | `false` | Disables the `kubernetes` module |
### Example
```toml
# ~/.config/starship.toml
[kubernetes]
symbol = "⛵ "
style = "dim green"
disabled = true
```
## Line Break
The `line_break` module separates the prompt into two lines.

View File

@ -45,6 +45,7 @@ chrono = "0.4"
sysinfo = "0.9.5"
byte-unit = "3.0.3"
starship_module_config_derive = { version = "0.20", path = "../starship_module_config_derive" }
yaml-rust = "0.4"
[dev-dependencies]
tempfile = "3.1.0"

View File

@ -20,6 +20,7 @@ pub const ALL_MODULES: &[&str] = &[
"hostname",
"java",
"jobs",
"kubernetes",
"line_break",
"memory_usage",
"nix_shell",

View File

@ -0,0 +1,182 @@
use ansi_term::Color;
use dirs;
use yaml_rust::YamlLoader;
use std::env;
use std::path;
use super::{Context, Module};
use crate::utils;
const KUBE_CHAR: &str = "";
fn get_kube_context(contents: &str) -> Option<(String, String)> {
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;
}
let ns = conf["contexts"]
.as_vec()
.and_then(|contexts| {
contexts
.iter()
.filter_map(|ctx| Some((ctx, ctx["name"].as_str()?)))
.find(|(_, name)| *name == current_ctx)
.and_then(|(ctx, _)| ctx["context"]["namespace"].as_str())
})
.unwrap_or("");
Some((current_ctx.to_string(), ns.to_string()))
}
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let filename = match env::var("KUBECONFIG") {
Ok(path) => path::PathBuf::from(path),
Err(_) => dirs::home_dir()?.join(".kube").join("config"),
};
let contents = utils::read_file(filename).ok()?;
match get_kube_context(&contents) {
Some(kube_cfg) => {
let (kube_ctx, kube_ns) = kube_cfg;
let mut module = context.new_module("kubernetes");
let module_style = module
.config_value_style("style")
.unwrap_or_else(|| Color::Cyan.bold());
module.set_style(module_style);
module.get_prefix().set_value("on ");
module.new_segment("symbol", KUBE_CHAR);
module.new_segment("context", &kube_ctx);
if kube_ns != "" {
module.new_segment("namespace", &format!(" ({})", kube_ns));
}
Some(module)
}
None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_empty_config() {
let input = "";
let result = get_kube_context(&input);
let expected = None;
assert_eq!(result, expected);
}
#[test]
fn parse_no_config() {
let input = r#"
apiVersion: v1
clusters: []
contexts: []
current-context: ""
kind: Config
preferences: {}
users: []
"#;
let result = get_kube_context(&input);
let expected = None;
assert_eq!(result, expected);
}
#[test]
fn parse_only_context() {
let input = r#"
apiVersion: v1
clusters: []
contexts:
- context:
cluster: test_cluster
user: test_user
name: test_context
current-context: test_context
kind: Config
preferences: {}
users: []
"#;
let result = get_kube_context(&input);
let expected = Some(("test_context".to_string(), "".to_string()));
assert_eq!(result, expected);
}
#[test]
fn parse_context_and_ns() {
let input = r#"
apiVersion: v1
clusters: []
contexts:
- context:
cluster: test_cluster
user: test_user
namespace: test_namespace
name: test_context
current-context: test_context
kind: Config
preferences: {}
users: []
"#;
let result = get_kube_context(&input);
let expected = Some(("test_context".to_string(), "test_namespace".to_string()));
assert_eq!(result, expected);
}
#[test]
fn parse_multiple_contexts() {
let input = r#"
apiVersion: v1
clusters: []
contexts:
- context:
cluster: another_cluster
user: another_user
namespace: another_namespace
name: another_context
- context:
cluster: test_cluster
user: test_user
namespace: test_namespace
name: test_context
current-context: test_context
kind: Config
preferences: {}
users: []
"#;
let result = get_kube_context(&input);
let expected = Some(("test_context".to_string(), "test_namespace".to_string()));
assert_eq!(result, expected);
}
#[test]
fn parse_broken_config() {
let input = r#"
---
dummy_string
"#;
let result = get_kube_context(&input);
let expected = None;
assert_eq!(result, expected);
}
}

View File

@ -11,6 +11,7 @@ mod golang;
mod hostname;
mod java;
mod jobs;
mod kubernetes;
mod line_break;
mod memory_usage;
mod nix_shell;
@ -44,6 +45,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
"git_branch" => git_branch::module(context),
"git_state" => git_state::module(context),
"git_status" => git_status::module(context),
"kubernetes" => kubernetes::module(context),
"username" => username::module(context),
#[cfg(feature = "battery")]
"battery" => battery::module(context),