diff --git a/tvc/src/client.rs b/tvc/src/client.rs index 5fdf091..634890e 100644 --- a/tvc/src/client.rs +++ b/tvc/src/client.rs @@ -4,6 +4,8 @@ use crate::config::turnkey::{Config, StoredApiKey}; use anyhow::{anyhow, bail, Context, Result}; use tracing::debug; use turnkey_api_key_stamper::TurnkeyP256ApiKey; +use turnkey_client::generated::external::data::v1::TvcApp; +use turnkey_client::generated::GetTvcAppRequest; use turnkey_client::TurnkeyClient; /// Number of *required* auth env vars: org_id, api_key_public, api_key_private. @@ -53,6 +55,21 @@ pub async fn build_client() -> Result { build_authed_client(&org_id, &api_base_url, &api_key_public, &api_key_private) } +pub async fn fetch_tvc_app(auth: &AuthenticatedClient, app_id: &str) -> Result { + let response = auth + .client + .get_tvc_app(GetTvcAppRequest { + organization_id: auth.org_id.clone(), + tvc_app_id: app_id.to_string(), + }) + .await + .context("failed to fetch app")?; + + response + .tvc_app + .ok_or_else(|| anyhow!("app not found: {app_id}")) +} + async fn load_credentials_from_config() -> Result<(String, String, String, String)> { let config = Config::load().await?; diff --git a/tvc/src/commands/app/create.rs b/tvc/src/commands/app/create.rs index 782a7e8..6806a85 100644 --- a/tvc/src/commands/app/create.rs +++ b/tvc/src/commands/app/create.rs @@ -146,7 +146,7 @@ fn build_create_tvc_app_intent(app_config: &AppConfig) -> CreateTvcAppIntent { .map(to_tvc_operator_set_params), share_set_id: app_config.share_set_id.clone(), share_set_params: share_set_params.as_ref().map(to_tvc_operator_set_params), - enable_egress: app_config.enable_egress, + enable_egress: app_config.enable_egress.into(), enable_debug_mode_deployments: app_config.dangerous_enable_debug_mode_deployments.into(), } } @@ -184,7 +184,7 @@ mod tests { AppConfig { name: "test-app".to_string(), quorum_public_key: KNOWN_QUORUM_KEY.to_string(), - enable_egress: Some(false), + enable_egress: false, manifest_set_id: None, manifest_set_params: Some(OperatorSetParams { name: "manifest-set".to_string(), @@ -212,6 +212,16 @@ mod tests { assert!(share_set_params.existing_operator_ids.is_empty()); } + #[test] + fn build_intent_sends_enable_egress() { + let mut config = valid_config(); + config.enable_egress = true; + + let intent = build_create_tvc_app_intent(&config); + + assert_eq!(intent.enable_egress, Some(true)); + } + #[test] fn build_intent_uses_custom_share_set_params_when_configured() { let mut config = valid_config(); diff --git a/tvc/src/commands/app/list.rs b/tvc/src/commands/app/list.rs index 1f98bb6..6d03e4b 100644 --- a/tvc/src/commands/app/list.rs +++ b/tvc/src/commands/app/list.rs @@ -5,6 +5,10 @@ use clap::Args as ClapArgs; use turnkey_client::generated::external::data::v1::TvcApp; use turnkey_client::generated::GetTvcAppsRequest; +use crate::commands::display::format_egress_enabled; + +const SEPARATOR_WIDTH: usize = 40; + /// List apps. #[derive(Debug, ClapArgs)] #[command(about, long_about = None)] @@ -49,15 +53,22 @@ fn filter_by_name(apps: &mut Vec, name: Option<&str>) { } fn render_app(app: &TvcApp) { - println!("Name: {}", app.name); - println!("ID: {}", app.id); - println!("Quorum Public Key: {}", app.quorum_public_key); let live = app.live_deployment_id.as_deref().unwrap_or("(none)"); - println!("Live Deployment: {live}"); + let mut lines = vec![ + format!("Name: {}", app.name), + format!("ID: {}", app.id), + format!("Quorum Public Key: {}", app.quorum_public_key), + format!("Live Deployment: {live}"), + format_egress_enabled(app.enable_egress), + ]; + if !app.public_domain.is_empty() { - println!("Public Domain: {}", app.public_domain); + lines.push(format!("Public Domain: {}", app.public_domain)); } - println!("{}", "─".repeat(40)); + + lines.push("─".repeat(SEPARATOR_WIDTH)); + + println!("{}", lines.join("\n")); } #[cfg(test)] @@ -145,4 +156,15 @@ mod tests { "(none)" ); } + + #[test] + fn egress_enabled_line_reflects_app_setting() { + let mut app = make_app("my-app"); + app.enable_egress = true; + + assert_eq!( + format_egress_enabled(app.enable_egress), + "Egress Enabled: yes" + ); + } } diff --git a/tvc/src/commands/app/status.rs b/tvc/src/commands/app/status.rs index 2d8cd0c..4cba54c 100644 --- a/tvc/src/commands/app/status.rs +++ b/tvc/src/commands/app/status.rs @@ -4,6 +4,9 @@ use anyhow::{anyhow, Context}; use clap::Args as ClapArgs; use turnkey_client::generated::GetAppStatusRequest; +use crate::client::fetch_tvc_app; +use crate::commands::display::format_egress_enabled; + /// Get the live status of an app from the cluster. #[derive(Debug, ClapArgs)] #[command(about, long_about = None)] @@ -33,9 +36,11 @@ pub async fn run(args: Args) -> anyhow::Result<()> { .app_status .ok_or_else(|| anyhow!("no status returned for app: {}", args.app_id))?, ); + let app = fetch_tvc_app(&auth, &args.app_id).await?; println!("App ID: {}", app_status.app_id); println!("Targeted Deployment: {}", app_status.targeted_deployment_id); + println!("{}", format_egress_enabled(app.enable_egress)); if app_status.deployments.is_empty() { println!(); diff --git a/tvc/src/commands/deploy/get_status.rs b/tvc/src/commands/deploy/get_status.rs index 6897a02..9e897b9 100644 --- a/tvc/src/commands/deploy/get_status.rs +++ b/tvc/src/commands/deploy/get_status.rs @@ -5,6 +5,9 @@ use clap::Args as ClapArgs; use turnkey_client::generated::external::data::v1::{AppStatus, DeploymentStatus}; use turnkey_client::generated::{GetAppStatusRequest, GetTvcDeploymentRequest}; +use crate::client::fetch_tvc_app; +use crate::commands::display::format_egress_enabled; + /// Get the live status of a deployment from the app status API. #[derive(Debug, ClapArgs)] #[command(about, long_about = None)] @@ -49,9 +52,11 @@ pub async fn run(args: Args) -> anyhow::Result<()> { .app_status .ok_or_else(|| anyhow!("no status returned for app: {}", deployment.app_id))?, ); + let app = fetch_tvc_app(&auth, &deployment.app_id).await?; println!("Deployment: {}", deployment.id); println!("App ID: {}", app_status.app_id); + println!("{}", format_egress_enabled(app.enable_egress)); println!( "Is Targeted Deployment: {}", if app_status.targeted_deployment_id == args.deploy_id { diff --git a/tvc/src/commands/deploy/status.rs b/tvc/src/commands/deploy/status.rs index c326b70..70e3109 100644 --- a/tvc/src/commands/deploy/status.rs +++ b/tvc/src/commands/deploy/status.rs @@ -5,6 +5,9 @@ use clap::Args as ClapArgs; use turnkey_client::generated::external::data::v1::TvcDeployment; use turnkey_client::generated::GetTvcDeploymentRequest; +use crate::client::fetch_tvc_app; +use crate::commands::display::{format_egress_enabled, yes_no}; + /// Get the status of a deployment. #[derive(Debug, ClapArgs)] #[command(about, long_about = None)] @@ -37,9 +40,11 @@ pub async fn run(args: Args) -> anyhow::Result<()> { .manifest .as_ref() .ok_or_else(|| anyhow::anyhow!("manifest not found in deployment"))?; + let app = fetch_tvc_app(&auth, &deployment.app_id).await?; println!("Deployment: {}", deployment.id); println!("App ID: {}", deployment.app_id); + println!("{}", format_egress_enabled(app.enable_egress)); println!("Manifest ID: {}", manifest.id); println!("QOS Version: {}", deployment.qos_version); println!("{}", format_marked_for_deletion(&deployment)); @@ -67,10 +72,7 @@ pub async fn run(args: Args) -> anyhow::Result<()> { } fn format_marked_for_deletion(deployment: &TvcDeployment) -> String { - format!( - "Marked for deletion: {}", - if deployment.delete { "yes" } else { "no" } - ) + format!("Marked for deletion: {}", yes_no(deployment.delete)) } #[cfg(test)] diff --git a/tvc/src/commands/display.rs b/tvc/src/commands/display.rs new file mode 100644 index 0000000..0ee78c5 --- /dev/null +++ b/tvc/src/commands/display.rs @@ -0,0 +1,24 @@ +//! Shared display helpers for CLI output. + +pub fn yes_no(value: bool) -> &'static str { + if value { + "yes" + } else { + "no" + } +} + +pub fn format_egress_enabled(enable_egress: bool) -> String { + format!("Egress Enabled: {}", yes_no(enable_egress)) +} + +#[cfg(test)] +mod tests { + use super::format_egress_enabled; + + #[test] + fn format_egress_enabled_formats_yes_and_no() { + assert_eq!(format_egress_enabled(true), "Egress Enabled: yes"); + assert_eq!(format_egress_enabled(false), "Egress Enabled: no"); + } +} diff --git a/tvc/src/commands/mod.rs b/tvc/src/commands/mod.rs index 4cba7dd..de2f737 100644 --- a/tvc/src/commands/mod.rs +++ b/tvc/src/commands/mod.rs @@ -8,5 +8,6 @@ pub mod app; pub mod app_status; pub mod confirmation; pub mod deploy; +pub mod display; pub mod keys; pub mod login; diff --git a/tvc/src/config/app.rs b/tvc/src/config/app.rs index 0551b1d..88aa973 100644 --- a/tvc/src/config/app.rs +++ b/tvc/src/config/app.rs @@ -13,7 +13,7 @@ pub struct AppConfig { pub name: String, pub quorum_public_key: String, #[serde(default)] - pub enable_egress: Option, + pub enable_egress: bool, #[serde(default)] pub manifest_set_id: Option, #[serde(default)] @@ -63,7 +63,7 @@ impl AppConfig { Self { name: "".to_string(), quorum_public_key: KNOWN_QUORUM_KEY.to_string(), - enable_egress: Some(false), + enable_egress: false, manifest_set_id: None, manifest_set_params: Some(OperatorSetParams { name: "".to_string(), @@ -224,7 +224,14 @@ mod tests { json["enableEgress"] = json!(true); let config: AppConfig = serde_json::from_value(json).unwrap(); - assert_eq!(config.enable_egress, Some(true)); + assert!(config.enable_egress); + } + + #[test] + fn config_defaults_enable_egress_to_false() { + let config: AppConfig = serde_json::from_value(valid_config_json()).unwrap(); + + assert!(!config.enable_egress); } #[test]