Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion openstack_tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ impl App {
let client_config = args.os_client_config_file.clone();
let client_secure_config = args.os_client_secure_file.clone();

// When --cloud-config-from-env is set, build the cloud config from
// environment variables (same behavior as the CLI).
let (env_cloud_name, env_cloud_config) = if args.cloud_config_from_env {
let cloud_name = args
.os_cloud_name
.clone()
.unwrap_or_else(|| String::from("envvars"));
let mut cfg = openstack_sdk::config::CloudConfig::from_env()?;
cfg.name = Some(cloud_name.clone());
(Some(cloud_name), Some(cfg))
} else {
(args.os_cloud_name.clone(), None)
};

// Is there a way to initialize HashMap with Box<dyn Foo> as keys in one operation?
let mut components: HashMap<Mode, Box<dyn Component>> = HashMap::new();
components.insert(Mode::Home, Box::new(Home::new()));
Expand Down Expand Up @@ -284,6 +298,7 @@ impl App {
client_config,
client_secure_config,
auth_helper_control_channel_tx_clone,
env_cloud_config,
)?;
tokio::spawn(async move {
if let Err(err) = cloud
Expand All @@ -307,7 +322,7 @@ impl App {
action_rx,
cloud_worker_tx: cloud_worker,
last_tick_key_events: Vec::new(),
cloud_name: args.os_cloud.clone(),
cloud_name: args.os_cloud.clone().or(env_cloud_name),
cloud_connected: false,
active_popup: None,
popups,
Expand Down
17 changes: 16 additions & 1 deletion openstack_tui/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,24 @@ pub struct Cli {
pub frame_rate: f64,

/// Cloud name to connect to after the start
#[arg(long, env = "OS_CLOUD")]
///
/// Conflicts with the `--cloud-config-from-env` option.
#[arg(long, env = "OS_CLOUD", conflicts_with = "cloud_config_from_env")]
pub os_cloud: Option<String>,

/// Get the cloud config from environment variables.
///
/// Conflicts with the `--os-cloud` option. No merging of environment variables with the
/// options from the `clouds.yaml` file done. This effectively limits the TUI to a single
/// cloud connection.
#[arg(long)]
pub cloud_config_from_env: bool,

/// Cloud name used when configuration is retrieved from environment variables. When not
/// specified the `envvars` would be used as a default.
#[arg(long, env = "OS_CLOUD_NAME")]
pub os_cloud_name: Option<String>,

/// Custom path to the `clouds.yaml` config file
#[arg(long, env = "OS_CLIENT_CONFIG_FILE", value_hint = ValueHint::FilePath)]
pub os_client_config_file: Option<PathBuf>,
Expand Down
49 changes: 39 additions & 10 deletions openstack_tui/src/cloud_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use eyre::{Result, eyre};
use openstack_sdk::{
AsyncOpenStack, MicroVersionStrategy, RenewHandle,
auth::auth_helper::{AuthHelper, AuthHelperError},
config::ConfigFile,
config::{CloudConfig, ConfigFile},
};
use secrecy::SecretString;
use std::path::PathBuf;
Expand Down Expand Up @@ -68,6 +68,10 @@ const AUTO_RENEW_MARGIN: TimeDelta = TimeDelta::seconds(59 * 60);
pub(crate) struct Cloud {
cloud_configs: ConfigFile,
pub(crate) cloud: Option<AsyncOpenStack>,
/// Pre-loaded cloud config from environment variables. When set, the
/// TUI operates in single-cloud mode similar to `--cloud-config-from-env`
/// in the CLI.
env_cloud_config: Option<openstack_sdk::config::CloudConfig>,
auth_helper: TuiAuthHelper,
/// Handle to the background token-renewal task for `cloud`. Dropped
/// (stopping the task) when replaced by a new connection/scope change,
Expand All @@ -87,26 +91,43 @@ impl Cloud {
client_config_config_file: Option<PathBuf>,
client_secure_config_file: Option<PathBuf>,
auth_helper_control_tx: mpsc::Sender<oneshot::Sender<AuthAction>>,
env_cloud_config: Option<CloudConfig>,
) -> Result<Self, TuiError> {
let cfg = ConfigFile::new_with_user_specified_configs(
client_config_config_file.as_deref(),
client_secure_config_file.as_deref(),
)?;
// When running with env-based config the config file is not needed
// and parsing it may fail for unrelated reasons. Skip the parse in
// that case so that a broken or missing `clouds.yaml` does not block
// `--cloud-config-from-env` operation.
let cfg = if env_cloud_config.is_some() {
ConfigFile {
cache: None,
clouds: None,
public_clouds: None,
}
} else {
ConfigFile::new_with_user_specified_configs(
client_config_config_file.as_deref(),
client_secure_config_file.as_deref(),
)?
};

Ok(Self {
cloud_configs: cfg,
cloud: None,
env_cloud_config,
auth_helper: TuiAuthHelper::new(auth_helper_control_tx),
_renew_handle: None,
})
}

pub async fn connect_to_cloud(&mut self, cloud: String) -> Result<()> {
debug!("Connecting to cloud {}", cloud);
let profile = self
.cloud_configs
.get_cloud_config(cloud.clone())?
.ok_or_else(|| eyre!("Cloud `{}` is not present in configuration files", cloud))?;
let profile = if let Some(ref env_config) = self.env_cloud_config {
env_config.clone()
} else {
self.cloud_configs
.get_cloud_config(cloud.clone())?
.ok_or_else(|| eyre!("Cloud `{}` is not present in configuration files", cloud))?
};
let session = AsyncOpenStack::builder(&profile)
.auth_helper(self.auth_helper.clone())
.renew_auth(false)
Expand Down Expand Up @@ -264,7 +285,15 @@ impl Cloud {
}
}
Action::ListClouds => {
app_tx.send(Action::Clouds(self.cloud_configs.get_available_clouds()))?;
if let Some(ref env_config) = self.env_cloud_config {
let cloud_name = env_config
.name
.clone()
.unwrap_or_else(|| String::from("envvars"));
app_tx.send(Action::Clouds(vec![cloud_name]))?;
} else {
app_tx.send(Action::Clouds(self.cloud_configs.get_available_clouds()))?;
}
}
Action::CloudChangeScope(ref scope) => {
let _ = self.switch_auth_scope(scope, &app_tx, &action).await;
Expand Down