diff --git a/packages/toolchain/cli/src/commands/actor/create.rs b/packages/toolchain/cli/src/commands/actor/create.rs index 20fc8bc870..0e5273b079 100644 --- a/packages/toolchain/cli/src/commands/actor/create.rs +++ b/packages/toolchain/cli/src/commands/actor/create.rs @@ -200,8 +200,8 @@ impl Opts { filter_tags: None, build_tags: Some(build_tags), version: self.version.clone(), - auto_create_routes: None, - auto_sync_routes: None, + skip_route_creation: None, + keep_existing_routes: None, non_interactive: false, }) .await?; diff --git a/packages/toolchain/cli/src/commands/deploy.rs b/packages/toolchain/cli/src/commands/deploy.rs index a10a24f510..566d68cc0b 100644 --- a/packages/toolchain/cli/src/commands/deploy.rs +++ b/packages/toolchain/cli/src/commands/deploy.rs @@ -20,15 +20,13 @@ pub struct Opts { /// Override the automatically generated version name #[clap(long)] version: Option, - - /// Automatically create routes for functions (non-interactive mode) + #[clap(long)] - auto_create_routes: Option, - - /// Automatically sync existing routes with configuration (non-interactive mode) + skip_route_creation: Option, + #[clap(long)] - auto_sync_routes: Option, - + keep_existing_routes: Option, + /// Run in non-interactive mode (no prompts) #[clap(long)] non_interactive: bool, @@ -60,8 +58,8 @@ impl Opts { filter_tags: filter_tags, build_tags: build_tags, version: self.version.clone(), - auto_create_routes: self.auto_create_routes, - auto_sync_routes: self.auto_sync_routes, + skip_route_creation: self.skip_route_creation, + keep_existing_routes: self.keep_existing_routes, non_interactive: self.non_interactive, }) .await?; diff --git a/packages/toolchain/cli/src/commands/function/endpoint.rs b/packages/toolchain/cli/src/commands/function/endpoint.rs new file mode 100644 index 0000000000..f3c13bdcab --- /dev/null +++ b/packages/toolchain/cli/src/commands/function/endpoint.rs @@ -0,0 +1,64 @@ +use anyhow::*; +use clap::Parser; +use toolchain::{ + rivet_api::{apis, models}, + ToolchainCtx, +}; + +/// Get information about a route endpoint +#[derive(Parser, Clone)] +pub struct Opts { + /// Name of the route to retrieve information about + function_name: String, + + /// Specify the environment to get the route from (will prompt if not specified) + #[clap(long, alias = "env", short = 'e')] + environment: Option, +} + +impl Opts { + pub async fn execute(&self) -> Result<()> { + let ctx = crate::util::login::load_or_login().await?; + let env = crate::util::env::get_or_select(&ctx, self.environment.as_ref()).await?; + + // Get route information + let route = get_route(&ctx, &env, &self.function_name).await?; + + match route { + Some(route) => { + println!("https://{}{}", route.hostname, route.path); + + Ok(()) + } + None => Err(anyhow!( + "Route '{}' not found in environment '{}'", + self.function_name, + env + )), + } + } +} + +// Helper function to get route if it exists +async fn get_route( + ctx: &ToolchainCtx, + env: &str, + route_id: &str, +) -> Result> { + let routes_response = apis::routes_api::routes_list( + &ctx.openapi_config_cloud, + Some(&ctx.project.name_id.to_string()), + Some(env), + ) + .await?; + + // Find route that matches the ID + let matching_route = routes_response + .routes + .iter() + .find(|route| route.id == *route_id) + .cloned(); + + Ok(matching_route) +} + diff --git a/packages/toolchain/cli/src/commands/function/list.rs b/packages/toolchain/cli/src/commands/function/list.rs new file mode 100644 index 0000000000..45a8a81689 --- /dev/null +++ b/packages/toolchain/cli/src/commands/function/list.rs @@ -0,0 +1,38 @@ +use anyhow::*; +use clap::Parser; +use toolchain::rivet_api::apis; + +/// List all functions for an environment +#[derive(Parser)] +pub struct Opts { + /// Specify the environment to list function for (will prompt if not specified) + #[clap(long, alias = "env", short = 'e')] + environment: Option, +} + +impl Opts { + pub async fn execute(&self) -> Result<()> { + let ctx = crate::util::login::load_or_login().await?; + let env = crate::util::env::get_or_select(&ctx, self.environment.as_ref()).await?; + + // Get routes + let routes_response = apis::routes_api::routes_list( + &ctx.openapi_config_cloud, + Some(&ctx.project.name_id.to_string()), + Some(&env), + ) + .await?; + + if routes_response.routes.is_empty() { + println!("No routes found for environment '{}'", env); + return Ok(()); + } + + for route in routes_response.routes { + println!("- {}: {}{}", route.id, route.hostname, route.path); + } + + Ok(()) + } +} + diff --git a/packages/toolchain/cli/src/commands/function/mod.rs b/packages/toolchain/cli/src/commands/function/mod.rs new file mode 100644 index 0000000000..51e890e9cc --- /dev/null +++ b/packages/toolchain/cli/src/commands/function/mod.rs @@ -0,0 +1,23 @@ +use anyhow::*; +use clap::Parser; + +pub mod endpoint; +pub mod list; + +/// Commands for managing routes +#[derive(Parser)] +pub enum SubCommand { + /// List all routes + List(list::Opts), + /// Get information about a specific route endpoint + Endpoint(endpoint::Opts), +} + +impl SubCommand { + pub async fn execute(&self) -> Result<()> { + match self { + SubCommand::List(opts) => opts.execute().await, + SubCommand::Endpoint(opts) => opts.execute().await, + } + } +} diff --git a/packages/toolchain/cli/src/commands/mod.rs b/packages/toolchain/cli/src/commands/mod.rs index 19d4c92cb0..f0d22858be 100644 --- a/packages/toolchain/cli/src/commands/mod.rs +++ b/packages/toolchain/cli/src/commands/mod.rs @@ -4,6 +4,7 @@ pub mod config; pub mod deno; pub mod deploy; pub mod environment; +pub mod function; pub mod login; pub mod logout; pub mod metadata; @@ -60,11 +61,13 @@ pub enum SubCommand { subcommand: region::SubCommand, }, /// Commands for managing routes - #[clap(alias = "r", alias = "endpoint")] - Route { + #[clap(alias = "f", alias = "func", alias = "route")] + Function { #[clap(subcommand)] - subcommand: route::SubCommand, + subcommand: function::SubCommand, }, + /// Get information about a specific route endpoint + Endpoint(function::endpoint::Opts), /// Commands for managing Rivet configuration Config { #[clap(subcommand)] @@ -102,7 +105,8 @@ impl SubCommand { SubCommand::Actor { subcommand } => subcommand.execute().await, SubCommand::Build { subcommand } => subcommand.execute().await, SubCommand::Region { subcommand } => subcommand.execute().await, - SubCommand::Route { subcommand } => subcommand.execute().await, + SubCommand::Function { subcommand } => subcommand.execute().await, + SubCommand::Endpoint(opts) => opts.execute().await, SubCommand::Config { subcommand } => subcommand.execute().await, SubCommand::Metadata { subcommand } => subcommand.execute().await, SubCommand::Deno(opts) => opts.execute().await, diff --git a/packages/toolchain/cli/src/util/deploy.rs b/packages/toolchain/cli/src/util/deploy.rs index db376cffa0..c3e8eecfa1 100644 --- a/packages/toolchain/cli/src/util/deploy.rs +++ b/packages/toolchain/cli/src/util/deploy.rs @@ -19,8 +19,8 @@ pub struct DeployOpts<'a> { pub filter_tags: Option>, pub build_tags: Option>, pub version: Option, - pub auto_create_routes: Option, - pub auto_sync_routes: Option, + pub skip_route_creation: Option, + pub keep_existing_routes: Option, pub non_interactive: bool, } @@ -69,14 +69,15 @@ pub async fn deploy(opts: DeployOpts<'_>) -> Result> { // Setup function routes setup_function_routes( - opts.ctx, - environment, - &config, + opts.ctx, + environment, + &config, &opts.filter_tags, - opts.auto_create_routes, - opts.auto_sync_routes, - opts.non_interactive - ).await?; + opts.skip_route_creation, + opts.keep_existing_routes, + opts.non_interactive, + ) + .await?; // Print summary print_summary(opts.ctx, environment); @@ -89,23 +90,23 @@ async fn setup_function_routes( environment: &toolchain::project::environment::TEMPEnvironment, config: &config::Config, filter_tags: &Option>, - auto_create_routes: Option, - auto_sync_routes: Option, + skip_route_creation: Option, + keep_existing_routes: Option, non_interactive: bool, ) -> Result<()> { - // Determine default hostname based on project & env - let default_hostname = format!( - "{}-{}.{}", - ctx.project.name_id, - environment.slug, - ctx.bootstrap - .domains - .job - .as_ref() - .context("bootstrap.domains.job")? - ); - for (fn_name, function) in &config.functions { + // Determine default hostname based on project & env + let default_hostname = format!( + "{}-{}-{fn_name}.{}", + ctx.project.name_id, + environment.slug, + ctx.bootstrap + .domains + .job + .as_ref() + .context("bootstrap.domains.job")? + ); + // TODO: Convert this in to a shared fn // Filter out builds that match the tags if let Some(filter) = &filter_tags { @@ -204,20 +205,22 @@ async fn setup_function_routes( ]; println!(); - + let choice_index = if non_interactive { // In non-interactive mode, use auto_sync_routes if provided, otherwise sync by default - if let Some(auto_sync) = auto_sync_routes { - if auto_sync { - println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)"); - 0 // Sync route with config - } else { + match keep_existing_routes { + Some(true) => { println!("Skipping route sync for '{fn_name}' (non-interactive mode)"); 1 // Keep existing route } - } else { - println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)"); - 0 // Default to sync in non-interactive mode + Some(false) => { + println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)"); + 0 // Sync route with config + } + None => { + println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)"); + 0 // Default to sync in non-interactive mode + } } } else { // Interactive mode - prompt the user @@ -304,17 +307,23 @@ async fn setup_function_routes( let choice_index = if non_interactive { // In non-interactive mode, use auto_create_routes if provided, otherwise create by default - if let Some(auto_create) = auto_create_routes { - if auto_create { - println!("Auto-creating route for function '{fn_name}' (non-interactive mode)"); - 0 // Create default route - } else { + match skip_route_creation { + Some(true) => { println!("Skipping route creation for '{fn_name}' (non-interactive mode)"); 1 // Skip route creation } - } else { - println!("Auto-creating route for function '{fn_name}' (non-interactive mode)"); - 0 // Default to create in non-interactive mode + Some(false) => { + println!( + "Auto-creating route for function '{fn_name}' (non-interactive mode)" + ); + 0 // Create default route + } + None => { + println!( + "Auto-creating route for function '{fn_name}' (non-interactive mode)" + ); + 0 // Default to create in non-interactive mode + } } } else { // Interactive mode - prompt the user @@ -436,4 +445,5 @@ async fn create_function_route( } Ok(()) -} \ No newline at end of file +} + diff --git a/packages/toolchain/toolchain/src/toolchain_ctx.rs b/packages/toolchain/toolchain/src/toolchain_ctx.rs index 8a850171e7..55fe1c8d63 100644 --- a/packages/toolchain/toolchain/src/toolchain_ctx.rs +++ b/packages/toolchain/toolchain/src/toolchain_ctx.rs @@ -50,7 +50,13 @@ pub fn cloud_config_from_env() -> Option<(String, String)> { /// If the credentials already exist or loading credentials from env. pub async fn has_cloud_config() -> Result { - if cloud_config_from_env().is_some() { + if let Some((api_endpoint, token)) = cloud_config_from_env() { + meta::mutate_project(&paths::data_dir()?, |meta| { + if meta.cloud.is_none() { + meta.cloud = Some(meta::Cloud::new(api_endpoint, token)) + } + }) + .await?; Ok(true) } else { meta::read_project(&paths::data_dir()?, |x| x.cloud.is_some()).await