Skip to content

Commit 9fc5a43

Browse files
andytomchipbuster
authored andcommitted
feat: Add Kubernetes Module (starship#404)
Adds a Kubernetes module, which works by parsing kubeconfig.
1 parent 6888f36 commit 9fc5a43

File tree

7 files changed

+233
-0
lines changed

7 files changed

+233
-0
lines changed

Cargo.lock

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ The prompt shows information you need while you're working, while staying sleek
8585
- `` — deleted files
8686
- Execution time of the last command if it exceeds the set threshold
8787
- Indicator for jobs in the background (``)
88+
- Current Kubernetes Cluster and Namespace (``)
8889

8990
## 🚀 Installation
9091

docs/config/README.md

+30
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ The default `prompt_order` is used to define the order in which modules are show
8585
prompt_order = [
8686
"username",
8787
"hostname",
88+
"kubernetes",
8889
"directory",
8990
"git_branch",
9091
"git_state",
@@ -528,6 +529,35 @@ symbol = "+ "
528529
threshold = 4
529530
```
530531

532+
533+
## Kubernetes
534+
535+
Displays the current Kubernetes context name and, if set, the namespace from
536+
the kubeconfig file. The namespace needs to be set in the kubeconfig file, this
537+
can be done via `kubectl config set-context starship-cluster --namespace
538+
astronaut`. If the `$KUBECONFIG` env var is set the module will use that if
539+
not it will use the `~/.kube/config`.
540+
541+
### Options
542+
543+
| Variable | Default | Description |
544+
| ---------- | ------------- | --------------------------------------------------- |
545+
| `symbol` | `"☸ "` | The symbol used before displaying the Cluster info. |
546+
| `style` | `"bold blue"` | The style for the module. |
547+
| `disabled` | `false` | Disables the `kubernetes` module |
548+
549+
### Example
550+
551+
```toml
552+
# ~/.config/starship.toml
553+
554+
[kubernetes]
555+
symbol = ""
556+
style = "dim green"
557+
disabled = true
558+
```
559+
560+
531561
## Line Break
532562

533563
The `line_break` module separates the prompt into two lines.

starship/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ chrono = "0.4"
4545
sysinfo = "0.9.5"
4646
byte-unit = "3.0.3"
4747
starship_module_config_derive = { version = "0.20", path = "../starship_module_config_derive" }
48+
yaml-rust = "0.4"
4849

4950
[dev-dependencies]
5051
tempfile = "3.1.0"

starship/src/module.rs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub const ALL_MODULES: &[&str] = &[
2020
"hostname",
2121
"java",
2222
"jobs",
23+
"kubernetes",
2324
"line_break",
2425
"memory_usage",
2526
"nix_shell",

starship/src/modules/kubernetes.rs

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use ansi_term::Color;
2+
use dirs;
3+
use yaml_rust::YamlLoader;
4+
5+
use std::env;
6+
use std::path;
7+
8+
use super::{Context, Module};
9+
use crate::utils;
10+
11+
const KUBE_CHAR: &str = "☸ ";
12+
13+
fn get_kube_context(contents: &str) -> Option<(String, String)> {
14+
let yaml_docs = YamlLoader::load_from_str(&contents).ok()?;
15+
if yaml_docs.is_empty() {
16+
return None;
17+
}
18+
let conf = &yaml_docs[0];
19+
20+
let current_ctx = conf["current-context"].as_str()?;
21+
22+
if current_ctx.is_empty() {
23+
return None;
24+
}
25+
26+
let ns = conf["contexts"]
27+
.as_vec()
28+
.and_then(|contexts| {
29+
contexts
30+
.iter()
31+
.filter_map(|ctx| Some((ctx, ctx["name"].as_str()?)))
32+
.find(|(_, name)| *name == current_ctx)
33+
.and_then(|(ctx, _)| ctx["context"]["namespace"].as_str())
34+
})
35+
.unwrap_or("");
36+
37+
Some((current_ctx.to_string(), ns.to_string()))
38+
}
39+
40+
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
41+
let filename = match env::var("KUBECONFIG") {
42+
Ok(path) => path::PathBuf::from(path),
43+
Err(_) => dirs::home_dir()?.join(".kube").join("config"),
44+
};
45+
46+
let contents = utils::read_file(filename).ok()?;
47+
48+
match get_kube_context(&contents) {
49+
Some(kube_cfg) => {
50+
let (kube_ctx, kube_ns) = kube_cfg;
51+
52+
let mut module = context.new_module("kubernetes");
53+
54+
let module_style = module
55+
.config_value_style("style")
56+
.unwrap_or_else(|| Color::Cyan.bold());
57+
module.set_style(module_style);
58+
module.get_prefix().set_value("on ");
59+
60+
module.new_segment("symbol", KUBE_CHAR);
61+
module.new_segment("context", &kube_ctx);
62+
if kube_ns != "" {
63+
module.new_segment("namespace", &format!(" ({})", kube_ns));
64+
}
65+
Some(module)
66+
}
67+
None => None,
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
use super::*;
74+
75+
#[test]
76+
fn parse_empty_config() {
77+
let input = "";
78+
let result = get_kube_context(&input);
79+
let expected = None;
80+
81+
assert_eq!(result, expected);
82+
}
83+
84+
#[test]
85+
fn parse_no_config() {
86+
let input = r#"
87+
apiVersion: v1
88+
clusters: []
89+
contexts: []
90+
current-context: ""
91+
kind: Config
92+
preferences: {}
93+
users: []
94+
"#;
95+
let result = get_kube_context(&input);
96+
let expected = None;
97+
98+
assert_eq!(result, expected);
99+
}
100+
101+
#[test]
102+
fn parse_only_context() {
103+
let input = r#"
104+
apiVersion: v1
105+
clusters: []
106+
contexts:
107+
- context:
108+
cluster: test_cluster
109+
user: test_user
110+
name: test_context
111+
current-context: test_context
112+
kind: Config
113+
preferences: {}
114+
users: []
115+
"#;
116+
let result = get_kube_context(&input);
117+
let expected = Some(("test_context".to_string(), "".to_string()));
118+
119+
assert_eq!(result, expected);
120+
}
121+
122+
#[test]
123+
fn parse_context_and_ns() {
124+
let input = r#"
125+
apiVersion: v1
126+
clusters: []
127+
contexts:
128+
- context:
129+
cluster: test_cluster
130+
user: test_user
131+
namespace: test_namespace
132+
name: test_context
133+
current-context: test_context
134+
kind: Config
135+
preferences: {}
136+
users: []
137+
"#;
138+
let result = get_kube_context(&input);
139+
let expected = Some(("test_context".to_string(), "test_namespace".to_string()));
140+
141+
assert_eq!(result, expected);
142+
}
143+
144+
#[test]
145+
fn parse_multiple_contexts() {
146+
let input = r#"
147+
apiVersion: v1
148+
clusters: []
149+
contexts:
150+
- context:
151+
cluster: another_cluster
152+
user: another_user
153+
namespace: another_namespace
154+
name: another_context
155+
- context:
156+
cluster: test_cluster
157+
user: test_user
158+
namespace: test_namespace
159+
name: test_context
160+
current-context: test_context
161+
kind: Config
162+
preferences: {}
163+
users: []
164+
"#;
165+
let result = get_kube_context(&input);
166+
let expected = Some(("test_context".to_string(), "test_namespace".to_string()));
167+
168+
assert_eq!(result, expected);
169+
}
170+
171+
#[test]
172+
fn parse_broken_config() {
173+
let input = r#"
174+
---
175+
dummy_string
176+
"#;
177+
let result = get_kube_context(&input);
178+
let expected = None;
179+
180+
assert_eq!(result, expected);
181+
}
182+
}

starship/src/modules/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod golang;
1111
mod hostname;
1212
mod java;
1313
mod jobs;
14+
mod kubernetes;
1415
mod line_break;
1516
mod memory_usage;
1617
mod nix_shell;
@@ -44,6 +45,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
4445
"git_branch" => git_branch::module(context),
4546
"git_state" => git_state::module(context),
4647
"git_status" => git_status::module(context),
48+
"kubernetes" => kubernetes::module(context),
4749
"username" => username::module(context),
4850
#[cfg(feature = "battery")]
4951
"battery" => battery::module(context),

0 commit comments

Comments
 (0)