mirror of
https://github.com/Llewellynvdm/starship.git
synced 2025-04-08 10:01:51 +00:00
feat(azure): Azure module (#3275)
* Azure module * make Semantic PR bot happy * Responding to review * Changing severity of logging event
This commit is contained in:
parent
e3a88a6ec1
commit
365b295433
@ -240,6 +240,7 @@ $memory_usage\
|
|||||||
$aws\
|
$aws\
|
||||||
$gcloud\
|
$gcloud\
|
||||||
$openstack\
|
$openstack\
|
||||||
|
$azure\
|
||||||
$env_var\
|
$env_var\
|
||||||
$crystal\
|
$crystal\
|
||||||
$custom\
|
$custom\
|
||||||
@ -344,6 +345,31 @@ style = "bold blue"
|
|||||||
symbol = "🅰 "
|
symbol = "🅰 "
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Azure
|
||||||
|
|
||||||
|
The `azure` module shows the current Azure Subscription. This is based on showing the name of the default subscription, as defined in the `~/.azure/azureProfile.json` file.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| ----------------- | ---------------------------------------- | ------------------------------------------ |
|
||||||
|
| `format` | `"on [$symbol($subscription)]($style) "` | The format for the Azure module to render. |
|
||||||
|
| `symbol` | `"ﴃ "` | The symbol used in the format. |
|
||||||
|
| `style` | `"blue bold"` | The style used in the format. |
|
||||||
|
| `disabled` | `true` | Disables the `azure` module. |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# ~/.config/starship.toml
|
||||||
|
|
||||||
|
[azure]
|
||||||
|
disabled = false
|
||||||
|
format = "on [$symbol($subscription)]($style) "
|
||||||
|
symbol = "ﴃ "
|
||||||
|
style = "blue bold"
|
||||||
|
```
|
||||||
|
|
||||||
## Battery
|
## Battery
|
||||||
|
|
||||||
The `battery` module shows how charged the device's battery is and its current charging status.
|
The `battery` module shows how charged the device's battery is and its current charging status.
|
||||||
|
22
src/configs/azure.rs
Normal file
22
src/configs/azure.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use crate::config::ModuleConfig;
|
||||||
|
use serde::Serialize;
|
||||||
|
use starship_module_config_derive::ModuleConfig;
|
||||||
|
|
||||||
|
#[derive(Clone, ModuleConfig, Serialize)]
|
||||||
|
pub struct AzureConfig<'a> {
|
||||||
|
pub format: &'a str,
|
||||||
|
pub symbol: &'a str,
|
||||||
|
pub style: &'a str,
|
||||||
|
pub disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for AzureConfig<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
AzureConfig {
|
||||||
|
format: "on [$symbol($subscription)]($style) ",
|
||||||
|
symbol: "ﴃ ",
|
||||||
|
style: "blue bold",
|
||||||
|
disabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ use serde::{self, Serialize};
|
|||||||
use starship_module_config_derive::ModuleConfig;
|
use starship_module_config_derive::ModuleConfig;
|
||||||
|
|
||||||
pub mod aws;
|
pub mod aws;
|
||||||
|
pub mod azure;
|
||||||
pub mod battery;
|
pub mod battery;
|
||||||
pub mod character;
|
pub mod character;
|
||||||
pub mod cmake;
|
pub mod cmake;
|
||||||
@ -84,6 +85,7 @@ pub struct FullConfig<'a> {
|
|||||||
pub add_newline: bool,
|
pub add_newline: bool,
|
||||||
// modules
|
// modules
|
||||||
aws: aws::AwsConfig<'a>,
|
aws: aws::AwsConfig<'a>,
|
||||||
|
azure: azure::AzureConfig<'a>,
|
||||||
battery: battery::BatteryConfig<'a>,
|
battery: battery::BatteryConfig<'a>,
|
||||||
character: character::CharacterConfig<'a>,
|
character: character::CharacterConfig<'a>,
|
||||||
cmake: cmake::CMakeConfig<'a>,
|
cmake: cmake::CMakeConfig<'a>,
|
||||||
@ -161,6 +163,7 @@ impl<'a> Default for FullConfig<'a> {
|
|||||||
add_newline: true,
|
add_newline: true,
|
||||||
|
|
||||||
aws: Default::default(),
|
aws: Default::default(),
|
||||||
|
azure: Default::default(),
|
||||||
battery: Default::default(),
|
battery: Default::default(),
|
||||||
character: Default::default(),
|
character: Default::default(),
|
||||||
cmake: Default::default(),
|
cmake: Default::default(),
|
||||||
|
@ -73,6 +73,7 @@ pub const PROMPT_ORDER: &[&str] = &[
|
|||||||
"aws",
|
"aws",
|
||||||
"gcloud",
|
"gcloud",
|
||||||
"openstack",
|
"openstack",
|
||||||
|
"azure",
|
||||||
"env_var",
|
"env_var",
|
||||||
"crystal",
|
"crystal",
|
||||||
"custom",
|
"custom",
|
||||||
|
@ -9,6 +9,7 @@ use std::time::Duration;
|
|||||||
// Default ordering is handled in configs/starship_root.rs
|
// Default ordering is handled in configs/starship_root.rs
|
||||||
pub const ALL_MODULES: &[&str] = &[
|
pub const ALL_MODULES: &[&str] = &[
|
||||||
"aws",
|
"aws",
|
||||||
|
"azure",
|
||||||
#[cfg(feature = "battery")]
|
#[cfg(feature = "battery")]
|
||||||
"battery",
|
"battery",
|
||||||
"character",
|
"character",
|
||||||
|
267
src/modules/azure.rs
Normal file
267
src/modules/azure.rs
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use super::{Context, Module, RootModuleConfig};
|
||||||
|
|
||||||
|
type JValue = serde_json::Value;
|
||||||
|
|
||||||
|
use crate::configs::azure::AzureConfig;
|
||||||
|
use crate::formatter::StringFormatter;
|
||||||
|
|
||||||
|
type SubscriptionName = String;
|
||||||
|
|
||||||
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
|
let mut module = context.new_module("azure");
|
||||||
|
let config = AzureConfig::try_load(module.config);
|
||||||
|
|
||||||
|
if config.disabled {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let subscription_name: Option<SubscriptionName> = get_azure_subscription_name(context);
|
||||||
|
if subscription_name.is_none() {
|
||||||
|
log::info!("Could not find Azure subscription name");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||||
|
formatter
|
||||||
|
.map_meta(|variable, _| match variable {
|
||||||
|
"symbol" => Some(config.symbol),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map_style(|variable| match variable {
|
||||||
|
"style" => Some(Ok(config.style)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|variable| match variable {
|
||||||
|
"subscription" => Some(Ok(subscription_name.as_ref().unwrap())),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.parse(None, Some(context))
|
||||||
|
});
|
||||||
|
|
||||||
|
module.set_segments(match parsed {
|
||||||
|
Ok(segments) => segments,
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("Error in module `azure`:\n{}", error);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(module)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_azure_subscription_name(context: &Context) -> Option<SubscriptionName> {
|
||||||
|
let mut config_path = get_config_file_location(context)?;
|
||||||
|
config_path.push("azureProfile.json");
|
||||||
|
|
||||||
|
let parsed_json = parse_json(&config_path)?;
|
||||||
|
|
||||||
|
let subscriptions = parsed_json.get("subscriptions")?.as_array()?;
|
||||||
|
let subscription_name = subscriptions.iter().find_map(|s| {
|
||||||
|
if s.get("isDefault")? == true {
|
||||||
|
Some(s.get("name")?.as_str()?.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if subscription_name.is_some() {
|
||||||
|
subscription_name
|
||||||
|
} else {
|
||||||
|
log::info!("Could not find subscription name");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_file_location(context: &Context) -> Option<PathBuf> {
|
||||||
|
context
|
||||||
|
.get_env("AZURE_CONFIG_DIR")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| {
|
||||||
|
let mut home = context.get_home()?;
|
||||||
|
home.push(".azure");
|
||||||
|
Some(home)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_json(json_file_path: &Path) -> Option<JValue> {
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
let json_file = File::open(&json_file_path).ok()?;
|
||||||
|
let mut reader = BufReader::new(json_file);
|
||||||
|
reader.read_to_end(&mut buffer).ok()?;
|
||||||
|
|
||||||
|
let bytes = buffer.as_mut_slice();
|
||||||
|
let decodedbuffer = bytes.strip_prefix(&[239, 187, 191]).unwrap_or(bytes);
|
||||||
|
|
||||||
|
if let Ok(parsed_json) = serde_json::from_slice(decodedbuffer) {
|
||||||
|
Some(parsed_json)
|
||||||
|
} else {
|
||||||
|
log::info!("Failed to parse json");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::modules::azure::parse_json;
|
||||||
|
use crate::test::ModuleRenderer;
|
||||||
|
use ansi_term::Color;
|
||||||
|
use ini::Ini;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
fn generate_test_config(dir: &TempDir, azure_profile_contents: &str) -> io::Result<()> {
|
||||||
|
save_string_to_file(
|
||||||
|
dir,
|
||||||
|
azure_profile_contents.to_string(),
|
||||||
|
String::from("azureProfile.json"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn subscription_set_correctly() -> io::Result<()> {
|
||||||
|
let dir = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let azure_profile_contents = r#"{
|
||||||
|
"installationId": "3deacd2a-b9db-77e1-aa42-23e2f8dfffc3",
|
||||||
|
"subscriptions": [
|
||||||
|
{
|
||||||
|
"id": "f568c543-d12e-de0b-3d85-69843598b565",
|
||||||
|
"name": "Subscription 2",
|
||||||
|
"state": "Enabled",
|
||||||
|
"user": {
|
||||||
|
"name": "user@domain.com",
|
||||||
|
"type": "user"
|
||||||
|
},
|
||||||
|
"isDefault": false,
|
||||||
|
"tenantId": "0e8a15ec-b0f5-d355-7062-8ece54c59aee",
|
||||||
|
"environmentName": "AzureCloud",
|
||||||
|
"homeTenantId": "0e8a15ec-b0f5-d355-7062-8ece54c59aee",
|
||||||
|
"managedByTenants": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d4442d26-ea6d-46c4-07cb-4f70b8ae5465",
|
||||||
|
"name": "Subscription 3",
|
||||||
|
"state": "Enabled",
|
||||||
|
"user": {
|
||||||
|
"name": "user@domain.com",
|
||||||
|
"type": "user"
|
||||||
|
},
|
||||||
|
"tenantId": "a4e1bb4b-5330-2d50-339d-b9674d3a87bc",
|
||||||
|
"environmentName": "AzureCloud",
|
||||||
|
"homeTenantId": "a4e1bb4b-5330-2d50-339d-b9674d3a87bc",
|
||||||
|
"managedByTenants": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f3935dc9-92b5-9a93-da7b-42c325d86939",
|
||||||
|
"name": "Subscription 1",
|
||||||
|
"state": "Enabled",
|
||||||
|
"user": {
|
||||||
|
"name": "user@domain.com",
|
||||||
|
"type": "user"
|
||||||
|
},
|
||||||
|
"isDefault": true,
|
||||||
|
"tenantId": "f0273a19-7779-e40a-00a1-53b8331b3bb6",
|
||||||
|
"environmentName": "AzureCloud",
|
||||||
|
"homeTenantId": "f0273a19-7779-e40a-00a1-53b8331b3bb6",
|
||||||
|
"managedByTenants": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
generate_test_config(&dir, azure_profile_contents)?;
|
||||||
|
let dir_path = &dir.path().to_string_lossy();
|
||||||
|
let actual = ModuleRenderer::new("azure")
|
||||||
|
.config(toml::toml! {
|
||||||
|
[azure]
|
||||||
|
disabled = false
|
||||||
|
})
|
||||||
|
.env("AZURE_CONFIG_DIR", dir_path.as_ref())
|
||||||
|
.collect();
|
||||||
|
let expected = Some(format!(
|
||||||
|
"on {} ",
|
||||||
|
Color::Blue.bold().paint("ﴃ Subscription 1")
|
||||||
|
));
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
dir.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subscription_azure_profile_empty() -> io::Result<()> {
|
||||||
|
let dir = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let mut clouds_config_ini = Ini::new();
|
||||||
|
clouds_config_ini
|
||||||
|
.with_section(Some("AzureCloud"))
|
||||||
|
.set("subscription", "f3935dc9-92b5-9a93-da7b-42c325d86939");
|
||||||
|
|
||||||
|
let azure_profile_contents = r#"{
|
||||||
|
"installationId": "3deacd2a-b9db-77e1-aa42-23e2f8dfffc3",
|
||||||
|
"subscriptions": []
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
generate_test_config(&dir, azure_profile_contents)?;
|
||||||
|
let dir_path = &dir.path().to_string_lossy();
|
||||||
|
let actual = ModuleRenderer::new("azure")
|
||||||
|
.config(toml::toml! {
|
||||||
|
[azure]
|
||||||
|
disabled = false
|
||||||
|
})
|
||||||
|
.env("AZURE_CONFIG_DIR", dir_path.as_ref())
|
||||||
|
.collect();
|
||||||
|
let expected = None;
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
dir.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn files_missing() -> io::Result<()> {
|
||||||
|
let dir = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let dir_path = &dir.path().to_string_lossy();
|
||||||
|
|
||||||
|
let actual = ModuleRenderer::new("azure")
|
||||||
|
.env("AZURE_CONFIG_DIR", dir_path.as_ref())
|
||||||
|
.collect();
|
||||||
|
let expected = None;
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
dir.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parsing() -> io::Result<()> {
|
||||||
|
let dir = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let bom = vec![239, 187, 191];
|
||||||
|
let mut bom_str = String::from_utf8(bom).unwrap();
|
||||||
|
let json_str = r#"{"testKey": "testValue"}"#;
|
||||||
|
bom_str.push_str(json_str);
|
||||||
|
|
||||||
|
let dir_path_no_bom = save_string_to_file(&dir, bom_str, String::from("bom.json"))?;
|
||||||
|
let parsed_json = parse_json(&dir_path_no_bom).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parsed_json.get("testKey").unwrap(), "testValue");
|
||||||
|
dir.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_string_to_file(
|
||||||
|
dir: &TempDir,
|
||||||
|
contents: String,
|
||||||
|
file_name: String,
|
||||||
|
) -> Result<PathBuf, io::Error> {
|
||||||
|
let bom_file_path = dir.path().join(file_name);
|
||||||
|
let mut bom_file = File::create(&bom_file_path)?;
|
||||||
|
bom_file.write_all(contents.as_bytes())?;
|
||||||
|
bom_file.sync_all()?;
|
||||||
|
Ok(bom_file_path)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
// While adding out new module add out module to src/module.rs ALL_MODULES const array also.
|
// While adding out new module add out module to src/module.rs ALL_MODULES const array also.
|
||||||
mod aws;
|
mod aws;
|
||||||
|
mod azure;
|
||||||
mod character;
|
mod character;
|
||||||
mod cmake;
|
mod cmake;
|
||||||
mod cmd_duration;
|
mod cmd_duration;
|
||||||
@ -84,6 +85,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
|||||||
// Keep these ordered alphabetically.
|
// Keep these ordered alphabetically.
|
||||||
// Default ordering is handled in configs/starship_root.rs
|
// Default ordering is handled in configs/starship_root.rs
|
||||||
"aws" => aws::module(context),
|
"aws" => aws::module(context),
|
||||||
|
"azure" => azure::module(context),
|
||||||
#[cfg(feature = "battery")]
|
#[cfg(feature = "battery")]
|
||||||
"battery" => battery::module(context),
|
"battery" => battery::module(context),
|
||||||
"character" => character::module(context),
|
"character" => character::module(context),
|
||||||
@ -171,6 +173,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
|||||||
pub fn description(module: &str) -> &'static str {
|
pub fn description(module: &str) -> &'static str {
|
||||||
match module {
|
match module {
|
||||||
"aws" => "The current AWS region and profile",
|
"aws" => "The current AWS region and profile",
|
||||||
|
"azure" => "The current Azure subscription",
|
||||||
"battery" => "The current charge of the device's battery and its current charging status",
|
"battery" => "The current charge of the device's battery and its current charging status",
|
||||||
"character" => {
|
"character" => {
|
||||||
"A character (usually an arrow) beside where the text is entered in your terminal"
|
"A character (usually an arrow) beside where the text is entered in your terminal"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user