Skip to content

Commit 129d8b5

Browse files
authored
feat(cli): Interactive cloud connection (#532)
When user has not specified `--os-cloud` param but the cli is running in an interactive mode we can invoke dialoguer to offer user configured clouds to select (similarly how it is done in the tui).
1 parent 4b5e022 commit 129d8b5

File tree

5 files changed

+51
-13
lines changed

5 files changed

+51
-13
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openstack_cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ clap = { workspace = true, features = ["color", "derive", "env"] }
4747
clap_complete = { workspace = true }
4848
cli-table = "^0.4"
4949
color-eyre = { workspace = true }
50-
dialoguer = {workspace = true}
50+
dialoguer = { workspace = true, features=["fuzzy-select"] }
5151
eyre = { version = "^0.6" }
5252
http = { workspace = true }
5353
human-panic = { workspace = true }

openstack_cli/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ pub enum OpenStackCliError {
147147
source: http::header::InvalidHeaderValue,
148148
},
149149

150+
/// User interaction using dialoguer crate failed
151+
#[error("dialoguer error `{}`", source)]
152+
DialoguerError {
153+
/// The source of the error.
154+
#[from]
155+
source: dialoguer::Error,
156+
},
157+
150158
/// Others.
151159
#[error(transparent)]
152160
Other(#[from] eyre::Report),

openstack_cli/src/lib.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use std::io::{self, IsTerminal};
2525

2626
use clap::{CommandFactory, Parser};
2727
use clap_complete::generate;
28-
use eyre::OptionExt;
28+
use dialoguer::FuzzySelect;
29+
use eyre::eyre;
2930
use tracing::{warn, Level};
3031

3132
use openstack_sdk::{
@@ -81,16 +82,30 @@ pub async fn entry_point() -> Result<(), OpenStackCliError> {
8182
.init();
8283

8384
let cfg = openstack_sdk::config::ConfigFile::new().unwrap();
85+
// Identify target cloud to connect to
86+
let cloud_name = match cli.global_opts.os_cloud {
87+
Some(ref cloud) => cloud.clone(),
88+
None => {
89+
if std::io::stdin().is_terminal() {
90+
// Cloud was not selected and we are in the potentially interactive mode (terminal)
91+
let mut profiles = cfg.get_available_clouds();
92+
profiles.sort();
93+
let selected_cloud_idx = FuzzySelect::new()
94+
.with_prompt("Please select cloud you want to connect to (use `--os-cloud` next time for efficiency)?")
95+
.items(&profiles)
96+
.interact()?;
97+
profiles[selected_cloud_idx].clone()
98+
} else {
99+
return Err(
100+
eyre!("`--os-cloud` or `OS_CLOUD` environment variable must be given").into(),
101+
);
102+
}
103+
}
104+
};
105+
// Get the connection details
84106
let profile = cfg
85-
.get_cloud_config(
86-
cli.global_opts
87-
.os_cloud
88-
.clone()
89-
.ok_or_eyre("`--os-cloud` or `OS_CLOUD` environment variable must be given")?,
90-
)?
91-
.ok_or(OpenStackCliError::ConnectionNotFound(
92-
cli.global_opts.os_cloud.clone().unwrap(),
93-
))?;
107+
.get_cloud_config(&cloud_name)?
108+
.ok_or(OpenStackCliError::ConnectionNotFound(cloud_name))?;
94109
let mut renew_auth: bool = false;
95110

96111
// Login command need to be analyzed before authorization
@@ -104,10 +119,12 @@ pub async fn entry_point() -> Result<(), OpenStackCliError> {
104119

105120
let mut session;
106121
if std::io::stdin().is_terminal() {
122+
// Interactive session (may ask for password/MFA/SSO)
107123
session = AsyncOpenStack::new_interactive(&profile, renew_auth)
108124
.await
109125
.map_err(|err| OpenStackCliError::Auth { source: err })?;
110126
} else {
127+
// Non-interactive session if i.e. scripted with chaining
111128
session = AsyncOpenStack::new(&profile)
112129
.await
113130
.map_err(|err| OpenStackCliError::Auth { source: err })?;

openstack_sdk/src/config.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,12 @@ impl ConfigFile {
385385
///
386386
/// This method does not raise the exception when the cloud is
387387
/// not found.
388-
pub fn get_cloud_config(&self, cloud_name: String) -> Result<Option<CloudConfig>, ConfigError> {
388+
pub fn get_cloud_config<S: AsRef<str>>(
389+
&self,
390+
cloud_name: S,
391+
) -> Result<Option<CloudConfig>, ConfigError> {
389392
if let Some(clouds) = &self.clouds {
390-
if let Some(cfg) = clouds.get(&cloud_name) {
393+
if let Some(cfg) = clouds.get(cloud_name.as_ref()) {
391394
let mut config = cfg.clone();
392395
if let Some(ref profile_name) = config.profile {
393396
let mut profile_definition: Option<&CloudConfig> = None;

0 commit comments

Comments
 (0)