Skip to content
Closed
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
4 changes: 2 additions & 2 deletions packages/toolchain/cli/src/commands/actor/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
16 changes: 7 additions & 9 deletions packages/toolchain/cli/src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ pub struct Opts {
/// Override the automatically generated version name
#[clap(long)]
version: Option<String>,

/// Automatically create routes for functions (non-interactive mode)

#[clap(long)]
auto_create_routes: Option<bool>,

/// Automatically sync existing routes with configuration (non-interactive mode)
skip_route_creation: Option<bool>,

#[clap(long)]
auto_sync_routes: Option<bool>,
keep_existing_routes: Option<bool>,

/// Run in non-interactive mode (no prompts)
#[clap(long)]
non_interactive: bool,
Expand Down Expand Up @@ -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?;
Expand Down
64 changes: 64 additions & 0 deletions packages/toolchain/cli/src/commands/function/endpoint.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

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,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Parameter name route_id is misleading since it's actually receiving a function name

Suggested change
route_id: &str,
function_name: &str,

) -> Result<Option<models::RoutesRoute>> {
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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Route lookup compares route.id with route_id but receives a function name - these may not match. Should likely compare against a different route field

.cloned();

Ok(matching_route)
}

38 changes: 38 additions & 0 deletions packages/toolchain/cli/src/commands/function/list.rs
Original file line number Diff line number Diff line change
@@ -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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: Typo in help text: 'list function for' should be 'list functions for'

Suggested change
/// Specify the environment to list function for (will prompt if not specified)
/// Specify the environment to list functions for (will prompt if not specified)

#[clap(long, alias = "env", short = 'e')]
environment: Option<String>,
}

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(())
}
}

23 changes: 23 additions & 0 deletions packages/toolchain/cli/src/commands/function/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use anyhow::*;
use clap::Parser;

pub mod endpoint;
pub mod list;

/// Commands for managing routes
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Module comment describes 'routes' but module name is 'function'. Consider updating comment or module name for consistency.

#[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,
}
}
}
12 changes: 8 additions & 4 deletions packages/toolchain/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Comment on lines +69 to +70
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The Endpoint command appears to duplicate functionality that should be under the Function subcommand. Consider moving this under Function instead of having it as a top-level command.

/// Commands for managing Rivet configuration
Config {
#[clap(subcommand)]
Expand Down Expand Up @@ -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,
Expand Down
92 changes: 51 additions & 41 deletions packages/toolchain/cli/src/util/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pub struct DeployOpts<'a> {
pub filter_tags: Option<HashMap<String, String>>,
pub build_tags: Option<HashMap<String, String>>,
pub version: Option<String>,
pub auto_create_routes: Option<bool>,
pub auto_sync_routes: Option<bool>,
pub skip_route_creation: Option<bool>,
pub keep_existing_routes: Option<bool>,
pub non_interactive: bool,
}

Expand Down Expand Up @@ -69,14 +69,15 @@ pub async fn deploy(opts: DeployOpts<'_>) -> Result<Vec<Uuid>> {

// 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);
Expand All @@ -89,23 +90,23 @@ async fn setup_function_routes(
environment: &toolchain::project::environment::TEMPEnvironment,
config: &config::Config,
filter_tags: &Option<HashMap<String, String>>,
auto_create_routes: Option<bool>,
auto_sync_routes: Option<bool>,
skip_route_creation: Option<bool>,
keep_existing_routes: Option<bool>,
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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -436,4 +445,5 @@ async fn create_function_route(
}

Ok(())
}
}

8 changes: 7 additions & 1 deletion packages/toolchain/toolchain/src/toolchain_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> {
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
Expand Down
Loading