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: 5 additions & 12 deletions crates/tower-cmd/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,8 @@ pub async fn refresh_session(

pub async fn list_teams(
config: &Config,
) -> Result<
tower_api::models::ListTeamsResponse,
Error<tower_api::apis::default_api::ListTeamsError>,
> {
) -> Result<tower_api::models::ListTeamsResponse, Error<tower_api::apis::default_api::ListTeamsError>>
{
let api_config = &config.into();

let params = tower_api::apis::default_api::ListTeamsParams {
Expand Down Expand Up @@ -1079,21 +1077,16 @@ pub async fn cancel_run(
config: &Config,
name: &str,
seq: i64,
) -> Result<
tower_api::models::CancelRunResponse,
Error<tower_api::apis::default_api::CancelRunError>,
> {
) -> Result<tower_api::models::CancelRunResponse, Error<tower_api::apis::default_api::CancelRunError>>
{
let api_config = &config.into();

let params = tower_api::apis::default_api::CancelRunParams {
name: name.to_string(),
seq,
};

unwrap_api_response(tower_api::apis::default_api::cancel_run(
api_config, params,
))
.await
unwrap_api_response(tower_api::apis::default_api::cancel_run(api_config, params)).await
}

impl ResponseEntity for tower_api::apis::default_api::CancelRunSuccess {
Expand Down
38 changes: 29 additions & 9 deletions crates/tower-cmd/src/apps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ pub fn apps_cmd() -> Command {
}

pub async fn do_logs(config: Config, cmd: &ArgMatches) {
let app_name_raw = cmd.get_one::<String>("app_name").expect("app_name is required");
let app_name_raw = cmd
.get_one::<String>("app_name")
.expect("app_name is required");
let (name, seq) = if let Some((name, num_str)) = app_name_raw.split_once('#') {
let num = num_str.parse::<i64>().unwrap_or_else(|_| output::die("Run number must be a number"));
let num = num_str
.parse::<i64>()
.unwrap_or_else(|_| output::die("Run number must be a number"));
(name.to_string(), num)
} else {
let num = match cmd.get_one::<i64>("run_number").copied() {
Expand All @@ -123,7 +127,9 @@ pub async fn do_logs(config: Config, cmd: &ArgMatches) {
}

pub async fn do_show(config: Config, cmd: &ArgMatches) {
let name = cmd.get_one::<String>("app_name").expect("app_name is required");
let name = cmd
.get_one::<String>("app_name")
.expect("app_name is required");

match api::describe_app(&config, &name).await {
Ok(app_response) => {
Expand Down Expand Up @@ -236,7 +242,9 @@ pub async fn do_create(config: Config, args: &ArgMatches) {
}

pub async fn do_delete(config: Config, cmd: &ArgMatches) {
let name = cmd.get_one::<String>("app_name").expect("app_name is required");
let name = cmd
.get_one::<String>("app_name")
.expect("app_name is required");

output::with_spinner("Deleting app", api::delete_app(&config, name)).await;
}
Expand All @@ -263,7 +271,8 @@ pub async fn do_cancel(config: Config, cmd: &ArgMatches) {

async fn latest_run_number(config: &Config, name: &str) -> i64 {
match api::describe_app(config, name).await {
Ok(resp) => resp.runs
Ok(resp) => resp
.runs
.iter()
.map(|r| r.number)
.max()
Expand Down Expand Up @@ -307,7 +316,9 @@ async fn follow_logs(config: Config, name: String, seq: i64) {
sleep(RUN_START_POLL_INTERVAL).await;

if wait_started.elapsed() > RUN_START_TIMEOUT {
output::error("Timed out waiting for run to start. The runner may be unavailable.");
output::error(
"Timed out waiting for run to start. The runner may be unavailable.",
);
return;
}

Expand Down Expand Up @@ -585,7 +596,9 @@ mod tests {
assert_eq!(cmd, "logs");
assert_eq!(sub_matches.get_one::<bool>("follow"), Some(&true));
assert_eq!(
sub_matches.get_one::<String>("app_name").map(|s| s.as_str()),
sub_matches
.get_one::<String>("app_name")
.map(|s| s.as_str()),
Some("hello-world#11")
);
assert_eq!(sub_matches.get_one::<i64>("run_number"), None);
Expand All @@ -599,15 +612,22 @@ mod tests {
let (_, sub_matches) = matches.subcommand().unwrap();

assert_eq!(
sub_matches.get_one::<String>("app_name").map(|s| s.as_str()),
sub_matches
.get_one::<String>("app_name")
.map(|s| s.as_str()),
Some("hello-world")
);
assert_eq!(sub_matches.get_one::<i64>("run_number"), Some(&11));
}

#[test]
fn test_terminal_statuses_explicit() {
let non_terminal = [Status::Scheduled, Status::Pending, Status::Running, Status::Retrying];
let non_terminal = [
Status::Scheduled,
Status::Pending,
Status::Running,
Status::Retrying,
];
for status in non_terminal {
let run = Run {
status,
Expand Down
7 changes: 3 additions & 4 deletions crates/tower-cmd/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,11 @@ pub async fn deploy_from_dir(
let path = dir.join("Towerfile");

let path_display = path.display().to_string();
let towerfile = Towerfile::from_path(path).map_err(|source| {
crate::Error::TowerfileLoadFailed {
let towerfile =
Towerfile::from_path(path).map_err(|source| crate::Error::TowerfileLoadFailed {
path: path_display,
source,
}
})?;
})?;
let api_config = config.into();

// Add app existence check before proceeding
Expand Down
28 changes: 17 additions & 11 deletions crates/tower-cmd/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,12 +859,15 @@ impl TowerService {
}
let name = request.name.clone();
Self::modify_towerfile(&request.common, |tf| {
tf.set_parameter(&name, Parameter {
name: name.clone(),
description: request.description.unwrap_or_default(),
default: request.default.unwrap_or_default(),
hidden: request.hidden,
});
tf.set_parameter(
&name,
Parameter {
name: name.clone(),
description: request.description.unwrap_or_default(),
default: request.default.unwrap_or_default(),
hidden: request.hidden,
},
);
Ok(format!("Added parameter '{name}'"))
})
}
Expand All @@ -878,7 +881,10 @@ impl TowerService {
) -> Result<CallToolResult, McpError> {
let name = request.name.clone();
Self::modify_towerfile(&request.common, |tf| {
let existing = tf.parameters.iter().find(|p| p.name == name)
let existing = tf
.parameters
.iter()
.find(|p| p.name == name)
.ok_or_else(|| format!("Parameter '{name}' not found"))?;
let target_name = request
.new_name
Expand All @@ -889,7 +895,9 @@ impl TowerService {
}
let param = Parameter {
name: target_name,
description: request.description.unwrap_or_else(|| existing.description.clone()),
description: request
.description
.unwrap_or_else(|| existing.description.clone()),
default: request.default.unwrap_or_else(|| existing.default.clone()),
hidden: request.hidden.unwrap_or(existing.hidden),
};
Expand All @@ -901,9 +909,7 @@ impl TowerService {
})
}

#[tool(
description = "Remove a parameter from the Towerfile. Optional: working_directory."
)]
#[tool(description = "Remove a parameter from the Towerfile. Optional: working_directory.")]
async fn tower_file_remove_parameter(
&self,
Parameters(request): Parameters<RemoveParameterRequest>,
Expand Down
30 changes: 21 additions & 9 deletions crates/tower-cmd/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,7 @@ pub async fn do_run(config: Config, args: &ArgMatches) {

/// do_run is the primary entrypoint into running apps both locally and remotely in Tower. It will
/// use the configuration to determine the requested way of running a Tower app.
pub async fn do_run_inner(
config: Config,
args: &ArgMatches,
) -> Result<(), Error> {
pub async fn do_run_inner(config: Config, args: &ArgMatches) -> Result<(), Error> {
let res = get_run_parameters(args);

// We always expect there to be an environment due to the fact that there is a
Expand Down Expand Up @@ -855,7 +852,10 @@ mod tests {
#[test]
fn app_name_parsed_as_positional_arg() {
let m = parse(&["my-app"]).unwrap();
assert_eq!(m.get_one::<String>("app_name").map(|s| s.as_str()), Some("my-app"));
assert_eq!(
m.get_one::<String>("app_name").map(|s| s.as_str()),
Some("my-app")
);
}

#[test]
Expand All @@ -868,8 +868,14 @@ mod tests {
fn unknown_flags_are_rejected() {
let err = parse(&["--param", "x=y"]).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("--param"), "should mention the bad flag: {msg}");
assert!(msg.contains("--parameter"), "should suggest the correct flag: {msg}");
assert!(
msg.contains("--param"),
"should mention the bad flag: {msg}"
);
assert!(
msg.contains("--parameter"),
"should suggest the correct flag: {msg}"
);
}

#[test]
Expand All @@ -887,15 +893,21 @@ mod tests {
#[test]
fn parameters_after_app_name() {
let m = parse(&["my-app", "-p", "key=val"]).unwrap();
assert_eq!(m.get_one::<String>("app_name").map(|s| s.as_str()), Some("my-app"));
assert_eq!(
m.get_one::<String>("app_name").map(|s| s.as_str()),
Some("my-app")
);
let params: Vec<&String> = m.get_many::<String>("parameters").unwrap().collect();
assert_eq!(params, vec!["key=val"]);
}

#[test]
fn parameters_before_app_name() {
let m = parse(&["-p", "key=val", "my-app"]).unwrap();
assert_eq!(m.get_one::<String>("app_name").map(|s| s.as_str()), Some("my-app"));
assert_eq!(
m.get_one::<String>("app_name").map(|s| s.as_str()),
Some("my-app")
);
let params: Vec<&String> = m.get_many::<String>("parameters").unwrap().collect();
assert_eq!(params, vec!["key=val"]);
}
Expand Down
8 changes: 6 additions & 2 deletions crates/tower-cmd/src/schedules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ pub async fn do_create(config: Config, args: &ArgMatches) {
}

pub async fn do_update(config: Config, args: &ArgMatches) {
let id_or_name = args.get_one::<String>("id_or_name").expect("id_or_name is required");
let id_or_name = args
.get_one::<String>("id_or_name")
.expect("id_or_name is required");
let cron = args.get_one::<String>("cron");
let parameters = parse_parameters(args);

Expand All @@ -185,7 +187,9 @@ pub async fn do_update(config: Config, args: &ArgMatches) {
}

pub async fn do_delete(config: Config, args: &ArgMatches) {
let schedule_id = args.get_one::<String>("schedule_id").expect("schedule_id is required");
let schedule_id = args
.get_one::<String>("schedule_id")
.expect("schedule_id is required");

output::with_spinner(
"Deleting schedule",
Expand Down
9 changes: 6 additions & 3 deletions crates/tower-cmd/src/secrets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,15 @@ pub async fn do_create(config: Config, args: &ArgMatches) {
}

pub async fn do_delete(config: Config, args: &ArgMatches) {
let secret_name_arg = args.get_one::<String>("secret_name").expect("secret_name is required");
let secret_name_arg = args
.get_one::<String>("secret_name")
.expect("secret_name is required");
let (environment, name) = if let Some((env, name)) = secret_name_arg.split_once('/') {
(env.to_string(), name.to_string())
} else {
let env = args.get_one::<String>("environment").expect("environment has default");
let env = args
.get_one::<String>("environment")
.expect("environment has default");
(env.clone(), secret_name_arg.clone())
};
debug!("deleting secret, environment={} name={}", environment, name);
Expand Down Expand Up @@ -240,4 +244,3 @@ async fn encrypt_and_create_secret(
.await
.map_err(SecretCreationError::CreateFailed)
}

3 changes: 2 additions & 1 deletion crates/tower-cmd/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub async fn do_login(config: Config, args: &ArgMatches) {

eprint!("Do you want to continue? [y/N] ");
let mut input = String::new();
if std::io::stdin().read_line(&mut input).is_err() || !input.trim().eq_ignore_ascii_case("y")
if std::io::stdin().read_line(&mut input).is_err()
|| !input.trim().eq_ignore_ascii_case("y")
{
return;
}
Expand Down
17 changes: 3 additions & 14 deletions crates/tower-cmd/src/skill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ fn generate_skill_md(root: Command) -> String {
}

fn append_command(out: &mut String, cmd: &Command, path: &[&str], depth: usize) {
let subcommands: Vec<_> = cmd
.get_subcommands()
.filter(|c| !c.is_hide_set())
.collect();
let subcommands: Vec<_> = cmd.get_subcommands().filter(|c| !c.is_hide_set()).collect();

for sub in &subcommands {
let name = sub.get_name();
Expand Down Expand Up @@ -71,12 +68,7 @@ fn append_command(out: &mut String, cmd: &Command, path: &[&str], depth: usize)
.get_help()
.map(|h| format!(" — {}", h))
.unwrap_or_default();
out.push_str(&format!(
"- `<{}>` {}{}\n",
arg.get_id(),
req,
help
));
out.push_str(&format!("- `<{}>` {}{}\n", arg.get_id(), req, help));
}
for arg in &named {
let long = arg.get_long().unwrap();
Expand All @@ -98,10 +90,7 @@ fn append_command(out: &mut String, cmd: &Command, path: &[&str], depth: usize)
out.push('\n');
}

let child_subs: Vec<_> = sub
.get_subcommands()
.filter(|c| !c.is_hide_set())
.collect();
let child_subs: Vec<_> = sub.get_subcommands().filter(|c| !c.is_hide_set()).collect();

if !child_subs.is_empty() {
append_command(out, sub, &full_path, depth + 1);
Expand Down
5 changes: 3 additions & 2 deletions crates/tower-cmd/src/teams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ async fn do_list_via_session(config: &Config) {
}

pub async fn do_switch(config: Config, args: &ArgMatches) {
let name = args.get_one::<String>("team_name").expect("team_name is required");
let name = args
.get_one::<String>("team_name")
.expect("team_name is required");

// Refresh the session first to ensure we have the latest teams data
let session = refresh_session(&config).await;
Expand Down Expand Up @@ -140,4 +142,3 @@ pub async fn do_switch(config: Config, args: &ArgMatches) {
}
}
}

Loading
Loading