Skip to content

Commit b3275d8

Browse files
Thomas Leematchai
Thomas Lee
authored andcommitted
feat: Show AWS region in aws module (starship#482)
1 parent a18408e commit b3275d8

File tree

4 files changed

+162
-6
lines changed

4 files changed

+162
-6
lines changed

docs/config/README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ prompt_order = [
114114

115115
## AWS
116116

117-
The `aws` module shows the current AWS profile. This is based on the
118-
`AWS_PROFILE` env var.
117+
The `aws` module shows the current AWS region and profile. This is based on
118+
`AWS_REGION`, `AWS_DEFAULT_REGION`, and `AWS_PROFILE` env var with
119+
`~/.aws/config` file.
119120

120121
### Options
121122

src/configs/aws.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use starship_module_config_derive::ModuleConfig;
77
pub struct AwsConfig<'a> {
88
pub symbol: SegmentConfig<'a>,
99
pub profile: SegmentConfig<'a>,
10+
pub region: SegmentConfig<'a>,
1011
pub style: Style,
1112
pub disabled: bool,
1213
}
@@ -16,6 +17,7 @@ impl<'a> RootModuleConfig<'a> for AwsConfig<'a> {
1617
AwsConfig {
1718
symbol: SegmentConfig::new("☁️ "),
1819
profile: SegmentConfig::default(),
20+
region: SegmentConfig::default(),
1921
style: Color::Yellow.bold(),
2022
disabled: false,
2123
}

src/modules/aws.rs

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,85 @@
11
use std::env;
2+
use std::fs::File;
3+
use std::io::{BufRead, BufReader};
4+
use std::path::PathBuf;
5+
use std::str::FromStr;
6+
7+
use dirs::home_dir;
28

39
use super::{Context, Module, RootModuleConfig};
410

511
use crate::configs::aws::AwsConfig;
612

13+
fn get_aws_region_from_config(aws_profile: &Option<String>) -> Option<String> {
14+
let config_location = env::var("AWS_CONFIG_FILE")
15+
.ok()
16+
.and_then(|path| PathBuf::from_str(&path).ok())
17+
.or_else(|| {
18+
let mut home = home_dir()?;
19+
home.push(".aws/config");
20+
Some(home)
21+
})?;
22+
23+
let file = File::open(&config_location).ok()?;
24+
let reader = BufReader::new(file);
25+
let lines = reader.lines().filter_map(Result::ok);
26+
27+
let region_line = if let Some(ref aws_profile) = aws_profile {
28+
lines
29+
.skip_while(|line| line != &format!("[profile {}]", aws_profile))
30+
.skip(1)
31+
.take_while(|line| !line.starts_with('['))
32+
.find(|line| line.starts_with("region"))
33+
} else {
34+
lines
35+
.skip_while(|line| line != "[default]")
36+
.skip(1)
37+
.take_while(|line| !line.starts_with('['))
38+
.find(|line| line.starts_with("region"))
39+
}?;
40+
41+
let region = region_line.split('=').nth(1)?;
42+
let region = region.trim();
43+
44+
Some(region.to_string())
45+
}
46+
47+
fn get_aws_region() -> Option<(String, String)> {
48+
env::var("AWS_DEFAULT_REGION")
49+
.ok()
50+
.map(|region| (String::new(), region))
51+
.or_else(|| {
52+
let aws_profile = env::var("AWS_PROFILE").ok();
53+
let aws_region = get_aws_region_from_config(&aws_profile);
54+
55+
if aws_profile.is_none() && aws_region.is_none() {
56+
None
57+
} else {
58+
Some((
59+
aws_profile.unwrap_or_default(),
60+
aws_region.unwrap_or_default(),
61+
))
62+
}
63+
})
64+
.or_else(|| {
65+
env::var("AWS_REGION")
66+
.ok()
67+
.map(|region| (String::new(), region))
68+
})
69+
}
70+
771
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
872
const AWS_PREFIX: &str = "on ";
973

10-
let aws_profile = env::var("AWS_PROFILE").ok()?;
11-
if aws_profile.is_empty() {
74+
let (aws_profile, aws_region) = get_aws_region()?;
75+
if aws_profile.is_empty() && aws_region.is_empty() {
1276
return None;
1377
}
78+
let aws_region = if aws_profile.is_empty() || aws_region.is_empty() {
79+
aws_region
80+
} else {
81+
format!("({})", aws_region)
82+
};
1483

1584
let mut module = context.new_module("aws");
1685
let config: AwsConfig = AwsConfig::try_load(module.config);
@@ -21,6 +90,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
2190

2291
module.create_segment("symbol", &config.symbol);
2392
module.create_segment("profile", &config.profile.with_value(&aws_profile));
93+
module.create_segment("region", &config.profile.with_value(&aws_region));
2494

2595
Some(module)
2696
}

tests/testsuite/aws.rs

+85-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,44 @@
1+
use std::fs::File;
2+
use std::io::{self, Write};
3+
14
use ansi_term::Color;
2-
use std::io;
35

46
use crate::common;
57

68
#[test]
7-
fn no_profile_set() -> io::Result<()> {
9+
fn no_region_set() -> io::Result<()> {
810
let output = common::render_module("aws").env_clear().output()?;
911
let expected = "";
1012
let actual = String::from_utf8(output.stdout).unwrap();
1113
assert_eq!(expected, actual);
1214
Ok(())
1315
}
1416

17+
#[test]
18+
fn region_set() -> io::Result<()> {
19+
let output = common::render_module("aws")
20+
.env_clear()
21+
.env("AWS_REGION", "ap-northeast-2")
22+
.output()?;
23+
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-2"));
24+
let actual = String::from_utf8(output.stdout).unwrap();
25+
assert_eq!(expected, actual);
26+
Ok(())
27+
}
28+
29+
#[test]
30+
fn default_region_set() -> io::Result<()> {
31+
let output = common::render_module("aws")
32+
.env_clear()
33+
.env("AWS_REGION", "ap-northeast-2")
34+
.env("AWS_DEFAULT_REGION", "ap-northeast-1")
35+
.output()?;
36+
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-1"));
37+
let actual = String::from_utf8(output.stdout).unwrap();
38+
assert_eq!(expected, actual);
39+
Ok(())
40+
}
41+
1542
#[test]
1643
fn profile_set() -> io::Result<()> {
1744
let output = common::render_module("aws")
@@ -23,3 +50,59 @@ fn profile_set() -> io::Result<()> {
2350
assert_eq!(expected, actual);
2451
Ok(())
2552
}
53+
54+
#[test]
55+
fn default_profile_set() -> io::Result<()> {
56+
let dir = common::new_tempdir()?;
57+
let config_path = dir.path().join("config");
58+
let mut file = File::create(&config_path)?;
59+
60+
file.write_all(
61+
"[default]
62+
region = us-east-1
63+
64+
[profile astronauts]
65+
region = us-east-2
66+
"
67+
.as_bytes(),
68+
)?;
69+
70+
let output = common::render_module("aws")
71+
.env_clear()
72+
.env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref())
73+
.output()?;
74+
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ us-east-1"));
75+
let actual = String::from_utf8(output.stdout).unwrap();
76+
assert_eq!(expected, actual);
77+
Ok(())
78+
}
79+
80+
#[test]
81+
fn profile_and_config_set() -> io::Result<()> {
82+
let dir = common::new_tempdir()?;
83+
let config_path = dir.path().join("config");
84+
let mut file = File::create(&config_path)?;
85+
86+
file.write_all(
87+
"[default]
88+
region = us-east-1
89+
90+
[profile astronauts]
91+
region = us-east-2
92+
"
93+
.as_bytes(),
94+
)?;
95+
96+
let output = common::render_module("aws")
97+
.env_clear()
98+
.env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref())
99+
.env("AWS_PROFILE", "astronauts")
100+
.output()?;
101+
let expected = format!(
102+
"on {} ",
103+
Color::Yellow.bold().paint("☁️ astronauts(us-east-2)")
104+
);
105+
let actual = String::from_utf8(output.stdout).unwrap();
106+
assert_eq!(expected, actual);
107+
Ok(())
108+
}

0 commit comments

Comments
 (0)