From 8f01b299528f940a566c57801ae65ea10490380c Mon Sep 17 00:00:00 2001 From: Vim <121349594+sammuti@users.noreply.github.com> Date: Tue, 5 May 2026 07:34:25 -0600 Subject: [PATCH 01/10] Remove debug logging from OpenTelemetry sdk (#273) --- crates/tower-cmd/src/api.rs | 17 +++---- crates/tower-cmd/src/apps.rs | 38 ++++++++++++---- crates/tower-cmd/src/deploy.rs | 7 ++- crates/tower-cmd/src/mcp.rs | 28 +++++++----- crates/tower-cmd/src/run.rs | 30 +++++++++---- crates/tower-cmd/src/schedules.rs | 8 +++- crates/tower-cmd/src/secrets.rs | 9 ++-- crates/tower-cmd/src/session.rs | 3 +- crates/tower-cmd/src/skill.rs | 17 ++----- crates/tower-cmd/src/teams.rs | 5 ++- crates/tower-cmd/src/util/deploy.rs | 12 +++-- crates/tower-package/src/core.rs | 30 +++++++++---- crates/tower-package/src/native.rs | 18 ++++++-- crates/tower-package/src/towerfile.rs | 52 +++++++++++++--------- crates/tower-package/src/wasm.rs | 6 +-- crates/tower-package/tests/package_test.rs | 39 +++++++++++----- crates/tower-runtime/src/lib.rs | 5 ++- crates/tower-telemetry/src/logging.rs | 9 +++- 18 files changed, 212 insertions(+), 121 deletions(-) diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index 0297bf11..8f0956b6 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -404,10 +404,8 @@ pub async fn refresh_session( pub async fn list_teams( config: &Config, -) -> Result< - tower_api::models::ListTeamsResponse, - Error, -> { +) -> Result> +{ let api_config = &config.into(); let params = tower_api::apis::default_api::ListTeamsParams { @@ -1079,10 +1077,8 @@ pub async fn cancel_run( config: &Config, name: &str, seq: i64, -) -> Result< - tower_api::models::CancelRunResponse, - Error, -> { +) -> Result> +{ let api_config = &config.into(); let params = tower_api::apis::default_api::CancelRunParams { @@ -1090,10 +1086,7 @@ pub async fn cancel_run( 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 { diff --git a/crates/tower-cmd/src/apps.rs b/crates/tower-cmd/src/apps.rs index ab632620..c6210dab 100644 --- a/crates/tower-cmd/src/apps.rs +++ b/crates/tower-cmd/src/apps.rs @@ -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::("app_name").expect("app_name is required"); + let app_name_raw = cmd + .get_one::("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::().unwrap_or_else(|_| output::die("Run number must be a number")); + let num = num_str + .parse::() + .unwrap_or_else(|_| output::die("Run number must be a number")); (name.to_string(), num) } else { let num = match cmd.get_one::("run_number").copied() { @@ -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::("app_name").expect("app_name is required"); + let name = cmd + .get_one::("app_name") + .expect("app_name is required"); match api::describe_app(&config, &name).await { Ok(app_response) => { @@ -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::("app_name").expect("app_name is required"); + let name = cmd + .get_one::("app_name") + .expect("app_name is required"); output::with_spinner("Deleting app", api::delete_app(&config, name)).await; } @@ -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() @@ -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; } @@ -585,7 +596,9 @@ mod tests { assert_eq!(cmd, "logs"); assert_eq!(sub_matches.get_one::("follow"), Some(&true)); assert_eq!( - sub_matches.get_one::("app_name").map(|s| s.as_str()), + sub_matches + .get_one::("app_name") + .map(|s| s.as_str()), Some("hello-world#11") ); assert_eq!(sub_matches.get_one::("run_number"), None); @@ -599,7 +612,9 @@ mod tests { let (_, sub_matches) = matches.subcommand().unwrap(); assert_eq!( - sub_matches.get_one::("app_name").map(|s| s.as_str()), + sub_matches + .get_one::("app_name") + .map(|s| s.as_str()), Some("hello-world") ); assert_eq!(sub_matches.get_one::("run_number"), Some(&11)); @@ -607,7 +622,12 @@ mod tests { #[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, diff --git a/crates/tower-cmd/src/deploy.rs b/crates/tower-cmd/src/deploy.rs index 069c1ca9..c5328f38 100644 --- a/crates/tower-cmd/src/deploy.rs +++ b/crates/tower-cmd/src/deploy.rs @@ -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 diff --git a/crates/tower-cmd/src/mcp.rs b/crates/tower-cmd/src/mcp.rs index d46ba454..0b069356 100644 --- a/crates/tower-cmd/src/mcp.rs +++ b/crates/tower-cmd/src/mcp.rs @@ -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}'")) }) } @@ -878,7 +881,10 @@ impl TowerService { ) -> Result { 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 @@ -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), }; @@ -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, diff --git a/crates/tower-cmd/src/run.rs b/crates/tower-cmd/src/run.rs index 64f6cd5d..9ad5a189 100644 --- a/crates/tower-cmd/src/run.rs +++ b/crates/tower-cmd/src/run.rs @@ -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 @@ -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::("app_name").map(|s| s.as_str()), Some("my-app")); + assert_eq!( + m.get_one::("app_name").map(|s| s.as_str()), + Some("my-app") + ); } #[test] @@ -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] @@ -887,7 +893,10 @@ mod tests { #[test] fn parameters_after_app_name() { let m = parse(&["my-app", "-p", "key=val"]).unwrap(); - assert_eq!(m.get_one::("app_name").map(|s| s.as_str()), Some("my-app")); + assert_eq!( + m.get_one::("app_name").map(|s| s.as_str()), + Some("my-app") + ); let params: Vec<&String> = m.get_many::("parameters").unwrap().collect(); assert_eq!(params, vec!["key=val"]); } @@ -895,7 +904,10 @@ mod tests { #[test] fn parameters_before_app_name() { let m = parse(&["-p", "key=val", "my-app"]).unwrap(); - assert_eq!(m.get_one::("app_name").map(|s| s.as_str()), Some("my-app")); + assert_eq!( + m.get_one::("app_name").map(|s| s.as_str()), + Some("my-app") + ); let params: Vec<&String> = m.get_many::("parameters").unwrap().collect(); assert_eq!(params, vec!["key=val"]); } diff --git a/crates/tower-cmd/src/schedules.rs b/crates/tower-cmd/src/schedules.rs index 59562cf2..cb6aa499 100644 --- a/crates/tower-cmd/src/schedules.rs +++ b/crates/tower-cmd/src/schedules.rs @@ -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::("id_or_name").expect("id_or_name is required"); + let id_or_name = args + .get_one::("id_or_name") + .expect("id_or_name is required"); let cron = args.get_one::("cron"); let parameters = parse_parameters(args); @@ -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::("schedule_id").expect("schedule_id is required"); + let schedule_id = args + .get_one::("schedule_id") + .expect("schedule_id is required"); output::with_spinner( "Deleting schedule", diff --git a/crates/tower-cmd/src/secrets.rs b/crates/tower-cmd/src/secrets.rs index e6a95393..f6baf5bd 100644 --- a/crates/tower-cmd/src/secrets.rs +++ b/crates/tower-cmd/src/secrets.rs @@ -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::("secret_name").expect("secret_name is required"); + let secret_name_arg = args + .get_one::("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::("environment").expect("environment has default"); + let env = args + .get_one::("environment") + .expect("environment has default"); (env.clone(), secret_name_arg.clone()) }; debug!("deleting secret, environment={} name={}", environment, name); @@ -240,4 +244,3 @@ async fn encrypt_and_create_secret( .await .map_err(SecretCreationError::CreateFailed) } - diff --git a/crates/tower-cmd/src/session.rs b/crates/tower-cmd/src/session.rs index 409d06cd..32d88987 100644 --- a/crates/tower-cmd/src/session.rs +++ b/crates/tower-cmd/src/session.rs @@ -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; } diff --git a/crates/tower-cmd/src/skill.rs b/crates/tower-cmd/src/skill.rs index 13caca60..be1ee7b7 100644 --- a/crates/tower-cmd/src/skill.rs +++ b/crates/tower-cmd/src/skill.rs @@ -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(); @@ -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(); @@ -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); diff --git a/crates/tower-cmd/src/teams.rs b/crates/tower-cmd/src/teams.rs index d0cc2413..abeaa666 100644 --- a/crates/tower-cmd/src/teams.rs +++ b/crates/tower-cmd/src/teams.rs @@ -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::("team_name").expect("team_name is required"); + let name = args + .get_one::("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; @@ -140,4 +142,3 @@ pub async fn do_switch(config: Config, args: &ArgMatches) { } } } - diff --git a/crates/tower-cmd/src/util/deploy.rs b/crates/tower-cmd/src/util/deploy.rs index 5a6d3619..23c56a3c 100644 --- a/crates/tower-cmd/src/util/deploy.rs +++ b/crates/tower-cmd/src/util/deploy.rs @@ -8,8 +8,8 @@ use tower_package::{compute_sha256_file, Package}; use tower_telemetry::debug; use tower_api::apis::configuration::Configuration; -use tower_api::apis::urlencode; use tower_api::apis::default_api::DeployAppError; +use tower_api::apis::urlencode; use tower_api::apis::Error; use tower_api::apis::ResponseContent; use tower_api::models::DeployAppResponse; @@ -122,10 +122,16 @@ pub async fn deploy_app_package( let encoded_app_name = urlencode(app_name); let url = if all_environments { - format!("{}/apps/{}/deploy?all_environments=true", base_url, encoded_app_name) + format!( + "{}/apps/{}/deploy?all_environments=true", + base_url, encoded_app_name + ) } else if let Some(env) = environment { let encoded_environment = urlencode(env); - format!("{}/apps/{}/deploy?environment={}", base_url, encoded_app_name, encoded_environment) + format!( + "{}/apps/{}/deploy?environment={}", + base_url, encoded_app_name, encoded_environment + ) } else { format!("{}/apps/{}/deploy", base_url, encoded_app_name) }; diff --git a/crates/tower-package/src/core.rs b/crates/tower-package/src/core.rs index ed35e264..b376f83a 100644 --- a/crates/tower-package/src/core.rs +++ b/crates/tower-package/src/core.rs @@ -124,11 +124,10 @@ pub struct BuiltPackage { // normalized (mtime/uid/gid zero, mode 0644) so the output is byte-deterministic for a given // input. pub fn build_package(inputs: PackageInputs) -> Result { - let towerfile_str = std::str::from_utf8(&inputs.towerfile_bytes).map_err(|e| { - Error::InvalidTowerfile { + let towerfile_str = + std::str::from_utf8(&inputs.towerfile_bytes).map_err(|e| Error::InvalidTowerfile { message: format!("Towerfile is not valid UTF-8: {}", e), - } - })?; + })?; let towerfile = Towerfile::from_toml(towerfile_str)?; let import_paths: Vec = towerfile @@ -138,14 +137,18 @@ pub fn build_package(inputs: PackageInputs) -> Result { .map(|p| format!("modules/{}", import_path_basename(&p.to_string_lossy()))) .collect(); - let mut entries: Vec = Vec::with_capacity(inputs.app_files.len() + inputs.module_files.len()); + let mut entries: Vec = + Vec::with_capacity(inputs.app_files.len() + inputs.module_files.len()); entries.extend(inputs.app_files); entries.extend(inputs.module_files); entries.sort_by(|a, b| a.archive_name.cmp(&b.archive_name)); let mut path_hashes: HashMap = HashMap::with_capacity(entries.len()); for entry in &entries { - path_hashes.insert(entry.archive_name.clone(), compute_sha256_bytes(&entry.bytes)); + path_hashes.insert( + entry.archive_name.clone(), + compute_sha256_bytes(&entry.bytes), + ); } let manifest = Manifest { @@ -256,15 +259,24 @@ mod test { .join("path") .join("to") .join("file.txt"); - assert_eq!(normalize_path(&path).unwrap(), "some/nested/path/to/file.txt"); + assert_eq!( + normalize_path(&path).unwrap(), + "some/nested/path/to/file.txt" + ); } #[test] fn test_build_package_is_deterministic() { let inputs = || PackageInputs { app_files: vec![ - Entry { archive_name: "app/b.py".into(), bytes: b"b".to_vec() }, - Entry { archive_name: "app/a.py".into(), bytes: b"a".to_vec() }, + Entry { + archive_name: "app/b.py".into(), + bytes: b"b".to_vec(), + }, + Entry { + archive_name: "app/a.py".into(), + bytes: b"a".to_vec(), + }, ], module_files: vec![], towerfile_bytes: b"[app]\nname = \"x\"\nscript = \"app/a.py\"\n".to_vec(), diff --git a/crates/tower-package/src/native.rs b/crates/tower-package/src/native.rs index e7a6d69a..0db79b38 100644 --- a/crates/tower-package/src/native.rs +++ b/crates/tower-package/src/native.rs @@ -150,7 +150,10 @@ impl Package { let archive_path = app_dir.join(logical_path); let archive_name = normalize_path(&archive_path)?; let bytes = tokio::fs::read(&physical_path).await?; - app_files.push(Entry { archive_name, bytes }); + app_files.push(Entry { + archive_name, + bytes, + }); } // Resolve modules. Archive names use the raw import_path basename so they stay in sync @@ -158,9 +161,13 @@ impl Package { let module_dir = PathBuf::from("modules"); let mut module_files: Vec = Vec::new(); - for (raw_import, canonical_import) in spec.import_paths.iter().zip(canonical_import_paths.iter()) { + for (raw_import, canonical_import) in + spec.import_paths.iter().zip(canonical_import_paths.iter()) + { let mut module_file_paths: HashMap = HashMap::new(); - resolver.resolve_path(canonical_import, &mut module_file_paths).await; + resolver + .resolve_path(canonical_import, &mut module_file_paths) + .await; let raw_basename = Path::new(raw_import) .file_name() @@ -175,7 +182,10 @@ impl Package { }; let archive_name = normalize_path(&archive_prefix.join(rel))?; let bytes = tokio::fs::read(&physical_path).await?; - module_files.push(Entry { archive_name, bytes }); + module_files.push(Entry { + archive_name, + bytes, + }); } } diff --git a/crates/tower-package/src/towerfile.rs b/crates/tower-package/src/towerfile.rs index e0de3b29..f86b2d53 100644 --- a/crates/tower-package/src/towerfile.rs +++ b/crates/tower-package/src/towerfile.rs @@ -134,9 +134,10 @@ impl Towerfile { use crate::error::Error as OuterError; let target_path = path.unwrap_or_else(|| std::path::Path::new("Towerfile")); - let serialized = toml::to_string_pretty(self).map_err(|err| OuterError::InvalidTowerfile { - message: err.to_string(), - })?; + let serialized = + toml::to_string_pretty(self).map_err(|err| OuterError::InvalidTowerfile { + message: err.to_string(), + })?; std::fs::write(target_path, serialized).map_err(|source| OuterError::Io { source })?; Ok(()) } @@ -315,12 +316,15 @@ mod test { let mut towerfile = crate::Towerfile::default(); assert_eq!(towerfile.parameters.len(), 0); - towerfile.set_parameter("test-param", crate::Parameter { - name: "test-param".to_string(), - description: "A test parameter".to_string(), - default: "default-value".to_string(), - hidden: false, - }); + towerfile.set_parameter( + "test-param", + crate::Parameter { + name: "test-param".to_string(), + description: "A test parameter".to_string(), + default: "default-value".to_string(), + hidden: false, + }, + ); assert_eq!(towerfile.parameters.len(), 1); assert_eq!(towerfile.parameters[0].name, "test-param"); @@ -329,12 +333,15 @@ mod test { assert!(!towerfile.parameters[0].hidden); // upsert should replace, not duplicate - towerfile.set_parameter("test-param", crate::Parameter { - name: "test-param".to_string(), - description: "Updated".to_string(), - default: "new-value".to_string(), - hidden: false, - }); + towerfile.set_parameter( + "test-param", + crate::Parameter { + name: "test-param".to_string(), + description: "Updated".to_string(), + default: "new-value".to_string(), + hidden: false, + }, + ); assert_eq!(towerfile.parameters.len(), 1); assert_eq!(towerfile.parameters[0].description, "Updated"); @@ -343,12 +350,15 @@ mod test { #[test] fn test_remove_parameter() { let mut towerfile = crate::Towerfile::default(); - towerfile.set_parameter("param1", crate::Parameter { - name: "param1".to_string(), - description: "".to_string(), - default: "".to_string(), - hidden: false, - }); + towerfile.set_parameter( + "param1", + crate::Parameter { + name: "param1".to_string(), + description: "".to_string(), + default: "".to_string(), + hidden: false, + }, + ); assert!(towerfile.remove_parameter("param1")); assert_eq!(towerfile.parameters.len(), 0); diff --git a/crates/tower-package/src/wasm.rs b/crates/tower-package/src/wasm.rs index 0649f327..219fbd3d 100644 --- a/crates/tower-package/src/wasm.rs +++ b/crates/tower-package/src/wasm.rs @@ -1,5 +1,5 @@ -use serde::Deserialize; use crate::core::{build_package, Entry, PackageInputs}; +use serde::Deserialize; use wasm_bindgen::prelude::*; #[derive(Deserialize)] @@ -52,7 +52,7 @@ pub fn build_package_wasm(inputs: JsValue) -> Result, JsError> { towerfile_bytes: js.towerfile_bytes.into_vec(), }; - let built = build_package(core_inputs) - .map_err(|e| JsError::new(&format!("build failed: {}", e)))?; + let built = + build_package(core_inputs).map_err(|e| JsError::new(&format!("build failed: {}", e)))?; Ok(built.bytes) } diff --git a/crates/tower-package/tests/package_test.rs b/crates/tower-package/tests/package_test.rs index 2eeef5c1..e39a020f 100644 --- a/crates/tower-package/tests/package_test.rs +++ b/crates/tower-package/tests/package_test.rs @@ -259,12 +259,14 @@ async fn it_packages_import_paths() { ); // Let's decode the manifest and make sure import paths are set correctly. - let manifest = Manifest::from_json(files.get("MANIFEST").unwrap()) - .expect("Manifest was not valid JSON"); + let manifest = + Manifest::from_json(files.get("MANIFEST").unwrap()).expect("Manifest was not valid JSON"); // Archive paths are always normalized to forward slashes regardless of OS. assert!( - manifest.import_paths.contains(&"modules/shared".to_string()), + manifest + .import_paths + .contains(&"modules/shared".to_string()), "Import paths {:?} did not contain expected path", manifest.import_paths ); @@ -323,11 +325,13 @@ async fn it_packages_import_paths_nested_within_base_dir() { ); // Verify the manifest import_paths entry matches the actual package structure. - let manifest = Manifest::from_json(files.get("MANIFEST").unwrap()) - .expect("Manifest was not valid JSON"); + let manifest = + Manifest::from_json(files.get("MANIFEST").unwrap()).expect("Manifest was not valid JSON"); assert!( - manifest.import_paths.contains(&"modules/shared".to_string()), + manifest + .import_paths + .contains(&"modules/shared".to_string()), "Import paths {:?} did not contain expected path modules/shared", manifest.import_paths ); @@ -425,7 +429,12 @@ async fn it_includes_subapp_towerfiles_but_excludes_root_towerfile() { create_test_file(tmp_dir.to_path_buf(), "main.py", "print('Hello, world!')").await; // Sub-app with its own Towerfile - create_test_file(tmp_dir.to_path_buf(), "subapp/Towerfile", "[app]\nname = \"subapp\"").await; + create_test_file( + tmp_dir.to_path_buf(), + "subapp/Towerfile", + "[app]\nname = \"subapp\"", + ) + .await; create_test_file(tmp_dir.to_path_buf(), "subapp/main.py", "print('subapp')").await; let spec = PackageSpec { @@ -506,15 +515,23 @@ hidden = true let package = Package::build(spec).await.expect("Failed to build package"); let files = read_package_files(package).await; - let manifest = Manifest::from_json(files.get("MANIFEST").unwrap()) - .expect("Manifest was not valid JSON"); + let manifest = + Manifest::from_json(files.get("MANIFEST").unwrap()).expect("Manifest was not valid JSON"); assert_eq!(manifest.parameters.len(), 2); - let visible = manifest.parameters.iter().find(|p| p.name == "visible_param").unwrap(); + let visible = manifest + .parameters + .iter() + .find(|p| p.name == "visible_param") + .unwrap(); assert!(!visible.hidden, "visible_param should not be hidden"); - let hidden = manifest.parameters.iter().find(|p| p.name == "hidden_param").unwrap(); + let hidden = manifest + .parameters + .iter() + .find(|p| p.name == "hidden_param") + .unwrap(); assert!(hidden.hidden, "hidden_param should be hidden"); assert_eq!(hidden.default, "secret"); } diff --git a/crates/tower-runtime/src/lib.rs b/crates/tower-runtime/src/lib.rs index 6ebbb061..1be53ce8 100644 --- a/crates/tower-runtime/src/lib.rs +++ b/crates/tower-runtime/src/lib.rs @@ -56,7 +56,10 @@ pub enum Status { impl Status { /// Returns true if this status represents a terminal state (run is finished) pub fn is_terminal(&self) -> bool { - matches!(self, Status::Exited | Status::Crashed { .. } | Status::Failed { .. }) + matches!( + self, + Status::Exited | Status::Crashed { .. } | Status::Failed { .. } + ) } } diff --git a/crates/tower-telemetry/src/logging.rs b/crates/tower-telemetry/src/logging.rs index 021e9cb9..1b717f70 100644 --- a/crates/tower-telemetry/src/logging.rs +++ b/crates/tower-telemetry/src/logging.rs @@ -179,7 +179,11 @@ fn should_use_color(destination: &LogDestination) -> bool { } } -fn create_fmt_layer(level: &LogLevel, format: LogFormat, destination: LogDestination) -> BoxedFmtLayer { +fn create_fmt_layer( + level: &LogLevel, + format: LogFormat, + destination: LogDestination, +) -> BoxedFmtLayer { let use_color = should_use_color(&destination); let with_target = *level < LogLevel::Warn; @@ -242,7 +246,8 @@ pub fn enable_logging(level: LogLevel, format: LogFormat, destination: LogDestin let filter = EnvFilter::new(&level) .add_directive("h2=off".parse().unwrap()) .add_directive("tower::buffer=off".parse().unwrap()) - .add_directive("hyper_util=off".parse().unwrap()); + .add_directive("hyper_util=off".parse().unwrap()) + .add_directive("opentelemetry=off".parse().unwrap()); let subscriber = tracing_subscriber::registry() .with(create_fmt_layer(&level, format, destination)) From ae8d2cb6beedf23859629adc72e9a9e6490d5914 Mon Sep 17 00:00:00 2001 From: Ben Lovell Date: Tue, 5 May 2026 15:35:16 +0200 Subject: [PATCH 02/10] fix: extend setuptools<82 retry to pyproject.toml apps (#272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy-setuptools retry only fired for requirements.txt projects, leaving pyproject.toml apps with no recovery path when their builds needed pkg_resources. The retry now drops out of uv sync into uv pip install with the project source (. for pyproject, -r requirements.txt otherwise) plus a setuptools<82 arg, so both kinds of project get the same fallback. Removes the should_use_legacy_setuptools_pin predicate and the spawn_requirements_install helper — the call site in tower-runtime is now just 'if res != 0 { retry }', and the file-type branch lives inline where the args are built. --- crates/tower-runtime/src/local.rs | 84 ++++++++++++----------------- crates/tower-uv/src/lib.rs | 86 ++++++++++++++++-------------- crates/tower-uv/tests/sync_test.rs | 56 ++++++++++++++++++- 3 files changed, 133 insertions(+), 93 deletions(-) diff --git a/crates/tower-runtime/src/local.rs b/crates/tower-runtime/src/local.rs index 745c4b57..e388fb04 100644 --- a/crates/tower-runtime/src/local.rs +++ b/crates/tower-runtime/src/local.rs @@ -256,33 +256,15 @@ async fn execute_local_app( } } } - Ok(mut child) => { - // Drain the logs to the output channel. - let stdout = child.stdout.take().expect("no stdout"); - tokio::spawn(drain_output( - FD::Stdout, - Channel::Setup, - opts.output_sender.clone(), - BufReader::new(stdout), - )); - - let stderr = child.stderr.take().expect("no stderr"); - tokio::spawn(drain_output( - FD::Stderr, - Channel::Setup, - opts.output_sender.clone(), - BufReader::new(stderr), - )); - - // Let's wait for the setup to finish. We don't care about the results. - let mut res = wait_for_process(ctx.clone(), &cancel_token, child).await; - - // If the requirements.txt install failed, retry with the legacy - // setuptools<82 pin. Some apps (those whose transitive deps rely on + Ok(child) => { + let mut res = run_setup_child(&ctx, &cancel_token, &opts.output_sender, child).await; + + // If the install failed, retry with the legacy setuptools<82 + // pin. Some apps (those whose transitive deps rely on // pkg_resources) need that pin to install successfully; we don't // apply it by default because it conflicts with apps whose deps // require setuptools>=82. - if res != 0 && uv.should_use_legacy_setuptools_pin(&working_dir) { + if res != 0 { let _ = opts.output_sender.send(Output { channel: Channel::Setup, fd: FD::Stdout, @@ -290,33 +272,10 @@ async fn execute_local_app( time: chrono::Utc::now(), }); - match uv + let retry_child = uv .sync_with_legacy_setuptools_pin(&working_dir, &env_vars) - .await - { - Err(e) => { - return Err(e.into()); - } - Ok(mut retry_child) => { - let stdout = retry_child.stdout.take().expect("no stdout"); - tokio::spawn(drain_output( - FD::Stdout, - Channel::Setup, - opts.output_sender.clone(), - BufReader::new(stdout), - )); - - let stderr = retry_child.stderr.take().expect("no stderr"); - tokio::spawn(drain_output( - FD::Stderr, - Channel::Setup, - opts.output_sender.clone(), - BufReader::new(stderr), - )); - - res = wait_for_process(ctx.clone(), &cancel_token, retry_child).await; - } - } + .await?; + res = run_setup_child(&ctx, &cancel_token, &opts.output_sender, retry_child).await; } if res != 0 { @@ -574,6 +533,31 @@ async fn kill_child_process(ctx: &tower_telemetry::Context, mut child: Child) { }; } +async fn run_setup_child( + ctx: &tower_telemetry::Context, + cancel_token: &CancellationToken, + output_sender: &OutputSender, + mut child: Child, +) -> i32 { + let stdout = child.stdout.take().expect("no stdout"); + tokio::spawn(drain_output( + FD::Stdout, + Channel::Setup, + output_sender.clone(), + BufReader::new(stdout), + )); + + let stderr = child.stderr.take().expect("no stderr"); + tokio::spawn(drain_output( + FD::Stderr, + Channel::Setup, + output_sender.clone(), + BufReader::new(stderr), + )); + + wait_for_process(ctx.clone(), cancel_token, child).await +} + async fn wait_for_process( ctx: tower_telemetry::Context, cancel_token: &CancellationToken, diff --git a/crates/tower-uv/src/lib.rs b/crates/tower-uv/src/lib.rs index a2e44bda..203fddab 100644 --- a/crates/tower-uv/src/lib.rs +++ b/crates/tower-uv/src/lib.rs @@ -357,29 +357,55 @@ impl Uv { &self.uv_path, cwd ); - self.spawn_requirements_install(cwd, env_vars, false).await + let mut cmd = self.pip_install(cwd); + cmd.arg("-r") + .arg(cwd.join("requirements.txt")) + .envs(env_vars); + + #[cfg(unix)] + { + cmd.process_group(0); + } + + if let Some(dir) = &self.cache_dir { + cmd.arg("--cache-dir").arg(dir); + } + + Ok(cmd.spawn()?) } else { // If there is no pyproject.toml or requirements.txt, then we can't sync. Err(Error::MissingPyprojectToml) } } - /// Returns whether a failed `sync()` for this directory is eligible for a - /// retry via [`sync_with_legacy_setuptools_pin`]. Only applies to projects - /// driven by `requirements.txt`; pyproject-based projects manage their own - /// setuptools dependency. - pub fn should_use_legacy_setuptools_pin(&self, cwd: &Path) -> bool { - cwd.join("requirements.txt").exists() + /// Builds a `uv pip install` command with our standard stdio/color setup. + /// Callers append source args (e.g. `-r requirements.txt` or `.`), any extra + /// packages, and `envs` before spawning. + fn pip_install(&self, cwd: &Path) -> Command { + let mut cmd = Command::new(&self.uv_path); + cmd.kill_on_drop(true) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .current_dir(cwd) + .arg("--color") + .arg("never") + .arg("pip") + .arg("install"); + cmd } - /// Re-runs the `requirements.txt` install with a `setuptools<82` pin appended. + /// Re-runs the install with a `setuptools<82` pin appended. /// /// setuptools 82 removed `pkg_resources`, but many legacy packages still import /// it without declaring the dependency. Pinning `setuptools<82` keeps it /// available. Some modern packages (e.g. dlt's transitive graph pinning /// `setuptools==82.0.1`) make this pin unsatisfiable, so it isn't applied up - /// front — callers should fall back to this only after a plain `sync()` - /// fails for a project using `requirements.txt`. + /// front — callers should fall back to this only after a plain `sync()` fails. + /// + /// Drops out of `uv sync` (which can't accept a CLI constraint) into `uv pip + /// install`, which can. The project source is `.` for a pyproject project or + /// `-r requirements.txt` otherwise. /// /// https://github.com/pypa/setuptools/issues/5174 pub async fn sync_with_legacy_setuptools_pin( @@ -387,44 +413,22 @@ impl Uv { cwd: &PathBuf, env_vars: &HashMap, ) -> Result { - if !cwd.join("requirements.txt").exists() { - return Err(Error::MissingPyprojectToml); - } - debug!( - "Retrying UV ({:?}) sync with setuptools<82 pin in {:?}", + "Retrying UV ({:?}) install with setuptools<82 pin in {:?}", &self.uv_path, cwd ); - self.spawn_requirements_install(cwd, env_vars, true).await - } - - async fn spawn_requirements_install( - &self, - cwd: &PathBuf, - env_vars: &HashMap, - pin_legacy_setuptools: bool, - ) -> Result { - let req_path = cwd.join("requirements.txt"); - - let mut cmd = Command::new(&self.uv_path); - cmd.kill_on_drop(true) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .current_dir(cwd) - .arg("--color") - .arg("never") - .arg("pip") - .arg("install") - .arg("-r") - .arg(&req_path); + let mut cmd = self.pip_install(cwd); - if pin_legacy_setuptools { - cmd.arg("setuptools<82"); + if cwd.join("pyproject.toml").exists() { + cmd.arg("."); + } else if cwd.join("requirements.txt").exists() { + cmd.arg("-r").arg(cwd.join("requirements.txt")); + } else { + return Err(Error::MissingPyprojectToml); } - cmd.envs(env_vars); + cmd.arg("setuptools<82").envs(env_vars); #[cfg(unix)] { diff --git a/crates/tower-uv/tests/sync_test.rs b/crates/tower-uv/tests/sync_test.rs index b2cc8387..df692164 100644 --- a/crates/tower-uv/tests/sync_test.rs +++ b/crates/tower-uv/tests/sync_test.rs @@ -116,7 +116,7 @@ async fn sync_with_legacy_setuptools_pin_fails_when_user_requires_modern_setupto } #[tokio::test] -async fn sync_with_legacy_setuptools_pin_errors_without_requirements_txt() { +async fn sync_with_legacy_setuptools_pin_errors_without_project_files() { let tmp = TempDir::new().expect("tempdir"); let cwd = tmp.path().to_path_buf(); @@ -125,6 +125,58 @@ async fn sync_with_legacy_setuptools_pin_errors_without_requirements_txt() { let result = uv.sync_with_legacy_setuptools_pin(&cwd, &env_vars).await; assert!( matches!(result, Err(tower_uv::Error::MissingPyprojectToml)), - "fallback should refuse to run without a requirements.txt" + "fallback should refuse to run without pyproject.toml or requirements.txt" + ); +} + +#[tokio::test] +async fn sync_with_legacy_setuptools_pin_fails_for_pyproject_requiring_modern_setuptools() { + let tmp = TempDir::new().expect("tempdir"); + let cwd = tmp.path().to_path_buf(); + + // Mirrors the requirements.txt counterpart: when the project pins + // setuptools>=82, the pin must conflict — proving it's actually applied. + tokio::fs::write( + cwd.join("pyproject.toml"), + "[project]\nname = \"test-app\"\nversion = \"0.0.1\"\nrequires-python = \">=3.10\"\ndependencies = [\"setuptools>=82\"]\n", + ) + .await + .expect("write pyproject.toml"); + + let uv = make_uv_with_venv(&cwd).await; + let env_vars: HashMap = HashMap::new(); + let child = uv + .sync_with_legacy_setuptools_pin(&cwd, &env_vars) + .await + .expect("retry spawn failed"); + let code = wait(child).await; + assert_ne!( + code, 0, + "sync_with_legacy_setuptools_pin should fail when the pyproject project requires setuptools>=82" + ); +} + +#[tokio::test] +async fn sync_with_legacy_setuptools_pin_installs_for_pyproject() { + let tmp = TempDir::new().expect("tempdir"); + let cwd = tmp.path().to_path_buf(); + + tokio::fs::write( + cwd.join("pyproject.toml"), + "[project]\nname = \"test-app\"\nversion = \"0.0.1\"\nrequires-python = \">=3.10\"\ndependencies = [\"six\"]\n", + ) + .await + .expect("write pyproject.toml"); + + let uv = make_uv_with_venv(&cwd).await; + let env_vars: HashMap = HashMap::new(); + let child = uv + .sync_with_legacy_setuptools_pin(&cwd, &env_vars) + .await + .expect("retry spawn failed"); + let code = wait(child).await; + assert_eq!( + code, 0, + "sync_with_legacy_setuptools_pin should succeed for a pyproject project" ); } From 6f9604c46ce7cd4bf6454722fd85d8489d356681 Mon Sep 17 00:00:00 2001 From: Brad Heller Date: Wed, 13 May 2026 10:58:38 +0100 Subject: [PATCH 03/10] Add starting state (#258) * chore: Upgrade to the latest API spec (from staging, for now) * chore: RunParameter becomes RunParameterInput * chore: Build failure prevented python client generation * chore: Refactor how we track run status and add `starting`. Fix some broken test infra for the new starting properties, too. * chore: Fix formatting * chore: Add `starting` to the rust side, too * chore: Regenerate API clients against prod schema Refresh Python and Rust clients from api.tower.dev now that the starting state has shipped. Adapt tower-cmd call sites: revert RunParameterInput -> RunParameter (prod kept the original name) and pass environment: None for the new optional DescribeAppParams field. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(tests): add new starting fields to mock API server The OpenAPI spec bump to v0.10.24 made `Run.starting_at` and `RunResults.starting` required (the former is `Option::deserialize`, so the JSON key must be present even if null). The mock server wasn't emitting them, so the new generated client deserialization fell into `UnknownValue` and surfaced as 204/"wasn't able to understand" errors across the integration suite. * chore: Add cancelled_child_runs to mock cancel response Regenerated CancelRunResponse now has a required cancelled_child_runs field; mock server was returning only {"run": ...} which broke deserialization and produced empty CLI output in integration tests. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- crates/tower-api/README.md | 30 +- crates/tower-api/src/apis/configuration.rs | 2 +- crates/tower-api/src/apis/default_api.rs | 663 ++++-------------- .../tower-api/src/apis/feature_flags_api.rs | 2 +- crates/tower-api/src/models/account.rs | 2 +- .../src/models/acknowledge_alert_response.rs | 2 +- .../models/acknowledge_all_alerts_response.rs | 2 +- crates/tower-api/src/models/alert.rs | 2 +- crates/tower-api/src/models/api_key.rs | 2 +- crates/tower-api/src/models/app.rs | 2 +- crates/tower-api/src/models/app_statistics.rs | 2 +- crates/tower-api/src/models/app_summary.rs | 2 +- crates/tower-api/src/models/app_version.rs | 2 +- .../src/models/authentication_context.rs | 2 +- .../src/models/batch_schedule_params.rs | 2 +- .../src/models/batch_schedule_response.rs | 2 +- .../src/models/cancel_run_response.rs | 14 +- crates/tower-api/src/models/catalog.rs | 2 +- .../tower-api/src/models/catalog_property.rs | 2 +- .../claim_device_login_ticket_params.rs | 2 +- .../claim_device_login_ticket_response.rs | 2 +- .../src/models/create_account_params.rs | 2 +- .../create_account_params_flags_struct.rs | 2 +- .../src/models/create_account_response.rs | 2 +- .../src/models/create_api_key_params.rs | 2 +- .../src/models/create_api_key_response.rs | 2 +- .../tower-api/src/models/create_app_params.rs | 2 +- .../src/models/create_app_response.rs | 2 +- .../src/models/create_catalog_params.rs | 6 +- .../src/models/create_catalog_response.rs | 2 +- .../create_device_login_ticket_response.rs | 2 +- .../src/models/create_environment_params.rs | 2 +- .../src/models/create_environment_response.rs | 2 +- .../src/models/create_guest_params.rs | 2 +- .../src/models/create_guest_response.rs | 2 +- .../models/create_sandbox_secrets_params.rs | 2 +- .../models/create_sandbox_secrets_response.rs | 2 +- .../src/models/create_schedule_params.rs | 4 +- .../src/models/create_schedule_response.rs | 2 +- .../src/models/create_secret_params.rs | 2 +- .../src/models/create_secret_response.rs | 2 +- .../src/models/create_session_params.rs | 2 +- .../src/models/create_session_response.rs | 2 +- .../src/models/create_team_params.rs | 2 +- .../src/models/create_team_response.rs | 2 +- .../src/models/create_webhook_params.rs | 2 +- .../src/models/create_webhook_response.rs | 2 +- .../src/models/delete_api_key_params.rs | 2 +- .../src/models/delete_api_key_response.rs | 2 +- .../src/models/delete_app_response.rs | 2 +- .../src/models/delete_catalog_response.rs | 2 +- .../src/models/delete_guest_output_body.rs | 2 +- .../src/models/delete_schedule_params.rs | 2 +- .../src/models/delete_schedule_response.rs | 2 +- .../src/models/delete_secret_response.rs | 2 +- .../src/models/delete_session_params.rs | 2 +- .../src/models/delete_session_response.rs | 2 +- .../models/delete_team_invitation_params.rs | 2 +- .../models/delete_team_invitation_response.rs | 2 +- .../src/models/delete_team_params.rs | 2 +- .../src/models/delete_team_response.rs | 2 +- .../src/models/delete_webhook_response.rs | 2 +- .../src/models/deploy_app_request.rs | 2 +- .../src/models/deploy_app_response.rs | 2 +- .../src/models/describe_account_body.rs | 2 +- .../src/models/describe_app_response.rs | 2 +- .../models/describe_app_version_response.rs | 2 +- .../describe_authentication_context_body.rs | 2 +- .../src/models/describe_catalog_response.rs | 2 +- .../describe_device_login_session_response.rs | 2 +- .../models/describe_email_preferences_body.rs | 2 +- .../src/models/describe_plan_response.rs | 2 +- .../src/models/describe_run_graph_response.rs | 2 +- .../src/models/describe_run_links.rs | 2 +- .../src/models/describe_run_logs_response.rs | 2 +- .../src/models/describe_run_response.rs | 2 +- .../models/describe_secrets_key_response.rs | 2 +- .../src/models/describe_session_response.rs | 2 +- .../src/models/describe_team_response.rs | 2 +- .../src/models/describe_webhook_response.rs | 2 +- .../src/models/email_subscriptions.rs | 2 +- .../src/models/encrypted_catalog_property.rs | 2 +- crates/tower-api/src/models/environment.rs | 2 +- crates/tower-api/src/models/error_detail.rs | 2 +- crates/tower-api/src/models/error_model.rs | 2 +- crates/tower-api/src/models/event_alert.rs | 2 +- crates/tower-api/src/models/event_error.rs | 2 +- crates/tower-api/src/models/event_log.rs | 2 +- .../tower-api/src/models/event_shouldertap.rs | 2 +- crates/tower-api/src/models/event_warning.rs | 2 +- .../src/models/export_catalogs_params.rs | 2 +- .../src/models/export_catalogs_response.rs | 2 +- .../src/models/export_secrets_params.rs | 2 +- .../src/models/export_secrets_response.rs | 2 +- .../tower-api/src/models/exported_catalog.rs | 2 +- .../src/models/exported_catalog_property.rs | 2 +- .../tower-api/src/models/exported_secret.rs | 2 +- crates/tower-api/src/models/feature.rs | 2 +- .../src/models/featurebase_identity.rs | 2 +- .../generate_app_statistics_response.rs | 2 +- ...organization_usage_time_series_response.rs | 34 + .../generate_run_statistics_response.rs | 2 +- .../generate_runner_credentials_response.rs | 2 +- .../models/get_feature_flag_response_body.rs | 2 +- crates/tower-api/src/models/guest.rs | 2 +- .../src/models/invite_team_member_params.rs | 2 +- .../src/models/invite_team_member_response.rs | 2 +- .../src/models/leave_team_response.rs | 2 +- .../src/models/list_alerts_response.rs | 2 +- .../src/models/list_api_keys_response.rs | 2 +- .../models/list_app_environments_response.rs | 2 +- .../src/models/list_app_versions_response.rs | 2 +- .../src/models/list_apps_response.rs | 2 +- .../src/models/list_catalogs_response.rs | 2 +- .../src/models/list_environments_response.rs | 2 +- .../src/models/list_guests_response.rs | 2 +- .../list_my_team_invitations_response.rs | 2 +- .../src/models/list_runners_response.rs | 2 +- .../src/models/list_runs_response.rs | 2 +- .../src/models/list_schedules_response.rs | 2 +- .../list_secret_environments_response.rs | 2 +- .../src/models/list_secrets_response.rs | 2 +- .../models/list_team_invitations_response.rs | 2 +- .../src/models/list_team_members_response.rs | 2 +- .../src/models/list_teams_response.rs | 2 +- .../src/models/list_webhooks_response.rs | 2 +- crates/tower-api/src/models/mod.rs | 36 +- crates/tower-api/src/models/organization.rs | 2 +- .../src/models/organization_usage.rs | 2 +- crates/tower-api/src/models/pagination.rs | 2 +- crates/tower-api/src/models/parameter.rs | 2 +- crates/tower-api/src/models/plan.rs | 2 +- .../src/models/refresh_session_params.rs | 2 +- .../src/models/refresh_session_response.rs | 2 +- .../regenerate_guest_login_url_params.rs | 2 +- .../regenerate_guest_login_url_response.rs | 2 +- .../src/models/remove_team_member_params.rs | 2 +- .../src/models/remove_team_member_response.rs | 2 +- .../models/resend_team_invitation_params.rs | 2 +- .../models/resend_team_invitation_response.rs | 2 +- crates/tower-api/src/models/run.rs | 12 +- .../src/models/run_app_initiator_data.rs | 2 +- crates/tower-api/src/models/run_app_params.rs | 2 +- .../tower-api/src/models/run_app_response.rs | 2 +- crates/tower-api/src/models/run_attempt.rs | 9 +- .../tower-api/src/models/run_failure_alert.rs | 2 +- crates/tower-api/src/models/run_graph_node.rs | 2 +- .../tower-api/src/models/run_graph_run_id.rs | 2 +- crates/tower-api/src/models/run_initiator.rs | 2 +- .../src/models/run_initiator_details.rs | 2 +- crates/tower-api/src/models/run_log_line.rs | 2 +- crates/tower-api/src/models/run_parameter.rs | 11 +- .../src/models/run_parameter_input.rs | 36 + crates/tower-api/src/models/run_results.rs | 7 +- .../tower-api/src/models/run_retry_policy.rs | 2 +- .../src/models/run_run_initiator_details.rs | 2 +- crates/tower-api/src/models/run_statistics.rs | 2 +- .../src/models/run_timeseries_point.rs | 7 +- crates/tower-api/src/models/runner.rs | 2 +- .../src/models/runner_credentials.rs | 2 +- crates/tower-api/src/models/schedule.rs | 4 +- .../models/schedule_run_initiator_details.rs | 2 +- .../src/models/search_runs_response.rs | 2 +- crates/tower-api/src/models/secret.rs | 2 +- .../src/models/server_sent_events_inner.rs | 2 +- .../src/models/server_sent_events_inner_1.rs | 2 +- .../src/models/server_sent_events_inner_2.rs | 2 +- crates/tower-api/src/models/session.rs | 3 +- crates/tower-api/src/models/shoulder_tap.rs | 2 +- crates/tower-api/src/models/sse_warning.rs | 2 +- .../src/models/statistics_settings.rs | 2 +- crates/tower-api/src/models/team.rs | 2 +- .../tower-api/src/models/team_invitation.rs | 2 +- .../tower-api/src/models/team_membership.rs | 2 +- .../src/models/test_webhook_response.rs | 2 +- crates/tower-api/src/models/token.rs | 2 +- .../src/models/update_account_params.rs | 2 +- .../src/models/update_account_response.rs | 2 +- .../models/update_app_environment_params.rs | 33 + .../models/update_app_environment_response.rs | 36 + .../tower-api/src/models/update_app_params.rs | 2 +- .../src/models/update_app_response.rs | 2 +- .../src/models/update_catalog_params.rs | 2 +- .../src/models/update_catalog_response.rs | 2 +- .../models/update_email_preferences_body.rs | 2 +- .../src/models/update_environment_params.rs | 2 +- .../src/models/update_environment_response.rs | 2 +- .../update_my_team_invitation_params.rs | 2 +- .../update_my_team_invitation_response.rs | 2 +- .../src/models/update_organization_params.rs | 2 +- .../models/update_organization_response.rs | 2 +- .../src/models/update_plan_params.rs | 2 +- .../src/models/update_plan_response.rs | 2 +- .../src/models/update_schedule_params.rs | 4 +- .../src/models/update_schedule_response.rs | 2 +- .../src/models/update_secret_params.rs | 2 +- .../src/models/update_secret_response.rs | 2 +- .../src/models/update_team_member_params.rs | 2 +- .../src/models/update_team_member_response.rs | 2 +- .../src/models/update_team_params.rs | 2 +- .../src/models/update_team_response.rs | 2 +- .../src/models/update_user_params.rs | 2 +- .../src/models/update_user_response.rs | 2 +- .../src/models/update_webhook_params.rs | 2 +- .../src/models/update_webhook_response.rs | 2 +- crates/tower-api/src/models/usage_limit.rs | 2 +- .../models/usage_metric_time_series_point.rs | 57 ++ crates/tower-api/src/models/user.rs | 2 +- crates/tower-api/src/models/webhook.rs | 2 +- crates/tower-cmd/src/api.rs | 3 + crates/tower-cmd/src/apps.rs | 9 +- crates/tower-cmd/src/util/apps.rs | 1 + src/tower/_client.py | 34 +- .../api/default/create_password_reset.py | 171 ----- .../api/default/delete_authenticator.py | 167 ----- .../api/default/deploy_app.py | 39 ++ .../api/default/describe_app.py | 19 + .../default/describe_organization_usage.py | 8 +- .../api/default/generate_authenticator.py | 134 ---- ...enerate_organization_usage_time_series.py} | 46 +- .../api/default/resend_email_verification.py | 136 ---- ...enticator.py => update_app_environment.py} | 98 ++- .../api/default/update_password_reset.py | 183 ----- .../api/default/verify_email.py | 171 ----- src/tower/tower_api_client/models/__init__.py | 40 +- .../models/cancel_run_response.py | 8 + .../models/create_authenticator_params.py | 62 -- .../models/create_authenticator_response.py | 60 -- .../models/create_catalog_params_type.py | 1 + .../models/create_password_reset_params.py | 54 -- .../models/create_password_reset_response.py | 54 -- .../models/create_schedule_params.py | 4 +- .../models/delete_authenticator_response.py | 60 -- .../models/generate_authenticator_response.py | 60 -- ...organization_usage_time_series_response.py | 68 ++ .../generate_run_statistics_status_item.py | 1 + .../models/list_authenticators_response.py | 70 -- .../models/list_runs_status_item.py | 1 + src/tower/tower_api_client/models/run.py | 27 +- .../tower_api_client/models/run_attempt.py | 27 +- .../tower_api_client/models/run_parameter.py | 23 +- .../tower_api_client/models/run_results.py | 8 + .../tower_api_client/models/run_status.py | 1 + .../models/run_timeseries_point.py | 8 + src/tower/tower_api_client/models/schedule.py | 3 +- .../models/search_runs_status_item.py | 1 + .../models/unverified_authenticator.py | 66 -- ...se.py => update_app_environment_params.py} | 22 +- ....py => update_app_environment_response.py} | 30 +- .../models/update_password_reset_params.py | 54 -- .../models/update_schedule_params.py | 4 +- .../models/usage_metric_time_series_point.py | 62 ++ .../usage_metric_time_series_point_name.py | 8 + .../models/verified_authenticator.py | 68 -- .../models/verify_email_params.py | 54 -- .../models/verify_email_response.py | 60 -- tests/mock-api-server/main.py | 5 +- tests/tower/test_client.py | 2 + 258 files changed, 1070 insertions(+), 2611 deletions(-) create mode 100644 crates/tower-api/src/models/generate_organization_usage_time_series_response.rs create mode 100644 crates/tower-api/src/models/run_parameter_input.rs create mode 100644 crates/tower-api/src/models/update_app_environment_params.rs create mode 100644 crates/tower-api/src/models/update_app_environment_response.rs create mode 100644 crates/tower-api/src/models/usage_metric_time_series_point.rs delete mode 100644 src/tower/tower_api_client/api/default/create_password_reset.py delete mode 100644 src/tower/tower_api_client/api/default/delete_authenticator.py delete mode 100644 src/tower/tower_api_client/api/default/generate_authenticator.py rename src/tower/tower_api_client/api/default/{list_authenticators.py => generate_organization_usage_time_series.py} (64%) delete mode 100644 src/tower/tower_api_client/api/default/resend_email_verification.py rename src/tower/tower_api_client/api/default/{create_authenticator.py => update_app_environment.py} (51%) delete mode 100644 src/tower/tower_api_client/api/default/update_password_reset.py delete mode 100644 src/tower/tower_api_client/api/default/verify_email.py delete mode 100644 src/tower/tower_api_client/models/create_authenticator_params.py delete mode 100644 src/tower/tower_api_client/models/create_authenticator_response.py delete mode 100644 src/tower/tower_api_client/models/create_password_reset_params.py delete mode 100644 src/tower/tower_api_client/models/create_password_reset_response.py delete mode 100644 src/tower/tower_api_client/models/delete_authenticator_response.py delete mode 100644 src/tower/tower_api_client/models/generate_authenticator_response.py create mode 100644 src/tower/tower_api_client/models/generate_organization_usage_time_series_response.py delete mode 100644 src/tower/tower_api_client/models/list_authenticators_response.py delete mode 100644 src/tower/tower_api_client/models/unverified_authenticator.py rename src/tower/tower_api_client/models/{update_password_reset_response.py => update_app_environment_params.py} (62%) rename src/tower/tower_api_client/models/{delete_authenticator_params.py => update_app_environment_response.py} (55%) delete mode 100644 src/tower/tower_api_client/models/update_password_reset_params.py create mode 100644 src/tower/tower_api_client/models/usage_metric_time_series_point.py create mode 100644 src/tower/tower_api_client/models/usage_metric_time_series_point_name.py delete mode 100644 src/tower/tower_api_client/models/verified_authenticator.py delete mode 100644 src/tower/tower_api_client/models/verify_email_params.py delete mode 100644 src/tower/tower_api_client/models/verify_email_response.py diff --git a/crates/tower-api/README.md b/crates/tower-api/README.md index cf17a590..34128aec 100644 --- a/crates/tower-api/README.md +++ b/crates/tower-api/README.md @@ -8,7 +8,7 @@ For more information, please visit [https://tower.dev](https://tower.dev) This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client. -- API version: v0.10.17 +- API version: v0.10.24 - Package version: 1.0.0 - Generator version: 7.19.0 - Build package: `org.openapitools.codegen.languages.RustClientCodegen` @@ -36,12 +36,10 @@ Class | Method | HTTP request | Description *DefaultApi* | [**create_account**](docs/DefaultApi.md#create_account) | **POST** /accounts | Create account *DefaultApi* | [**create_api_key**](docs/DefaultApi.md#create_api_key) | **POST** /api-keys | Create API Key *DefaultApi* | [**create_app**](docs/DefaultApi.md#create_app) | **POST** /apps | Create app -*DefaultApi* | [**create_authenticator**](docs/DefaultApi.md#create_authenticator) | **POST** /authenticators | Create authenticator *DefaultApi* | [**create_catalog**](docs/DefaultApi.md#create_catalog) | **POST** /catalogs | Create catalog *DefaultApi* | [**create_device_login_ticket**](docs/DefaultApi.md#create_device_login_ticket) | **GET** /login/device | Create device login ticket *DefaultApi* | [**create_environment**](docs/DefaultApi.md#create_environment) | **POST** /environments | Create environment *DefaultApi* | [**create_guest**](docs/DefaultApi.md#create_guest) | **POST** /guests | Create guest -*DefaultApi* | [**create_password_reset**](docs/DefaultApi.md#create_password_reset) | **POST** /accounts/password-reset | Create password reset *DefaultApi* | [**create_sandbox_secrets**](docs/DefaultApi.md#create_sandbox_secrets) | **POST** /sandbox/secrets | Create Tower-provided sandbox secrets *DefaultApi* | [**create_schedule**](docs/DefaultApi.md#create_schedule) | **POST** /schedules | Create schedule *DefaultApi* | [**create_secret**](docs/DefaultApi.md#create_secret) | **POST** /secrets | Create secret @@ -52,7 +50,6 @@ Class | Method | HTTP request | Description *DefaultApi* | [**delete_alert**](docs/DefaultApi.md#delete_alert) | **DELETE** /alerts/{alert_id} | Delete alert *DefaultApi* | [**delete_api_key**](docs/DefaultApi.md#delete_api_key) | **DELETE** /api-keys | Delete API key *DefaultApi* | [**delete_app**](docs/DefaultApi.md#delete_app) | **DELETE** /apps/{name} | Delete app -*DefaultApi* | [**delete_authenticator**](docs/DefaultApi.md#delete_authenticator) | **DELETE** /authenticators | Delete authenticator *DefaultApi* | [**delete_catalog**](docs/DefaultApi.md#delete_catalog) | **DELETE** /catalogs/{name} | Delete catalog *DefaultApi* | [**delete_guest**](docs/DefaultApi.md#delete_guest) | **DELETE** /guests/{guest_id} | Delete guest *DefaultApi* | [**delete_schedule**](docs/DefaultApi.md#delete_schedule) | **DELETE** /schedules | Delete schedule @@ -81,7 +78,7 @@ Class | Method | HTTP request | Description *DefaultApi* | [**export_catalogs**](docs/DefaultApi.md#export_catalogs) | **POST** /catalogs/export | Export catalogs *DefaultApi* | [**export_secrets**](docs/DefaultApi.md#export_secrets) | **POST** /secrets/export | Export secrets *DefaultApi* | [**generate_app_statistics**](docs/DefaultApi.md#generate_app_statistics) | **GET** /stats/apps | Generate app statistics -*DefaultApi* | [**generate_authenticator**](docs/DefaultApi.md#generate_authenticator) | **POST** /authenticators/generate | Generate authenticator +*DefaultApi* | [**generate_organization_usage_time_series**](docs/DefaultApi.md#generate_organization_usage_time_series) | **GET** /usage/time-series | Get organization usage as time series *DefaultApi* | [**generate_run_statistics**](docs/DefaultApi.md#generate_run_statistics) | **GET** /stats/runs | Generate run statistics *DefaultApi* | [**generate_runner_credentials**](docs/DefaultApi.md#generate_runner_credentials) | **POST** /runners/credentials | Generate runner credentials *DefaultApi* | [**invite_team_member**](docs/DefaultApi.md#invite_team_member) | **POST** /teams/{name}/invites | Invite team member @@ -91,7 +88,6 @@ Class | Method | HTTP request | Description *DefaultApi* | [**list_app_environments**](docs/DefaultApi.md#list_app_environments) | **GET** /apps/{name}/environments | List app environments *DefaultApi* | [**list_app_versions**](docs/DefaultApi.md#list_app_versions) | **GET** /apps/{name}/versions | List app versions *DefaultApi* | [**list_apps**](docs/DefaultApi.md#list_apps) | **GET** /apps | List apps -*DefaultApi* | [**list_authenticators**](docs/DefaultApi.md#list_authenticators) | **GET** /authenticators | List authenticators *DefaultApi* | [**list_catalogs**](docs/DefaultApi.md#list_catalogs) | **GET** /catalogs | List catalogs *DefaultApi* | [**list_environments**](docs/DefaultApi.md#list_environments) | **GET** /environments | List environments *DefaultApi* | [**list_guests**](docs/DefaultApi.md#list_guests) | **GET** /guests | List guests @@ -108,7 +104,6 @@ Class | Method | HTTP request | Description *DefaultApi* | [**refresh_session**](docs/DefaultApi.md#refresh_session) | **POST** /session/refresh | Refresh session *DefaultApi* | [**regenerate_guest_login_url**](docs/DefaultApi.md#regenerate_guest_login_url) | **POST** /guests/{guest_id}/login-url | Regenerate guest login URL *DefaultApi* | [**remove_team_member**](docs/DefaultApi.md#remove_team_member) | **DELETE** /teams/{name}/members | Remove team member -*DefaultApi* | [**resend_email_verification**](docs/DefaultApi.md#resend_email_verification) | **POST** /user/resend-verification | Resent email verification *DefaultApi* | [**resend_team_invitation**](docs/DefaultApi.md#resend_team_invitation) | **POST** /teams/{name}/invites/resend | Resend team invitation *DefaultApi* | [**run_app**](docs/DefaultApi.md#run_app) | **POST** /apps/{name}/runs | Run app *DefaultApi* | [**search_runs**](docs/DefaultApi.md#search_runs) | **GET** /runs | Search runs @@ -117,12 +112,12 @@ Class | Method | HTTP request | Description *DefaultApi* | [**stream_shouldertaps**](docs/DefaultApi.md#stream_shouldertaps) | **GET** /shouldertaps/stream | Stream shouldertaps *DefaultApi* | [**update_account**](docs/DefaultApi.md#update_account) | **PUT** /accounts/{name} | Update account *DefaultApi* | [**update_app**](docs/DefaultApi.md#update_app) | **PUT** /apps/{name} | Update app +*DefaultApi* | [**update_app_environment**](docs/DefaultApi.md#update_app_environment) | **PUT** /apps/{name}/environments/{environment} | Update app environment *DefaultApi* | [**update_catalog**](docs/DefaultApi.md#update_catalog) | **PUT** /catalogs/{name} | Update catalog *DefaultApi* | [**update_email_preferences**](docs/DefaultApi.md#update_email_preferences) | **PUT** /user/email-preferences | Update email preferences *DefaultApi* | [**update_environment**](docs/DefaultApi.md#update_environment) | **PUT** /environments/{name} | Update environment *DefaultApi* | [**update_my_team_invitation**](docs/DefaultApi.md#update_my_team_invitation) | **PUT** /team-invites | Update my team invitation *DefaultApi* | [**update_organization**](docs/DefaultApi.md#update_organization) | **PUT** /organizations/{name} | Update organization -*DefaultApi* | [**update_password_reset**](docs/DefaultApi.md#update_password_reset) | **POST** /accounts/password-reset/{code} | Update password reset *DefaultApi* | [**update_plan**](docs/DefaultApi.md#update_plan) | **PUT** /plan | Update plan *DefaultApi* | [**update_schedule**](docs/DefaultApi.md#update_schedule) | **PUT** /schedules/{idOrName} | Update schedule *DefaultApi* | [**update_secret**](docs/DefaultApi.md#update_secret) | **PUT** /secrets/{name} | Update secret @@ -130,7 +125,6 @@ Class | Method | HTTP request | Description *DefaultApi* | [**update_team_member**](docs/DefaultApi.md#update_team_member) | **PUT** /teams/{name}/members | Update team member *DefaultApi* | [**update_user**](docs/DefaultApi.md#update_user) | **PUT** /user | Update user profile *DefaultApi* | [**update_webhook**](docs/DefaultApi.md#update_webhook) | **PUT** /webhooks/{name} | Update webhook -*DefaultApi* | [**verify_email**](docs/DefaultApi.md#verify_email) | **POST** /user/verify | Verify email *FeatureFlagsApi* | [**get_feature_flag_value**](docs/FeatureFlagsApi.md#get_feature_flag_value) | **GET** /feature-flags/{key} | Get feature flag value @@ -160,8 +154,6 @@ Class | Method | HTTP request | Description - [CreateApiKeyResponse](docs/CreateApiKeyResponse.md) - [CreateAppParams](docs/CreateAppParams.md) - [CreateAppResponse](docs/CreateAppResponse.md) - - [CreateAuthenticatorParams](docs/CreateAuthenticatorParams.md) - - [CreateAuthenticatorResponse](docs/CreateAuthenticatorResponse.md) - [CreateCatalogParams](docs/CreateCatalogParams.md) - [CreateCatalogResponse](docs/CreateCatalogResponse.md) - [CreateDeviceLoginTicketResponse](docs/CreateDeviceLoginTicketResponse.md) @@ -169,8 +161,6 @@ Class | Method | HTTP request | Description - [CreateEnvironmentResponse](docs/CreateEnvironmentResponse.md) - [CreateGuestParams](docs/CreateGuestParams.md) - [CreateGuestResponse](docs/CreateGuestResponse.md) - - [CreatePasswordResetParams](docs/CreatePasswordResetParams.md) - - [CreatePasswordResetResponse](docs/CreatePasswordResetResponse.md) - [CreateSandboxSecretsParams](docs/CreateSandboxSecretsParams.md) - [CreateSandboxSecretsResponse](docs/CreateSandboxSecretsResponse.md) - [CreateScheduleParams](docs/CreateScheduleParams.md) @@ -186,8 +176,6 @@ Class | Method | HTTP request | Description - [DeleteApiKeyParams](docs/DeleteApiKeyParams.md) - [DeleteApiKeyResponse](docs/DeleteApiKeyResponse.md) - [DeleteAppResponse](docs/DeleteAppResponse.md) - - [DeleteAuthenticatorParams](docs/DeleteAuthenticatorParams.md) - - [DeleteAuthenticatorResponse](docs/DeleteAuthenticatorResponse.md) - [DeleteCatalogResponse](docs/DeleteCatalogResponse.md) - [DeleteGuestOutputBody](docs/DeleteGuestOutputBody.md) - [DeleteScheduleParams](docs/DeleteScheduleParams.md) @@ -238,7 +226,7 @@ Class | Method | HTTP request | Description - [Feature](docs/Feature.md) - [FeaturebaseIdentity](docs/FeaturebaseIdentity.md) - [GenerateAppStatisticsResponse](docs/GenerateAppStatisticsResponse.md) - - [GenerateAuthenticatorResponse](docs/GenerateAuthenticatorResponse.md) + - [GenerateOrganizationUsageTimeSeriesResponse](docs/GenerateOrganizationUsageTimeSeriesResponse.md) - [GenerateRunStatisticsResponse](docs/GenerateRunStatisticsResponse.md) - [GenerateRunnerCredentialsResponse](docs/GenerateRunnerCredentialsResponse.md) - [GetFeatureFlagResponseBody](docs/GetFeatureFlagResponseBody.md) @@ -251,7 +239,6 @@ Class | Method | HTTP request | Description - [ListAppEnvironmentsResponse](docs/ListAppEnvironmentsResponse.md) - [ListAppVersionsResponse](docs/ListAppVersionsResponse.md) - [ListAppsResponse](docs/ListAppsResponse.md) - - [ListAuthenticatorsResponse](docs/ListAuthenticatorsResponse.md) - [ListCatalogsResponse](docs/ListCatalogsResponse.md) - [ListEnvironmentsResponse](docs/ListEnvironmentsResponse.md) - [ListGuestsResponse](docs/ListGuestsResponse.md) @@ -313,9 +300,10 @@ Class | Method | HTTP request | Description - [TeamMembership](docs/TeamMembership.md) - [TestWebhookResponse](docs/TestWebhookResponse.md) - [Token](docs/Token.md) - - [UnverifiedAuthenticator](docs/UnverifiedAuthenticator.md) - [UpdateAccountParams](docs/UpdateAccountParams.md) - [UpdateAccountResponse](docs/UpdateAccountResponse.md) + - [UpdateAppEnvironmentParams](docs/UpdateAppEnvironmentParams.md) + - [UpdateAppEnvironmentResponse](docs/UpdateAppEnvironmentResponse.md) - [UpdateAppParams](docs/UpdateAppParams.md) - [UpdateAppResponse](docs/UpdateAppResponse.md) - [UpdateCatalogParams](docs/UpdateCatalogParams.md) @@ -327,8 +315,6 @@ Class | Method | HTTP request | Description - [UpdateMyTeamInvitationResponse](docs/UpdateMyTeamInvitationResponse.md) - [UpdateOrganizationParams](docs/UpdateOrganizationParams.md) - [UpdateOrganizationResponse](docs/UpdateOrganizationResponse.md) - - [UpdatePasswordResetParams](docs/UpdatePasswordResetParams.md) - - [UpdatePasswordResetResponse](docs/UpdatePasswordResetResponse.md) - [UpdatePlanParams](docs/UpdatePlanParams.md) - [UpdatePlanResponse](docs/UpdatePlanResponse.md) - [UpdateScheduleParams](docs/UpdateScheduleParams.md) @@ -344,10 +330,8 @@ Class | Method | HTTP request | Description - [UpdateWebhookParams](docs/UpdateWebhookParams.md) - [UpdateWebhookResponse](docs/UpdateWebhookResponse.md) - [UsageLimit](docs/UsageLimit.md) + - [UsageMetricTimeSeriesPoint](docs/UsageMetricTimeSeriesPoint.md) - [User](docs/User.md) - - [VerifiedAuthenticator](docs/VerifiedAuthenticator.md) - - [VerifyEmailParams](docs/VerifyEmailParams.md) - - [VerifyEmailResponse](docs/VerifyEmailResponse.md) - [Webhook](docs/Webhook.md) diff --git a/crates/tower-api/src/apis/configuration.rs b/crates/tower-api/src/apis/configuration.rs index 2fb4a64e..8c0cc1d9 100644 --- a/crates/tower-api/src/apis/configuration.rs +++ b/crates/tower-api/src/apis/configuration.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/apis/default_api.rs b/crates/tower-api/src/apis/default_api.rs index d80db99c..3b081631 100644 --- a/crates/tower-api/src/apis/default_api.rs +++ b/crates/tower-api/src/apis/default_api.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -67,12 +67,6 @@ pub struct CreateAppParams { pub create_app_params: models::CreateAppParams, } -/// struct for passing parameters to the method [`create_authenticator`] -#[derive(Clone, Debug)] -pub struct CreateAuthenticatorParams { - pub create_authenticator_params: models::CreateAuthenticatorParams, -} - /// struct for passing parameters to the method [`create_catalog`] #[derive(Clone, Debug)] pub struct CreateCatalogParams { @@ -91,12 +85,6 @@ pub struct CreateGuestParams { pub create_guest_params: models::CreateGuestParams, } -/// struct for passing parameters to the method [`create_password_reset`] -#[derive(Clone, Debug)] -pub struct CreatePasswordResetParams { - pub create_password_reset_params: models::CreatePasswordResetParams, -} - /// struct for passing parameters to the method [`create_sandbox_secrets`] #[derive(Clone, Debug)] pub struct CreateSandboxSecretsParams { @@ -159,12 +147,6 @@ pub struct DeleteAppParams { pub name: String, } -/// struct for passing parameters to the method [`delete_authenticator`] -#[derive(Clone, Debug)] -pub struct DeleteAuthenticatorParams { - pub delete_authenticator_params: models::DeleteAuthenticatorParams, -} - /// struct for passing parameters to the method [`delete_catalog`] #[derive(Clone, Debug)] pub struct DeleteCatalogParams { @@ -234,6 +216,10 @@ pub struct DeployAppParams { pub x_tower_checksum_sha256: Option, /// Size of the uploaded bundle in bytes. pub content_length: Option, + /// The environment to deploy to. + pub environment: Option, + /// Whether to deploy to all environments for this app. If true, the 'environment' query parameter is ignored. + pub all_environments: Option, } /// struct for passing parameters to the method [`describe_account`] @@ -256,6 +242,8 @@ pub struct DescribeAppParams { pub end_at: Option, /// Timezone for the statistics (e.g., 'Europe/Berlin'). Defaults to UTC. pub timezone: Option, + /// The environment to resolve the app version against. Defaults to 'default'. + pub environment: Option, } /// struct for passing parameters to the method [`describe_app_version`] @@ -645,6 +633,16 @@ pub struct UpdateAppParams { pub update_app_params: models::UpdateAppParams, } +/// struct for passing parameters to the method [`update_app_environment`] +#[derive(Clone, Debug)] +pub struct UpdateAppEnvironmentParams { + /// The name of the app. + pub name: String, + /// The name of the environment. + pub environment: String, + pub update_app_environment_params: models::UpdateAppEnvironmentParams, +} + /// struct for passing parameters to the method [`update_catalog`] #[derive(Clone, Debug)] pub struct UpdateCatalogParams { @@ -681,14 +679,6 @@ pub struct UpdateOrganizationParams { pub update_organization_params: models::UpdateOrganizationParams, } -/// struct for passing parameters to the method [`update_password_reset`] -#[derive(Clone, Debug)] -pub struct UpdatePasswordResetParams { - /// The password reset code that was sent to you - pub code: String, - pub update_password_reset_params: models::UpdatePasswordResetParams, -} - /// struct for passing parameters to the method [`update_plan`] #[derive(Clone, Debug)] pub struct UpdatePlanParams { @@ -740,12 +730,6 @@ pub struct UpdateWebhookParams { pub update_webhook_params: models::UpdateWebhookParams, } -/// struct for passing parameters to the method [`verify_email`] -#[derive(Clone, Debug)] -pub struct VerifyEmailParams { - pub verify_email_params: models::VerifyEmailParams, -} - /// struct for typed successes of method [`acknowledge_alert`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -818,14 +802,6 @@ pub enum CreateAppSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`create_authenticator`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum CreateAuthenticatorSuccess { - Status200(models::CreateAuthenticatorResponse), - UnknownValue(serde_json::Value), -} - /// struct for typed successes of method [`create_catalog`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -858,14 +834,6 @@ pub enum CreateGuestSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`create_password_reset`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum CreatePasswordResetSuccess { - Status200(models::CreatePasswordResetResponse), - UnknownValue(serde_json::Value), -} - /// struct for typed successes of method [`create_sandbox_secrets`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -946,14 +914,6 @@ pub enum DeleteAppSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`delete_authenticator`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DeleteAuthenticatorSuccess { - Status200(models::DeleteAuthenticatorResponse), - UnknownValue(serde_json::Value), -} - /// struct for typed successes of method [`delete_catalog`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1178,11 +1138,11 @@ pub enum GenerateAppStatisticsSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`generate_authenticator`] +/// struct for typed successes of method [`generate_organization_usage_time_series`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum GenerateAuthenticatorSuccess { - Status200(models::GenerateAuthenticatorResponse), +pub enum GenerateOrganizationUsageTimeSeriesSuccess { + Status200(models::GenerateOrganizationUsageTimeSeriesResponse), UnknownValue(serde_json::Value), } @@ -1258,14 +1218,6 @@ pub enum ListAppsSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`list_authenticators`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ListAuthenticatorsSuccess { - Status200(models::ListAuthenticatorsResponse), - UnknownValue(serde_json::Value), -} - /// struct for typed successes of method [`list_catalogs`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1394,14 +1346,6 @@ pub enum RemoveTeamMemberSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`resend_email_verification`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ResendEmailVerificationSuccess { - Status204(), - UnknownValue(serde_json::Value), -} - /// struct for typed successes of method [`resend_team_invitation`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1466,6 +1410,14 @@ pub enum UpdateAppSuccess { UnknownValue(serde_json::Value), } +/// struct for typed successes of method [`update_app_environment`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum UpdateAppEnvironmentSuccess { + Status200(models::UpdateAppEnvironmentResponse), + UnknownValue(serde_json::Value), +} + /// struct for typed successes of method [`update_catalog`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1506,14 +1458,6 @@ pub enum UpdateOrganizationSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`update_password_reset`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum UpdatePasswordResetSuccess { - Status200(models::UpdatePasswordResetResponse), - UnknownValue(serde_json::Value), -} - /// struct for typed successes of method [`update_plan`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1570,14 +1514,6 @@ pub enum UpdateWebhookSuccess { UnknownValue(serde_json::Value), } -/// struct for typed successes of method [`verify_email`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum VerifyEmailSuccess { - Status200(models::VerifyEmailResponse), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`acknowledge_alert`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1650,14 +1586,6 @@ pub enum CreateAppError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`create_authenticator`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum CreateAuthenticatorError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`create_catalog`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1690,14 +1618,6 @@ pub enum CreateGuestError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`create_password_reset`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum CreatePasswordResetError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`create_sandbox_secrets`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1781,14 +1701,6 @@ pub enum DeleteAppError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`delete_authenticator`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DeleteAuthenticatorError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`delete_catalog`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -2017,10 +1929,10 @@ pub enum GenerateAppStatisticsError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`generate_authenticator`] +/// struct for typed errors of method [`generate_organization_usage_time_series`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum GenerateAuthenticatorError { +pub enum GenerateOrganizationUsageTimeSeriesError { DefaultResponse(models::ErrorModel), UnknownValue(serde_json::Value), } @@ -2097,14 +2009,6 @@ pub enum ListAppsError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`list_authenticators`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ListAuthenticatorsError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`list_catalogs`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -2233,14 +2137,6 @@ pub enum RemoveTeamMemberError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`resend_email_verification`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ResendEmailVerificationError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`resend_team_invitation`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -2305,6 +2201,14 @@ pub enum UpdateAppError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`update_app_environment`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum UpdateAppEnvironmentError { + DefaultResponse(models::ErrorModel), + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`update_catalog`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -2351,14 +2255,6 @@ pub enum UpdateOrganizationError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`update_password_reset`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum UpdatePasswordResetError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`update_plan`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -2415,14 +2311,6 @@ pub enum UpdateWebhookError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`verify_email`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum VerifyEmailError { - DefaultResponse(models::ErrorModel), - UnknownValue(serde_json::Value), -} - /// Mark an alert as acknowledged pub async fn acknowledge_alert( configuration: &configuration::Configuration, @@ -2940,64 +2828,6 @@ pub async fn create_app( } } -/// Associates an authenticator with your account, where the authenticator is identified by the URL with an otpauth URI scheme. -pub async fn create_authenticator( - configuration: &configuration::Configuration, - params: CreateAuthenticatorParams, -) -> Result, Error> { - let uri_str = format!("{}/authenticators", configuration.base_path); - let mut req_builder = configuration - .client - .request(reqwest::Method::POST, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - if let Some(ref token) = configuration.bearer_access_token { - req_builder = req_builder.bearer_auth(token.to_owned()); - }; - if let Some(ref apikey) = configuration.api_key { - let key = apikey.key.clone(); - let value = match apikey.prefix { - Some(ref prefix) => format!("{} {}", prefix, key), - None => key, - }; - req_builder = req_builder.header("X-API-Key", value); - }; - req_builder = req_builder.json(¶ms.create_authenticator_params); - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} - /// Create a new catalog object in the currently authenticated account. pub async fn create_catalog( configuration: &configuration::Configuration, @@ -3215,53 +3045,6 @@ pub async fn create_guest( } } -/// Starts the password reset process for an account. If an email address exists for the account supplied, you will get a reset password email. -pub async fn create_password_reset( - configuration: &configuration::Configuration, - params: CreatePasswordResetParams, -) -> Result, Error> { - let uri_str = format!("{}/accounts/password-reset", configuration.base_path); - let mut req_builder = configuration - .client - .request(reqwest::Method::POST, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - req_builder = req_builder.json(¶ms.create_password_reset_params); - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} - /// Creates secrets with Tower-provided default values for the specified keys in the given environment. pub async fn create_sandbox_secrets( configuration: &configuration::Configuration, @@ -3835,64 +3618,6 @@ pub async fn delete_app( } } -/// Removes an authenticator from your account so you're no longer required to provide it at login. -pub async fn delete_authenticator( - configuration: &configuration::Configuration, - params: DeleteAuthenticatorParams, -) -> Result, Error> { - let uri_str = format!("{}/authenticators", configuration.base_path); - let mut req_builder = configuration - .client - .request(reqwest::Method::DELETE, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - if let Some(ref token) = configuration.bearer_access_token { - req_builder = req_builder.bearer_auth(token.to_owned()); - }; - if let Some(ref apikey) = configuration.api_key { - let key = apikey.key.clone(); - let value = match apikey.prefix { - Some(ref prefix) => format!("{} {}", prefix, key), - None => key, - }; - req_builder = req_builder.header("X-API-Key", value); - }; - req_builder = req_builder.json(¶ms.delete_authenticator_params); - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} - /// Delete a new catalog object in the currently authenticated account. pub async fn delete_catalog( configuration: &configuration::Configuration, @@ -4392,6 +4117,12 @@ pub async fn deploy_app( .client .request(reqwest::Method::POST, &uri_str); + if let Some(ref param_value) = params.environment { + req_builder = req_builder.query(&[("environment", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.all_environments { + req_builder = req_builder.query(&[("all_environments", ¶m_value.to_string())]); + } if let Some(ref user_agent) = configuration.user_agent { req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); } @@ -4529,6 +4260,9 @@ pub async fn describe_app( if let Some(ref param_value) = params.timezone { req_builder = req_builder.query(&[("timezone", ¶m_value.to_string())]); } + if let Some(ref param_value) = params.environment { + req_builder = req_builder.query(&[("environment", ¶m_value.to_string())]); + } if let Some(ref user_agent) = configuration.user_agent { req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); } @@ -4863,7 +4597,7 @@ pub async fn describe_email_preferences( } } -/// Describe usage statistics for the user's organization. +/// Describe usage statistics for the user's organization for the current billing cycle. pub async fn describe_organization_usage( configuration: &configuration::Configuration, ) -> Result, Error> @@ -5565,14 +5299,15 @@ pub async fn generate_app_statistics( } } -/// Generates a new authenticator for the user. This is used to set up two-factor authentication. -pub async fn generate_authenticator( +/// Get the current billing cycle usage as a time series. +pub async fn generate_organization_usage_time_series( configuration: &configuration::Configuration, -) -> Result, Error> { - let uri_str = format!("{}/authenticators/generate", configuration.base_path); - let mut req_builder = configuration - .client - .request(reqwest::Method::POST, &uri_str); +) -> Result< + ResponseContent, + Error, +> { + let uri_str = format!("{}/usage/time-series", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); if let Some(ref user_agent) = configuration.user_agent { req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); @@ -5602,7 +5337,8 @@ pub async fn generate_authenticator( if !status.is_client_error() && !status.is_server_error() { let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); + let entity: Option = + serde_json::from_str(&content).ok(); Ok(ResponseContent { tower_trace_id, status, @@ -5611,7 +5347,8 @@ pub async fn generate_authenticator( }) } else { let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); + let entity: Option = + serde_json::from_str(&content).ok(); Err(Error::ResponseError(ResponseContent { tower_trace_id, status, @@ -6214,60 +5951,6 @@ pub async fn list_apps( } } -/// Enumerates the authenticators associated with the current users' account -pub async fn list_authenticators( - configuration: &configuration::Configuration, -) -> Result, Error> { - let uri_str = format!("{}/authenticators", configuration.base_path); - let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - if let Some(ref token) = configuration.bearer_access_token { - req_builder = req_builder.bearer_auth(token.to_owned()); - }; - if let Some(ref apikey) = configuration.api_key { - let key = apikey.key.clone(); - let value = match apikey.prefix { - Some(ref prefix) => format!("{} {}", prefix, key), - None => key, - }; - req_builder = req_builder.header("X-API-Key", value); - }; - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} - /// Lists all the catalogs associated with your current account. pub async fn list_catalogs( configuration: &configuration::Configuration, @@ -7267,62 +6950,6 @@ pub async fn remove_team_member( } } -/// If a user doesn't have a verified email address, this API endpoint will send a new confirmation email to them -pub async fn resend_email_verification( - configuration: &configuration::Configuration, -) -> Result, Error> { - let uri_str = format!("{}/user/resend-verification", configuration.base_path); - let mut req_builder = configuration - .client - .request(reqwest::Method::POST, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - if let Some(ref token) = configuration.bearer_access_token { - req_builder = req_builder.bearer_auth(token.to_owned()); - }; - if let Some(ref apikey) = configuration.api_key { - let key = apikey.key.clone(); - let value = match apikey.prefix { - Some(ref prefix) => format!("{} {}", prefix, key), - None => key, - }; - req_builder = req_builder.header("X-API-Key", value); - }; - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} - /// Resend a team invitation to a user if they need a reminder or if they lost it pub async fn resend_team_invitation( configuration: &configuration::Configuration, @@ -7824,6 +7451,67 @@ pub async fn update_app( } } +/// Update the configuration of an app in a specific environment, such as which version is deployed. +pub async fn update_app_environment( + configuration: &configuration::Configuration, + params: UpdateAppEnvironmentParams, +) -> Result, Error> { + let uri_str = format!( + "{}/apps/{name}/environments/{environment}", + configuration.base_path, + name = crate::apis::urlencode(params.name), + environment = crate::apis::urlencode(params.environment) + ); + let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + if let Some(ref apikey) = configuration.api_key { + let key = apikey.key.clone(); + let value = match apikey.prefix { + Some(ref prefix) => format!("{} {}", prefix, key), + None => key, + }; + req_builder = req_builder.header("X-API-Key", value); + }; + req_builder = req_builder.json(¶ms.update_app_environment_params); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + + let tower_trace_id = resp + .headers() + .get("x-tower-trace-id") + .and_then(|v| v.to_str().ok()) + .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Ok(ResponseContent { + tower_trace_id, + status, + content, + entity, + }) + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + tower_trace_id, + status, + content, + entity, + })) + } +} + /// Update a new catalog object in the currently authenticated account. pub async fn update_catalog( configuration: &configuration::Configuration, @@ -8116,57 +7804,6 @@ pub async fn update_organization( } } -/// Updates the password reset code with the new password -pub async fn update_password_reset( - configuration: &configuration::Configuration, - params: UpdatePasswordResetParams, -) -> Result, Error> { - let uri_str = format!( - "{}/accounts/password-reset/{code}", - configuration.base_path, - code = crate::apis::urlencode(params.code) - ); - let mut req_builder = configuration - .client - .request(reqwest::Method::POST, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - req_builder = req_builder.json(¶ms.update_password_reset_params); - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} - pub async fn update_plan( configuration: &configuration::Configuration, params: UpdatePlanParams, @@ -8576,61 +8213,3 @@ pub async fn update_webhook( })) } } - -/// If the user hasn't verified their email address, this API endpoint allows them to send a confirmation token they received via email to indeed verify they can receive emails. -pub async fn verify_email( - configuration: &configuration::Configuration, - params: VerifyEmailParams, -) -> Result, Error> { - let uri_str = format!("{}/user/verify", configuration.base_path); - let mut req_builder = configuration - .client - .request(reqwest::Method::POST, &uri_str); - - if let Some(ref user_agent) = configuration.user_agent { - req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); - } - if let Some(ref token) = configuration.bearer_access_token { - req_builder = req_builder.bearer_auth(token.to_owned()); - }; - if let Some(ref apikey) = configuration.api_key { - let key = apikey.key.clone(); - let value = match apikey.prefix { - Some(ref prefix) => format!("{} {}", prefix, key), - None => key, - }; - req_builder = req_builder.header("X-API-Key", value); - }; - req_builder = req_builder.json(¶ms.verify_email_params); - - let req = req_builder.build()?; - let resp = configuration.client.execute(req).await?; - - let status = resp.status(); - - let tower_trace_id = resp - .headers() - .get("x-tower-trace-id") - .and_then(|v| v.to_str().ok()) - .map_or(String::from(DEFAULT_TOWER_TRACE_ID), String::from); - - if !status.is_client_error() && !status.is_server_error() { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Ok(ResponseContent { - tower_trace_id, - status, - content, - entity, - }) - } else { - let content = resp.text().await?; - let entity: Option = serde_json::from_str(&content).ok(); - Err(Error::ResponseError(ResponseContent { - tower_trace_id, - status, - content, - entity, - })) - } -} diff --git a/crates/tower-api/src/apis/feature_flags_api.rs b/crates/tower-api/src/apis/feature_flags_api.rs index df1a7e95..1e9a0237 100644 --- a/crates/tower-api/src/apis/feature_flags_api.rs +++ b/crates/tower-api/src/apis/feature_flags_api.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/account.rs b/crates/tower-api/src/models/account.rs index 6bc16d2f..116672a5 100644 --- a/crates/tower-api/src/models/account.rs +++ b/crates/tower-api/src/models/account.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/acknowledge_alert_response.rs b/crates/tower-api/src/models/acknowledge_alert_response.rs index 69a4d41e..3cf58938 100644 --- a/crates/tower-api/src/models/acknowledge_alert_response.rs +++ b/crates/tower-api/src/models/acknowledge_alert_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/acknowledge_all_alerts_response.rs b/crates/tower-api/src/models/acknowledge_all_alerts_response.rs index 0d7abb66..91060002 100644 --- a/crates/tower-api/src/models/acknowledge_all_alerts_response.rs +++ b/crates/tower-api/src/models/acknowledge_all_alerts_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/alert.rs b/crates/tower-api/src/models/alert.rs index 1d0a28f6..16e47b0c 100644 --- a/crates/tower-api/src/models/alert.rs +++ b/crates/tower-api/src/models/alert.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/api_key.rs b/crates/tower-api/src/models/api_key.rs index a6ef1419..1f112ff5 100644 --- a/crates/tower-api/src/models/api_key.rs +++ b/crates/tower-api/src/models/api_key.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/app.rs b/crates/tower-api/src/models/app.rs index e13daf09..abbc4c54 100644 --- a/crates/tower-api/src/models/app.rs +++ b/crates/tower-api/src/models/app.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/app_statistics.rs b/crates/tower-api/src/models/app_statistics.rs index ea5e9193..f2d9c680 100644 --- a/crates/tower-api/src/models/app_statistics.rs +++ b/crates/tower-api/src/models/app_statistics.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/app_summary.rs b/crates/tower-api/src/models/app_summary.rs index 1cc996c9..a2eb79df 100644 --- a/crates/tower-api/src/models/app_summary.rs +++ b/crates/tower-api/src/models/app_summary.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/app_version.rs b/crates/tower-api/src/models/app_version.rs index 7fed8482..d1262e21 100644 --- a/crates/tower-api/src/models/app_version.rs +++ b/crates/tower-api/src/models/app_version.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/authentication_context.rs b/crates/tower-api/src/models/authentication_context.rs index db0ea04f..ba6f7a8b 100644 --- a/crates/tower-api/src/models/authentication_context.rs +++ b/crates/tower-api/src/models/authentication_context.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/batch_schedule_params.rs b/crates/tower-api/src/models/batch_schedule_params.rs index 6283b874..54d803d5 100644 --- a/crates/tower-api/src/models/batch_schedule_params.rs +++ b/crates/tower-api/src/models/batch_schedule_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/batch_schedule_response.rs b/crates/tower-api/src/models/batch_schedule_response.rs index 3bbfeb99..95604266 100644 --- a/crates/tower-api/src/models/batch_schedule_response.rs +++ b/crates/tower-api/src/models/batch_schedule_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/cancel_run_response.rs b/crates/tower-api/src/models/cancel_run_response.rs index 49769543..07da93b7 100644 --- a/crates/tower-api/src/models/cancel_run_response.rs +++ b/crates/tower-api/src/models/cancel_run_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -17,13 +17,21 @@ pub struct CancelRunResponse { /// A URL to the JSON Schema for this object. #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] pub schema: Option, + /// Number of descendant runs that were also cancelled as part of this cascade. + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "cancelled_child_runs")] + pub cancelled_child_runs: i64, #[serde_as(as = "DefaultOnNull")] #[serde(rename = "run")] pub run: models::Run, } impl CancelRunResponse { - pub fn new(run: models::Run) -> CancelRunResponse { - CancelRunResponse { schema: None, run } + pub fn new(cancelled_child_runs: i64, run: models::Run) -> CancelRunResponse { + CancelRunResponse { + schema: None, + cancelled_child_runs, + run, + } } } diff --git a/crates/tower-api/src/models/catalog.rs b/crates/tower-api/src/models/catalog.rs index 47c54003..cfc5944f 100644 --- a/crates/tower-api/src/models/catalog.rs +++ b/crates/tower-api/src/models/catalog.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/catalog_property.rs b/crates/tower-api/src/models/catalog_property.rs index 89ecb996..708c9a98 100644 --- a/crates/tower-api/src/models/catalog_property.rs +++ b/crates/tower-api/src/models/catalog_property.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/claim_device_login_ticket_params.rs b/crates/tower-api/src/models/claim_device_login_ticket_params.rs index 1513f934..4571e526 100644 --- a/crates/tower-api/src/models/claim_device_login_ticket_params.rs +++ b/crates/tower-api/src/models/claim_device_login_ticket_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/claim_device_login_ticket_response.rs b/crates/tower-api/src/models/claim_device_login_ticket_response.rs index 8d90761f..c671b5eb 100644 --- a/crates/tower-api/src/models/claim_device_login_ticket_response.rs +++ b/crates/tower-api/src/models/claim_device_login_ticket_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_account_params.rs b/crates/tower-api/src/models/create_account_params.rs index 84a22a12..336e8686 100644 --- a/crates/tower-api/src/models/create_account_params.rs +++ b/crates/tower-api/src/models/create_account_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_account_params_flags_struct.rs b/crates/tower-api/src/models/create_account_params_flags_struct.rs index d5ed4904..328e26c0 100644 --- a/crates/tower-api/src/models/create_account_params_flags_struct.rs +++ b/crates/tower-api/src/models/create_account_params_flags_struct.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_account_response.rs b/crates/tower-api/src/models/create_account_response.rs index 16828667..1ea4ae6c 100644 --- a/crates/tower-api/src/models/create_account_response.rs +++ b/crates/tower-api/src/models/create_account_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_api_key_params.rs b/crates/tower-api/src/models/create_api_key_params.rs index 582d9b5f..2530d81d 100644 --- a/crates/tower-api/src/models/create_api_key_params.rs +++ b/crates/tower-api/src/models/create_api_key_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_api_key_response.rs b/crates/tower-api/src/models/create_api_key_response.rs index d7bacc8f..d30a2857 100644 --- a/crates/tower-api/src/models/create_api_key_response.rs +++ b/crates/tower-api/src/models/create_api_key_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_app_params.rs b/crates/tower-api/src/models/create_app_params.rs index b49de310..4e988c8f 100644 --- a/crates/tower-api/src/models/create_app_params.rs +++ b/crates/tower-api/src/models/create_app_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_app_response.rs b/crates/tower-api/src/models/create_app_response.rs index 36d1f4b7..cd76db8f 100644 --- a/crates/tower-api/src/models/create_app_response.rs +++ b/crates/tower-api/src/models/create_app_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_catalog_params.rs b/crates/tower-api/src/models/create_catalog_params.rs index a48906ae..6d68e41e 100644 --- a/crates/tower-api/src/models/create_catalog_params.rs +++ b/crates/tower-api/src/models/create_catalog_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -60,6 +60,8 @@ pub enum Type { Lakekeeper, #[serde(rename = "tower-catalog")] TowerCatalog, + #[serde(rename = "s3-tables")] + S3Tables, } impl Default for Type { @@ -80,6 +82,7 @@ impl<'de> Deserialize<'de> for Type { "cloudflare-r2-catalog" => Ok(Self::CloudflareR2Catalog), "lakekeeper" => Ok(Self::Lakekeeper), "tower-catalog" => Ok(Self::TowerCatalog), + "s3-tables" => Ok(Self::S3Tables), _ => Err(serde::de::Error::unknown_variant( &s, &[ @@ -88,6 +91,7 @@ impl<'de> Deserialize<'de> for Type { "cloudflare-r2-catalog", "lakekeeper", "tower-catalog", + "s3-tables", ], )), } diff --git a/crates/tower-api/src/models/create_catalog_response.rs b/crates/tower-api/src/models/create_catalog_response.rs index 1b7feab8..b18d7639 100644 --- a/crates/tower-api/src/models/create_catalog_response.rs +++ b/crates/tower-api/src/models/create_catalog_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_device_login_ticket_response.rs b/crates/tower-api/src/models/create_device_login_ticket_response.rs index cf2cdf04..8ed291d2 100644 --- a/crates/tower-api/src/models/create_device_login_ticket_response.rs +++ b/crates/tower-api/src/models/create_device_login_ticket_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_environment_params.rs b/crates/tower-api/src/models/create_environment_params.rs index 985bfd26..7f5841f2 100644 --- a/crates/tower-api/src/models/create_environment_params.rs +++ b/crates/tower-api/src/models/create_environment_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_environment_response.rs b/crates/tower-api/src/models/create_environment_response.rs index b92f1e3d..d6b5d4ac 100644 --- a/crates/tower-api/src/models/create_environment_response.rs +++ b/crates/tower-api/src/models/create_environment_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_guest_params.rs b/crates/tower-api/src/models/create_guest_params.rs index 1d929417..6d24139a 100644 --- a/crates/tower-api/src/models/create_guest_params.rs +++ b/crates/tower-api/src/models/create_guest_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_guest_response.rs b/crates/tower-api/src/models/create_guest_response.rs index 797cf9d9..efaa7b59 100644 --- a/crates/tower-api/src/models/create_guest_response.rs +++ b/crates/tower-api/src/models/create_guest_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_sandbox_secrets_params.rs b/crates/tower-api/src/models/create_sandbox_secrets_params.rs index 1cec5160..ff6cc634 100644 --- a/crates/tower-api/src/models/create_sandbox_secrets_params.rs +++ b/crates/tower-api/src/models/create_sandbox_secrets_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_sandbox_secrets_response.rs b/crates/tower-api/src/models/create_sandbox_secrets_response.rs index 85969a9b..afeb5d6b 100644 --- a/crates/tower-api/src/models/create_sandbox_secrets_response.rs +++ b/crates/tower-api/src/models/create_sandbox_secrets_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_schedule_params.rs b/crates/tower-api/src/models/create_schedule_params.rs index f1f7b453..a87807d0 100644 --- a/crates/tower-api/src/models/create_schedule_params.rs +++ b/crates/tower-api/src/models/create_schedule_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -21,7 +21,7 @@ pub struct CreateScheduleParams { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "app_name")] pub app_name: String, - /// The specific app version to run (if omitted, will use the app's default version) + /// This property is deprecated and ignored. Schedules inherit the version from their environment. #[serde( rename = "app_version", default, diff --git a/crates/tower-api/src/models/create_schedule_response.rs b/crates/tower-api/src/models/create_schedule_response.rs index a10c413e..f2746869 100644 --- a/crates/tower-api/src/models/create_schedule_response.rs +++ b/crates/tower-api/src/models/create_schedule_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_secret_params.rs b/crates/tower-api/src/models/create_secret_params.rs index 695f87d7..d074ecf0 100644 --- a/crates/tower-api/src/models/create_secret_params.rs +++ b/crates/tower-api/src/models/create_secret_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_secret_response.rs b/crates/tower-api/src/models/create_secret_response.rs index 613dfc98..6e67efbe 100644 --- a/crates/tower-api/src/models/create_secret_response.rs +++ b/crates/tower-api/src/models/create_secret_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_session_params.rs b/crates/tower-api/src/models/create_session_params.rs index dca56e2b..f911bb0e 100644 --- a/crates/tower-api/src/models/create_session_params.rs +++ b/crates/tower-api/src/models/create_session_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_session_response.rs b/crates/tower-api/src/models/create_session_response.rs index fb8368b2..1b522b5b 100644 --- a/crates/tower-api/src/models/create_session_response.rs +++ b/crates/tower-api/src/models/create_session_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_team_params.rs b/crates/tower-api/src/models/create_team_params.rs index 9c8d77f3..6b1df789 100644 --- a/crates/tower-api/src/models/create_team_params.rs +++ b/crates/tower-api/src/models/create_team_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_team_response.rs b/crates/tower-api/src/models/create_team_response.rs index 45004172..a4641a5f 100644 --- a/crates/tower-api/src/models/create_team_response.rs +++ b/crates/tower-api/src/models/create_team_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_webhook_params.rs b/crates/tower-api/src/models/create_webhook_params.rs index 613a2a42..2efa854f 100644 --- a/crates/tower-api/src/models/create_webhook_params.rs +++ b/crates/tower-api/src/models/create_webhook_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/create_webhook_response.rs b/crates/tower-api/src/models/create_webhook_response.rs index a558436e..905ee30e 100644 --- a/crates/tower-api/src/models/create_webhook_response.rs +++ b/crates/tower-api/src/models/create_webhook_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_api_key_params.rs b/crates/tower-api/src/models/delete_api_key_params.rs index ceea7328..08b09485 100644 --- a/crates/tower-api/src/models/delete_api_key_params.rs +++ b/crates/tower-api/src/models/delete_api_key_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_api_key_response.rs b/crates/tower-api/src/models/delete_api_key_response.rs index dd5042f6..6532f3c0 100644 --- a/crates/tower-api/src/models/delete_api_key_response.rs +++ b/crates/tower-api/src/models/delete_api_key_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_app_response.rs b/crates/tower-api/src/models/delete_app_response.rs index 6ad3f4e4..23ee6d59 100644 --- a/crates/tower-api/src/models/delete_app_response.rs +++ b/crates/tower-api/src/models/delete_app_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_catalog_response.rs b/crates/tower-api/src/models/delete_catalog_response.rs index 94a6436f..1eab81e6 100644 --- a/crates/tower-api/src/models/delete_catalog_response.rs +++ b/crates/tower-api/src/models/delete_catalog_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_guest_output_body.rs b/crates/tower-api/src/models/delete_guest_output_body.rs index 494227a2..4cad97b8 100644 --- a/crates/tower-api/src/models/delete_guest_output_body.rs +++ b/crates/tower-api/src/models/delete_guest_output_body.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_schedule_params.rs b/crates/tower-api/src/models/delete_schedule_params.rs index 4cbe866b..7ac3e40a 100644 --- a/crates/tower-api/src/models/delete_schedule_params.rs +++ b/crates/tower-api/src/models/delete_schedule_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_schedule_response.rs b/crates/tower-api/src/models/delete_schedule_response.rs index 5a145359..2bb66758 100644 --- a/crates/tower-api/src/models/delete_schedule_response.rs +++ b/crates/tower-api/src/models/delete_schedule_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_secret_response.rs b/crates/tower-api/src/models/delete_secret_response.rs index 13f0c7a5..6b243271 100644 --- a/crates/tower-api/src/models/delete_secret_response.rs +++ b/crates/tower-api/src/models/delete_secret_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_session_params.rs b/crates/tower-api/src/models/delete_session_params.rs index 02e2b3dc..68502ec9 100644 --- a/crates/tower-api/src/models/delete_session_params.rs +++ b/crates/tower-api/src/models/delete_session_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_session_response.rs b/crates/tower-api/src/models/delete_session_response.rs index 66eeb6cb..fd2a8cca 100644 --- a/crates/tower-api/src/models/delete_session_response.rs +++ b/crates/tower-api/src/models/delete_session_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_team_invitation_params.rs b/crates/tower-api/src/models/delete_team_invitation_params.rs index edf7fb18..e3612231 100644 --- a/crates/tower-api/src/models/delete_team_invitation_params.rs +++ b/crates/tower-api/src/models/delete_team_invitation_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_team_invitation_response.rs b/crates/tower-api/src/models/delete_team_invitation_response.rs index e86f224f..8c158558 100644 --- a/crates/tower-api/src/models/delete_team_invitation_response.rs +++ b/crates/tower-api/src/models/delete_team_invitation_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_team_params.rs b/crates/tower-api/src/models/delete_team_params.rs index d67705a1..68d0be3d 100644 --- a/crates/tower-api/src/models/delete_team_params.rs +++ b/crates/tower-api/src/models/delete_team_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_team_response.rs b/crates/tower-api/src/models/delete_team_response.rs index 04902420..0231e905 100644 --- a/crates/tower-api/src/models/delete_team_response.rs +++ b/crates/tower-api/src/models/delete_team_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/delete_webhook_response.rs b/crates/tower-api/src/models/delete_webhook_response.rs index 327a4fc4..f7988d8b 100644 --- a/crates/tower-api/src/models/delete_webhook_response.rs +++ b/crates/tower-api/src/models/delete_webhook_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/deploy_app_request.rs b/crates/tower-api/src/models/deploy_app_request.rs index 4f7a20d5..0636309a 100644 --- a/crates/tower-api/src/models/deploy_app_request.rs +++ b/crates/tower-api/src/models/deploy_app_request.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/deploy_app_response.rs b/crates/tower-api/src/models/deploy_app_response.rs index a4cb448b..e3000f51 100644 --- a/crates/tower-api/src/models/deploy_app_response.rs +++ b/crates/tower-api/src/models/deploy_app_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_account_body.rs b/crates/tower-api/src/models/describe_account_body.rs index ce51e184..d860bc7b 100644 --- a/crates/tower-api/src/models/describe_account_body.rs +++ b/crates/tower-api/src/models/describe_account_body.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_app_response.rs b/crates/tower-api/src/models/describe_app_response.rs index af78089a..252b611f 100644 --- a/crates/tower-api/src/models/describe_app_response.rs +++ b/crates/tower-api/src/models/describe_app_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_app_version_response.rs b/crates/tower-api/src/models/describe_app_version_response.rs index d7d64477..88011575 100644 --- a/crates/tower-api/src/models/describe_app_version_response.rs +++ b/crates/tower-api/src/models/describe_app_version_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_authentication_context_body.rs b/crates/tower-api/src/models/describe_authentication_context_body.rs index 05c1e4fb..49ae2ae9 100644 --- a/crates/tower-api/src/models/describe_authentication_context_body.rs +++ b/crates/tower-api/src/models/describe_authentication_context_body.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_catalog_response.rs b/crates/tower-api/src/models/describe_catalog_response.rs index 3bf15cf2..131c83ca 100644 --- a/crates/tower-api/src/models/describe_catalog_response.rs +++ b/crates/tower-api/src/models/describe_catalog_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_device_login_session_response.rs b/crates/tower-api/src/models/describe_device_login_session_response.rs index 8b2e870c..9f002161 100644 --- a/crates/tower-api/src/models/describe_device_login_session_response.rs +++ b/crates/tower-api/src/models/describe_device_login_session_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_email_preferences_body.rs b/crates/tower-api/src/models/describe_email_preferences_body.rs index b15f801e..37609c33 100644 --- a/crates/tower-api/src/models/describe_email_preferences_body.rs +++ b/crates/tower-api/src/models/describe_email_preferences_body.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_plan_response.rs b/crates/tower-api/src/models/describe_plan_response.rs index 5028d8db..debd454f 100644 --- a/crates/tower-api/src/models/describe_plan_response.rs +++ b/crates/tower-api/src/models/describe_plan_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_run_graph_response.rs b/crates/tower-api/src/models/describe_run_graph_response.rs index 35c2f7d8..63e9f060 100644 --- a/crates/tower-api/src/models/describe_run_graph_response.rs +++ b/crates/tower-api/src/models/describe_run_graph_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_run_links.rs b/crates/tower-api/src/models/describe_run_links.rs index d676a1f6..508248ce 100644 --- a/crates/tower-api/src/models/describe_run_links.rs +++ b/crates/tower-api/src/models/describe_run_links.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_run_logs_response.rs b/crates/tower-api/src/models/describe_run_logs_response.rs index 3118866b..2abee200 100644 --- a/crates/tower-api/src/models/describe_run_logs_response.rs +++ b/crates/tower-api/src/models/describe_run_logs_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_run_response.rs b/crates/tower-api/src/models/describe_run_response.rs index 081e8721..b14ea569 100644 --- a/crates/tower-api/src/models/describe_run_response.rs +++ b/crates/tower-api/src/models/describe_run_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_secrets_key_response.rs b/crates/tower-api/src/models/describe_secrets_key_response.rs index 16bd9f80..cbf925f0 100644 --- a/crates/tower-api/src/models/describe_secrets_key_response.rs +++ b/crates/tower-api/src/models/describe_secrets_key_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_session_response.rs b/crates/tower-api/src/models/describe_session_response.rs index f52d184f..2d6af965 100644 --- a/crates/tower-api/src/models/describe_session_response.rs +++ b/crates/tower-api/src/models/describe_session_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_team_response.rs b/crates/tower-api/src/models/describe_team_response.rs index a1323547..f866d4eb 100644 --- a/crates/tower-api/src/models/describe_team_response.rs +++ b/crates/tower-api/src/models/describe_team_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/describe_webhook_response.rs b/crates/tower-api/src/models/describe_webhook_response.rs index ef66530c..b38ae16e 100644 --- a/crates/tower-api/src/models/describe_webhook_response.rs +++ b/crates/tower-api/src/models/describe_webhook_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/email_subscriptions.rs b/crates/tower-api/src/models/email_subscriptions.rs index 18d42b2d..63f26c6b 100644 --- a/crates/tower-api/src/models/email_subscriptions.rs +++ b/crates/tower-api/src/models/email_subscriptions.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/encrypted_catalog_property.rs b/crates/tower-api/src/models/encrypted_catalog_property.rs index 227a1afe..ae3bad2a 100644 --- a/crates/tower-api/src/models/encrypted_catalog_property.rs +++ b/crates/tower-api/src/models/encrypted_catalog_property.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/environment.rs b/crates/tower-api/src/models/environment.rs index 3e3ec43b..fca84227 100644 --- a/crates/tower-api/src/models/environment.rs +++ b/crates/tower-api/src/models/environment.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/error_detail.rs b/crates/tower-api/src/models/error_detail.rs index ffc77035..b38f2403 100644 --- a/crates/tower-api/src/models/error_detail.rs +++ b/crates/tower-api/src/models/error_detail.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/error_model.rs b/crates/tower-api/src/models/error_model.rs index 43500163..aad23278 100644 --- a/crates/tower-api/src/models/error_model.rs +++ b/crates/tower-api/src/models/error_model.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/event_alert.rs b/crates/tower-api/src/models/event_alert.rs index a43eb580..2be91340 100644 --- a/crates/tower-api/src/models/event_alert.rs +++ b/crates/tower-api/src/models/event_alert.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/event_error.rs b/crates/tower-api/src/models/event_error.rs index f55f33bb..f7728cc3 100644 --- a/crates/tower-api/src/models/event_error.rs +++ b/crates/tower-api/src/models/event_error.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/event_log.rs b/crates/tower-api/src/models/event_log.rs index 00bc043d..2ddb3bdd 100644 --- a/crates/tower-api/src/models/event_log.rs +++ b/crates/tower-api/src/models/event_log.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/event_shouldertap.rs b/crates/tower-api/src/models/event_shouldertap.rs index 38ab0fd6..ae2d8478 100644 --- a/crates/tower-api/src/models/event_shouldertap.rs +++ b/crates/tower-api/src/models/event_shouldertap.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/event_warning.rs b/crates/tower-api/src/models/event_warning.rs index 12907c95..5bf5d34b 100644 --- a/crates/tower-api/src/models/event_warning.rs +++ b/crates/tower-api/src/models/event_warning.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/export_catalogs_params.rs b/crates/tower-api/src/models/export_catalogs_params.rs index 8e347e07..bb3405be 100644 --- a/crates/tower-api/src/models/export_catalogs_params.rs +++ b/crates/tower-api/src/models/export_catalogs_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/export_catalogs_response.rs b/crates/tower-api/src/models/export_catalogs_response.rs index d2b43036..97e63115 100644 --- a/crates/tower-api/src/models/export_catalogs_response.rs +++ b/crates/tower-api/src/models/export_catalogs_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/export_secrets_params.rs b/crates/tower-api/src/models/export_secrets_params.rs index a74c2ca8..edbbae54 100644 --- a/crates/tower-api/src/models/export_secrets_params.rs +++ b/crates/tower-api/src/models/export_secrets_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/export_secrets_response.rs b/crates/tower-api/src/models/export_secrets_response.rs index 32a1dc7d..3b0abe0e 100644 --- a/crates/tower-api/src/models/export_secrets_response.rs +++ b/crates/tower-api/src/models/export_secrets_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/exported_catalog.rs b/crates/tower-api/src/models/exported_catalog.rs index 24830dd1..487b6316 100644 --- a/crates/tower-api/src/models/exported_catalog.rs +++ b/crates/tower-api/src/models/exported_catalog.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/exported_catalog_property.rs b/crates/tower-api/src/models/exported_catalog_property.rs index c0c8ef2f..d94e101c 100644 --- a/crates/tower-api/src/models/exported_catalog_property.rs +++ b/crates/tower-api/src/models/exported_catalog_property.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/exported_secret.rs b/crates/tower-api/src/models/exported_secret.rs index 3ea9181a..ca789489 100644 --- a/crates/tower-api/src/models/exported_secret.rs +++ b/crates/tower-api/src/models/exported_secret.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/feature.rs b/crates/tower-api/src/models/feature.rs index 8ea4778c..6e5ca4e0 100644 --- a/crates/tower-api/src/models/feature.rs +++ b/crates/tower-api/src/models/feature.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/featurebase_identity.rs b/crates/tower-api/src/models/featurebase_identity.rs index edb77672..faa671c0 100644 --- a/crates/tower-api/src/models/featurebase_identity.rs +++ b/crates/tower-api/src/models/featurebase_identity.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/generate_app_statistics_response.rs b/crates/tower-api/src/models/generate_app_statistics_response.rs index 87930793..f81187c5 100644 --- a/crates/tower-api/src/models/generate_app_statistics_response.rs +++ b/crates/tower-api/src/models/generate_app_statistics_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/generate_organization_usage_time_series_response.rs b/crates/tower-api/src/models/generate_organization_usage_time_series_response.rs new file mode 100644 index 00000000..68ea2be1 --- /dev/null +++ b/crates/tower-api/src/models/generate_organization_usage_time_series_response.rs @@ -0,0 +1,34 @@ +/* + * Tower API + * + * REST API to interact with Tower Services. + * + * The version of the OpenAPI document: v0.10.24 + * Contact: hello@tower.dev + * Generated by: https://openapi-generator.tech + */ +use crate::models; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::{serde_as, DefaultOnNull}; + +#[serde_as] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct GenerateOrganizationUsageTimeSeriesResponse { + /// A URL to the JSON Schema for this object. + #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] + pub schema: Option, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "series")] + pub series: Vec, +} + +impl GenerateOrganizationUsageTimeSeriesResponse { + pub fn new( + series: Vec, + ) -> GenerateOrganizationUsageTimeSeriesResponse { + GenerateOrganizationUsageTimeSeriesResponse { + schema: None, + series, + } + } +} diff --git a/crates/tower-api/src/models/generate_run_statistics_response.rs b/crates/tower-api/src/models/generate_run_statistics_response.rs index e18b3ebb..70bc530f 100644 --- a/crates/tower-api/src/models/generate_run_statistics_response.rs +++ b/crates/tower-api/src/models/generate_run_statistics_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/generate_runner_credentials_response.rs b/crates/tower-api/src/models/generate_runner_credentials_response.rs index aca2d164..0cf2c196 100644 --- a/crates/tower-api/src/models/generate_runner_credentials_response.rs +++ b/crates/tower-api/src/models/generate_runner_credentials_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/get_feature_flag_response_body.rs b/crates/tower-api/src/models/get_feature_flag_response_body.rs index 0ca2be66..cfe6bb45 100644 --- a/crates/tower-api/src/models/get_feature_flag_response_body.rs +++ b/crates/tower-api/src/models/get_feature_flag_response_body.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/guest.rs b/crates/tower-api/src/models/guest.rs index ea2930cd..1d4490cc 100644 --- a/crates/tower-api/src/models/guest.rs +++ b/crates/tower-api/src/models/guest.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/invite_team_member_params.rs b/crates/tower-api/src/models/invite_team_member_params.rs index f730c147..ecf9d14b 100644 --- a/crates/tower-api/src/models/invite_team_member_params.rs +++ b/crates/tower-api/src/models/invite_team_member_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/invite_team_member_response.rs b/crates/tower-api/src/models/invite_team_member_response.rs index 8011f368..0d8acffb 100644 --- a/crates/tower-api/src/models/invite_team_member_response.rs +++ b/crates/tower-api/src/models/invite_team_member_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/leave_team_response.rs b/crates/tower-api/src/models/leave_team_response.rs index 095887bc..ec53a0b7 100644 --- a/crates/tower-api/src/models/leave_team_response.rs +++ b/crates/tower-api/src/models/leave_team_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_alerts_response.rs b/crates/tower-api/src/models/list_alerts_response.rs index 498bf635..1c076702 100644 --- a/crates/tower-api/src/models/list_alerts_response.rs +++ b/crates/tower-api/src/models/list_alerts_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_api_keys_response.rs b/crates/tower-api/src/models/list_api_keys_response.rs index 1fe51b8f..43d8ac38 100644 --- a/crates/tower-api/src/models/list_api_keys_response.rs +++ b/crates/tower-api/src/models/list_api_keys_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_app_environments_response.rs b/crates/tower-api/src/models/list_app_environments_response.rs index e2f1c979..76a2c759 100644 --- a/crates/tower-api/src/models/list_app_environments_response.rs +++ b/crates/tower-api/src/models/list_app_environments_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_app_versions_response.rs b/crates/tower-api/src/models/list_app_versions_response.rs index 926cb45d..730443b3 100644 --- a/crates/tower-api/src/models/list_app_versions_response.rs +++ b/crates/tower-api/src/models/list_app_versions_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_apps_response.rs b/crates/tower-api/src/models/list_apps_response.rs index 216ed560..58d49b1d 100644 --- a/crates/tower-api/src/models/list_apps_response.rs +++ b/crates/tower-api/src/models/list_apps_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_catalogs_response.rs b/crates/tower-api/src/models/list_catalogs_response.rs index 829e14fd..c5db3834 100644 --- a/crates/tower-api/src/models/list_catalogs_response.rs +++ b/crates/tower-api/src/models/list_catalogs_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_environments_response.rs b/crates/tower-api/src/models/list_environments_response.rs index 4d7e7ea5..de3b3700 100644 --- a/crates/tower-api/src/models/list_environments_response.rs +++ b/crates/tower-api/src/models/list_environments_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_guests_response.rs b/crates/tower-api/src/models/list_guests_response.rs index 9dec442d..d8c366c8 100644 --- a/crates/tower-api/src/models/list_guests_response.rs +++ b/crates/tower-api/src/models/list_guests_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_my_team_invitations_response.rs b/crates/tower-api/src/models/list_my_team_invitations_response.rs index 53a270c3..9c87365f 100644 --- a/crates/tower-api/src/models/list_my_team_invitations_response.rs +++ b/crates/tower-api/src/models/list_my_team_invitations_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_runners_response.rs b/crates/tower-api/src/models/list_runners_response.rs index 745674bd..edcea666 100644 --- a/crates/tower-api/src/models/list_runners_response.rs +++ b/crates/tower-api/src/models/list_runners_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_runs_response.rs b/crates/tower-api/src/models/list_runs_response.rs index 6f819a1a..3d622433 100644 --- a/crates/tower-api/src/models/list_runs_response.rs +++ b/crates/tower-api/src/models/list_runs_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_schedules_response.rs b/crates/tower-api/src/models/list_schedules_response.rs index 209b42ed..7eaa5f94 100644 --- a/crates/tower-api/src/models/list_schedules_response.rs +++ b/crates/tower-api/src/models/list_schedules_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_secret_environments_response.rs b/crates/tower-api/src/models/list_secret_environments_response.rs index fdb1d4d1..1c4a0297 100644 --- a/crates/tower-api/src/models/list_secret_environments_response.rs +++ b/crates/tower-api/src/models/list_secret_environments_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_secrets_response.rs b/crates/tower-api/src/models/list_secrets_response.rs index 3292d62c..623502ac 100644 --- a/crates/tower-api/src/models/list_secrets_response.rs +++ b/crates/tower-api/src/models/list_secrets_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_team_invitations_response.rs b/crates/tower-api/src/models/list_team_invitations_response.rs index 8ecb25c4..5b138a92 100644 --- a/crates/tower-api/src/models/list_team_invitations_response.rs +++ b/crates/tower-api/src/models/list_team_invitations_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_team_members_response.rs b/crates/tower-api/src/models/list_team_members_response.rs index da6d3ba9..8564b6c5 100644 --- a/crates/tower-api/src/models/list_team_members_response.rs +++ b/crates/tower-api/src/models/list_team_members_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_teams_response.rs b/crates/tower-api/src/models/list_teams_response.rs index 55c3122e..8fea8f2f 100644 --- a/crates/tower-api/src/models/list_teams_response.rs +++ b/crates/tower-api/src/models/list_teams_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/list_webhooks_response.rs b/crates/tower-api/src/models/list_webhooks_response.rs index abead618..ce232d2b 100644 --- a/crates/tower-api/src/models/list_webhooks_response.rs +++ b/crates/tower-api/src/models/list_webhooks_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/mod.rs b/crates/tower-api/src/models/mod.rs index 400fb7cb..5a3a8045 100644 --- a/crates/tower-api/src/models/mod.rs +++ b/crates/tower-api/src/models/mod.rs @@ -46,10 +46,6 @@ pub mod create_app_params; pub use self::create_app_params::CreateAppParams; pub mod create_app_response; pub use self::create_app_response::CreateAppResponse; -pub mod create_authenticator_params; -pub use self::create_authenticator_params::CreateAuthenticatorParams; -pub mod create_authenticator_response; -pub use self::create_authenticator_response::CreateAuthenticatorResponse; pub mod create_catalog_params; pub use self::create_catalog_params::CreateCatalogParams; pub mod create_catalog_response; @@ -64,10 +60,6 @@ pub mod create_guest_params; pub use self::create_guest_params::CreateGuestParams; pub mod create_guest_response; pub use self::create_guest_response::CreateGuestResponse; -pub mod create_password_reset_params; -pub use self::create_password_reset_params::CreatePasswordResetParams; -pub mod create_password_reset_response; -pub use self::create_password_reset_response::CreatePasswordResetResponse; pub mod create_sandbox_secrets_params; pub use self::create_sandbox_secrets_params::CreateSandboxSecretsParams; pub mod create_sandbox_secrets_response; @@ -98,10 +90,6 @@ pub mod delete_api_key_response; pub use self::delete_api_key_response::DeleteApiKeyResponse; pub mod delete_app_response; pub use self::delete_app_response::DeleteAppResponse; -pub mod delete_authenticator_params; -pub use self::delete_authenticator_params::DeleteAuthenticatorParams; -pub mod delete_authenticator_response; -pub use self::delete_authenticator_response::DeleteAuthenticatorResponse; pub mod delete_catalog_response; pub use self::delete_catalog_response::DeleteCatalogResponse; pub mod delete_guest_output_body; @@ -202,8 +190,8 @@ pub mod featurebase_identity; pub use self::featurebase_identity::FeaturebaseIdentity; pub mod generate_app_statistics_response; pub use self::generate_app_statistics_response::GenerateAppStatisticsResponse; -pub mod generate_authenticator_response; -pub use self::generate_authenticator_response::GenerateAuthenticatorResponse; +pub mod generate_organization_usage_time_series_response; +pub use self::generate_organization_usage_time_series_response::GenerateOrganizationUsageTimeSeriesResponse; pub mod generate_run_statistics_response; pub use self::generate_run_statistics_response::GenerateRunStatisticsResponse; pub mod generate_runner_credentials_response; @@ -228,8 +216,6 @@ pub mod list_app_versions_response; pub use self::list_app_versions_response::ListAppVersionsResponse; pub mod list_apps_response; pub use self::list_apps_response::ListAppsResponse; -pub mod list_authenticators_response; -pub use self::list_authenticators_response::ListAuthenticatorsResponse; pub mod list_catalogs_response; pub use self::list_catalogs_response::ListCatalogsResponse; pub mod list_environments_response; @@ -352,12 +338,14 @@ pub mod test_webhook_response; pub use self::test_webhook_response::TestWebhookResponse; pub mod token; pub use self::token::Token; -pub mod unverified_authenticator; -pub use self::unverified_authenticator::UnverifiedAuthenticator; pub mod update_account_params; pub use self::update_account_params::UpdateAccountParams; pub mod update_account_response; pub use self::update_account_response::UpdateAccountResponse; +pub mod update_app_environment_params; +pub use self::update_app_environment_params::UpdateAppEnvironmentParams; +pub mod update_app_environment_response; +pub use self::update_app_environment_response::UpdateAppEnvironmentResponse; pub mod update_app_params; pub use self::update_app_params::UpdateAppParams; pub mod update_app_response; @@ -380,10 +368,6 @@ pub mod update_organization_params; pub use self::update_organization_params::UpdateOrganizationParams; pub mod update_organization_response; pub use self::update_organization_response::UpdateOrganizationResponse; -pub mod update_password_reset_params; -pub use self::update_password_reset_params::UpdatePasswordResetParams; -pub mod update_password_reset_response; -pub use self::update_password_reset_response::UpdatePasswordResetResponse; pub mod update_plan_params; pub use self::update_plan_params::UpdatePlanParams; pub mod update_plan_response; @@ -414,13 +398,9 @@ pub mod update_webhook_response; pub use self::update_webhook_response::UpdateWebhookResponse; pub mod usage_limit; pub use self::usage_limit::UsageLimit; +pub mod usage_metric_time_series_point; +pub use self::usage_metric_time_series_point::UsageMetricTimeSeriesPoint; pub mod user; pub use self::user::User; -pub mod verified_authenticator; -pub use self::verified_authenticator::VerifiedAuthenticator; -pub mod verify_email_params; -pub use self::verify_email_params::VerifyEmailParams; -pub mod verify_email_response; -pub use self::verify_email_response::VerifyEmailResponse; pub mod webhook; pub use self::webhook::Webhook; diff --git a/crates/tower-api/src/models/organization.rs b/crates/tower-api/src/models/organization.rs index 9ca50392..43aad387 100644 --- a/crates/tower-api/src/models/organization.rs +++ b/crates/tower-api/src/models/organization.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/organization_usage.rs b/crates/tower-api/src/models/organization_usage.rs index a78cc92e..26ee42fb 100644 --- a/crates/tower-api/src/models/organization_usage.rs +++ b/crates/tower-api/src/models/organization_usage.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/pagination.rs b/crates/tower-api/src/models/pagination.rs index d94c1eab..94a36b98 100644 --- a/crates/tower-api/src/models/pagination.rs +++ b/crates/tower-api/src/models/pagination.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/parameter.rs b/crates/tower-api/src/models/parameter.rs index 39ea4fbd..811bd79f 100644 --- a/crates/tower-api/src/models/parameter.rs +++ b/crates/tower-api/src/models/parameter.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/plan.rs b/crates/tower-api/src/models/plan.rs index 6b640e6d..f830dd76 100644 --- a/crates/tower-api/src/models/plan.rs +++ b/crates/tower-api/src/models/plan.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/refresh_session_params.rs b/crates/tower-api/src/models/refresh_session_params.rs index 0350f6e9..37f9619c 100644 --- a/crates/tower-api/src/models/refresh_session_params.rs +++ b/crates/tower-api/src/models/refresh_session_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/refresh_session_response.rs b/crates/tower-api/src/models/refresh_session_response.rs index ba3b045a..f8f2d7c4 100644 --- a/crates/tower-api/src/models/refresh_session_response.rs +++ b/crates/tower-api/src/models/refresh_session_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/regenerate_guest_login_url_params.rs b/crates/tower-api/src/models/regenerate_guest_login_url_params.rs index 25bf9376..8030a2ff 100644 --- a/crates/tower-api/src/models/regenerate_guest_login_url_params.rs +++ b/crates/tower-api/src/models/regenerate_guest_login_url_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/regenerate_guest_login_url_response.rs b/crates/tower-api/src/models/regenerate_guest_login_url_response.rs index 07778e18..130e4623 100644 --- a/crates/tower-api/src/models/regenerate_guest_login_url_response.rs +++ b/crates/tower-api/src/models/regenerate_guest_login_url_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/remove_team_member_params.rs b/crates/tower-api/src/models/remove_team_member_params.rs index 6a40c0d9..ba59de99 100644 --- a/crates/tower-api/src/models/remove_team_member_params.rs +++ b/crates/tower-api/src/models/remove_team_member_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/remove_team_member_response.rs b/crates/tower-api/src/models/remove_team_member_response.rs index 5a21575c..da320640 100644 --- a/crates/tower-api/src/models/remove_team_member_response.rs +++ b/crates/tower-api/src/models/remove_team_member_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/resend_team_invitation_params.rs b/crates/tower-api/src/models/resend_team_invitation_params.rs index 21862271..cbbcbf51 100644 --- a/crates/tower-api/src/models/resend_team_invitation_params.rs +++ b/crates/tower-api/src/models/resend_team_invitation_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/resend_team_invitation_response.rs b/crates/tower-api/src/models/resend_team_invitation_response.rs index aae602ff..90181711 100644 --- a/crates/tower-api/src/models/resend_team_invitation_response.rs +++ b/crates/tower-api/src/models/resend_team_invitation_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run.rs b/crates/tower-api/src/models/run.rs index 90011c99..4e84df41 100644 --- a/crates/tower-api/src/models/run.rs +++ b/crates/tower-api/src/models/run.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -74,8 +74,12 @@ pub struct Run { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "scheduled_at")] pub scheduled_at: String, + /// When the run started executing your code. #[serde(rename = "started_at", deserialize_with = "Option::deserialize")] pub started_at: Option, + /// When the runner environment started to get setup. + #[serde(rename = "starting_at", deserialize_with = "Option::deserialize")] + pub starting_at: Option, #[serde_as(as = "DefaultOnNull")] #[serde(rename = "status")] pub status: Status, @@ -110,6 +114,7 @@ impl Run { run_id: String, scheduled_at: String, started_at: Option, + starting_at: Option, status: Status, status_group: StatusGroup, ) -> Run { @@ -134,6 +139,7 @@ impl Run { run_id, scheduled_at, started_at, + starting_at, status, status_group, subdomain: None, @@ -149,6 +155,8 @@ pub enum Status { Retrying, #[serde(rename = "pending")] Pending, + #[serde(rename = "starting")] + Starting, #[serde(rename = "running")] Running, #[serde(rename = "crashed")] @@ -177,6 +185,7 @@ impl<'de> Deserialize<'de> for Status { "scheduled" => Ok(Self::Scheduled), "retrying" => Ok(Self::Retrying), "pending" => Ok(Self::Pending), + "starting" => Ok(Self::Starting), "running" => Ok(Self::Running), "crashed" => Ok(Self::Crashed), "errored" => Ok(Self::Errored), @@ -188,6 +197,7 @@ impl<'de> Deserialize<'de> for Status { "scheduled", "retrying", "pending", + "starting", "running", "crashed", "errored", diff --git a/crates/tower-api/src/models/run_app_initiator_data.rs b/crates/tower-api/src/models/run_app_initiator_data.rs index 5f6a080c..6c8556b3 100644 --- a/crates/tower-api/src/models/run_app_initiator_data.rs +++ b/crates/tower-api/src/models/run_app_initiator_data.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_app_params.rs b/crates/tower-api/src/models/run_app_params.rs index a630423e..741ed3cd 100644 --- a/crates/tower-api/src/models/run_app_params.rs +++ b/crates/tower-api/src/models/run_app_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_app_response.rs b/crates/tower-api/src/models/run_app_response.rs index 480407ca..abf80054 100644 --- a/crates/tower-api/src/models/run_app_response.rs +++ b/crates/tower-api/src/models/run_app_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_attempt.rs b/crates/tower-api/src/models/run_attempt.rs index 15ca45ac..85b59d80 100644 --- a/crates/tower-api/src/models/run_attempt.rs +++ b/crates/tower-api/src/models/run_attempt.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -24,9 +24,12 @@ pub struct RunAttempt { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "seq")] pub seq: i64, - /// When this attempt started. + /// When the run started executing your code. #[serde(rename = "started_at", deserialize_with = "Option::deserialize")] pub started_at: Option, + /// When the runner environment started to get setup. + #[serde(rename = "starting_at", deserialize_with = "Option::deserialize")] + pub starting_at: Option, /// Terminal status of this attempt. #[serde_as(as = "DefaultOnNull")] #[serde(rename = "status")] @@ -39,6 +42,7 @@ impl RunAttempt { exit_code: Option, seq: i64, started_at: Option, + starting_at: Option, status: String, ) -> RunAttempt { RunAttempt { @@ -46,6 +50,7 @@ impl RunAttempt { exit_code, seq, started_at, + starting_at, status, } } diff --git a/crates/tower-api/src/models/run_failure_alert.rs b/crates/tower-api/src/models/run_failure_alert.rs index 0f30f2d3..93ce4473 100644 --- a/crates/tower-api/src/models/run_failure_alert.rs +++ b/crates/tower-api/src/models/run_failure_alert.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_graph_node.rs b/crates/tower-api/src/models/run_graph_node.rs index d0eb6652..316a3882 100644 --- a/crates/tower-api/src/models/run_graph_node.rs +++ b/crates/tower-api/src/models/run_graph_node.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_graph_run_id.rs b/crates/tower-api/src/models/run_graph_run_id.rs index b5f3937f..fc7047d9 100644 --- a/crates/tower-api/src/models/run_graph_run_id.rs +++ b/crates/tower-api/src/models/run_graph_run_id.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_initiator.rs b/crates/tower-api/src/models/run_initiator.rs index fac88e3b..00cbf42f 100644 --- a/crates/tower-api/src/models/run_initiator.rs +++ b/crates/tower-api/src/models/run_initiator.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_initiator_details.rs b/crates/tower-api/src/models/run_initiator_details.rs index 4c21e555..cc6ff315 100644 --- a/crates/tower-api/src/models/run_initiator_details.rs +++ b/crates/tower-api/src/models/run_initiator_details.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_log_line.rs b/crates/tower-api/src/models/run_log_line.rs index b10efadc..1dcad0c9 100644 --- a/crates/tower-api/src/models/run_log_line.rs +++ b/crates/tower-api/src/models/run_log_line.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_parameter.rs b/crates/tower-api/src/models/run_parameter.rs index 4f3fb67b..cb98bfcd 100644 --- a/crates/tower-api/src/models/run_parameter.rs +++ b/crates/tower-api/src/models/run_parameter.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -17,6 +17,14 @@ pub struct RunParameter { /// Whether this parameter is hidden/secret. Defaults to false. #[serde(rename = "hidden", skip_serializing_if = "Option::is_none")] pub hidden: Option, + /// Whether this parameter's value was supplied as an override at run/schedule creation time (true) or comes from the app version's default (false). + #[serde( + rename = "is_override", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub is_override: Option>, #[serde_as(as = "DefaultOnNull")] #[serde(rename = "name")] pub name: String, @@ -29,6 +37,7 @@ impl RunParameter { pub fn new(name: String, value: String) -> RunParameter { RunParameter { hidden: None, + is_override: None, name, value, } diff --git a/crates/tower-api/src/models/run_parameter_input.rs b/crates/tower-api/src/models/run_parameter_input.rs new file mode 100644 index 00000000..08ffe6dd --- /dev/null +++ b/crates/tower-api/src/models/run_parameter_input.rs @@ -0,0 +1,36 @@ +/* + * Tower API + * + * REST API to interact with Tower Services. + * + * The version of the OpenAPI document: 0.10.23-preview + * Contact: hello@tower.dev + * Generated by: https://openapi-generator.tech + */ +use crate::models; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::{serde_as, DefaultOnNull}; + +#[serde_as] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct RunParameterInput { + /// Whether this parameter is hidden/secret. Defaults to false. + #[serde(rename = "hidden", skip_serializing_if = "Option::is_none")] + pub hidden: Option, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "name")] + pub name: String, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "value")] + pub value: String, +} + +impl RunParameterInput { + pub fn new(name: String, value: String) -> RunParameterInput { + RunParameterInput { + hidden: None, + name, + value, + } + } +} diff --git a/crates/tower-api/src/models/run_results.rs b/crates/tower-api/src/models/run_results.rs index 538fcba3..72d6c3e7 100644 --- a/crates/tower-api/src/models/run_results.rs +++ b/crates/tower-api/src/models/run_results.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -35,6 +35,9 @@ pub struct RunResults { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "running")] pub running: i64, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "starting")] + pub starting: i64, } impl RunResults { @@ -46,6 +49,7 @@ impl RunResults { pending: i64, retrying: i64, running: i64, + starting: i64, ) -> RunResults { RunResults { cancelled, @@ -55,6 +59,7 @@ impl RunResults { pending, retrying, running, + starting, } } } diff --git a/crates/tower-api/src/models/run_retry_policy.rs b/crates/tower-api/src/models/run_retry_policy.rs index 7484ef72..bfe9489d 100644 --- a/crates/tower-api/src/models/run_retry_policy.rs +++ b/crates/tower-api/src/models/run_retry_policy.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_run_initiator_details.rs b/crates/tower-api/src/models/run_run_initiator_details.rs index 2f1b3eb4..4015ed98 100644 --- a/crates/tower-api/src/models/run_run_initiator_details.rs +++ b/crates/tower-api/src/models/run_run_initiator_details.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_statistics.rs b/crates/tower-api/src/models/run_statistics.rs index 86744b58..af3a61f8 100644 --- a/crates/tower-api/src/models/run_statistics.rs +++ b/crates/tower-api/src/models/run_statistics.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/run_timeseries_point.rs b/crates/tower-api/src/models/run_timeseries_point.rs index a4021b24..23bd370d 100644 --- a/crates/tower-api/src/models/run_timeseries_point.rs +++ b/crates/tower-api/src/models/run_timeseries_point.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -42,6 +42,9 @@ pub struct RunTimeseriesPoint { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "scheduled")] pub scheduled: i64, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "starting")] + pub starting: i64, } impl RunTimeseriesPoint { @@ -55,6 +58,7 @@ impl RunTimeseriesPoint { retrying: i64, running: i64, scheduled: i64, + starting: i64, ) -> RunTimeseriesPoint { RunTimeseriesPoint { cancelled, @@ -66,6 +70,7 @@ impl RunTimeseriesPoint { retrying, running, scheduled, + starting, } } } diff --git a/crates/tower-api/src/models/runner.rs b/crates/tower-api/src/models/runner.rs index 50e4c806..3835beeb 100644 --- a/crates/tower-api/src/models/runner.rs +++ b/crates/tower-api/src/models/runner.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/runner_credentials.rs b/crates/tower-api/src/models/runner_credentials.rs index 9833f13f..1fd53e86 100644 --- a/crates/tower-api/src/models/runner_credentials.rs +++ b/crates/tower-api/src/models/runner_credentials.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/schedule.rs b/crates/tower-api/src/models/schedule.rs index a8dde710..fde474d2 100644 --- a/crates/tower-api/src/models/schedule.rs +++ b/crates/tower-api/src/models/schedule.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -22,7 +22,7 @@ pub struct Schedule { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "app_status")] pub app_status: AppStatus, - /// The specific app version to run, or null for the default version + /// This property is deprecated. Schedules inherit the version from their environment. This field returns the environment's current version. #[serde(rename = "app_version", skip_serializing_if = "Option::is_none")] pub app_version: Option, /// The timestamp when the schedule was created diff --git a/crates/tower-api/src/models/schedule_run_initiator_details.rs b/crates/tower-api/src/models/schedule_run_initiator_details.rs index 78e15549..ae103d42 100644 --- a/crates/tower-api/src/models/schedule_run_initiator_details.rs +++ b/crates/tower-api/src/models/schedule_run_initiator_details.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/search_runs_response.rs b/crates/tower-api/src/models/search_runs_response.rs index bda0b32c..9971ebaf 100644 --- a/crates/tower-api/src/models/search_runs_response.rs +++ b/crates/tower-api/src/models/search_runs_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/secret.rs b/crates/tower-api/src/models/secret.rs index 29e9025e..fdbc37b9 100644 --- a/crates/tower-api/src/models/secret.rs +++ b/crates/tower-api/src/models/secret.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/server_sent_events_inner.rs b/crates/tower-api/src/models/server_sent_events_inner.rs index 37f57f13..777b2918 100644 --- a/crates/tower-api/src/models/server_sent_events_inner.rs +++ b/crates/tower-api/src/models/server_sent_events_inner.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/server_sent_events_inner_1.rs b/crates/tower-api/src/models/server_sent_events_inner_1.rs index 5e671541..4374a4c9 100644 --- a/crates/tower-api/src/models/server_sent_events_inner_1.rs +++ b/crates/tower-api/src/models/server_sent_events_inner_1.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/server_sent_events_inner_2.rs b/crates/tower-api/src/models/server_sent_events_inner_2.rs index 98911abe..aa3416da 100644 --- a/crates/tower-api/src/models/server_sent_events_inner_2.rs +++ b/crates/tower-api/src/models/server_sent_events_inner_2.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/session.rs b/crates/tower-api/src/models/session.rs index 0cba667a..63873a02 100644 --- a/crates/tower-api/src/models/session.rs +++ b/crates/tower-api/src/models/session.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -14,6 +14,7 @@ use serde_with::{serde_as, DefaultOnNull}; #[serde_as] #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Session { + /// This property is deprecated. It will be removed in a future version. #[serde_as(as = "DefaultOnNull")] #[serde(rename = "featurebase_identity")] pub featurebase_identity: models::FeaturebaseIdentity, diff --git a/crates/tower-api/src/models/shoulder_tap.rs b/crates/tower-api/src/models/shoulder_tap.rs index dec17e33..3d5b91f7 100644 --- a/crates/tower-api/src/models/shoulder_tap.rs +++ b/crates/tower-api/src/models/shoulder_tap.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/sse_warning.rs b/crates/tower-api/src/models/sse_warning.rs index 78b4f748..28dc3f9e 100644 --- a/crates/tower-api/src/models/sse_warning.rs +++ b/crates/tower-api/src/models/sse_warning.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/statistics_settings.rs b/crates/tower-api/src/models/statistics_settings.rs index 6ab7bb5e..8026f7dc 100644 --- a/crates/tower-api/src/models/statistics_settings.rs +++ b/crates/tower-api/src/models/statistics_settings.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/team.rs b/crates/tower-api/src/models/team.rs index 08da5e37..537b57a1 100644 --- a/crates/tower-api/src/models/team.rs +++ b/crates/tower-api/src/models/team.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/team_invitation.rs b/crates/tower-api/src/models/team_invitation.rs index f7aeeff6..1a649f7b 100644 --- a/crates/tower-api/src/models/team_invitation.rs +++ b/crates/tower-api/src/models/team_invitation.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/team_membership.rs b/crates/tower-api/src/models/team_membership.rs index 0d19d661..ab61bc59 100644 --- a/crates/tower-api/src/models/team_membership.rs +++ b/crates/tower-api/src/models/team_membership.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/test_webhook_response.rs b/crates/tower-api/src/models/test_webhook_response.rs index 186b1e56..edb69308 100644 --- a/crates/tower-api/src/models/test_webhook_response.rs +++ b/crates/tower-api/src/models/test_webhook_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/token.rs b/crates/tower-api/src/models/token.rs index e0ec1806..bd119e00 100644 --- a/crates/tower-api/src/models/token.rs +++ b/crates/tower-api/src/models/token.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_account_params.rs b/crates/tower-api/src/models/update_account_params.rs index 7190e849..7f051a0b 100644 --- a/crates/tower-api/src/models/update_account_params.rs +++ b/crates/tower-api/src/models/update_account_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_account_response.rs b/crates/tower-api/src/models/update_account_response.rs index 09653197..6b289c94 100644 --- a/crates/tower-api/src/models/update_account_response.rs +++ b/crates/tower-api/src/models/update_account_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_app_environment_params.rs b/crates/tower-api/src/models/update_app_environment_params.rs new file mode 100644 index 00000000..d0fbc7f2 --- /dev/null +++ b/crates/tower-api/src/models/update_app_environment_params.rs @@ -0,0 +1,33 @@ +/* + * Tower API + * + * REST API to interact with Tower Services. + * + * The version of the OpenAPI document: v0.10.24 + * Contact: hello@tower.dev + * Generated by: https://openapi-generator.tech + */ +use crate::models; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::{serde_as, DefaultOnNull}; + +#[serde_as] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UpdateAppEnvironmentParams { + /// A URL to the JSON Schema for this object. + #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] + pub schema: Option, + /// The version to deploy to this environment, e.g. 'v1', 'v2'. + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "version")] + pub version: String, +} + +impl UpdateAppEnvironmentParams { + pub fn new(version: String) -> UpdateAppEnvironmentParams { + UpdateAppEnvironmentParams { + schema: None, + version, + } + } +} diff --git a/crates/tower-api/src/models/update_app_environment_response.rs b/crates/tower-api/src/models/update_app_environment_response.rs new file mode 100644 index 00000000..0da5a43b --- /dev/null +++ b/crates/tower-api/src/models/update_app_environment_response.rs @@ -0,0 +1,36 @@ +/* + * Tower API + * + * REST API to interact with Tower Services. + * + * The version of the OpenAPI document: v0.10.24 + * Contact: hello@tower.dev + * Generated by: https://openapi-generator.tech + */ +use crate::models; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::{serde_as, DefaultOnNull}; + +#[serde_as] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UpdateAppEnvironmentResponse { + /// A URL to the JSON Schema for this object. + #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] + pub schema: Option, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "environment")] + pub environment: String, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "version")] + pub version: String, +} + +impl UpdateAppEnvironmentResponse { + pub fn new(environment: String, version: String) -> UpdateAppEnvironmentResponse { + UpdateAppEnvironmentResponse { + schema: None, + environment, + version, + } + } +} diff --git a/crates/tower-api/src/models/update_app_params.rs b/crates/tower-api/src/models/update_app_params.rs index 72b44704..7b030fa8 100644 --- a/crates/tower-api/src/models/update_app_params.rs +++ b/crates/tower-api/src/models/update_app_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_app_response.rs b/crates/tower-api/src/models/update_app_response.rs index 6aa3fdfa..1b6e34a0 100644 --- a/crates/tower-api/src/models/update_app_response.rs +++ b/crates/tower-api/src/models/update_app_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_catalog_params.rs b/crates/tower-api/src/models/update_catalog_params.rs index 6732636f..8546d7be 100644 --- a/crates/tower-api/src/models/update_catalog_params.rs +++ b/crates/tower-api/src/models/update_catalog_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_catalog_response.rs b/crates/tower-api/src/models/update_catalog_response.rs index b2db4456..f95ad2df 100644 --- a/crates/tower-api/src/models/update_catalog_response.rs +++ b/crates/tower-api/src/models/update_catalog_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_email_preferences_body.rs b/crates/tower-api/src/models/update_email_preferences_body.rs index 5c0f1e0b..03e86fbf 100644 --- a/crates/tower-api/src/models/update_email_preferences_body.rs +++ b/crates/tower-api/src/models/update_email_preferences_body.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_environment_params.rs b/crates/tower-api/src/models/update_environment_params.rs index 9be932eb..750e217b 100644 --- a/crates/tower-api/src/models/update_environment_params.rs +++ b/crates/tower-api/src/models/update_environment_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_environment_response.rs b/crates/tower-api/src/models/update_environment_response.rs index e7a74643..0d94e746 100644 --- a/crates/tower-api/src/models/update_environment_response.rs +++ b/crates/tower-api/src/models/update_environment_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_my_team_invitation_params.rs b/crates/tower-api/src/models/update_my_team_invitation_params.rs index 5c17075e..388fce68 100644 --- a/crates/tower-api/src/models/update_my_team_invitation_params.rs +++ b/crates/tower-api/src/models/update_my_team_invitation_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_my_team_invitation_response.rs b/crates/tower-api/src/models/update_my_team_invitation_response.rs index 8f077b5a..bcbf9136 100644 --- a/crates/tower-api/src/models/update_my_team_invitation_response.rs +++ b/crates/tower-api/src/models/update_my_team_invitation_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_organization_params.rs b/crates/tower-api/src/models/update_organization_params.rs index cf153c0d..2912b546 100644 --- a/crates/tower-api/src/models/update_organization_params.rs +++ b/crates/tower-api/src/models/update_organization_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_organization_response.rs b/crates/tower-api/src/models/update_organization_response.rs index b485de14..782b6124 100644 --- a/crates/tower-api/src/models/update_organization_response.rs +++ b/crates/tower-api/src/models/update_organization_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_plan_params.rs b/crates/tower-api/src/models/update_plan_params.rs index 283ce407..6252fb2a 100644 --- a/crates/tower-api/src/models/update_plan_params.rs +++ b/crates/tower-api/src/models/update_plan_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_plan_response.rs b/crates/tower-api/src/models/update_plan_response.rs index 9cdfcd1b..c1bb2d58 100644 --- a/crates/tower-api/src/models/update_plan_response.rs +++ b/crates/tower-api/src/models/update_plan_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_schedule_params.rs b/crates/tower-api/src/models/update_schedule_params.rs index 44a0e210..ba2541c8 100644 --- a/crates/tower-api/src/models/update_schedule_params.rs +++ b/crates/tower-api/src/models/update_schedule_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ @@ -17,7 +17,7 @@ pub struct UpdateScheduleParams { /// A URL to the JSON Schema for this object. #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] pub schema: Option, - /// The specific app version to run (if omitted, will use the app's default version) + /// This property is deprecated and ignored. Schedules inherit the version from their environment. #[serde( rename = "app_version", default, diff --git a/crates/tower-api/src/models/update_schedule_response.rs b/crates/tower-api/src/models/update_schedule_response.rs index 91949516..a75eeca5 100644 --- a/crates/tower-api/src/models/update_schedule_response.rs +++ b/crates/tower-api/src/models/update_schedule_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_secret_params.rs b/crates/tower-api/src/models/update_secret_params.rs index 40a5affe..b4f02c80 100644 --- a/crates/tower-api/src/models/update_secret_params.rs +++ b/crates/tower-api/src/models/update_secret_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_secret_response.rs b/crates/tower-api/src/models/update_secret_response.rs index 33cc1518..589c5013 100644 --- a/crates/tower-api/src/models/update_secret_response.rs +++ b/crates/tower-api/src/models/update_secret_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_team_member_params.rs b/crates/tower-api/src/models/update_team_member_params.rs index 8886b6e2..5b34f5d7 100644 --- a/crates/tower-api/src/models/update_team_member_params.rs +++ b/crates/tower-api/src/models/update_team_member_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_team_member_response.rs b/crates/tower-api/src/models/update_team_member_response.rs index 8c530240..42511c70 100644 --- a/crates/tower-api/src/models/update_team_member_response.rs +++ b/crates/tower-api/src/models/update_team_member_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_team_params.rs b/crates/tower-api/src/models/update_team_params.rs index a9796866..4dcef519 100644 --- a/crates/tower-api/src/models/update_team_params.rs +++ b/crates/tower-api/src/models/update_team_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_team_response.rs b/crates/tower-api/src/models/update_team_response.rs index 8fa559a9..28c1734c 100644 --- a/crates/tower-api/src/models/update_team_response.rs +++ b/crates/tower-api/src/models/update_team_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_user_params.rs b/crates/tower-api/src/models/update_user_params.rs index 0377d738..a95860ca 100644 --- a/crates/tower-api/src/models/update_user_params.rs +++ b/crates/tower-api/src/models/update_user_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_user_response.rs b/crates/tower-api/src/models/update_user_response.rs index 359bb2c3..727a11d9 100644 --- a/crates/tower-api/src/models/update_user_response.rs +++ b/crates/tower-api/src/models/update_user_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_webhook_params.rs b/crates/tower-api/src/models/update_webhook_params.rs index 79b84cfd..2272a311 100644 --- a/crates/tower-api/src/models/update_webhook_params.rs +++ b/crates/tower-api/src/models/update_webhook_params.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/update_webhook_response.rs b/crates/tower-api/src/models/update_webhook_response.rs index 833f61ca..90ba489a 100644 --- a/crates/tower-api/src/models/update_webhook_response.rs +++ b/crates/tower-api/src/models/update_webhook_response.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/usage_limit.rs b/crates/tower-api/src/models/usage_limit.rs index 985ce95b..53252d37 100644 --- a/crates/tower-api/src/models/usage_limit.rs +++ b/crates/tower-api/src/models/usage_limit.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/usage_metric_time_series_point.rs b/crates/tower-api/src/models/usage_metric_time_series_point.rs new file mode 100644 index 00000000..38d14c80 --- /dev/null +++ b/crates/tower-api/src/models/usage_metric_time_series_point.rs @@ -0,0 +1,57 @@ +/* + * Tower API + * + * REST API to interact with Tower Services. + * + * The version of the OpenAPI document: v0.10.24 + * Contact: hello@tower.dev + * Generated by: https://openapi-generator.tech + */ +use crate::models; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::{serde_as, DefaultOnNull}; + +#[serde_as] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UsageMetricTimeSeriesPoint { + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "date")] + pub date: String, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "name")] + pub name: Name, + #[serde_as(as = "DefaultOnNull")] + #[serde(rename = "value")] + pub value: i64, +} + +impl UsageMetricTimeSeriesPoint { + pub fn new(date: String, name: Name, value: i64) -> UsageMetricTimeSeriesPoint { + UsageMetricTimeSeriesPoint { date, name, value } + } +} +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] +pub enum Name { + #[serde(rename = "run_seconds")] + RunSeconds, +} + +impl Default for Name { + fn default() -> Name { + Self::RunSeconds + } +} + +impl<'de> Deserialize<'de> for Name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.to_lowercase().as_str() { + "run_seconds" => Ok(Self::RunSeconds), + _ => Err(serde::de::Error::unknown_variant(&s, &["run_seconds"])), + } + } +} diff --git a/crates/tower-api/src/models/user.rs b/crates/tower-api/src/models/user.rs index 7c641aed..d7e31814 100644 --- a/crates/tower-api/src/models/user.rs +++ b/crates/tower-api/src/models/user.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-api/src/models/webhook.rs b/crates/tower-api/src/models/webhook.rs index 6e3aaab6..ad5db127 100644 --- a/crates/tower-api/src/models/webhook.rs +++ b/crates/tower-api/src/models/webhook.rs @@ -3,7 +3,7 @@ * * REST API to interact with Tower Services. * - * The version of the OpenAPI document: v0.10.17 + * The version of the OpenAPI document: v0.10.24 * Contact: hello@tower.dev * Generated by: https://openapi-generator.tech */ diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index 8f0956b6..1da1637b 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -34,6 +34,7 @@ pub async fn describe_app( start_at: None, end_at: None, timezone: None, + environment: None, }; unwrap_api_response(tower_api::apis::default_api::describe_app( @@ -933,6 +934,7 @@ pub async fn create_schedule( params .into_iter() .map(|(key, value)| RunParameter { + is_override: None, name: key, value, hidden: None, @@ -971,6 +973,7 @@ pub async fn update_schedule( params .into_iter() .map(|(key, value)| RunParameter { + is_override: None, name: key, value, hidden: None, diff --git a/crates/tower-cmd/src/apps.rs b/crates/tower-cmd/src/apps.rs index c6210dab..9d821d7b 100644 --- a/crates/tower-cmd/src/apps.rs +++ b/crates/tower-cmd/src/apps.rs @@ -563,9 +563,9 @@ fn is_run_finished(run: &Run) -> bool { fn is_run_started(run: &Run) -> bool { match run.status { - tower_api::models::run::Status::Scheduled | tower_api::models::run::Status::Pending => { - false - } + tower_api::models::run::Status::Scheduled + | tower_api::models::run::Status::Pending + | tower_api::models::run::Status::Starting => false, _ => true, } } @@ -656,6 +656,7 @@ mod tests { let status = Status::Scheduled; match status { Status::Scheduled => {} + Status::Starting => {} Status::Pending => {} Status::Running => {} Status::Retrying => {} @@ -668,7 +669,7 @@ mod tests { #[test] fn test_run_started_statuses() { - let not_started = [Status::Scheduled, Status::Pending]; + let not_started = [Status::Scheduled, Status::Pending, Status::Starting]; for status in not_started { let run = Run { status, diff --git a/crates/tower-cmd/src/util/apps.rs b/crates/tower-cmd/src/util/apps.rs index e6e14524..3b6cf49c 100644 --- a/crates/tower-cmd/src/util/apps.rs +++ b/crates/tower-cmd/src/util/apps.rs @@ -22,6 +22,7 @@ pub async fn ensure_app_exists( start_at: None, end_at: None, timezone: None, + environment: None, }, ) .await; diff --git a/src/tower/_client.py b/src/tower/_client.py index 02124280..9ec118f7 100644 --- a/src/tower/_client.py +++ b/src/tower/_client.py @@ -24,6 +24,7 @@ RunAppParams, RunAppParamsParameters, RunAppResponse, + RunStatus, ) from .tower_api_client.models.error_model import ErrorModel from .tower_api_client.errors import UnexpectedStatus @@ -44,6 +45,33 @@ # API before we just give up entirely. DEFAULT_NUM_TIMEOUT_RETRIES = 5 +# TERMINAL_RUN_STATUSES are the run statuses that indicate that a run has finished, +# regardless of whether it completed successfully or not. +TERMINAL_RUN_STATUSES = [ + RunStatus.EXITED, + RunStatus.CANCELLED, + RunStatus.CRASHED, + RunStatus.ERRORED, +] + +# SUCCESSFUL_RUN_STATUSES are the run statuses that indicate that a run has finished and +# completed successfully. +SUCCESSFUL_RUN_STATUSES = [RunStatus.EXITED] + +# FAILED_RUN_STATUSES are the run statuses that indicate that a run has finished but +# did not complete successfully. +FAILED_RUN_STATUSES = [RunStatus.ERRORED, RunStatus.CANCELLED, RunStatus.CRASHED] + +# AWAITING_RUN_STATUSES are the run statuses that indicate that a run is either currently +# running or is expected to run in the near future. +AWAITING_RUN_STATUSES = [ + RunStatus.PENDING, + RunStatus.SCHEDULED, + RunStatus.RUNNING, + RunStatus.STARTING, + RunStatus.RETRYING, +] + def run_app( name: str, @@ -279,7 +307,7 @@ def _is_failed_run(run: Run) -> bool: Returns: bool: True if the run has failed, False otherwise. """ - return run.status in ["crashed", "cancelled", "errored"] + return run.status in FAILED_RUN_STATUSES def _is_successful_run(run: Run) -> bool: @@ -292,7 +320,7 @@ def _is_successful_run(run: Run) -> bool: Returns: bool: True if the run was successful, False otherwise. """ - return run.status in ["exited"] + return run.status in SUCCESSFUL_RUN_STATUSES def _is_run_awaiting_completion(run: Run) -> bool: @@ -305,7 +333,7 @@ def _is_run_awaiting_completion(run: Run) -> bool: Returns: bool: True if the run is awaiting run or currently running, False otherwise. """ - return run.status in ["pending", "scheduled", "running"] + return run.status in AWAITING_RUN_STATUSES def _env_client( diff --git a/src/tower/tower_api_client/api/default/create_password_reset.py b/src/tower/tower_api_client/api/default/create_password_reset.py deleted file mode 100644 index d109aa3a..00000000 --- a/src/tower/tower_api_client/api/default/create_password_reset.py +++ /dev/null @@ -1,171 +0,0 @@ -from http import HTTPStatus -from typing import Any - -import httpx - -from ...client import AuthenticatedClient, Client -from ...models.create_password_reset_params import CreatePasswordResetParams -from ...models.create_password_reset_response import CreatePasswordResetResponse -from ...models.error_model import ErrorModel -from ...types import Response - - -def _get_kwargs( - *, - body: CreatePasswordResetParams, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/accounts/password-reset", - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> CreatePasswordResetResponse | ErrorModel: - if response.status_code == 200: - response_200 = CreatePasswordResetResponse.from_dict(response.json()) - - return response_200 - - response_default = ErrorModel.from_dict(response.json()) - - return response_default - - -def _build_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[CreatePasswordResetResponse | ErrorModel]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient | Client, - body: CreatePasswordResetParams, -) -> Response[CreatePasswordResetResponse | ErrorModel]: - """Create password reset - - Starts the password reset process for an account. If an email address exists for the account - supplied, you will get a reset password email. - - Args: - body (CreatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[CreatePasswordResetResponse | ErrorModel] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient | Client, - body: CreatePasswordResetParams, -) -> CreatePasswordResetResponse | ErrorModel | None: - """Create password reset - - Starts the password reset process for an account. If an email address exists for the account - supplied, you will get a reset password email. - - Args: - body (CreatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - CreatePasswordResetResponse | ErrorModel - """ - - return sync_detailed( - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient | Client, - body: CreatePasswordResetParams, -) -> Response[CreatePasswordResetResponse | ErrorModel]: - """Create password reset - - Starts the password reset process for an account. If an email address exists for the account - supplied, you will get a reset password email. - - Args: - body (CreatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[CreatePasswordResetResponse | ErrorModel] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient | Client, - body: CreatePasswordResetParams, -) -> CreatePasswordResetResponse | ErrorModel | None: - """Create password reset - - Starts the password reset process for an account. If an email address exists for the account - supplied, you will get a reset password email. - - Args: - body (CreatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - CreatePasswordResetResponse | ErrorModel - """ - - return ( - await asyncio_detailed( - client=client, - body=body, - ) - ).parsed diff --git a/src/tower/tower_api_client/api/default/delete_authenticator.py b/src/tower/tower_api_client/api/default/delete_authenticator.py deleted file mode 100644 index 29997b88..00000000 --- a/src/tower/tower_api_client/api/default/delete_authenticator.py +++ /dev/null @@ -1,167 +0,0 @@ -from http import HTTPStatus -from typing import Any - -import httpx - -from ...client import AuthenticatedClient, Client -from ...models.delete_authenticator_params import DeleteAuthenticatorParams -from ...models.delete_authenticator_response import DeleteAuthenticatorResponse -from ...models.error_model import ErrorModel -from ...types import Response - - -def _get_kwargs( - *, - body: DeleteAuthenticatorParams, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "delete", - "url": "/authenticators", - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> DeleteAuthenticatorResponse | ErrorModel: - if response.status_code == 200: - response_200 = DeleteAuthenticatorResponse.from_dict(response.json()) - - return response_200 - - response_default = ErrorModel.from_dict(response.json()) - - return response_default - - -def _build_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[DeleteAuthenticatorResponse | ErrorModel]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient, - body: DeleteAuthenticatorParams, -) -> Response[DeleteAuthenticatorResponse | ErrorModel]: - """Delete authenticator - - Removes an authenticator from your account so you're no longer required to provide it at login. - - Args: - body (DeleteAuthenticatorParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[DeleteAuthenticatorResponse | ErrorModel] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient, - body: DeleteAuthenticatorParams, -) -> DeleteAuthenticatorResponse | ErrorModel | None: - """Delete authenticator - - Removes an authenticator from your account so you're no longer required to provide it at login. - - Args: - body (DeleteAuthenticatorParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - DeleteAuthenticatorResponse | ErrorModel - """ - - return sync_detailed( - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient, - body: DeleteAuthenticatorParams, -) -> Response[DeleteAuthenticatorResponse | ErrorModel]: - """Delete authenticator - - Removes an authenticator from your account so you're no longer required to provide it at login. - - Args: - body (DeleteAuthenticatorParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[DeleteAuthenticatorResponse | ErrorModel] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient, - body: DeleteAuthenticatorParams, -) -> DeleteAuthenticatorResponse | ErrorModel | None: - """Delete authenticator - - Removes an authenticator from your account so you're no longer required to provide it at login. - - Args: - body (DeleteAuthenticatorParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - DeleteAuthenticatorResponse | ErrorModel - """ - - return ( - await asyncio_detailed( - client=client, - body=body, - ) - ).parsed diff --git a/src/tower/tower_api_client/api/default/deploy_app.py b/src/tower/tower_api_client/api/default/deploy_app.py index 902a7b34..3e822ae0 100644 --- a/src/tower/tower_api_client/api/default/deploy_app.py +++ b/src/tower/tower_api_client/api/default/deploy_app.py @@ -16,6 +16,8 @@ def _get_kwargs( name: str, *, body: DeployAppJsonBody | File | Unset = UNSET, + environment: str | Unset = UNSET, + all_environments: bool | Unset = False, x_tower_checksum_sha256: str | Unset = UNSET, content_length: int | Unset = UNSET, ) -> dict[str, Any]: @@ -26,11 +28,20 @@ def _get_kwargs( if not isinstance(content_length, Unset): headers["Content-Length"] = str(content_length) + params: dict[str, Any] = {} + + params["environment"] = environment + + params["all_environments"] = all_environments + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "post", "url": "/apps/{name}/deploy".format( name=quote(str(name), safe=""), ), + "params": params, } if isinstance(body, DeployAppJsonBody): @@ -91,6 +102,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: DeployAppJsonBody | File | Unset = UNSET, + environment: str | Unset = UNSET, + all_environments: bool | Unset = False, x_tower_checksum_sha256: str | Unset = UNSET, content_length: int | Unset = UNSET, ) -> Response[DeployAppResponse | ErrorModel]: @@ -101,6 +114,9 @@ def sync_detailed( Args: name (str): The name of the app to deploy. + environment (str | Unset): The environment to deploy to. + all_environments (bool | Unset): Whether to deploy to all environments for this app. If + true, the 'environment' query parameter is ignored. Default: False. x_tower_checksum_sha256 (str | Unset): The SHA256 hash of the content, used to verify integrity. content_length (int | Unset): Size of the uploaded bundle in bytes. @@ -120,6 +136,8 @@ def sync_detailed( kwargs = _get_kwargs( name=name, body=body, + environment=environment, + all_environments=all_environments, x_tower_checksum_sha256=x_tower_checksum_sha256, content_length=content_length, ) @@ -136,6 +154,8 @@ def sync( *, client: AuthenticatedClient, body: DeployAppJsonBody | File | Unset = UNSET, + environment: str | Unset = UNSET, + all_environments: bool | Unset = False, x_tower_checksum_sha256: str | Unset = UNSET, content_length: int | Unset = UNSET, ) -> DeployAppResponse | ErrorModel | None: @@ -146,6 +166,9 @@ def sync( Args: name (str): The name of the app to deploy. + environment (str | Unset): The environment to deploy to. + all_environments (bool | Unset): Whether to deploy to all environments for this app. If + true, the 'environment' query parameter is ignored. Default: False. x_tower_checksum_sha256 (str | Unset): The SHA256 hash of the content, used to verify integrity. content_length (int | Unset): Size of the uploaded bundle in bytes. @@ -166,6 +189,8 @@ def sync( name=name, client=client, body=body, + environment=environment, + all_environments=all_environments, x_tower_checksum_sha256=x_tower_checksum_sha256, content_length=content_length, ).parsed @@ -176,6 +201,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: DeployAppJsonBody | File | Unset = UNSET, + environment: str | Unset = UNSET, + all_environments: bool | Unset = False, x_tower_checksum_sha256: str | Unset = UNSET, content_length: int | Unset = UNSET, ) -> Response[DeployAppResponse | ErrorModel]: @@ -186,6 +213,9 @@ async def asyncio_detailed( Args: name (str): The name of the app to deploy. + environment (str | Unset): The environment to deploy to. + all_environments (bool | Unset): Whether to deploy to all environments for this app. If + true, the 'environment' query parameter is ignored. Default: False. x_tower_checksum_sha256 (str | Unset): The SHA256 hash of the content, used to verify integrity. content_length (int | Unset): Size of the uploaded bundle in bytes. @@ -205,6 +235,8 @@ async def asyncio_detailed( kwargs = _get_kwargs( name=name, body=body, + environment=environment, + all_environments=all_environments, x_tower_checksum_sha256=x_tower_checksum_sha256, content_length=content_length, ) @@ -219,6 +251,8 @@ async def asyncio( *, client: AuthenticatedClient, body: DeployAppJsonBody | File | Unset = UNSET, + environment: str | Unset = UNSET, + all_environments: bool | Unset = False, x_tower_checksum_sha256: str | Unset = UNSET, content_length: int | Unset = UNSET, ) -> DeployAppResponse | ErrorModel | None: @@ -229,6 +263,9 @@ async def asyncio( Args: name (str): The name of the app to deploy. + environment (str | Unset): The environment to deploy to. + all_environments (bool | Unset): Whether to deploy to all environments for this app. If + true, the 'environment' query parameter is ignored. Default: False. x_tower_checksum_sha256 (str | Unset): The SHA256 hash of the content, used to verify integrity. content_length (int | Unset): Size of the uploaded bundle in bytes. @@ -250,6 +287,8 @@ async def asyncio( name=name, client=client, body=body, + environment=environment, + all_environments=all_environments, x_tower_checksum_sha256=x_tower_checksum_sha256, content_length=content_length, ) diff --git a/src/tower/tower_api_client/api/default/describe_app.py b/src/tower/tower_api_client/api/default/describe_app.py index c9561e96..e5b4605d 100644 --- a/src/tower/tower_api_client/api/default/describe_app.py +++ b/src/tower/tower_api_client/api/default/describe_app.py @@ -18,6 +18,7 @@ def _get_kwargs( start_at: datetime.datetime | Unset = UNSET, end_at: datetime.datetime | Unset = UNSET, timezone: str | Unset = "UTC", + environment: str | Unset = "default", ) -> dict[str, Any]: params: dict[str, Any] = {} @@ -35,6 +36,8 @@ def _get_kwargs( params["timezone"] = timezone + params["environment"] = environment + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} _kwargs: dict[str, Any] = { @@ -80,6 +83,7 @@ def sync_detailed( start_at: datetime.datetime | Unset = UNSET, end_at: datetime.datetime | Unset = UNSET, timezone: str | Unset = "UTC", + environment: str | Unset = "default", ) -> Response[DescribeAppResponse | ErrorModel]: """Describe app @@ -94,6 +98,8 @@ def sync_detailed( (inclusive). Provide timestamps in ISO-8601 format. timezone (str | Unset): Timezone for the statistics (e.g., 'Europe/Berlin'). Defaults to UTC. Default: 'UTC'. + environment (str | Unset): The environment to resolve the app version against. Defaults to + 'default'. Default: 'default'. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -109,6 +115,7 @@ def sync_detailed( start_at=start_at, end_at=end_at, timezone=timezone, + environment=environment, ) response = client.get_httpx_client().request( @@ -126,6 +133,7 @@ def sync( start_at: datetime.datetime | Unset = UNSET, end_at: datetime.datetime | Unset = UNSET, timezone: str | Unset = "UTC", + environment: str | Unset = "default", ) -> DescribeAppResponse | ErrorModel | None: """Describe app @@ -140,6 +148,8 @@ def sync( (inclusive). Provide timestamps in ISO-8601 format. timezone (str | Unset): Timezone for the statistics (e.g., 'Europe/Berlin'). Defaults to UTC. Default: 'UTC'. + environment (str | Unset): The environment to resolve the app version against. Defaults to + 'default'. Default: 'default'. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -156,6 +166,7 @@ def sync( start_at=start_at, end_at=end_at, timezone=timezone, + environment=environment, ).parsed @@ -167,6 +178,7 @@ async def asyncio_detailed( start_at: datetime.datetime | Unset = UNSET, end_at: datetime.datetime | Unset = UNSET, timezone: str | Unset = "UTC", + environment: str | Unset = "default", ) -> Response[DescribeAppResponse | ErrorModel]: """Describe app @@ -181,6 +193,8 @@ async def asyncio_detailed( (inclusive). Provide timestamps in ISO-8601 format. timezone (str | Unset): Timezone for the statistics (e.g., 'Europe/Berlin'). Defaults to UTC. Default: 'UTC'. + environment (str | Unset): The environment to resolve the app version against. Defaults to + 'default'. Default: 'default'. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -196,6 +210,7 @@ async def asyncio_detailed( start_at=start_at, end_at=end_at, timezone=timezone, + environment=environment, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -211,6 +226,7 @@ async def asyncio( start_at: datetime.datetime | Unset = UNSET, end_at: datetime.datetime | Unset = UNSET, timezone: str | Unset = "UTC", + environment: str | Unset = "default", ) -> DescribeAppResponse | ErrorModel | None: """Describe app @@ -225,6 +241,8 @@ async def asyncio( (inclusive). Provide timestamps in ISO-8601 format. timezone (str | Unset): Timezone for the statistics (e.g., 'Europe/Berlin'). Defaults to UTC. Default: 'UTC'. + environment (str | Unset): The environment to resolve the app version against. Defaults to + 'default'. Default: 'default'. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -242,5 +260,6 @@ async def asyncio( start_at=start_at, end_at=end_at, timezone=timezone, + environment=environment, ) ).parsed diff --git a/src/tower/tower_api_client/api/default/describe_organization_usage.py b/src/tower/tower_api_client/api/default/describe_organization_usage.py index 9cfc44c2..ca1759a5 100644 --- a/src/tower/tower_api_client/api/default/describe_organization_usage.py +++ b/src/tower/tower_api_client/api/default/describe_organization_usage.py @@ -48,7 +48,7 @@ def sync_detailed( ) -> Response[ErrorModel | OrganizationUsage]: """Describe organization usage - Describe usage statistics for the user's organization. + Describe usage statistics for the user's organization for the current billing cycle. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -73,7 +73,7 @@ def sync( ) -> ErrorModel | OrganizationUsage | None: """Describe organization usage - Describe usage statistics for the user's organization. + Describe usage statistics for the user's organization for the current billing cycle. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -94,7 +94,7 @@ async def asyncio_detailed( ) -> Response[ErrorModel | OrganizationUsage]: """Describe organization usage - Describe usage statistics for the user's organization. + Describe usage statistics for the user's organization for the current billing cycle. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -117,7 +117,7 @@ async def asyncio( ) -> ErrorModel | OrganizationUsage | None: """Describe organization usage - Describe usage statistics for the user's organization. + Describe usage statistics for the user's organization for the current billing cycle. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/src/tower/tower_api_client/api/default/generate_authenticator.py b/src/tower/tower_api_client/api/default/generate_authenticator.py deleted file mode 100644 index a2f21bb8..00000000 --- a/src/tower/tower_api_client/api/default/generate_authenticator.py +++ /dev/null @@ -1,134 +0,0 @@ -from http import HTTPStatus -from typing import Any - -import httpx - -from ...client import AuthenticatedClient, Client -from ...models.error_model import ErrorModel -from ...models.generate_authenticator_response import GenerateAuthenticatorResponse -from ...types import Response - - -def _get_kwargs() -> dict[str, Any]: - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/authenticators/generate", - } - - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> ErrorModel | GenerateAuthenticatorResponse: - if response.status_code == 200: - response_200 = GenerateAuthenticatorResponse.from_dict(response.json()) - - return response_200 - - response_default = ErrorModel.from_dict(response.json()) - - return response_default - - -def _build_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[ErrorModel | GenerateAuthenticatorResponse]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient, -) -> Response[ErrorModel | GenerateAuthenticatorResponse]: - """Generate authenticator - - Generates a new authenticator for the user. This is used to set up two-factor authentication. - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[ErrorModel | GenerateAuthenticatorResponse] - """ - - kwargs = _get_kwargs() - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient, -) -> ErrorModel | GenerateAuthenticatorResponse | None: - """Generate authenticator - - Generates a new authenticator for the user. This is used to set up two-factor authentication. - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - ErrorModel | GenerateAuthenticatorResponse - """ - - return sync_detailed( - client=client, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient, -) -> Response[ErrorModel | GenerateAuthenticatorResponse]: - """Generate authenticator - - Generates a new authenticator for the user. This is used to set up two-factor authentication. - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[ErrorModel | GenerateAuthenticatorResponse] - """ - - kwargs = _get_kwargs() - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient, -) -> ErrorModel | GenerateAuthenticatorResponse | None: - """Generate authenticator - - Generates a new authenticator for the user. This is used to set up two-factor authentication. - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - ErrorModel | GenerateAuthenticatorResponse - """ - - return ( - await asyncio_detailed( - client=client, - ) - ).parsed diff --git a/src/tower/tower_api_client/api/default/list_authenticators.py b/src/tower/tower_api_client/api/default/generate_organization_usage_time_series.py similarity index 64% rename from src/tower/tower_api_client/api/default/list_authenticators.py rename to src/tower/tower_api_client/api/default/generate_organization_usage_time_series.py index 9029a659..c866ff4c 100644 --- a/src/tower/tower_api_client/api/default/list_authenticators.py +++ b/src/tower/tower_api_client/api/default/generate_organization_usage_time_series.py @@ -5,14 +5,16 @@ from ...client import AuthenticatedClient, Client from ...models.error_model import ErrorModel -from ...models.list_authenticators_response import ListAuthenticatorsResponse +from ...models.generate_organization_usage_time_series_response import ( + GenerateOrganizationUsageTimeSeriesResponse, +) from ...types import Response def _get_kwargs() -> dict[str, Any]: _kwargs: dict[str, Any] = { "method": "get", - "url": "/authenticators", + "url": "/usage/time-series", } return _kwargs @@ -20,9 +22,11 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response( *, client: AuthenticatedClient | Client, response: httpx.Response -) -> ErrorModel | ListAuthenticatorsResponse: +) -> ErrorModel | GenerateOrganizationUsageTimeSeriesResponse: if response.status_code == 200: - response_200 = ListAuthenticatorsResponse.from_dict(response.json()) + response_200 = GenerateOrganizationUsageTimeSeriesResponse.from_dict( + response.json() + ) return response_200 @@ -33,7 +37,7 @@ def _parse_response( def _build_response( *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[ErrorModel | ListAuthenticatorsResponse]: +) -> Response[ErrorModel | GenerateOrganizationUsageTimeSeriesResponse]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -45,17 +49,17 @@ def _build_response( def sync_detailed( *, client: AuthenticatedClient, -) -> Response[ErrorModel | ListAuthenticatorsResponse]: - """List authenticators +) -> Response[ErrorModel | GenerateOrganizationUsageTimeSeriesResponse]: + """Get organization usage as time series - Enumerates the authenticators associated with the current users' account + Get the current billing cycle usage as a time series. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[ErrorModel | ListAuthenticatorsResponse] + Response[ErrorModel | GenerateOrganizationUsageTimeSeriesResponse] """ kwargs = _get_kwargs() @@ -70,17 +74,17 @@ def sync_detailed( def sync( *, client: AuthenticatedClient, -) -> ErrorModel | ListAuthenticatorsResponse | None: - """List authenticators +) -> ErrorModel | GenerateOrganizationUsageTimeSeriesResponse | None: + """Get organization usage as time series - Enumerates the authenticators associated with the current users' account + Get the current billing cycle usage as a time series. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - ErrorModel | ListAuthenticatorsResponse + ErrorModel | GenerateOrganizationUsageTimeSeriesResponse """ return sync_detailed( @@ -91,17 +95,17 @@ def sync( async def asyncio_detailed( *, client: AuthenticatedClient, -) -> Response[ErrorModel | ListAuthenticatorsResponse]: - """List authenticators +) -> Response[ErrorModel | GenerateOrganizationUsageTimeSeriesResponse]: + """Get organization usage as time series - Enumerates the authenticators associated with the current users' account + Get the current billing cycle usage as a time series. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[ErrorModel | ListAuthenticatorsResponse] + Response[ErrorModel | GenerateOrganizationUsageTimeSeriesResponse] """ kwargs = _get_kwargs() @@ -114,17 +118,17 @@ async def asyncio_detailed( async def asyncio( *, client: AuthenticatedClient, -) -> ErrorModel | ListAuthenticatorsResponse | None: - """List authenticators +) -> ErrorModel | GenerateOrganizationUsageTimeSeriesResponse | None: + """Get organization usage as time series - Enumerates the authenticators associated with the current users' account + Get the current billing cycle usage as a time series. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - ErrorModel | ListAuthenticatorsResponse + ErrorModel | GenerateOrganizationUsageTimeSeriesResponse """ return ( diff --git a/src/tower/tower_api_client/api/default/resend_email_verification.py b/src/tower/tower_api_client/api/default/resend_email_verification.py deleted file mode 100644 index 3740d15e..00000000 --- a/src/tower/tower_api_client/api/default/resend_email_verification.py +++ /dev/null @@ -1,136 +0,0 @@ -from http import HTTPStatus -from typing import Any, cast - -import httpx - -from ...client import AuthenticatedClient, Client -from ...models.error_model import ErrorModel -from ...types import Response - - -def _get_kwargs() -> dict[str, Any]: - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/user/resend-verification", - } - - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Any | ErrorModel: - if response.status_code == 204: - response_204 = cast(Any, None) - return response_204 - - response_default = ErrorModel.from_dict(response.json()) - - return response_default - - -def _build_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[Any | ErrorModel]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient, -) -> Response[Any | ErrorModel]: - """Resent email verification - - If a user doesn't have a verified email address, this API endpoint will send a new confirmation - email to them - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Any | ErrorModel] - """ - - kwargs = _get_kwargs() - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient, -) -> Any | ErrorModel | None: - """Resent email verification - - If a user doesn't have a verified email address, this API endpoint will send a new confirmation - email to them - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Any | ErrorModel - """ - - return sync_detailed( - client=client, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient, -) -> Response[Any | ErrorModel]: - """Resent email verification - - If a user doesn't have a verified email address, this API endpoint will send a new confirmation - email to them - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Any | ErrorModel] - """ - - kwargs = _get_kwargs() - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient, -) -> Any | ErrorModel | None: - """Resent email verification - - If a user doesn't have a verified email address, this API endpoint will send a new confirmation - email to them - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Any | ErrorModel - """ - - return ( - await asyncio_detailed( - client=client, - ) - ).parsed diff --git a/src/tower/tower_api_client/api/default/create_authenticator.py b/src/tower/tower_api_client/api/default/update_app_environment.py similarity index 51% rename from src/tower/tower_api_client/api/default/create_authenticator.py rename to src/tower/tower_api_client/api/default/update_app_environment.py index 25d4e6fd..1c3d2ac3 100644 --- a/src/tower/tower_api_client/api/default/create_authenticator.py +++ b/src/tower/tower_api_client/api/default/update_app_environment.py @@ -1,24 +1,30 @@ from http import HTTPStatus from typing import Any +from urllib.parse import quote import httpx from ...client import AuthenticatedClient, Client -from ...models.create_authenticator_params import CreateAuthenticatorParams -from ...models.create_authenticator_response import CreateAuthenticatorResponse from ...models.error_model import ErrorModel +from ...models.update_app_environment_params import UpdateAppEnvironmentParams +from ...models.update_app_environment_response import UpdateAppEnvironmentResponse from ...types import Response def _get_kwargs( + name: str, + environment: str, *, - body: CreateAuthenticatorParams, + body: UpdateAppEnvironmentParams, ) -> dict[str, Any]: headers: dict[str, Any] = {} _kwargs: dict[str, Any] = { - "method": "post", - "url": "/authenticators", + "method": "put", + "url": "/apps/{name}/environments/{environment}".format( + name=quote(str(name), safe=""), + environment=quote(str(environment), safe=""), + ), } _kwargs["json"] = body.to_dict() @@ -31,9 +37,9 @@ def _get_kwargs( def _parse_response( *, client: AuthenticatedClient | Client, response: httpx.Response -) -> CreateAuthenticatorResponse | ErrorModel: +) -> ErrorModel | UpdateAppEnvironmentResponse: if response.status_code == 200: - response_200 = CreateAuthenticatorResponse.from_dict(response.json()) + response_200 = UpdateAppEnvironmentResponse.from_dict(response.json()) return response_200 @@ -44,7 +50,7 @@ def _parse_response( def _build_response( *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[CreateAuthenticatorResponse | ErrorModel]: +) -> Response[ErrorModel | UpdateAppEnvironmentResponse]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -54,27 +60,32 @@ def _build_response( def sync_detailed( + name: str, + environment: str, *, client: AuthenticatedClient, - body: CreateAuthenticatorParams, -) -> Response[CreateAuthenticatorResponse | ErrorModel]: - """Create authenticator + body: UpdateAppEnvironmentParams, +) -> Response[ErrorModel | UpdateAppEnvironmentResponse]: + """Update app environment - Associates an authenticator with your account, where the authenticator is identified by the URL with - an otpauth URI scheme. + Update the configuration of an app in a specific environment, such as which version is deployed. Args: - body (CreateAuthenticatorParams): + name (str): The name of the app. + environment (str): The name of the environment. + body (UpdateAppEnvironmentParams): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[CreateAuthenticatorResponse | ErrorModel] + Response[ErrorModel | UpdateAppEnvironmentResponse] """ kwargs = _get_kwargs( + name=name, + environment=environment, body=body, ) @@ -86,54 +97,64 @@ def sync_detailed( def sync( + name: str, + environment: str, *, client: AuthenticatedClient, - body: CreateAuthenticatorParams, -) -> CreateAuthenticatorResponse | ErrorModel | None: - """Create authenticator + body: UpdateAppEnvironmentParams, +) -> ErrorModel | UpdateAppEnvironmentResponse | None: + """Update app environment - Associates an authenticator with your account, where the authenticator is identified by the URL with - an otpauth URI scheme. + Update the configuration of an app in a specific environment, such as which version is deployed. Args: - body (CreateAuthenticatorParams): + name (str): The name of the app. + environment (str): The name of the environment. + body (UpdateAppEnvironmentParams): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - CreateAuthenticatorResponse | ErrorModel + ErrorModel | UpdateAppEnvironmentResponse """ return sync_detailed( + name=name, + environment=environment, client=client, body=body, ).parsed async def asyncio_detailed( + name: str, + environment: str, *, client: AuthenticatedClient, - body: CreateAuthenticatorParams, -) -> Response[CreateAuthenticatorResponse | ErrorModel]: - """Create authenticator + body: UpdateAppEnvironmentParams, +) -> Response[ErrorModel | UpdateAppEnvironmentResponse]: + """Update app environment - Associates an authenticator with your account, where the authenticator is identified by the URL with - an otpauth URI scheme. + Update the configuration of an app in a specific environment, such as which version is deployed. Args: - body (CreateAuthenticatorParams): + name (str): The name of the app. + environment (str): The name of the environment. + body (UpdateAppEnvironmentParams): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[CreateAuthenticatorResponse | ErrorModel] + Response[ErrorModel | UpdateAppEnvironmentResponse] """ kwargs = _get_kwargs( + name=name, + environment=environment, body=body, ) @@ -143,28 +164,33 @@ async def asyncio_detailed( async def asyncio( + name: str, + environment: str, *, client: AuthenticatedClient, - body: CreateAuthenticatorParams, -) -> CreateAuthenticatorResponse | ErrorModel | None: - """Create authenticator + body: UpdateAppEnvironmentParams, +) -> ErrorModel | UpdateAppEnvironmentResponse | None: + """Update app environment - Associates an authenticator with your account, where the authenticator is identified by the URL with - an otpauth URI scheme. + Update the configuration of an app in a specific environment, such as which version is deployed. Args: - body (CreateAuthenticatorParams): + name (str): The name of the app. + environment (str): The name of the environment. + body (UpdateAppEnvironmentParams): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - CreateAuthenticatorResponse | ErrorModel + ErrorModel | UpdateAppEnvironmentResponse """ return ( await asyncio_detailed( + name=name, + environment=environment, client=client, body=body, ) diff --git a/src/tower/tower_api_client/api/default/update_password_reset.py b/src/tower/tower_api_client/api/default/update_password_reset.py deleted file mode 100644 index 33097d74..00000000 --- a/src/tower/tower_api_client/api/default/update_password_reset.py +++ /dev/null @@ -1,183 +0,0 @@ -from http import HTTPStatus -from typing import Any -from urllib.parse import quote - -import httpx - -from ...client import AuthenticatedClient, Client -from ...models.error_model import ErrorModel -from ...models.update_password_reset_params import UpdatePasswordResetParams -from ...models.update_password_reset_response import UpdatePasswordResetResponse -from ...types import Response - - -def _get_kwargs( - code: str, - *, - body: UpdatePasswordResetParams, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/accounts/password-reset/{code}".format( - code=quote(str(code), safe=""), - ), - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> ErrorModel | UpdatePasswordResetResponse: - if response.status_code == 200: - response_200 = UpdatePasswordResetResponse.from_dict(response.json()) - - return response_200 - - response_default = ErrorModel.from_dict(response.json()) - - return response_default - - -def _build_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[ErrorModel | UpdatePasswordResetResponse]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - code: str, - *, - client: AuthenticatedClient | Client, - body: UpdatePasswordResetParams, -) -> Response[ErrorModel | UpdatePasswordResetResponse]: - """Update password reset - - Updates the password reset code with the new password - - Args: - code (str): The password reset code that was sent to you - body (UpdatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[ErrorModel | UpdatePasswordResetResponse] - """ - - kwargs = _get_kwargs( - code=code, - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - code: str, - *, - client: AuthenticatedClient | Client, - body: UpdatePasswordResetParams, -) -> ErrorModel | UpdatePasswordResetResponse | None: - """Update password reset - - Updates the password reset code with the new password - - Args: - code (str): The password reset code that was sent to you - body (UpdatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - ErrorModel | UpdatePasswordResetResponse - """ - - return sync_detailed( - code=code, - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - code: str, - *, - client: AuthenticatedClient | Client, - body: UpdatePasswordResetParams, -) -> Response[ErrorModel | UpdatePasswordResetResponse]: - """Update password reset - - Updates the password reset code with the new password - - Args: - code (str): The password reset code that was sent to you - body (UpdatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[ErrorModel | UpdatePasswordResetResponse] - """ - - kwargs = _get_kwargs( - code=code, - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - code: str, - *, - client: AuthenticatedClient | Client, - body: UpdatePasswordResetParams, -) -> ErrorModel | UpdatePasswordResetResponse | None: - """Update password reset - - Updates the password reset code with the new password - - Args: - code (str): The password reset code that was sent to you - body (UpdatePasswordResetParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - ErrorModel | UpdatePasswordResetResponse - """ - - return ( - await asyncio_detailed( - code=code, - client=client, - body=body, - ) - ).parsed diff --git a/src/tower/tower_api_client/api/default/verify_email.py b/src/tower/tower_api_client/api/default/verify_email.py deleted file mode 100644 index 7a797a8e..00000000 --- a/src/tower/tower_api_client/api/default/verify_email.py +++ /dev/null @@ -1,171 +0,0 @@ -from http import HTTPStatus -from typing import Any - -import httpx - -from ...client import AuthenticatedClient, Client -from ...models.error_model import ErrorModel -from ...models.verify_email_params import VerifyEmailParams -from ...models.verify_email_response import VerifyEmailResponse -from ...types import Response - - -def _get_kwargs( - *, - body: VerifyEmailParams, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/user/verify", - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> ErrorModel | VerifyEmailResponse: - if response.status_code == 200: - response_200 = VerifyEmailResponse.from_dict(response.json()) - - return response_200 - - response_default = ErrorModel.from_dict(response.json()) - - return response_default - - -def _build_response( - *, client: AuthenticatedClient | Client, response: httpx.Response -) -> Response[ErrorModel | VerifyEmailResponse]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient, - body: VerifyEmailParams, -) -> Response[ErrorModel | VerifyEmailResponse]: - """Verify email - - If the user hasn't verified their email address, this API endpoint allows them to send a - confirmation token they received via email to indeed verify they can receive emails. - - Args: - body (VerifyEmailParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[ErrorModel | VerifyEmailResponse] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient, - body: VerifyEmailParams, -) -> ErrorModel | VerifyEmailResponse | None: - """Verify email - - If the user hasn't verified their email address, this API endpoint allows them to send a - confirmation token they received via email to indeed verify they can receive emails. - - Args: - body (VerifyEmailParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - ErrorModel | VerifyEmailResponse - """ - - return sync_detailed( - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient, - body: VerifyEmailParams, -) -> Response[ErrorModel | VerifyEmailResponse]: - """Verify email - - If the user hasn't verified their email address, this API endpoint allows them to send a - confirmation token they received via email to indeed verify they can receive emails. - - Args: - body (VerifyEmailParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[ErrorModel | VerifyEmailResponse] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient, - body: VerifyEmailParams, -) -> ErrorModel | VerifyEmailResponse | None: - """Verify email - - If the user hasn't verified their email address, this API endpoint allows them to send a - confirmation token they received via email to indeed verify they can receive emails. - - Args: - body (VerifyEmailParams): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - ErrorModel | VerifyEmailResponse - """ - - return ( - await asyncio_detailed( - client=client, - body=body, - ) - ).parsed diff --git a/src/tower/tower_api_client/models/__init__.py b/src/tower/tower_api_client/models/__init__.py index ae9cfab0..c3cdbb98 100644 --- a/src/tower/tower_api_client/models/__init__.py +++ b/src/tower/tower_api_client/models/__init__.py @@ -26,8 +26,6 @@ from .create_api_key_response import CreateAPIKeyResponse from .create_app_params import CreateAppParams from .create_app_response import CreateAppResponse -from .create_authenticator_params import CreateAuthenticatorParams -from .create_authenticator_response import CreateAuthenticatorResponse from .create_catalog_params import CreateCatalogParams from .create_catalog_params_type import CreateCatalogParamsType from .create_catalog_response import CreateCatalogResponse @@ -36,8 +34,6 @@ from .create_environment_response import CreateEnvironmentResponse from .create_guest_params import CreateGuestParams from .create_guest_response import CreateGuestResponse -from .create_password_reset_params import CreatePasswordResetParams -from .create_password_reset_response import CreatePasswordResetResponse from .create_sandbox_secrets_params import CreateSandboxSecretsParams from .create_sandbox_secrets_response import CreateSandboxSecretsResponse from .create_schedule_params import CreateScheduleParams @@ -55,8 +51,6 @@ from .delete_api_key_params import DeleteAPIKeyParams from .delete_api_key_response import DeleteAPIKeyResponse from .delete_app_response import DeleteAppResponse -from .delete_authenticator_params import DeleteAuthenticatorParams -from .delete_authenticator_response import DeleteAuthenticatorResponse from .delete_catalog_response import DeleteCatalogResponse from .delete_guest_output_body import DeleteGuestOutputBody from .delete_schedule_params import DeleteScheduleParams @@ -102,7 +96,9 @@ from .feature import Feature from .featurebase_identity import FeaturebaseIdentity from .generate_app_statistics_response import GenerateAppStatisticsResponse -from .generate_authenticator_response import GenerateAuthenticatorResponse +from .generate_organization_usage_time_series_response import ( + GenerateOrganizationUsageTimeSeriesResponse, +) from .generate_run_statistics_response import GenerateRunStatisticsResponse from .generate_run_statistics_status_item import GenerateRunStatisticsStatusItem from .generate_runner_credentials_response import GenerateRunnerCredentialsResponse @@ -122,7 +118,6 @@ from .list_apps_filter import ListAppsFilter from .list_apps_response import ListAppsResponse from .list_apps_sort import ListAppsSort -from .list_authenticators_response import ListAuthenticatorsResponse from .list_catalogs_response import ListCatalogsResponse from .list_environments_response import ListEnvironmentsResponse from .list_guests_response import ListGuestsResponse @@ -198,10 +193,11 @@ from .team_membership_role import TeamMembershipRole from .test_webhook_response import TestWebhookResponse from .token import Token -from .unverified_authenticator import UnverifiedAuthenticator from .update_account_params import UpdateAccountParams from .update_account_params_execution_region import UpdateAccountParamsExecutionRegion from .update_account_response import UpdateAccountResponse +from .update_app_environment_params import UpdateAppEnvironmentParams +from .update_app_environment_response import UpdateAppEnvironmentResponse from .update_app_params import UpdateAppParams from .update_app_response import UpdateAppResponse from .update_catalog_params import UpdateCatalogParams @@ -213,8 +209,6 @@ from .update_my_team_invitation_response import UpdateMyTeamInvitationResponse from .update_organization_params import UpdateOrganizationParams from .update_organization_response import UpdateOrganizationResponse -from .update_password_reset_params import UpdatePasswordResetParams -from .update_password_reset_response import UpdatePasswordResetResponse from .update_plan_params import UpdatePlanParams from .update_plan_response import UpdatePlanResponse from .update_schedule_params import UpdateScheduleParams @@ -234,10 +228,9 @@ from .update_webhook_params import UpdateWebhookParams from .update_webhook_response import UpdateWebhookResponse from .usage_limit import UsageLimit +from .usage_metric_time_series_point import UsageMetricTimeSeriesPoint +from .usage_metric_time_series_point_name import UsageMetricTimeSeriesPointName from .user import User -from .verified_authenticator import VerifiedAuthenticator -from .verify_email_params import VerifyEmailParams -from .verify_email_response import VerifyEmailResponse from .webhook import Webhook from .webhook_state import WebhookState @@ -268,8 +261,6 @@ "CreateAPIKeyResponse", "CreateAppParams", "CreateAppResponse", - "CreateAuthenticatorParams", - "CreateAuthenticatorResponse", "CreateCatalogParams", "CreateCatalogParamsType", "CreateCatalogResponse", @@ -278,8 +269,6 @@ "CreateEnvironmentResponse", "CreateGuestParams", "CreateGuestResponse", - "CreatePasswordResetParams", - "CreatePasswordResetResponse", "CreateSandboxSecretsParams", "CreateSandboxSecretsResponse", "CreateScheduleParams", @@ -297,8 +286,6 @@ "DeleteAPIKeyParams", "DeleteAPIKeyResponse", "DeleteAppResponse", - "DeleteAuthenticatorParams", - "DeleteAuthenticatorResponse", "DeleteCatalogResponse", "DeleteGuestOutputBody", "DeleteScheduleParams", @@ -344,7 +331,7 @@ "Feature", "FeaturebaseIdentity", "GenerateAppStatisticsResponse", - "GenerateAuthenticatorResponse", + "GenerateOrganizationUsageTimeSeriesResponse", "GenerateRunnerCredentialsResponse", "GenerateRunStatisticsResponse", "GenerateRunStatisticsStatusItem", @@ -362,7 +349,6 @@ "ListAppsResponse", "ListAppsSort", "ListAppVersionsResponse", - "ListAuthenticatorsResponse", "ListCatalogsResponse", "ListEnvironmentsResponse", "ListGuestsResponse", @@ -438,10 +424,11 @@ "TeamMembershipRole", "TestWebhookResponse", "Token", - "UnverifiedAuthenticator", "UpdateAccountParams", "UpdateAccountParamsExecutionRegion", "UpdateAccountResponse", + "UpdateAppEnvironmentParams", + "UpdateAppEnvironmentResponse", "UpdateAppParams", "UpdateAppResponse", "UpdateCatalogParams", @@ -453,8 +440,6 @@ "UpdateMyTeamInvitationResponse", "UpdateOrganizationParams", "UpdateOrganizationResponse", - "UpdatePasswordResetParams", - "UpdatePasswordResetResponse", "UpdatePlanParams", "UpdatePlanResponse", "UpdateScheduleParams", @@ -474,10 +459,9 @@ "UpdateWebhookParams", "UpdateWebhookResponse", "UsageLimit", + "UsageMetricTimeSeriesPoint", + "UsageMetricTimeSeriesPointName", "User", - "VerifiedAuthenticator", - "VerifyEmailParams", - "VerifyEmailResponse", "Webhook", "WebhookState", ) diff --git a/src/tower/tower_api_client/models/cancel_run_response.py b/src/tower/tower_api_client/models/cancel_run_response.py index a6a509e3..1d2a63a5 100644 --- a/src/tower/tower_api_client/models/cancel_run_response.py +++ b/src/tower/tower_api_client/models/cancel_run_response.py @@ -18,15 +18,19 @@ class CancelRunResponse: """ Attributes: + cancelled_child_runs (int): Number of descendant runs that were also cancelled as part of this cascade. run (Run): schema (str | Unset): A URL to the JSON Schema for this object. Example: https://api.tower.dev/v1/schemas/CancelRunResponse.json. """ + cancelled_child_runs: int run: Run schema: str | Unset = UNSET def to_dict(self) -> dict[str, Any]: + cancelled_child_runs = self.cancelled_child_runs + run = self.run.to_dict() schema = self.schema @@ -35,6 +39,7 @@ def to_dict(self) -> dict[str, Any]: field_dict.update( { + "cancelled_child_runs": cancelled_child_runs, "run": run, } ) @@ -48,11 +53,14 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.run import Run d = dict(src_dict) + cancelled_child_runs = d.pop("cancelled_child_runs") + run = Run.from_dict(d.pop("run")) schema = d.pop("$schema", UNSET) cancel_run_response = cls( + cancelled_child_runs=cancelled_child_runs, run=run, schema=schema, ) diff --git a/src/tower/tower_api_client/models/create_authenticator_params.py b/src/tower/tower_api_client/models/create_authenticator_params.py deleted file mode 100644 index c425e583..00000000 --- a/src/tower/tower_api_client/models/create_authenticator_params.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="CreateAuthenticatorParams") - - -@_attrs_define -class CreateAuthenticatorParams: - """ - Attributes: - authenticator_url (str): The authenticator URL with an otpauth scheme that identifies this authenticator - verification_code (str): A code taken from the authenticator as verification that it's correctly configured. - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/CreateAuthenticatorParams.json. - """ - - authenticator_url: str - verification_code: str - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - authenticator_url = self.authenticator_url - - verification_code = self.verification_code - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "authenticator_url": authenticator_url, - "verification_code": verification_code, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - authenticator_url = d.pop("authenticator_url") - - verification_code = d.pop("verification_code") - - schema = d.pop("$schema", UNSET) - - create_authenticator_params = cls( - authenticator_url=authenticator_url, - verification_code=verification_code, - schema=schema, - ) - - return create_authenticator_params diff --git a/src/tower/tower_api_client/models/create_authenticator_response.py b/src/tower/tower_api_client/models/create_authenticator_response.py deleted file mode 100644 index f783573d..00000000 --- a/src/tower/tower_api_client/models/create_authenticator_response.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.verified_authenticator import VerifiedAuthenticator - - -T = TypeVar("T", bound="CreateAuthenticatorResponse") - - -@_attrs_define -class CreateAuthenticatorResponse: - """ - Attributes: - authenticator (VerifiedAuthenticator): - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/CreateAuthenticatorResponse.json. - """ - - authenticator: VerifiedAuthenticator - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - authenticator = self.authenticator.to_dict() - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "authenticator": authenticator, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.verified_authenticator import VerifiedAuthenticator - - d = dict(src_dict) - authenticator = VerifiedAuthenticator.from_dict(d.pop("authenticator")) - - schema = d.pop("$schema", UNSET) - - create_authenticator_response = cls( - authenticator=authenticator, - schema=schema, - ) - - return create_authenticator_response diff --git a/src/tower/tower_api_client/models/create_catalog_params_type.py b/src/tower/tower_api_client/models/create_catalog_params_type.py index 4c186f02..342cdc2a 100644 --- a/src/tower/tower_api_client/models/create_catalog_params_type.py +++ b/src/tower/tower_api_client/models/create_catalog_params_type.py @@ -5,6 +5,7 @@ class CreateCatalogParamsType(str, Enum): APACHE_POLARIS = "apache-polaris" CLOUDFLARE_R2_CATALOG = "cloudflare-r2-catalog" LAKEKEEPER = "lakekeeper" + S3_TABLES = "s3-tables" SNOWFLAKE_OPEN_CATALOG = "snowflake-open-catalog" TOWER_CATALOG = "tower-catalog" diff --git a/src/tower/tower_api_client/models/create_password_reset_params.py b/src/tower/tower_api_client/models/create_password_reset_params.py deleted file mode 100644 index 78c9bac3..00000000 --- a/src/tower/tower_api_client/models/create_password_reset_params.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="CreatePasswordResetParams") - - -@_attrs_define -class CreatePasswordResetParams: - """ - Attributes: - email (str): The email address to send the password reset email to - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/CreatePasswordResetParams.json. - """ - - email: str - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - email = self.email - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "email": email, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - email = d.pop("email") - - schema = d.pop("$schema", UNSET) - - create_password_reset_params = cls( - email=email, - schema=schema, - ) - - return create_password_reset_params diff --git a/src/tower/tower_api_client/models/create_password_reset_response.py b/src/tower/tower_api_client/models/create_password_reset_response.py deleted file mode 100644 index 07371f08..00000000 --- a/src/tower/tower_api_client/models/create_password_reset_response.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="CreatePasswordResetResponse") - - -@_attrs_define -class CreatePasswordResetResponse: - """ - Attributes: - ok (bool): A boolean indicating the request was successfully processed. - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/CreatePasswordResetResponse.json. - """ - - ok: bool - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - ok = self.ok - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "ok": ok, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - ok = d.pop("ok") - - schema = d.pop("$schema", UNSET) - - create_password_reset_response = cls( - ok=ok, - schema=schema, - ) - - return create_password_reset_response diff --git a/src/tower/tower_api_client/models/create_schedule_params.py b/src/tower/tower_api_client/models/create_schedule_params.py index f722db1d..79b0d774 100644 --- a/src/tower/tower_api_client/models/create_schedule_params.py +++ b/src/tower/tower_api_client/models/create_schedule_params.py @@ -26,8 +26,8 @@ class CreateScheduleParams: cron (str): The cron expression defining when the app should run schema (str | Unset): A URL to the JSON Schema for this object. Example: https://api.tower.dev/v1/schemas/CreateScheduleParams.json. - app_version (None | str | Unset): The specific app version to run (if omitted, will use the app's default - version) + app_version (None | str | Unset): This property is deprecated and ignored. Schedules inherit the version from + their environment. environment (str | Unset): The environment to run the app in Default: 'default'. name (None | str | Unset): The name for this schedule. Must be unique per environment. If not set, one will be generated for you. diff --git a/src/tower/tower_api_client/models/delete_authenticator_response.py b/src/tower/tower_api_client/models/delete_authenticator_response.py deleted file mode 100644 index e4076887..00000000 --- a/src/tower/tower_api_client/models/delete_authenticator_response.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.verified_authenticator import VerifiedAuthenticator - - -T = TypeVar("T", bound="DeleteAuthenticatorResponse") - - -@_attrs_define -class DeleteAuthenticatorResponse: - """ - Attributes: - authenticator (VerifiedAuthenticator): - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/DeleteAuthenticatorResponse.json. - """ - - authenticator: VerifiedAuthenticator - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - authenticator = self.authenticator.to_dict() - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "authenticator": authenticator, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.verified_authenticator import VerifiedAuthenticator - - d = dict(src_dict) - authenticator = VerifiedAuthenticator.from_dict(d.pop("authenticator")) - - schema = d.pop("$schema", UNSET) - - delete_authenticator_response = cls( - authenticator=authenticator, - schema=schema, - ) - - return delete_authenticator_response diff --git a/src/tower/tower_api_client/models/generate_authenticator_response.py b/src/tower/tower_api_client/models/generate_authenticator_response.py deleted file mode 100644 index 1a2a39f0..00000000 --- a/src/tower/tower_api_client/models/generate_authenticator_response.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.unverified_authenticator import UnverifiedAuthenticator - - -T = TypeVar("T", bound="GenerateAuthenticatorResponse") - - -@_attrs_define -class GenerateAuthenticatorResponse: - """ - Attributes: - authenticator (UnverifiedAuthenticator): - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/GenerateAuthenticatorResponse.json. - """ - - authenticator: UnverifiedAuthenticator - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - authenticator = self.authenticator.to_dict() - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "authenticator": authenticator, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.unverified_authenticator import UnverifiedAuthenticator - - d = dict(src_dict) - authenticator = UnverifiedAuthenticator.from_dict(d.pop("authenticator")) - - schema = d.pop("$schema", UNSET) - - generate_authenticator_response = cls( - authenticator=authenticator, - schema=schema, - ) - - return generate_authenticator_response diff --git a/src/tower/tower_api_client/models/generate_organization_usage_time_series_response.py b/src/tower/tower_api_client/models/generate_organization_usage_time_series_response.py new file mode 100644 index 00000000..eb71c6a8 --- /dev/null +++ b/src/tower/tower_api_client/models/generate_organization_usage_time_series_response.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.usage_metric_time_series_point import UsageMetricTimeSeriesPoint + + +T = TypeVar("T", bound="GenerateOrganizationUsageTimeSeriesResponse") + + +@_attrs_define +class GenerateOrganizationUsageTimeSeriesResponse: + """ + Attributes: + series (list[UsageMetricTimeSeriesPoint]): + schema (str | Unset): A URL to the JSON Schema for this object. Example: + https://api.tower.dev/v1/schemas/GenerateOrganizationUsageTimeSeriesResponse.json. + """ + + series: list[UsageMetricTimeSeriesPoint] + schema: str | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + series = [] + for series_item_data in self.series: + series_item = series_item_data.to_dict() + series.append(series_item) + + schema = self.schema + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "series": series, + } + ) + if schema is not UNSET: + field_dict["$schema"] = schema + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.usage_metric_time_series_point import UsageMetricTimeSeriesPoint + + d = dict(src_dict) + series = [] + _series = d.pop("series") + for series_item_data in _series: + series_item = UsageMetricTimeSeriesPoint.from_dict(series_item_data) + + series.append(series_item) + + schema = d.pop("$schema", UNSET) + + generate_organization_usage_time_series_response = cls( + series=series, + schema=schema, + ) + + return generate_organization_usage_time_series_response diff --git a/src/tower/tower_api_client/models/generate_run_statistics_status_item.py b/src/tower/tower_api_client/models/generate_run_statistics_status_item.py index 84597b2a..3c11c0e1 100644 --- a/src/tower/tower_api_client/models/generate_run_statistics_status_item.py +++ b/src/tower/tower_api_client/models/generate_run_statistics_status_item.py @@ -9,6 +9,7 @@ class GenerateRunStatisticsStatusItem(str, Enum): PENDING = "pending" RETRYING = "retrying" RUNNING = "running" + STARTING = "starting" def __str__(self) -> str: return str(self.value) diff --git a/src/tower/tower_api_client/models/list_authenticators_response.py b/src/tower/tower_api_client/models/list_authenticators_response.py deleted file mode 100644 index ba5bcac4..00000000 --- a/src/tower/tower_api_client/models/list_authenticators_response.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.verified_authenticator import VerifiedAuthenticator - - -T = TypeVar("T", bound="ListAuthenticatorsResponse") - - -@_attrs_define -class ListAuthenticatorsResponse: - """ - Attributes: - authenticators (list[VerifiedAuthenticator]): - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/ListAuthenticatorsResponse.json. - """ - - authenticators: list[VerifiedAuthenticator] - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - authenticators = [] - for authenticators_item_data in self.authenticators: - authenticators_item = authenticators_item_data.to_dict() - authenticators.append(authenticators_item) - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "authenticators": authenticators, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.verified_authenticator import VerifiedAuthenticator - - d = dict(src_dict) - authenticators = [] - _authenticators = d.pop("authenticators") - for authenticators_item_data in _authenticators: - authenticators_item = VerifiedAuthenticator.from_dict( - authenticators_item_data - ) - - authenticators.append(authenticators_item) - - schema = d.pop("$schema", UNSET) - - list_authenticators_response = cls( - authenticators=authenticators, - schema=schema, - ) - - return list_authenticators_response diff --git a/src/tower/tower_api_client/models/list_runs_status_item.py b/src/tower/tower_api_client/models/list_runs_status_item.py index 46671e20..b05f9cca 100644 --- a/src/tower/tower_api_client/models/list_runs_status_item.py +++ b/src/tower/tower_api_client/models/list_runs_status_item.py @@ -9,6 +9,7 @@ class ListRunsStatusItem(str, Enum): PENDING = "pending" RETRYING = "retrying" RUNNING = "running" + STARTING = "starting" def __str__(self) -> str: return str(self.value) diff --git a/src/tower/tower_api_client/models/run.py b/src/tower/tower_api_client/models/run.py index 3eb0219b..748503d9 100644 --- a/src/tower/tower_api_client/models/run.py +++ b/src/tower/tower_api_client/models/run.py @@ -41,7 +41,8 @@ class Run: parameters (list[RunParameter]): Parameters used to invoke this run. run_id (str): scheduled_at (datetime.datetime): - started_at (datetime.datetime | None): + started_at (datetime.datetime | None): When the run started executing your code. + starting_at (datetime.datetime | None): When the runner environment started to get setup. status (RunStatus): status_group (RunStatusGroup): app_slug (str | Unset): This property is deprecated. Use app_name instead. @@ -68,6 +69,7 @@ class Run: run_id: str scheduled_at: datetime.datetime started_at: datetime.datetime | None + starting_at: datetime.datetime | None status: RunStatus status_group: RunStatusGroup app_slug: str | Unset = UNSET @@ -125,6 +127,12 @@ def to_dict(self) -> dict[str, Any]: else: started_at = self.started_at + starting_at: None | str + if isinstance(self.starting_at, datetime.datetime): + starting_at = self.starting_at.isoformat() + else: + starting_at = self.starting_at + status = self.status.value status_group = self.status_group.value @@ -170,6 +178,7 @@ def to_dict(self) -> dict[str, Any]: "run_id": run_id, "scheduled_at": scheduled_at, "started_at": started_at, + "starting_at": starting_at, "status": status, "status_group": status_group, } @@ -276,6 +285,21 @@ def _parse_started_at(data: object) -> datetime.datetime | None: started_at = _parse_started_at(d.pop("started_at")) + def _parse_starting_at(data: object) -> datetime.datetime | None: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + starting_at_type_0 = isoparse(data) + + return starting_at_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.datetime | None, data) + + starting_at = _parse_starting_at(d.pop("starting_at")) + status = RunStatus(d.pop("status")) status_group = RunStatusGroup(d.pop("status_group")) @@ -326,6 +350,7 @@ def _parse_subdomain(data: object) -> None | str | Unset: run_id=run_id, scheduled_at=scheduled_at, started_at=started_at, + starting_at=starting_at, status=status, status_group=status_group, app_slug=app_slug, diff --git a/src/tower/tower_api_client/models/run_attempt.py b/src/tower/tower_api_client/models/run_attempt.py index da912e6d..bcc3bff9 100644 --- a/src/tower/tower_api_client/models/run_attempt.py +++ b/src/tower/tower_api_client/models/run_attempt.py @@ -17,7 +17,8 @@ class RunAttempt: ended_at (datetime.datetime | None): When this attempt ended. exit_code (int | None): Exit code for this attempt. seq (int): 1-based attempt number. - started_at (datetime.datetime | None): When this attempt started. + started_at (datetime.datetime | None): When the run started executing your code. + starting_at (datetime.datetime | None): When the runner environment started to get setup. status (str): Terminal status of this attempt. """ @@ -25,6 +26,7 @@ class RunAttempt: exit_code: int | None seq: int started_at: datetime.datetime | None + starting_at: datetime.datetime | None status: str def to_dict(self) -> dict[str, Any]: @@ -45,6 +47,12 @@ def to_dict(self) -> dict[str, Any]: else: started_at = self.started_at + starting_at: None | str + if isinstance(self.starting_at, datetime.datetime): + starting_at = self.starting_at.isoformat() + else: + starting_at = self.starting_at + status = self.status field_dict: dict[str, Any] = {} @@ -55,6 +63,7 @@ def to_dict(self) -> dict[str, Any]: "exit_code": exit_code, "seq": seq, "started_at": started_at, + "starting_at": starting_at, "status": status, } ) @@ -104,6 +113,21 @@ def _parse_started_at(data: object) -> datetime.datetime | None: started_at = _parse_started_at(d.pop("started_at")) + def _parse_starting_at(data: object) -> datetime.datetime | None: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + starting_at_type_0 = isoparse(data) + + return starting_at_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.datetime | None, data) + + starting_at = _parse_starting_at(d.pop("starting_at")) + status = d.pop("status") run_attempt = cls( @@ -111,6 +135,7 @@ def _parse_started_at(data: object) -> datetime.datetime | None: exit_code=exit_code, seq=seq, started_at=started_at, + starting_at=starting_at, status=status, ) diff --git a/src/tower/tower_api_client/models/run_parameter.py b/src/tower/tower_api_client/models/run_parameter.py index f668ccc1..6d805c9b 100644 --- a/src/tower/tower_api_client/models/run_parameter.py +++ b/src/tower/tower_api_client/models/run_parameter.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, TypeVar +from typing import Any, TypeVar, cast from attrs import define as _attrs_define @@ -17,11 +17,14 @@ class RunParameter: name (str): value (str): hidden (bool | Unset): Whether this parameter is hidden/secret. Defaults to false. Default: False. + is_override (bool | None | Unset): Whether this parameter's value was supplied as an override at run/schedule + creation time (true) or comes from the app version's default (false). """ name: str value: str hidden: bool | Unset = False + is_override: bool | None | Unset = UNSET def to_dict(self) -> dict[str, Any]: name = self.name @@ -30,6 +33,12 @@ def to_dict(self) -> dict[str, Any]: hidden = self.hidden + is_override: bool | None | Unset + if isinstance(self.is_override, Unset): + is_override = UNSET + else: + is_override = self.is_override + field_dict: dict[str, Any] = {} field_dict.update( @@ -40,6 +49,8 @@ def to_dict(self) -> dict[str, Any]: ) if hidden is not UNSET: field_dict["hidden"] = hidden + if is_override is not UNSET: + field_dict["is_override"] = is_override return field_dict @@ -52,10 +63,20 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: hidden = d.pop("hidden", UNSET) + def _parse_is_override(data: object) -> bool | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(bool | None | Unset, data) + + is_override = _parse_is_override(d.pop("is_override", UNSET)) + run_parameter = cls( name=name, value=value, hidden=hidden, + is_override=is_override, ) return run_parameter diff --git a/src/tower/tower_api_client/models/run_results.py b/src/tower/tower_api_client/models/run_results.py index 37c69711..9cdc3f01 100644 --- a/src/tower/tower_api_client/models/run_results.py +++ b/src/tower/tower_api_client/models/run_results.py @@ -19,6 +19,7 @@ class RunResults: pending (int): retrying (int): running (int): + starting (int): """ cancelled: int @@ -28,6 +29,7 @@ class RunResults: pending: int retrying: int running: int + starting: int def to_dict(self) -> dict[str, Any]: cancelled = self.cancelled @@ -44,6 +46,8 @@ def to_dict(self) -> dict[str, Any]: running = self.running + starting = self.starting + field_dict: dict[str, Any] = {} field_dict.update( @@ -55,6 +59,7 @@ def to_dict(self) -> dict[str, Any]: "pending": pending, "retrying": retrying, "running": running, + "starting": starting, } ) @@ -77,6 +82,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: running = d.pop("running") + starting = d.pop("starting") + run_results = cls( cancelled=cancelled, crashed=crashed, @@ -85,6 +92,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: pending=pending, retrying=retrying, running=running, + starting=starting, ) return run_results diff --git a/src/tower/tower_api_client/models/run_status.py b/src/tower/tower_api_client/models/run_status.py index 1cad2eb3..99e6d8aa 100644 --- a/src/tower/tower_api_client/models/run_status.py +++ b/src/tower/tower_api_client/models/run_status.py @@ -10,6 +10,7 @@ class RunStatus(str, Enum): RETRYING = "retrying" RUNNING = "running" SCHEDULED = "scheduled" + STARTING = "starting" def __str__(self) -> str: return str(self.value) diff --git a/src/tower/tower_api_client/models/run_timeseries_point.py b/src/tower/tower_api_client/models/run_timeseries_point.py index de83c155..99b5bc63 100644 --- a/src/tower/tower_api_client/models/run_timeseries_point.py +++ b/src/tower/tower_api_client/models/run_timeseries_point.py @@ -23,6 +23,7 @@ class RunTimeseriesPoint: retrying (int): running (int): scheduled (int): + starting (int): """ cancelled: int @@ -34,6 +35,7 @@ class RunTimeseriesPoint: retrying: int running: int scheduled: int + starting: int def to_dict(self) -> dict[str, Any]: cancelled = self.cancelled @@ -54,6 +56,8 @@ def to_dict(self) -> dict[str, Any]: scheduled = self.scheduled + starting = self.starting + field_dict: dict[str, Any] = {} field_dict.update( @@ -67,6 +71,7 @@ def to_dict(self) -> dict[str, Any]: "retrying": retrying, "running": running, "scheduled": scheduled, + "starting": starting, } ) @@ -93,6 +98,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: scheduled = d.pop("scheduled") + starting = d.pop("starting") + run_timeseries_point = cls( cancelled=cancelled, crashed=crashed, @@ -103,6 +110,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: retrying=retrying, running=running, scheduled=scheduled, + starting=starting, ) return run_timeseries_point diff --git a/src/tower/tower_api_client/models/schedule.py b/src/tower/tower_api_client/models/schedule.py index 42f77665..d34ea239 100644 --- a/src/tower/tower_api_client/models/schedule.py +++ b/src/tower/tower_api_client/models/schedule.py @@ -35,7 +35,8 @@ class Schedule: timezone (str): The IANA timezone identifier that the cron expression is evaluated in (e.g., 'America/New_York', 'Europe/London'). Defaults to 'UTC'. updated_at (datetime.datetime): The timestamp when the schedule was last updated - app_version (str | Unset): The specific app version to run, or null for the default version + app_version (str | Unset): This property is deprecated. Schedules inherit the version from their environment. + This field returns the environment's current version. parameters (list[RunParameter] | Unset): The parameters to pass when running the app """ diff --git a/src/tower/tower_api_client/models/search_runs_status_item.py b/src/tower/tower_api_client/models/search_runs_status_item.py index afac0331..b1b65541 100644 --- a/src/tower/tower_api_client/models/search_runs_status_item.py +++ b/src/tower/tower_api_client/models/search_runs_status_item.py @@ -9,6 +9,7 @@ class SearchRunsStatusItem(str, Enum): PENDING = "pending" RETRYING = "retrying" RUNNING = "running" + STARTING = "starting" def __str__(self) -> str: return str(self.value) diff --git a/src/tower/tower_api_client/models/unverified_authenticator.py b/src/tower/tower_api_client/models/unverified_authenticator.py deleted file mode 100644 index 6fd77faa..00000000 --- a/src/tower/tower_api_client/models/unverified_authenticator.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define - -T = TypeVar("T", bound="UnverifiedAuthenticator") - - -@_attrs_define -class UnverifiedAuthenticator: - """ - Attributes: - issuer (str): The issuer of the unverified authenticator. - key (str): The key of the unverified authenticator. - label (str): The label that is used for this unverified authenticator. - url (str): The full URL of the authenticator. - """ - - issuer: str - key: str - label: str - url: str - - def to_dict(self) -> dict[str, Any]: - issuer = self.issuer - - key = self.key - - label = self.label - - url = self.url - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "issuer": issuer, - "key": key, - "label": label, - "url": url, - } - ) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - issuer = d.pop("issuer") - - key = d.pop("key") - - label = d.pop("label") - - url = d.pop("url") - - unverified_authenticator = cls( - issuer=issuer, - key=key, - label=label, - url=url, - ) - - return unverified_authenticator diff --git a/src/tower/tower_api_client/models/update_password_reset_response.py b/src/tower/tower_api_client/models/update_app_environment_params.py similarity index 62% rename from src/tower/tower_api_client/models/update_password_reset_response.py rename to src/tower/tower_api_client/models/update_app_environment_params.py index 3e18fc74..14fc1f28 100644 --- a/src/tower/tower_api_client/models/update_password_reset_response.py +++ b/src/tower/tower_api_client/models/update_app_environment_params.py @@ -7,23 +7,23 @@ from ..types import UNSET, Unset -T = TypeVar("T", bound="UpdatePasswordResetResponse") +T = TypeVar("T", bound="UpdateAppEnvironmentParams") @_attrs_define -class UpdatePasswordResetResponse: +class UpdateAppEnvironmentParams: """ Attributes: - ok (bool): A boolean indicating the request was successfully processed. + version (str): The version to deploy to this environment, e.g. 'v1', 'v2'. schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/UpdatePasswordResetResponse.json. + https://api.tower.dev/v1/schemas/UpdateAppEnvironmentParams.json. """ - ok: bool + version: str schema: str | Unset = UNSET def to_dict(self) -> dict[str, Any]: - ok = self.ok + version = self.version schema = self.schema @@ -31,7 +31,7 @@ def to_dict(self) -> dict[str, Any]: field_dict.update( { - "ok": ok, + "version": version, } ) if schema is not UNSET: @@ -42,13 +42,13 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - ok = d.pop("ok") + version = d.pop("version") schema = d.pop("$schema", UNSET) - update_password_reset_response = cls( - ok=ok, + update_app_environment_params = cls( + version=version, schema=schema, ) - return update_password_reset_response + return update_app_environment_params diff --git a/src/tower/tower_api_client/models/delete_authenticator_params.py b/src/tower/tower_api_client/models/update_app_environment_response.py similarity index 55% rename from src/tower/tower_api_client/models/delete_authenticator_params.py rename to src/tower/tower_api_client/models/update_app_environment_response.py index 375f3c9b..a64310e4 100644 --- a/src/tower/tower_api_client/models/delete_authenticator_params.py +++ b/src/tower/tower_api_client/models/update_app_environment_response.py @@ -7,23 +7,27 @@ from ..types import UNSET, Unset -T = TypeVar("T", bound="DeleteAuthenticatorParams") +T = TypeVar("T", bound="UpdateAppEnvironmentResponse") @_attrs_define -class DeleteAuthenticatorParams: +class UpdateAppEnvironmentResponse: """ Attributes: - authenticator_id (str): The ID of the authenticator to delete + environment (str): + version (str): schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/DeleteAuthenticatorParams.json. + https://api.tower.dev/v1/schemas/UpdateAppEnvironmentResponse.json. """ - authenticator_id: str + environment: str + version: str schema: str | Unset = UNSET def to_dict(self) -> dict[str, Any]: - authenticator_id = self.authenticator_id + environment = self.environment + + version = self.version schema = self.schema @@ -31,7 +35,8 @@ def to_dict(self) -> dict[str, Any]: field_dict.update( { - "authenticator_id": authenticator_id, + "environment": environment, + "version": version, } ) if schema is not UNSET: @@ -42,13 +47,16 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - authenticator_id = d.pop("authenticator_id") + environment = d.pop("environment") + + version = d.pop("version") schema = d.pop("$schema", UNSET) - delete_authenticator_params = cls( - authenticator_id=authenticator_id, + update_app_environment_response = cls( + environment=environment, + version=version, schema=schema, ) - return delete_authenticator_params + return update_app_environment_response diff --git a/src/tower/tower_api_client/models/update_password_reset_params.py b/src/tower/tower_api_client/models/update_password_reset_params.py deleted file mode 100644 index a7dd2720..00000000 --- a/src/tower/tower_api_client/models/update_password_reset_params.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="UpdatePasswordResetParams") - - -@_attrs_define -class UpdatePasswordResetParams: - """ - Attributes: - password (str): The new password that you want to set for your account - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/UpdatePasswordResetParams.json. - """ - - password: str - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - password = self.password - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "password": password, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - password = d.pop("password") - - schema = d.pop("$schema", UNSET) - - update_password_reset_params = cls( - password=password, - schema=schema, - ) - - return update_password_reset_params diff --git a/src/tower/tower_api_client/models/update_schedule_params.py b/src/tower/tower_api_client/models/update_schedule_params.py index 23a80a7e..fdb50089 100644 --- a/src/tower/tower_api_client/models/update_schedule_params.py +++ b/src/tower/tower_api_client/models/update_schedule_params.py @@ -24,8 +24,8 @@ class UpdateScheduleParams: Attributes: schema (str | Unset): A URL to the JSON Schema for this object. Example: https://api.tower.dev/v1/schemas/UpdateScheduleParams.json. - app_version (None | str | Unset): The specific app version to run (if omitted, will use the app's default - version) + app_version (None | str | Unset): This property is deprecated and ignored. Schedules inherit the version from + their environment. cron (str | Unset): The cron expression defining when the app should run environment (str | Unset): The environment to run the app in Default: 'default'. name (None | str | Unset): The name for this schedule. Must be unique per team. diff --git a/src/tower/tower_api_client/models/usage_metric_time_series_point.py b/src/tower/tower_api_client/models/usage_metric_time_series_point.py new file mode 100644 index 00000000..862b7c15 --- /dev/null +++ b/src/tower/tower_api_client/models/usage_metric_time_series_point.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from dateutil.parser import isoparse + +from ..models.usage_metric_time_series_point_name import UsageMetricTimeSeriesPointName + +T = TypeVar("T", bound="UsageMetricTimeSeriesPoint") + + +@_attrs_define +class UsageMetricTimeSeriesPoint: + """ + Attributes: + date (datetime.datetime): + name (UsageMetricTimeSeriesPointName): + value (int): + """ + + date: datetime.datetime + name: UsageMetricTimeSeriesPointName + value: int + + def to_dict(self) -> dict[str, Any]: + date = self.date.isoformat() + + name = self.name.value + + value = self.value + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "date": date, + "name": name, + "value": value, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + date = isoparse(d.pop("date")) + + name = UsageMetricTimeSeriesPointName(d.pop("name")) + + value = d.pop("value") + + usage_metric_time_series_point = cls( + date=date, + name=name, + value=value, + ) + + return usage_metric_time_series_point diff --git a/src/tower/tower_api_client/models/usage_metric_time_series_point_name.py b/src/tower/tower_api_client/models/usage_metric_time_series_point_name.py new file mode 100644 index 00000000..4e55c479 --- /dev/null +++ b/src/tower/tower_api_client/models/usage_metric_time_series_point_name.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class UsageMetricTimeSeriesPointName(str, Enum): + RUN_SECONDS = "run_seconds" + + def __str__(self) -> str: + return str(self.value) diff --git a/src/tower/tower_api_client/models/verified_authenticator.py b/src/tower/tower_api_client/models/verified_authenticator.py deleted file mode 100644 index d9ee4201..00000000 --- a/src/tower/tower_api_client/models/verified_authenticator.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define -from dateutil.parser import isoparse - -T = TypeVar("T", bound="VerifiedAuthenticator") - - -@_attrs_define -class VerifiedAuthenticator: - """ - Attributes: - created_at (datetime.datetime): The ISO8601 timestamp indicating when this authenticator was created - id (str): The ID of this authenticator - issuer (str): The issuer of the unverified authenticator. - label (str): The label that is used for this unverified authenticator. - """ - - created_at: datetime.datetime - id: str - issuer: str - label: str - - def to_dict(self) -> dict[str, Any]: - created_at = self.created_at.isoformat() - - id = self.id - - issuer = self.issuer - - label = self.label - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "created_at": created_at, - "id": id, - "issuer": issuer, - "label": label, - } - ) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - created_at = isoparse(d.pop("created_at")) - - id = d.pop("id") - - issuer = d.pop("issuer") - - label = d.pop("label") - - verified_authenticator = cls( - created_at=created_at, - id=id, - issuer=issuer, - label=label, - ) - - return verified_authenticator diff --git a/src/tower/tower_api_client/models/verify_email_params.py b/src/tower/tower_api_client/models/verify_email_params.py deleted file mode 100644 index 32e1b394..00000000 --- a/src/tower/tower_api_client/models/verify_email_params.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="VerifyEmailParams") - - -@_attrs_define -class VerifyEmailParams: - """ - Attributes: - code (str): - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/VerifyEmailParams.json. - """ - - code: str - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - code = self.code - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "code": code, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - code = d.pop("code") - - schema = d.pop("$schema", UNSET) - - verify_email_params = cls( - code=code, - schema=schema, - ) - - return verify_email_params diff --git a/src/tower/tower_api_client/models/verify_email_response.py b/src/tower/tower_api_client/models/verify_email_response.py deleted file mode 100644 index 40f4135d..00000000 --- a/src/tower/tower_api_client/models/verify_email_response.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar - -from attrs import define as _attrs_define - -from ..types import UNSET, Unset - -if TYPE_CHECKING: - from ..models.user import User - - -T = TypeVar("T", bound="VerifyEmailResponse") - - -@_attrs_define -class VerifyEmailResponse: - """ - Attributes: - user (User): - schema (str | Unset): A URL to the JSON Schema for this object. Example: - https://api.tower.dev/v1/schemas/VerifyEmailResponse.json. - """ - - user: User - schema: str | Unset = UNSET - - def to_dict(self) -> dict[str, Any]: - user = self.user.to_dict() - - schema = self.schema - - field_dict: dict[str, Any] = {} - - field_dict.update( - { - "user": user, - } - ) - if schema is not UNSET: - field_dict["$schema"] = schema - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.user import User - - d = dict(src_dict) - user = User.from_dict(d.pop("user")) - - schema = d.pop("$schema", UNSET) - - verify_email_response = cls( - user=user, - schema=schema, - ) - - return verify_email_response diff --git a/tests/mock-api-server/main.py b/tests/mock-api-server/main.py index 7faff7d0..185c743a 100644 --- a/tests/mock-api-server/main.py +++ b/tests/mock-api-server/main.py @@ -69,6 +69,7 @@ async def log_requests(request: Request, call_next): "pending": 0, "retrying": 0, "running": 0, + "starting": 0, }, "subdomain": None, "is_externally_accessible": False, @@ -160,6 +161,7 @@ async def create_app(app_data: Dict[str, Any]): "pending": 0, "retrying": 0, "running": 0, + "starting": 0, }, "schedule": None, "short_description": description or "", @@ -283,6 +285,7 @@ async def run_app(name: str, run_params: Dict[str, Any]): "created_at": datetime.datetime.now().isoformat(), "scheduled_at": datetime.datetime.now().isoformat(), "cancelled_at": None, + "starting_at": datetime.datetime.now().isoformat(), "started_at": datetime.datetime.now().isoformat(), "ended_at": None, "app_version": mock_apps_db[name].get("version", "1.0.0"), @@ -355,7 +358,7 @@ async def cancel_run(name: str, seq: int): run_data["status"] = "cancelled" run_data["status_group"] = "successful" run_data["cancelled_at"] = now_iso() - return {"run": run_data} + return {"run": run_data, "cancelled_child_runs": 0} raise HTTPException( status_code=404, detail=f"Run sequence {seq} not found for app '{name}'" diff --git a/tests/tower/test_client.py b/tests/tower/test_client.py index beb48cc7..04e44e0d 100644 --- a/tests/tower/test_client.py +++ b/tests/tower/test_client.py @@ -50,6 +50,7 @@ def _create_run_response( "number": number, "run_id": run_id, "scheduled_at": "2025-04-25T20:54:58.761867Z", + "starting_at": "2025-04-25T20:54:59.366937Z", "started_at": "2025-04-25T20:54:59.366937Z", "status": status, "exit_code": None, @@ -100,6 +101,7 @@ def _create_run( number=number, run_id=run_id, scheduled_at="2025-04-25T20:54:58.761867Z", + starting_at="2025-04-25T20:54:59.366937Z", started_at="2025-04-25T20:54:59.366937Z", status=status, status_group=status_group, From bad0d74343ee8e48c4538d0e7af9dc4e6e28c847 Mon Sep 17 00:00:00 2001 From: Brad Heller Date: Wed, 13 May 2026 14:03:34 +0100 Subject: [PATCH 04/10] Bump version to v0.3.62 --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4238e116..13215edd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "config" -version = "0.3.61" +version = "0.3.62" dependencies = [ "base64", "chrono", @@ -598,7 +598,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto" -version = "0.3.61" +version = "0.3.62" dependencies = [ "aes-gcm", "base64", @@ -3348,7 +3348,7 @@ dependencies = [ [[package]] name = "testutils" -version = "0.3.61" +version = "0.3.62" dependencies = [ "pem", "rsa", @@ -3618,7 +3618,7 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" -version = "0.3.61" +version = "0.3.62" dependencies = [ "config", "pyo3", @@ -3646,7 +3646,7 @@ dependencies = [ [[package]] name = "tower-api" -version = "0.3.61" +version = "0.3.62" dependencies = [ "reqwest", "serde", @@ -3658,7 +3658,7 @@ dependencies = [ [[package]] name = "tower-cmd" -version = "0.3.61" +version = "0.3.62" dependencies = [ "axum", "bytes", @@ -3728,7 +3728,7 @@ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-package" -version = "0.3.61" +version = "0.3.62" dependencies = [ "async-compression", "flate2", @@ -3752,7 +3752,7 @@ dependencies = [ [[package]] name = "tower-runtime" -version = "0.3.61" +version = "0.3.62" dependencies = [ "async-trait", "chrono", @@ -3775,7 +3775,7 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tower-telemetry" -version = "0.3.61" +version = "0.3.62" dependencies = [ "tracing", "tracing-appender", @@ -3784,7 +3784,7 @@ dependencies = [ [[package]] name = "tower-uv" -version = "0.3.61" +version = "0.3.62" dependencies = [ "async-compression", "async_zip", @@ -3803,7 +3803,7 @@ dependencies = [ [[package]] name = "tower-version" -version = "0.3.61" +version = "0.3.62" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index a59a228d..8045433c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.3.61" +version = "0.3.62" description = "Tower is the best way to host Python data apps in production" rust-version = "1.81" authors = ["Brad Heller ", "Ben Lovell "] diff --git a/pyproject.toml b/pyproject.toml index d67129a4..a6eb82fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "tower" -version = "0.3.61" +version = "0.3.62" description = "Tower CLI and runtime environment for Tower." authors = [{ name = "Tower Computing Inc.", email = "brad@tower.dev" }] readme = "README.md" diff --git a/uv.lock b/uv.lock index 7f1540e4..05fcc6ee 100644 --- a/uv.lock +++ b/uv.lock @@ -2642,7 +2642,7 @@ wheels = [ [[package]] name = "tower" -version = "0.3.61" +version = "0.3.62" source = { editable = "." } dependencies = [ { name = "attrs" }, From d44c67c007faf36d74e963df8a3b80267982d3c4 Mon Sep 17 00:00:00 2001 From: "Simon Rosenberger (Bumm)" <41942954+codingcyclist@users.noreply.github.com> Date: Wed, 13 May 2026 17:42:30 +0200 Subject: [PATCH 05/10] Fix/pagination (#275) * fix: fetch all pages in list commands via reusable pagination helper All list commands (apps, teams, secrets, catalogs, environments, schedules) only fetched the first page from the API, silently truncating results. Added a PaginatedResponse trait and generic fetch_all_pages helper that iterates through all pages. Both CLI and MCP tools benefit since they share the same api.rs layer. Co-Authored-By: Claude Opus 4.6 * test: add pagination integration tests and mock server support - Mock server now respects page/page_size query params on all list endpoints - Added paginate() helper and mock_max_page_size override for testing - Added /test/seed-apps and /test/reset endpoints for test data setup - Added cli_pagination.feature with scenarios verifying multi-page fetching Co-Authored-By: Claude Opus 4.6 * fix: pagination test accounts for pre-existing test app The mock server re-adds predeployed-test-app on reset, so JSON mode returns 26 (25 seeded + 1 pre-existing). Changed assertion to "at least 25". Also pinned mock server to Python <3.14 (pyo3 compat). Co-Authored-By: Claude Opus 4.6 * Fix python 3.14 is too new for pyo3 bindings * style: fix black formatting in test files Co-Authored-By: Claude Opus 4.6 * fix: avoid race condition in pagination test by using natural page overflow Instead of capping page_size server-side (shared mutable state across parallel workers), seed 105 apps so pagination naturally exceeds the CLI's page_size=100, producing 2 pages without any global state changes. Co-Authored-By: Claude Opus 4.6 * fix: improve error diagnostics for pagination deserialization failures Include truncated response content in error messages to help debug CI-only deserialization failures. Remove unused debug step from tests. Co-Authored-By: Claude Opus 4.6 * fix: include serde deserialization error in diagnostic message This will reveal the exact field/type mismatch causing CI pagination test failures. Co-Authored-By: Claude Opus 4.6 * fix: run pagination tests serially to avoid shared mock state races Tag cli_pagination.feature with @serial and split the test runner into two phases: parallel features first (excluding @serial), then serial features without --jobs. Co-Authored-By: Claude Opus 4.6 * fix: strip run_results from mock list response to match real API The real Tower API does not include run_results in list app responses. Including it in the mock caused deserialization failures on CI where a cached wheel expected a newer RunResults schema with a 'starting' field. Co-Authored-By: Claude Opus 4.6 * style: fix black formatting in mock server Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- crates/tower-cmd/src/api.rs | 276 +++++++++++++----- crates/tower-cmd/src/apps.rs | 7 +- crates/tower-cmd/src/catalogs.rs | 7 +- crates/tower-cmd/src/environments.rs | 8 +- crates/tower-cmd/src/mcp.rs | 17 +- crates/tower-cmd/src/schedules.rs | 9 +- crates/tower-cmd/src/secrets.rs | 7 +- crates/tower-cmd/src/teams.rs | 5 +- .../features/cli_pagination.feature | 16 + tests/integration/features/steps/cli_steps.py | 46 ++- tests/integration/run_tests.py | 30 +- tests/mock-api-server/main.py | 157 ++++++++-- tests/mock-api-server/pyproject.toml | 2 +- .../tower_mock_api.egg-info/PKG-INFO | 2 +- tests/mock-api-server/uv.lock | 57 +--- 15 files changed, 444 insertions(+), 202 deletions(-) create mode 100644 tests/integration/features/cli_pagination.feature diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index 1da1637b..4daeab17 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -3,10 +3,12 @@ use futures_util::StreamExt; use http::StatusCode; use reqwest_eventsource::{Event, EventSource}; use std::collections::HashMap; +use std::future::Future; use tokio::sync::mpsc; use tower_api::apis::configuration; use tower_api::apis::Error; use tower_api::apis::ResponseContent; +use tower_api::models::Pagination; use tower_api::models::RunParameter; use tower_telemetry::debug; @@ -19,6 +21,74 @@ pub trait ResponseEntity { fn extract_data(self) -> Option; } +/// Trait for API responses that contain paginated items. +trait PaginatedResponse { + type Item; + fn pagination(&self) -> &Pagination; + fn into_items(self) -> Vec; +} + +impl PaginatedResponse for tower_api::models::ListAppsResponse { + type Item = tower_api::models::AppSummary; + fn pagination(&self) -> &Pagination { &self.pages } + fn into_items(self) -> Vec { self.apps } +} + +impl PaginatedResponse for tower_api::models::ListTeamsResponse { + type Item = tower_api::models::Team; + fn pagination(&self) -> &Pagination { &self.pages } + fn into_items(self) -> Vec { self.teams } +} + +impl PaginatedResponse for tower_api::models::ListSecretsResponse { + type Item = tower_api::models::Secret; + fn pagination(&self) -> &Pagination { &self.pages } + fn into_items(self) -> Vec { self.secrets } +} + +impl PaginatedResponse for tower_api::models::ListCatalogsResponse { + type Item = tower_api::models::Catalog; + fn pagination(&self) -> &Pagination { &self.pages } + fn into_items(self) -> Vec { self.catalogs } +} + +impl PaginatedResponse for tower_api::models::ListEnvironmentsResponse { + type Item = tower_api::models::Environment; + fn pagination(&self) -> &Pagination { &self.pages } + fn into_items(self) -> Vec { self.environments } +} + +impl PaginatedResponse for tower_api::models::ListSchedulesResponse { + type Item = tower_api::models::Schedule; + fn pagination(&self) -> &Pagination { &self.pages } + fn into_items(self) -> Vec { self.schedules } +} + +/// Fetches all pages from a paginated API endpoint. +async fn fetch_all_pages(fetch: F) -> Result, Error> +where + R: PaginatedResponse, + F: Fn(i64, i64) -> Fut, + Fut: Future>>, +{ + let page_size: i64 = 100; + let mut all_items = Vec::new(); + let mut page: i64 = 0; + + loop { + let response = fetch(page, page_size).await?; + let num_pages = response.pagination().num_pages; + all_items.extend(response.into_items()); + + page += 1; + if page >= num_pages || page >= 100 { + break; + } + } + + Ok(all_items) +} + pub async fn describe_app( config: &Config, name: &str, @@ -45,21 +115,26 @@ pub async fn describe_app( pub async fn list_apps( config: &Config, -) -> Result> +) -> Result, Error> { - let api_config = &config.into(); - - let params = tower_api::apis::default_api::ListAppsParams { - query: None, - page: None, - page_size: None, - num_runs: Some(0), - sort: None, - filter: None, - environment: None, - }; + let api_config: configuration::Configuration = config.into(); - unwrap_api_response(tower_api::apis::default_api::list_apps(api_config, params)).await + fetch_all_pages(|page, page_size| { + let api_config = &api_config; + async move { + let params = tower_api::apis::default_api::ListAppsParams { + query: None, + page: Some(page), + page_size: Some(page_size), + num_runs: Some(0), + sort: None, + filter: None, + environment: None, + }; + unwrap_api_response(tower_api::apis::default_api::list_apps(api_config, params)).await + } + }) + .await } pub async fn create_app( @@ -225,22 +300,27 @@ pub async fn list_catalogs( config: &Config, env: &str, all: bool, -) -> Result< - tower_api::models::ListCatalogsResponse, - Error, -> { - let api_config = &config.into(); - - let params = tower_api::apis::default_api::ListCatalogsParams { - environment: Some(env.to_string()), - all: Some(all), - page: None, - page_size: None, - }; - - unwrap_api_response(tower_api::apis::default_api::list_catalogs( - api_config, params, - )) +) -> Result, Error> +{ + let api_config: configuration::Configuration = config.into(); + let env = env.to_string(); + + fetch_all_pages(|page, page_size| { + let api_config = &api_config; + let env = &env; + async move { + let params = tower_api::apis::default_api::ListCatalogsParams { + environment: Some(env.to_string()), + all: Some(all), + page: Some(page), + page_size: Some(page_size), + }; + unwrap_api_response(tower_api::apis::default_api::list_catalogs( + api_config, params, + )) + .await + } + }) .await } @@ -269,22 +349,27 @@ pub async fn list_secrets( config: &Config, env: &str, all: bool, -) -> Result< - tower_api::models::ListSecretsResponse, - Error, -> { - let api_config = &config.into(); - - let params = tower_api::apis::default_api::ListSecretsParams { - environment: Some(env.to_string()), - all: Some(all), - page: None, - page_size: None, - }; - - unwrap_api_response(tower_api::apis::default_api::list_secrets( - api_config, params, - )) +) -> Result, Error> +{ + let api_config: configuration::Configuration = config.into(); + let env = env.to_string(); + + fetch_all_pages(|page, page_size| { + let api_config = &api_config; + let env = &env; + async move { + let params = tower_api::apis::default_api::ListSecretsParams { + environment: Some(env.to_string()), + all: Some(all), + page: Some(page), + page_size: Some(page_size), + }; + unwrap_api_response(tower_api::apis::default_api::list_secrets( + api_config, params, + )) + .await + } + }) .await } @@ -405,16 +490,20 @@ pub async fn refresh_session( pub async fn list_teams( config: &Config, -) -> Result> -{ - let api_config = &config.into(); - - let params = tower_api::apis::default_api::ListTeamsParams { - page: None, - page_size: None, - }; +) -> Result, Error> { + let api_config: configuration::Configuration = config.into(); - unwrap_api_response(tower_api::apis::default_api::list_teams(api_config, params)).await + fetch_all_pages(|page, page_size| { + let api_config = &api_config; + async move { + let params = tower_api::apis::default_api::ListTeamsParams { + page: Some(page), + page_size: Some(page_size), + }; + unwrap_api_response(tower_api::apis::default_api::list_teams(api_config, params)).await + } + }) + .await } pub enum LogStreamEvent { @@ -575,6 +664,7 @@ async fn unwrap_api_response(api_call: F) -> Result> where F: std::future::Future, Error>>, T: ResponseEntity, + T::Data: serde::de::DeserializeOwned, { match api_call.await { Ok(response) => { @@ -594,11 +684,28 @@ where if let Some(data) = entity.extract_data() { Ok(data) } else { + let truncated = if response.content.len() > 500 { + format!("{}...(truncated)", &response.content[..500]) + } else { + response.content.clone() + }; + // Try explicit deserialization to get the actual error message + let deser_error = serde_json::from_str::(&response.content) + .err() + .map(|e| format!(" Deserialization error: {}", e)) + .unwrap_or_default(); + debug!( + "Failed to extract data from API response:{} Content: {}", + deser_error, truncated + ); let err = Error::ResponseError( tower_api::apis::ResponseContent { tower_trace_id: "".to_string(), status: StatusCode::NO_CONTENT, - content: "Received a response from the server that the CLI wasn't able to understand".to_string(), + content: format!( + "Received a response from the server that the CLI wasn't able to understand.{} Response: {}", + deser_error, truncated + ), entity: None, }, ); @@ -859,18 +966,24 @@ impl ResponseEntity for tower_api::apis::default_api::ListEnvironmentsSuccess { pub async fn list_environments( config: &Config, ) -> Result< - tower_api::models::ListEnvironmentsResponse, + Vec, Error, > { - let api_config = &config.into(); - let params = tower_api::apis::default_api::ListEnvironmentsParams { - page: Some(0), - page_size: Some(1000), - }; + let api_config: configuration::Configuration = config.into(); - unwrap_api_response(tower_api::apis::default_api::list_environments( - api_config, params, - )) + fetch_all_pages(|page, page_size| { + let api_config = &api_config; + async move { + let params = tower_api::apis::default_api::ListEnvironmentsParams { + page: Some(page), + page_size: Some(page_size), + }; + unwrap_api_response(tower_api::apis::default_api::list_environments( + api_config, params, + )) + .await + } + }) .await } @@ -900,21 +1013,26 @@ pub async fn list_schedules( config: &Config, _app_name: Option<&str>, environment: Option<&str>, -) -> Result< - tower_api::models::ListSchedulesResponse, - Error, -> { - let api_config = &config.into(); - - let params = tower_api::apis::default_api::ListSchedulesParams { - environment: environment.map(String::from), - page: None, - page_size: None, - }; - - unwrap_api_response(tower_api::apis::default_api::list_schedules( - api_config, params, - )) +) -> Result, Error> +{ + let api_config: configuration::Configuration = config.into(); + let environment = environment.map(String::from); + + fetch_all_pages(|page, page_size| { + let api_config = &api_config; + let environment = &environment; + async move { + let params = tower_api::apis::default_api::ListSchedulesParams { + environment: environment.clone(), + page: Some(page), + page_size: Some(page_size), + }; + unwrap_api_response(tower_api::apis::default_api::list_schedules( + api_config, params, + )) + .await + } + }) .await } diff --git a/crates/tower-cmd/src/apps.rs b/crates/tower-cmd/src/apps.rs index 9d821d7b..3a779426 100644 --- a/crates/tower-cmd/src/apps.rs +++ b/crates/tower-cmd/src/apps.rs @@ -210,10 +210,9 @@ pub async fn do_show(config: Config, cmd: &ArgMatches) { } pub async fn do_list_apps(config: Config) { - let resp = output::with_spinner("Listing apps", api::list_apps(&config)).await; + let apps = output::with_spinner("Listing apps", api::list_apps(&config)).await; - let items = resp - .apps + let items = apps .iter() .map(|app_summary| { let app = &app_summary.app; @@ -225,7 +224,7 @@ pub async fn do_list_apps(config: Config) { format!("{}\n{}", output::title(&app.name), desc) }) .collect(); - output::list(items, Some(&resp.apps)); + output::list(items, Some(&apps)); } pub async fn do_create(config: Config, args: &ArgMatches) { diff --git a/crates/tower-cmd/src/catalogs.rs b/crates/tower-cmd/src/catalogs.rs index 034e650a..06fb87f2 100644 --- a/crates/tower-cmd/src/catalogs.rs +++ b/crates/tower-cmd/src/catalogs.rs @@ -54,15 +54,14 @@ pub async fn do_list(config: Config, args: &ArgMatches) { let all = cmd::get_bool_flag(args, "all"); let env = cmd::get_string_flag(args, "environment"); - let list_response = + let catalogs = output::with_spinner("Listing catalogs", api::list_catalogs(&config, &env, all)).await; let headers = vec!["Name", "Type", "Environment"] .into_iter() .map(str::to_string) .collect(); - let data = list_response - .catalogs + let data = catalogs .iter() .map(|catalog| { vec![ @@ -72,7 +71,7 @@ pub async fn do_list(config: Config, args: &ArgMatches) { ] }) .collect(); - output::table(headers, data, Some(&list_response.catalogs)); + output::table(headers, data, Some(&catalogs)); } pub async fn do_show(config: Config, args: &ArgMatches) { diff --git a/crates/tower-cmd/src/environments.rs b/crates/tower-cmd/src/environments.rs index 970677e4..a9dfad37 100644 --- a/crates/tower-cmd/src/environments.rs +++ b/crates/tower-cmd/src/environments.rs @@ -23,18 +23,18 @@ pub fn environments_cmd() -> Command { } pub async fn do_list(config: Config) { - let resp = output::with_spinner("Listing environments", api::list_environments(&config)).await; + let environments = + output::with_spinner("Listing environments", api::list_environments(&config)).await; let headers = vec!["Name".to_string()]; - let envs_data: Vec> = resp - .environments + let envs_data: Vec> = environments .iter() .map(|env| vec![env.name.clone()]) .collect(); // Display the table using the existing table function - output::table(headers, envs_data, Some(&resp.environments)); + output::table(headers, envs_data, Some(&environments)); } pub async fn do_create(config: Config, args: &ArgMatches) { diff --git a/crates/tower-cmd/src/mcp.rs b/crates/tower-cmd/src/mcp.rs index 0b069356..59cb2296 100644 --- a/crates/tower-cmd/src/mcp.rs +++ b/crates/tower-cmd/src/mcp.rs @@ -335,15 +335,14 @@ impl TowerService { } async fn list_teams_via_api(&self) -> Result { - let response = api::list_teams(&self.config).await.map_err(|e| { + let teams = api::list_teams(&self.config).await.map_err(|e| { McpError::internal_error( "Failed to list teams", Some(json!({"error": e.to_string()})), ) })?; - let teams: Vec = response - .teams + let teams: Vec = teams .into_iter() .map(|team| json!({"name": team.name})) .collect(); @@ -467,9 +466,8 @@ impl TowerService { #[tool(description = "List all apps in your Tower account")] async fn tower_apps_list(&self) -> Result { match api::list_apps(&self.config).await { - Ok(response) => { - let apps: Vec = response - .apps + Ok(apps) => { + let apps: Vec = apps .into_iter() .map(|app_summary| { let app = app_summary.app; @@ -594,7 +592,7 @@ impl TowerService { let all = request.all.as_deref() == Some("true"); match api::list_secrets(&self.config, environment, all).await { - Ok(response) => Self::json_success(json!({"secrets": response.secrets})), + Ok(secrets) => Self::json_success(json!({"secrets": secrets})), Err(e) => Self::error_result("Failed to list secrets", e), } } @@ -1036,9 +1034,8 @@ IMPORTANT REMINDERS: #[tool(description = "List all schedules for apps")] async fn tower_schedules_list(&self) -> Result { match api::list_schedules(&self.config, None, None).await { - Ok(response) => { - let schedules = response - .schedules + Ok(schedules) => { + let schedules = schedules .into_iter() .map(|mut schedule| { if let Some(parameters) = schedule.parameters.as_mut() { diff --git a/crates/tower-cmd/src/schedules.rs b/crates/tower-cmd/src/schedules.rs index cb6aa499..3fafd1d9 100644 --- a/crates/tower-cmd/src/schedules.rs +++ b/crates/tower-cmd/src/schedules.rs @@ -114,13 +114,13 @@ pub async fn do_list(config: Config, args: &ArgMatches) { let app = args.get_one::("app").map(|s| s.as_str()); let environment = args.get_one::("environment").map(|s| s.as_str()); - let response = output::with_spinner( + let schedules = output::with_spinner( "Listing schedules", api::list_schedules(&config, app, environment), ) .await; - if response.schedules.is_empty() { + if schedules.is_empty() { output::write("No schedules found.\n"); return; } @@ -130,8 +130,7 @@ pub async fn do_list(config: Config, args: &ArgMatches) { .map(str::to_string) .collect(); - let rows: Vec> = response - .schedules + let rows: Vec> = schedules .iter() .map(|schedule| { let status = match schedule.status { @@ -149,7 +148,7 @@ pub async fn do_list(config: Config, args: &ArgMatches) { }) .collect(); - output::table(headers, rows, Some(&response.schedules)); + output::table(headers, rows, Some(&schedules)); } pub async fn do_create(config: Config, args: &ArgMatches) { diff --git a/crates/tower-cmd/src/secrets.rs b/crates/tower-cmd/src/secrets.rs index f6baf5bd..16c67009 100644 --- a/crates/tower-cmd/src/secrets.rs +++ b/crates/tower-cmd/src/secrets.rs @@ -133,15 +133,14 @@ pub async fn do_list(config: Config, args: &ArgMatches) { .collect(); output::table(headers, data, Some(&list_response.secrets)); } else { - let list_response = + let secrets = output::with_spinner("Listing secrets", api::list_secrets(&config, &env, all)).await; let headers = vec!["Secret", "Environment", "Preview"] .into_iter() .map(str::to_string) .collect(); - let data = list_response - .secrets + let data = secrets .iter() .map(|secret| { vec![ @@ -151,7 +150,7 @@ pub async fn do_list(config: Config, args: &ArgMatches) { ] }) .collect(); - output::table(headers, data, Some(&list_response.secrets)); + output::table(headers, data, Some(&secrets)); } } diff --git a/crates/tower-cmd/src/teams.rs b/crates/tower-cmd/src/teams.rs index abeaa666..36783a9e 100644 --- a/crates/tower-cmd/src/teams.rs +++ b/crates/tower-cmd/src/teams.rs @@ -56,12 +56,11 @@ pub async fn do_list(config: Config) { } async fn do_list_via_api(config: &Config) { - let resp = output::with_spinner("Fetching teams", api::list_teams(config)).await; + let teams = output::with_spinner("Fetching teams", api::list_teams(config)).await; let headers = vec!["Name".to_string()]; - let teams_data: Vec> = resp - .teams + let teams_data: Vec> = teams .iter() .map(|team| vec![team.name.clone()]) .collect(); diff --git a/tests/integration/features/cli_pagination.feature b/tests/integration/features/cli_pagination.feature new file mode 100644 index 00000000..ada10629 --- /dev/null +++ b/tests/integration/features/cli_pagination.feature @@ -0,0 +1,16 @@ +@serial +Feature: CLI Pagination + As a developer using Tower CLI + I want all items to be returned from list commands + So that I don't miss apps or resources due to pagination limits + + Scenario: CLI apps list fetches all pages when results exceed page size + Given the mock API has 105 seeded apps + When I run "tower apps list" via CLI + Then the output should contain all 105 seeded app names + + Scenario: CLI apps list in JSON mode returns all paginated results + Given the mock API has 105 seeded apps + When I run "tower apps list --json" via CLI + Then the output should be valid JSON + And the JSON should contain at least 105 apps diff --git a/tests/integration/features/steps/cli_steps.py b/tests/integration/features/steps/cli_steps.py index b9a24d39..f2f82934 100644 --- a/tests/integration/features/steps/cli_steps.py +++ b/tests/integration/features/steps/cli_steps.py @@ -9,7 +9,8 @@ import re from datetime import datetime from pathlib import Path -from behave import given, when, then +import requests +from behave import given, when, then, step from dirty_equals import IsStr, IsPartialDict @@ -432,3 +433,46 @@ def step_app_description_should_be(context, expected_description): assert ( actual_description == expected_description ), f"Expected description '{expected_description}', got '{actual_description}'" + + +# Pagination test steps + + +@step("the mock API has {count:d} seeded apps") +def step_seed_apps(context, count): + """Seed the mock API with a number of apps to test pagination.""" + # Reset first to get a clean slate + requests.post(f"{context.tower_url}/test/reset") + # Seed apps (no page_size override — uses default 20, so >20 apps forces pagination) + resp = requests.post( + f"{context.tower_url}/test/seed-apps", + json={"count": count}, + ) + assert resp.status_code == 200, f"Failed to seed apps: {resp.text}" + context.seeded_app_count = count + + +@step("the output should contain all {count:d} seeded app names") +def step_output_should_contain_all_seeded_apps(context, count): + """Verify the CLI output contains all seeded app names.""" + # Strip ANSI codes for checking + output = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", context.cli_output) + missing = [] + for i in range(count): + name = f"paginated-app-{i:03d}" + if name not in output: + missing.append(name) + assert not missing, ( + f"Missing {len(missing)} apps from output (first 5): {missing[:5]}\n" + f"Full output: {output[:2000]}" + ) + + +@step("the JSON should contain at least {count:d} apps") +def step_json_should_contain_at_least_n_apps(context, count): + """Verify JSON output contains at least the expected number of apps.""" + data = parse_cli_json(context) + assert isinstance(data, list), f"Expected JSON array, got: {type(data)}" + assert ( + len(data) >= count + ), f"Expected at least {count} apps in JSON, got {len(data)}" diff --git a/tests/integration/run_tests.py b/tests/integration/run_tests.py index 9b1174d4..b41b2544 100755 --- a/tests/integration/run_tests.py +++ b/tests/integration/run_tests.py @@ -188,10 +188,34 @@ def main(): else: log("Running integration tests...") - result = subprocess.run( - ["behave", str(test_dir)] + args, cwd=Path(__file__).parent, env=env + # Run parallel tests first (exclude @serial tagged features) + parallel_result = subprocess.run( + ["behave", str(test_dir), "--tags=~@serial"] + args, + cwd=Path(__file__).parent, + env=env, + ) + + # Run @serial tagged features sequentially (no --jobs flag) + serial_args = [a for a in args if a not in ("--jobs", "-j") and not a.isdigit()] + # Remove --jobs N pair from args + serial_args = [] + skip_next = False + for a in args: + if skip_next: + skip_next = False + continue + if a in ("--jobs", "-j"): + skip_next = True + continue + serial_args.append(a) + + serial_result = subprocess.run( + ["behave", str(test_dir), "--tags=@serial"] + serial_args, + cwd=Path(__file__).parent, + env=env, ) - return result.returncode + + return parallel_result.returncode or serial_result.returncode except KeyboardInterrupt: log("Tests interrupted by user") diff --git a/tests/mock-api-server/main.py b/tests/mock-api-server/main.py index 185c743a..5e86b7c7 100644 --- a/tests/mock-api-server/main.py +++ b/tests/mock-api-server/main.py @@ -48,6 +48,7 @@ async def log_requests(request: Request, call_next): mock_runs_db = {} mock_schedules_db = {} mock_deployed_apps = set() # Track which apps have been deployed +mock_max_page_size: Optional[int] = None # Override max page size for testing # Pre-populate with test-app for CLI validation/spinner tests mock_apps_db["predeployed-test-app"] = { @@ -112,20 +113,22 @@ async def read_root(): # Placeholder for /v1/apps endpoints @app.get("/v1/apps") -async def list_apps(): - # Format apps as AppSummary objects +async def list_apps(page: Optional[int] = None, page_size: Optional[int] = None): + # Format apps as AppSummary objects (matching real API list format) app_summaries = [] + # Fields to exclude from list view (real API doesn't return these in list) + list_excluded_fields = {"run_results"} for app_data in mock_apps_db.values(): - app_summaries.append({"app": app_data, "runs": []}) # Empty runs for list view + app_for_list = { + k: v for k, v in app_data.items() if k not in list_excluded_fields + } + app_summaries.append({"app": app_for_list, "runs": []}) + + page_items, pages = paginate(app_summaries, page, page_size) return { - "apps": app_summaries, - "pages": { - "page": 1, - "total": len(mock_apps_db), - "num_pages": 1, - "page_size": 20, - }, + "apps": page_items, + "pages": pages, } @@ -367,15 +370,11 @@ async def cancel_run(name: str, seq: int): # Placeholder for /secrets endpoints @app.get("/v1/secrets") -async def list_secrets(): +async def list_secrets(page: Optional[int] = None, page_size: Optional[int] = None): + items, pages = paginate(list(mock_secrets_db.values()), page, page_size) return { - "secrets": list(mock_secrets_db.values()), - "pages": { - "page": 1, - "total": len(mock_secrets_db), - "num_pages": 1, - "page_size": 20, - }, + "secrets": items, + "pages": pages, } @@ -416,8 +415,9 @@ async def delete_secret(name: str, environment: str = "default"): # Placeholder for /teams endpoints @app.get("/v1/teams") -async def list_teams(): - return {"teams": list(mock_teams_db.values())} +async def list_teams(page: Optional[int] = None, page_size: Optional[int] = None): + items, pages = paginate(list(mock_teams_db.values()), page, page_size) + return {"teams": items, "pages": pages} @app.post("/v1/teams") @@ -461,6 +461,31 @@ async def describe_secrets_key(): return {"public_key": mock_public_key} +def paginate(items: list, page: Optional[int], page_size: Optional[int]) -> tuple: + """Apply pagination to a list of items. Returns (page_items, pages_metadata).""" + global mock_max_page_size + if page_size is None: + page_size = 20 + # Cap page_size if mock override is set (simulates server-side limit) + if mock_max_page_size is not None: + page_size = min(page_size, mock_max_page_size) + if page is None: + page = 0 + + total = len(items) + num_pages = max(1, (total + page_size - 1) // page_size) + start = page * page_size + end = start + page_size + page_items = items[start:end] + + return page_items, { + "page": page, + "total": total, + "num_pages": num_pages, + "page_size": page_size, + } + + def empty_paginated_response(key: str): """Create empty paginated response for any resource type.""" return {key: [], "pages": {"page": 1, "total": 0, "num_pages": 1, "page_size": 20}} @@ -641,16 +666,12 @@ async def stream_run_logs(name: str, seq: int): # Schedule endpoints @app.get("/v1/schedules") -async def list_schedules(): +async def list_schedules(page: Optional[int] = None, page_size: Optional[int] = None): """Mock endpoint for listing schedules.""" + items, pages = paginate(list(mock_schedules_db.values()), page, page_size) return { - "schedules": list(mock_schedules_db.values()), - "pages": { - "page": 1, - "total": len(mock_schedules_db), - "num_pages": 1, - "page_size": 20, - }, + "schedules": items, + "pages": pages, } @@ -711,3 +732,83 @@ async def delete_schedule(schedule_data: Dict[str, Any]): @app.get("/health") async def health_check(): return {"status": "healthy", "timestamp": datetime.datetime.now().isoformat()} + + +# Test helper endpoints (for seeding data in integration tests) +@app.post("/test/seed-apps") +async def seed_apps(data: Dict[str, Any]): + """Seed the mock apps DB with a specified number of apps.""" + global mock_max_page_size + count = data.get("count", 10) + page_size_override = data.get("page_size_override") + if page_size_override is not None: + mock_max_page_size = page_size_override + + for i in range(count): + name = f"paginated-app-{i:03d}" + mock_apps_db[name] = { + "name": name, + "owner": "mock_owner", + "short_description": f"App number {i}", + "version": None, + "schedule": None, + "created_at": now_iso(), + "next_run_at": None, + "health_status": "healthy", + "pending_timeout": 300, + "running_timeout": 0, + "run_results": { + "cancelled": 0, + "crashed": 0, + "errored": 0, + "exited": 0, + "pending": 0, + "retrying": 0, + "running": 0, + }, + "subdomain": None, + "is_externally_accessible": False, + "status": "active", + } + + return {"seeded": count} + + +@app.post("/test/reset") +async def reset_test_data(): + """Reset all mock data stores to initial state.""" + global mock_max_page_size + mock_max_page_size = None + mock_apps_db.clear() + mock_secrets_db.clear() + mock_teams_db.clear() + mock_runs_db.clear() + mock_schedules_db.clear() + mock_deployed_apps.clear() + + # Re-add the pre-populated test app + mock_apps_db["predeployed-test-app"] = { + "name": "predeployed-test-app", + "owner": "mock_owner", + "short_description": "Pre-existing test app for CLI tests", + "version": None, + "schedule": None, + "created_at": now_iso(), + "next_run_at": None, + "health_status": "healthy", + "pending_timeout": 300, + "running_timeout": 0, + "run_results": { + "cancelled": 0, + "crashed": 0, + "errored": 0, + "exited": 0, + "pending": 0, + "retrying": 0, + "running": 0, + }, + "subdomain": None, + "is_externally_accessible": False, + } + mock_deployed_apps.add("predeployed-test-app") + return {"status": "reset"} diff --git a/tests/mock-api-server/pyproject.toml b/tests/mock-api-server/pyproject.toml index fc24c4b0..011d6f85 100644 --- a/tests/mock-api-server/pyproject.toml +++ b/tests/mock-api-server/pyproject.toml @@ -6,7 +6,7 @@ dependencies = [ "fastapi==0.111.0", "uvicorn==0.30.1", ] -requires-python = ">=3.9" +requires-python = ">=3.9,<3.14" [build-system] requires = ["setuptools>=61.0"] diff --git a/tests/mock-api-server/tower_mock_api.egg-info/PKG-INFO b/tests/mock-api-server/tower_mock_api.egg-info/PKG-INFO index 7d46deca..ebcad901 100644 --- a/tests/mock-api-server/tower_mock_api.egg-info/PKG-INFO +++ b/tests/mock-api-server/tower_mock_api.egg-info/PKG-INFO @@ -2,6 +2,6 @@ Metadata-Version: 2.4 Name: tower-mock-api Version: 0.1.0 Summary: A mock API server for Tower CLI testing. -Requires-Python: >=3.9 +Requires-Python: <3.14,>=3.9 Requires-Dist: fastapi==0.111.0 Requires-Dist: uvicorn==0.30.1 diff --git a/tests/mock-api-server/uv.lock b/tests/mock-api-server/uv.lock index 3c3a88c4..0f582d76 100644 --- a/tests/mock-api-server/uv.lock +++ b/tests/mock-api-server/uv.lock @@ -1,6 +1,6 @@ version = 1 -revision = 2 -requires-python = ">=3.9" +revision = 3 +requires-python = ">=3.9, <3.14" resolution-markers = [ "python_full_version >= '3.10'", "python_full_version < '3.10'", @@ -420,17 +420,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/5b/e398449080ce6b4c8fcadad57e51fa16f65768e1b142ba90b23ac5d10801/orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5", size = 124402, upload-time = "2025-08-12T15:11:44.036Z" }, { url = "https://files.pythonhosted.org/packages/b3/66/429e4608e124debfc4790bfc37131f6958e59510ba3b542d5fc163be8e5f/orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d", size = 119498, upload-time = "2025-08-12T15:11:45.864Z" }, { url = "https://files.pythonhosted.org/packages/7b/04/f8b5f317cce7ad3580a9ad12d7e2df0714dfa8a83328ecddd367af802f5b/orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535", size = 114051, upload-time = "2025-08-12T15:11:47.555Z" }, - { url = "https://files.pythonhosted.org/packages/74/83/2c363022b26c3c25b3708051a19d12f3374739bb81323f05b284392080c0/orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7", size = 226406, upload-time = "2025-08-12T15:11:49.445Z" }, - { url = "https://files.pythonhosted.org/packages/b0/a7/aa3c973de0b33fc93b4bd71691665ffdfeae589ea9d0625584ab10a7d0f5/orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81", size = 115788, upload-time = "2025-08-12T15:11:50.992Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/e45f233dfd09fdbb052ec46352363dca3906618e1a2b264959c18f809d0b/orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f", size = 111318, upload-time = "2025-08-12T15:11:52.495Z" }, - { url = "https://files.pythonhosted.org/packages/3e/23/cf5a73c4da6987204cbbf93167f353ff0c5013f7c5e5ef845d4663a366da/orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7", size = 121231, upload-time = "2025-08-12T15:11:53.941Z" }, - { url = "https://files.pythonhosted.org/packages/40/1d/47468a398ae68a60cc21e599144e786e035bb12829cb587299ecebc088f1/orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4", size = 119204, upload-time = "2025-08-12T15:11:55.409Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d9/f99433d89b288b5bc8836bffb32a643f805e673cf840ef8bab6e73ced0d1/orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f", size = 392237, upload-time = "2025-08-12T15:11:57.18Z" }, - { url = "https://files.pythonhosted.org/packages/d4/dc/1b9d80d40cebef603325623405136a29fb7d08c877a728c0943dd066c29a/orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7", size = 134578, upload-time = "2025-08-12T15:11:58.844Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/72e7a4c5b6485ef4e83ef6aba7f1dd041002bad3eb5d1d106ca5b0fc02c6/orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6", size = 123799, upload-time = "2025-08-12T15:12:00.352Z" }, - { url = "https://files.pythonhosted.org/packages/c8/3e/a3d76b392e7acf9b34dc277171aad85efd6accc75089bb35b4c614990ea9/orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f", size = 124461, upload-time = "2025-08-12T15:12:01.854Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e3/75c6a596ff8df9e4a5894813ff56695f0a218e6ea99420b4a645c4f7795d/orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8", size = 119494, upload-time = "2025-08-12T15:12:03.337Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3d/9e74742fc261c5ca473c96bb3344d03995869e1dc6402772c60afb97736a/orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67", size = 114046, upload-time = "2025-08-12T15:12:04.87Z" }, { url = "https://files.pythonhosted.org/packages/4f/08/8ebc6dcac0938376b7e61dff432c33958505ae4c185dda3fa1e6f46ac40b/orjson-3.11.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:957f10c7b5bce3d3f2ad577f3b307c784f5dabafcce3b836229c269c11841c86", size = 226498, upload-time = "2025-08-12T15:12:06.51Z" }, { url = "https://files.pythonhosted.org/packages/ff/74/a97c8e2bc75a27dfeeb1b289645053f1889125447f3b7484a2e34ac55d2a/orjson-3.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a669e31ab8eb466c9142ac7a4be2bb2758ad236a31ef40dcd4cf8774ab40f33", size = 111529, upload-time = "2025-08-12T15:12:08.21Z" }, { url = "https://files.pythonhosted.org/packages/78/c3/55121b5722a1a4e4610a411866cfeada5314dc498cd42435b590353009d2/orjson-3.11.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adedf7d887416c51ad49de3c53b111887e0b63db36c6eb9f846a8430952303d8", size = 116213, upload-time = "2025-08-12T15:12:09.776Z" }, @@ -812,28 +801,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/50/8856e24bec5e2fc7f775d867aeb7a3f137359356200ac44658f1f2c834b2/ujson-5.11.0-cp313-cp313-win32.whl", hash = "sha256:8fa2af7c1459204b7a42e98263b069bd535ea0cd978b4d6982f35af5a04a4241", size = 39753, upload-time = "2025-08-20T11:56:01.345Z" }, { url = "https://files.pythonhosted.org/packages/5b/d8/1baee0f4179a4d0f5ce086832147b6cc9b7731c24ca08e14a3fdb8d39c32/ujson-5.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:34032aeca4510a7c7102bd5933f59a37f63891f30a0706fb46487ab6f0edf8f0", size = 43866, upload-time = "2025-08-20T11:56:02.552Z" }, { url = "https://files.pythonhosted.org/packages/a9/8c/6d85ef5be82c6d66adced3ec5ef23353ed710a11f70b0b6a836878396334/ujson-5.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:ce076f2df2e1aa62b685086fbad67f2b1d3048369664b4cdccc50707325401f9", size = 38363, upload-time = "2025-08-20T11:56:03.688Z" }, - { url = "https://files.pythonhosted.org/packages/28/08/4518146f4984d112764b1dfa6fb7bad691c44a401adadaa5e23ccd930053/ujson-5.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65724738c73645db88f70ba1f2e6fb678f913281804d5da2fd02c8c5839af302", size = 55462, upload-time = "2025-08-20T11:56:04.873Z" }, - { url = "https://files.pythonhosted.org/packages/29/37/2107b9a62168867a692654d8766b81bd2fd1e1ba13e2ec90555861e02b0c/ujson-5.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29113c003ca33ab71b1b480bde952fbab2a0b6b03a4ee4c3d71687cdcbd1a29d", size = 53246, upload-time = "2025-08-20T11:56:06.054Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f8/25583c70f83788edbe3ca62ce6c1b79eff465d78dec5eb2b2b56b3e98b33/ujson-5.11.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c44c703842024d796b4c78542a6fcd5c3cb948b9fc2a73ee65b9c86a22ee3638", size = 57631, upload-time = "2025-08-20T11:56:07.374Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ca/19b3a632933a09d696f10dc1b0dfa1d692e65ad507d12340116ce4f67967/ujson-5.11.0-cp314-cp314-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:e750c436fb90edf85585f5c62a35b35082502383840962c6983403d1bd96a02c", size = 59877, upload-time = "2025-08-20T11:56:08.534Z" }, - { url = "https://files.pythonhosted.org/packages/55/7a/4572af5324ad4b2bfdd2321e898a527050290147b4ea337a79a0e4e87ec7/ujson-5.11.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f278b31a7c52eb0947b2db55a5133fbc46b6f0ef49972cd1a80843b72e135aba", size = 57363, upload-time = "2025-08-20T11:56:09.758Z" }, - { url = "https://files.pythonhosted.org/packages/7b/71/a2b8c19cf4e1efe53cf439cdf7198ac60ae15471d2f1040b490c1f0f831f/ujson-5.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ab2cb8351d976e788669c8281465d44d4e94413718af497b4e7342d7b2f78018", size = 1036394, upload-time = "2025-08-20T11:56:11.168Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3e/7b98668cba3bb3735929c31b999b374ebc02c19dfa98dfebaeeb5c8597ca/ujson-5.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:090b4d11b380ae25453100b722d0609d5051ffe98f80ec52853ccf8249dfd840", size = 1195837, upload-time = "2025-08-20T11:56:12.6Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ea/8870f208c20b43571a5c409ebb2fe9b9dba5f494e9e60f9314ac01ea8f78/ujson-5.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:80017e870d882d5517d28995b62e4e518a894f932f1e242cbc802a2fd64d365c", size = 1088837, upload-time = "2025-08-20T11:56:14.15Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/c0e6607e37fa47929920a685a968c6b990a802dec65e9c5181e97845985d/ujson-5.11.0-cp314-cp314-win32.whl", hash = "sha256:1d663b96eb34c93392e9caae19c099ec4133ba21654b081956613327f0e973ac", size = 41022, upload-time = "2025-08-20T11:56:15.509Z" }, - { url = "https://files.pythonhosted.org/packages/4e/56/f4fe86b4c9000affd63e9219e59b222dc48b01c534533093e798bf617a7e/ujson-5.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:849e65b696f0d242833f1df4182096cedc50d414215d1371fca85c541fbff629", size = 45111, upload-time = "2025-08-20T11:56:16.597Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f3/669437f0280308db4783b12a6d88c00730b394327d8334cc7a32ef218e64/ujson-5.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:e73df8648c9470af2b6a6bf5250d4744ad2cf3d774dcf8c6e31f018bdd04d764", size = 39682, upload-time = "2025-08-20T11:56:17.763Z" }, - { url = "https://files.pythonhosted.org/packages/6e/cd/e9809b064a89fe5c4184649adeb13c1b98652db3f8518980b04227358574/ujson-5.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:de6e88f62796372fba1de973c11138f197d3e0e1d80bcb2b8aae1e826096d433", size = 55759, upload-time = "2025-08-20T11:56:18.882Z" }, - { url = "https://files.pythonhosted.org/packages/1b/be/ae26a6321179ebbb3a2e2685b9007c71bcda41ad7a77bbbe164005e956fc/ujson-5.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e56ef8066f11b80d620985ae36869a3ff7e4b74c3b6129182ec5d1df0255f3", size = 53634, upload-time = "2025-08-20T11:56:20.012Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/fb4a220ee6939db099f4cfeeae796ecb91e7584ad4d445d4ca7f994a9135/ujson-5.11.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a325fd2c3a056cf6c8e023f74a0c478dd282a93141356ae7f16d5309f5ff823", size = 58547, upload-time = "2025-08-20T11:56:21.175Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f8/fc4b952b8f5fea09ea3397a0bd0ad019e474b204cabcb947cead5d4d1ffc/ujson-5.11.0-cp314-cp314t-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:a0af6574fc1d9d53f4ff371f58c96673e6d988ed2b5bf666a6143c782fa007e9", size = 60489, upload-time = "2025-08-20T11:56:22.342Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e5/af5491dfda4f8b77e24cf3da68ee0d1552f99a13e5c622f4cef1380925c3/ujson-5.11.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10f29e71ecf4ecd93a6610bd8efa8e7b6467454a363c3d6416db65de883eb076", size = 58035, upload-time = "2025-08-20T11:56:23.92Z" }, - { url = "https://files.pythonhosted.org/packages/c4/09/0945349dd41f25cc8c38d78ace49f14c5052c5bbb7257d2f466fa7bdb533/ujson-5.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a0a9b76a89827a592656fe12e000cf4f12da9692f51a841a4a07aa4c7ecc41c", size = 1037212, upload-time = "2025-08-20T11:56:25.274Z" }, - { url = "https://files.pythonhosted.org/packages/49/44/8e04496acb3d5a1cbee3a54828d9652f67a37523efa3d3b18a347339680a/ujson-5.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b16930f6a0753cdc7d637b33b4e8f10d5e351e1fb83872ba6375f1e87be39746", size = 1196500, upload-time = "2025-08-20T11:56:27.517Z" }, - { url = "https://files.pythonhosted.org/packages/64/ae/4bc825860d679a0f208a19af2f39206dfd804ace2403330fdc3170334a2f/ujson-5.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:04c41afc195fd477a59db3a84d5b83a871bd648ef371cf8c6f43072d89144eef", size = 1089487, upload-time = "2025-08-20T11:56:29.07Z" }, - { url = "https://files.pythonhosted.org/packages/30/ed/5a057199fb0a5deabe0957073a1c1c1c02a3e99476cd03daee98ea21fa57/ujson-5.11.0-cp314-cp314t-win32.whl", hash = "sha256:aa6d7a5e09217ff93234e050e3e380da62b084e26b9f2e277d2606406a2fc2e5", size = 41859, upload-time = "2025-08-20T11:56:30.495Z" }, - { url = "https://files.pythonhosted.org/packages/aa/03/b19c6176bdf1dc13ed84b886e99677a52764861b6cc023d5e7b6ebda249d/ujson-5.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:48055e1061c1bb1f79e75b4ac39e821f3f35a9b82de17fce92c3140149009bec", size = 46183, upload-time = "2025-08-20T11:56:31.574Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ca/a0413a3874b2dc1708b8796ca895bf363292f9c70b2e8ca482b7dbc0259d/ujson-5.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1194b943e951092db611011cb8dbdb6cf94a3b816ed07906e14d3bc6ce0e90ab", size = 40264, upload-time = "2025-08-20T11:56:32.773Z" }, { url = "https://files.pythonhosted.org/packages/39/bf/c6f59cdf74ce70bd937b97c31c42fd04a5ed1a9222d0197e77e4bd899841/ujson-5.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65f3c279f4ed4bf9131b11972040200c66ae040368abdbb21596bf1564899694", size = 55283, upload-time = "2025-08-20T11:56:33.947Z" }, { url = "https://files.pythonhosted.org/packages/8d/c1/a52d55638c0c644b8a63059f95ad5ffcb4ad8f60d8bc3e8680f78e77cc75/ujson-5.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99c49400572cd77050894e16864a335225191fd72a818ea6423ae1a06467beac", size = 53168, upload-time = "2025-08-20T11:56:35.141Z" }, { url = "https://files.pythonhosted.org/packages/75/6c/e64e19a01d59c8187d01ffc752ee3792a09f5edaaac2a0402de004459dd7/ujson-5.11.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0654a2691fc252c3c525e3d034bb27b8a7546c9d3eb33cd29ce6c9feda361a6a", size = 57809, upload-time = "2025-08-20T11:56:36.293Z" }, @@ -987,26 +954,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, { url = "https://files.pythonhosted.org/packages/47/8a/a45db804b9f0740f8408626ab2bca89c3136432e57c4673b50180bf85dd9/watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa", size = 406400, upload-time = "2025-06-15T19:06:30.233Z" }, { url = "https://files.pythonhosted.org/packages/64/06/a08684f628fb41addd451845aceedc2407dc3d843b4b060a7c4350ddee0c/watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433", size = 397920, upload-time = "2025-06-15T19:06:31.315Z" }, { url = "https://files.pythonhosted.org/packages/79/e6/e10d5675af653b1b07d4156906858041149ca222edaf8995877f2605ba9e/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4", size = 451196, upload-time = "2025-06-15T19:06:32.435Z" }, From 4deb0e40bbc75d562829d69303a4b93c66ed373b Mon Sep 17 00:00:00 2001 From: "Simon Rosenberger (Bumm)" <41942954+codingcyclist@users.noreply.github.com> Date: Thu, 14 May 2026 09:14:27 +0200 Subject: [PATCH 06/10] Align api cli, and mcp (#278) * feat: add environment support to apps list/show CLI and MCP tools The Tower API scopes apps to environments but the CLI and MCP tools were not fully exposing this. This commit: - Adds --environment/-e flag to `tower apps list` and `tower apps show` (defaulting to "default") - Adds environment parameter to MCP tools: tower_deploy, tower_apps_list, tower_apps_show, and tower_run_remote - Adds new MCP tools: tower_catalogs_list and tower_catalogs_show - Includes CLI arg parsing tests for the new flags Co-Authored-By: Claude Opus 4.6 * fix: default apps list to no environment filter The API returns all apps when environment is omitted, and only filters when explicitly provided. Updated list_apps to take Option<&str> so the default behavior shows all apps across environments. The environment param is still available for explicit filtering. Also added version field to MCP apps list output. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- crates/tower-cmd/src/api.rs | 8 ++- crates/tower-cmd/src/apps.rs | 83 ++++++++++++++++++++++-- crates/tower-cmd/src/lib.rs | 2 +- crates/tower-cmd/src/mcp.rs | 118 ++++++++++++++++++++++++++++++++--- 4 files changed, 193 insertions(+), 18 deletions(-) diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index 4daeab17..a09ce26f 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -92,6 +92,7 @@ where pub async fn describe_app( config: &Config, name: &str, + environment: Option<&str>, ) -> Result< tower_api::models::DescribeAppResponse, Error, @@ -104,7 +105,7 @@ pub async fn describe_app( start_at: None, end_at: None, timezone: None, - environment: None, + environment: environment.map(|s| s.to_string()), }; unwrap_api_response(tower_api::apis::default_api::describe_app( @@ -115,12 +116,15 @@ pub async fn describe_app( pub async fn list_apps( config: &Config, + environment: Option<&str>, ) -> Result, Error> { let api_config: configuration::Configuration = config.into(); + let environment = environment.map(|s| s.to_string()); fetch_all_pages(|page, page_size| { let api_config = &api_config; + let environment = &environment; async move { let params = tower_api::apis::default_api::ListAppsParams { query: None, @@ -129,7 +133,7 @@ pub async fn list_apps( num_runs: Some(0), sort: None, filter: None, - environment: None, + environment: environment.clone(), }; unwrap_api_response(tower_api::apis::default_api::list_apps(api_config, params)).await } diff --git a/crates/tower-cmd/src/apps.rs b/crates/tower-cmd/src/apps.rs index 3a779426..e04c3dc9 100644 --- a/crates/tower-cmd/src/apps.rs +++ b/crates/tower-cmd/src/apps.rs @@ -5,13 +5,24 @@ use tokio::time::{sleep, Duration, Instant}; use tower_api::models::{Run, RunLogLine}; -use crate::{api, output}; +use crate::{api, output, util::cmd}; pub fn apps_cmd() -> Command { Command::new("apps") .about("Manage the apps in your current Tower account") .arg_required_else_help(true) - .subcommand(Command::new("list").about("List all apps in your Tower account")) + .subcommand( + Command::new("list") + .arg( + Arg::new("environment") + .short('e') + .long("environment") + .value_parser(value_parser!(String)) + .help("Filter apps by environment") + .action(clap::ArgAction::Set), + ) + .about("List all apps in your Tower account"), + ) .subcommand( Command::new("show") .arg( @@ -21,6 +32,15 @@ pub fn apps_cmd() -> Command { .required(true) .help("Name of the app"), ) + .arg( + Arg::new("environment") + .short('e') + .long("environment") + .default_value("default") + .value_parser(value_parser!(String)) + .help("The environment to resolve the app against") + .action(clap::ArgAction::Set), + ) .about("Show details for a Tower app and its recent runs"), ) .subcommand( @@ -130,8 +150,9 @@ pub async fn do_show(config: Config, cmd: &ArgMatches) { let name = cmd .get_one::("app_name") .expect("app_name is required"); + let env = cmd::get_string_flag(cmd, "environment"); - match api::describe_app(&config, &name).await { + match api::describe_app(&config, &name, Some(&env)).await { Ok(app_response) => { if output::get_output_mode().is_json() { output::json(&app_response); @@ -209,8 +230,9 @@ pub async fn do_show(config: Config, cmd: &ArgMatches) { } } -pub async fn do_list_apps(config: Config) { - let apps = output::with_spinner("Listing apps", api::list_apps(&config)).await; +pub async fn do_list_apps(config: Config, args: &ArgMatches) { + let env = args.get_one::("environment").map(|s| s.as_str()); + let apps = output::with_spinner("Listing apps", api::list_apps(&config, env)).await; let items = apps .iter() @@ -269,7 +291,7 @@ 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 { + match api::describe_app(config, name, None).await { Ok(resp) => resp .runs .iter() @@ -808,4 +830,53 @@ mod tests { let result = apps_cmd().try_get_matches_from(["apps", "cancel"]); assert!(result.is_err()); } + + #[test] + fn list_defaults_to_no_environment_filter() { + let matches = apps_cmd() + .try_get_matches_from(["apps", "list"]) + .unwrap(); + let (_, list_args) = matches.subcommand().unwrap(); + + assert_eq!(list_args.get_one::("environment"), None); + } + + #[test] + fn list_accepts_environment_flag() { + let matches = apps_cmd() + .try_get_matches_from(["apps", "list", "-e", "production"]) + .unwrap(); + let (_, list_args) = matches.subcommand().unwrap(); + + assert_eq!( + list_args.get_one::("environment").map(|s| s.as_str()), + Some("production") + ); + } + + #[test] + fn show_defaults_to_default_environment() { + let matches = apps_cmd() + .try_get_matches_from(["apps", "show", "my-app"]) + .unwrap(); + let (_, show_args) = matches.subcommand().unwrap(); + + assert_eq!( + show_args.get_one::("environment").unwrap(), + "default" + ); + } + + #[test] + fn show_accepts_environment_flag() { + let matches = apps_cmd() + .try_get_matches_from(["apps", "show", "my-app", "-e", "production"]) + .unwrap(); + let (_, show_args) = matches.subcommand().unwrap(); + + assert_eq!( + show_args.get_one::("environment").unwrap(), + "production" + ); + } } diff --git a/crates/tower-cmd/src/lib.rs b/crates/tower-cmd/src/lib.rs index e42789b7..5873aac8 100644 --- a/crates/tower-cmd/src/lib.rs +++ b/crates/tower-cmd/src/lib.rs @@ -123,7 +123,7 @@ impl App { let apps_command = sub_matches.subcommand(); match apps_command { - Some(("list", _)) => apps::do_list_apps(sessionized_config).await, + Some(("list", args)) => apps::do_list_apps(sessionized_config, args).await, Some(("create", args)) => apps::do_create(sessionized_config, args).await, Some(("show", args)) => apps::do_show(sessionized_config, args).await, Some(("logs", args)) => apps::do_logs(sessionized_config, args).await, diff --git a/crates/tower-cmd/src/mcp.rs b/crates/tower-cmd/src/mcp.rs index 59cb2296..aeedb285 100644 --- a/crates/tower-cmd/src/mcp.rs +++ b/crates/tower-cmd/src/mcp.rs @@ -159,6 +159,44 @@ struct RunRequest { #[serde(flatten)] common: CommonParams, parameters: Option>, + /// The environment to run the app in (defaults to "default") + environment: Option, +} + +#[derive(Debug, Deserialize, JsonSchema)] +struct DeployRequest { + #[serde(flatten)] + common: CommonParams, + /// The environment to deploy to (defaults to "default") + environment: Option, +} + +#[derive(Debug, Deserialize, JsonSchema)] +struct ListAppsRequest { + /// Filter apps by environment. If not provided, apps across all environments are returned. + environment: Option, +} + +#[derive(Debug, Deserialize, JsonSchema)] +struct ShowAppRequest { + /// Name of the app + name: String, + /// The environment to resolve the app against (defaults to "default") + environment: Option, +} + +#[derive(Debug, Deserialize, JsonSchema)] +struct ListCatalogsRequest { + /// The environment to list catalogs from (defaults to "default") + environment: Option, +} + +#[derive(Debug, Deserialize, JsonSchema)] +struct ShowCatalogRequest { + /// Name of the catalog + name: String, + /// The environment the catalog belongs to (defaults to "default") + environment: Option, } pub fn mcp_cmd() -> Command { @@ -464,8 +502,12 @@ impl TowerService { // share constants directly. MCP-only descriptions (with Prerequisites/Optional) are // intentionally more detailed and don't need a CLI counterpart. #[tool(description = "List all apps in your Tower account")] - async fn tower_apps_list(&self) -> Result { - match api::list_apps(&self.config).await { + async fn tower_apps_list( + &self, + Parameters(request): Parameters, + ) -> Result { + let environment = request.environment.as_deref(); + match api::list_apps(&self.config, environment).await { Ok(apps) => { let apps: Vec = apps .into_iter() @@ -474,6 +516,7 @@ impl TowerService { json!({ "name": app.name, "description": app.short_description, + "version": app.version, "created_at": app.created_at, "status": format!("{:?}", app.status) }) @@ -499,9 +542,10 @@ impl TowerService { #[tool(description = "Show details for a Tower app and its recent runs")] async fn tower_apps_show( &self, - Parameters(request): Parameters, + Parameters(request): Parameters, ) -> Result { - match api::describe_app(&self.config, &request.name).await { + let environment = request.environment.as_deref().unwrap_or("default"); + match api::describe_app(&self.config, &request.name, Some(environment)).await { Ok(response) => { let data = json!({ "app": { @@ -657,6 +701,61 @@ impl TowerService { } } + #[tool(description = "List catalogs in your Tower account")] + async fn tower_catalogs_list( + &self, + Parameters(request): Parameters, + ) -> Result { + let environment = request.environment.as_deref().unwrap_or("default"); + match api::list_catalogs(&self.config, environment, false).await { + Ok(catalogs) => { + let catalogs: Vec = catalogs + .into_iter() + .map(|catalog| { + json!({ + "name": catalog.name, + "type": catalog.r#type, + "environment": catalog.environment, + }) + }) + .collect(); + Self::json_success(json!({"catalogs": catalogs})) + } + Err(e) => Self::error_result("Failed to list catalogs", e), + } + } + + #[tool(description = "Show details for a catalog, including its property names")] + async fn tower_catalogs_show( + &self, + Parameters(request): Parameters, + ) -> Result { + let environment = request.environment.as_deref().unwrap_or("default"); + match api::describe_catalog(&self.config, &request.name, environment).await { + Ok(response) => { + let catalog = &response.catalog; + let properties: Vec = catalog + .properties + .iter() + .map(|prop| { + json!({ + "name": prop.name, + "environment_variable": prop.environment_variable, + "preview": prop.preview, + }) + }) + .collect(); + Self::json_success(json!({ + "name": catalog.name, + "type": catalog.r#type, + "environment": catalog.environment, + "properties": properties, + })) + } + Err(e) => Self::error_result("Failed to show catalog", e), + } + } + #[tool(description = "List teams you belong to")] async fn tower_teams_list(&self) -> Result { if self.config.api_key.is_some() { @@ -703,14 +802,15 @@ impl TowerService { } #[tool( - description = "Deploy to Tower cloud. Prerequisites: Towerfile, tower_apps_create. Optional: working_directory." + description = "Deploy to Tower cloud. Prerequisites: Towerfile, tower_apps_create. Optional: working_directory, environment." )] async fn tower_deploy( &self, - Parameters(request): Parameters, + Parameters(request): Parameters, ) -> Result { let working_dir = Self::resolve_working_directory(&request.common); - let deploy_target = deploy::DeployTarget::Environment("default".to_string()); + let env = request.environment.unwrap_or_else(|| "default".to_string()); + let deploy_target = deploy::DeployTarget::Environment(env); match deploy::deploy_from_dir(self.config.clone(), working_dir, true, deploy_target).await { Ok(_) => Self::text_success("Deploy completed successfully".to_string()), @@ -770,7 +870,7 @@ impl TowerService { let config = self.config.clone(); let working_dir = Self::resolve_working_directory(&request.common); let path = working_dir; - let env = "default"; + let env = request.environment.unwrap_or_else(|| "default".to_string()); let params = request.parameters.unwrap_or_default(); // Load Towerfile to get app name @@ -782,7 +882,7 @@ impl TowerService { let app_name = towerfile.app.name.clone(); let (result, output) = Self::execute_with_streaming(&ctx, || { - run::do_run_remote(config, path, env, params, None, true) + run::do_run_remote(config, path, &env, params, None, true) }) .await; match result { From ff0588c9db68c594fb4e6da74469c45b49860ca1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 08:15:55 +0100 Subject: [PATCH 07/10] chore: regenerate SKILL.md (#280) Co-authored-by: codingcyclist <41942954+codingcyclist@users.noreply.github.com> --- skills/tower/SKILL.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/skills/tower/SKILL.md b/skills/tower/SKILL.md index 5a3a0176..13ca2c05 100644 --- a/skills/tower/SKILL.md +++ b/skills/tower/SKILL.md @@ -151,6 +151,10 @@ Manage the apps in your current Tower account List all apps in your Tower account +**Arguments:** + +- `-e`, `--environment` — Filter apps by environment + #### `tower apps show` Show details for a Tower app and its recent runs @@ -158,6 +162,7 @@ Show details for a Tower app and its recent runs **Arguments:** - `` *(required)* — Name of the app +- `-e`, `--environment` — The environment to resolve the app against #### `tower apps logs` From df1a41bde4d49725d2e262f98de85d6a8cdb514a Mon Sep 17 00:00:00 2001 From: Ben Lovell Date: Thu, 14 May 2026 12:29:54 +0200 Subject: [PATCH 08/10] fix: drop ring from TLS path; bump aarch64 wheel base to manylinux_2_28 (#277) * ci: bump aarch64 wheel base from manylinux2014 to manylinux_2_28 The aarch64 linux-cross job built wheels in the CentOS 7 / GCC 4.8 toolchain that ships with manylinux2014. Switching to manylinux_2_28 (AlmaLinux 8, GCC 8+) gives the build a newer assembler, which avoids a class of miscompilations in crates that ship hand-written aarch64 asm. * fix: switch rustls CryptoProvider from ring to aws-lc-rs reqwest's rustls-tls feature implicitly enables rustls/ring, which pulls ring v0.17 into the TLS path. ring's aarch64 build produces TLS signature-verify failures on some glibc builds of our wheels. Swap every reqwest TLS feature to the *-no-provider variant and install aws-lc-rs as the process-wide default CryptoProvider at CLI startup. The install is also called at the tower-uv reqwest entry point so its standalone unit test (which bypasses App::new) has a provider installed before the first TLS handshake. After this change, `cargo tree -i ring` is empty. * fix: lock in tower-api template + drop dead aarch64 CFLAGS scripts/rust-client-templates/Cargo.mustache hardcodes `rustls-tls` in the generated reqwest dep. Patch the template so the next generate-rust-api-client.sh run preserves the aws-lc-rs feature choice instead of reverting tower-api's Cargo.toml. The CFLAGS_aarch64_unknown_linux_gnu workaround in build-binaries.yml existed only to coax ring's build script into detecting ARMv8. With ring gone from the dep tree, the workaround is dead. --- .github/workflows/build-binaries.yml | 7 +- Cargo.lock | 148 ++++++++----------- Cargo.toml | 3 +- crates/tower-api/Cargo.toml | 2 +- crates/tower-cmd/Cargo.toml | 1 + crates/tower-cmd/src/lib.rs | 2 + crates/tower-uv/Cargo.toml | 1 + crates/tower-uv/src/install.rs | 2 + scripts/rust-client-templates/Cargo.mustache | 2 +- 9 files changed, 70 insertions(+), 98 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index decb1e2f..7c0ae1b0 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -268,16 +268,11 @@ jobs: uses: PyO3/maturin-action@v1.50.1 with: target: ${{ matrix.platform.target }} - manylinux: auto + manylinux: 2_28 docker-options: ${{ matrix.platform.maturin_docker_options }} args: --release --locked --out dist -i python3.10 python3.11 python3.12 python3.13 python3.14 env: CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse - # Set the CFLAGS for the aarch64 target, defining the ARM architecture - # for the ring crate's build script. This is a workaround for the - # issue where the ring crate's build script is not able to detect the - # ARM architecture. - CFLAGS_aarch64_unknown_linux_gnu: "-march=armv8-a -D__ARM_ARCH=8" - uses: uraimo/run-on-arch-action@v2 if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}} name: Test wheel diff --git a/Cargo.lock b/Cargo.lock index 13215edd..cf4ffe33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.4" @@ -342,6 +364,8 @@ version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -463,6 +487,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -898,6 +931,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.19" @@ -1023,6 +1062,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -1148,10 +1193,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1161,11 +1204,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", ] [[package]] @@ -1656,6 +1697,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1761,12 +1812,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "maplit" version = "1.0.2" @@ -2297,61 +2342,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "quinn" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.5.10", - "thiserror 2.0.12", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" -dependencies = [ - "bytes", - "getrandom 0.3.3", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.5.10", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.40" @@ -2556,7 +2546,6 @@ dependencies = [ "mime_guess", "percent-encoding", "pin-project-lite", - "quinn", "rustls", "rustls-pki-types", "serde", @@ -2698,12 +2687,6 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustix" version = "0.38.44" @@ -2736,8 +2719,8 @@ version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ + "aws-lc-rs", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -2750,7 +2733,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "web-time", "zeroize", ] @@ -2760,6 +2742,7 @@ version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3455,21 +3438,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tmpdir" version = "1.0.0" @@ -3680,6 +3648,7 @@ dependencies = [ "rmcp", "rpassword", "rsa", + "rustls", "schemars 1.0.4", "serde", "serde_json", @@ -3794,6 +3763,7 @@ dependencies = [ "hex", "regex", "reqwest", + "rustls", "seahash", "tempfile", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 8045433c..cddd184b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,8 +43,9 @@ pyo3 = { version = "0.28", features = ["extension-module"] } promptly = "0.3" rand = "0.8" regex = "1" -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-webpki-roots-no-provider", "stream"] } reqwest-eventsource = { version = "0.6" } +rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs", "std", "tls12"] } rpassword = "7" rsa = "0.9" seahash = "4.1" diff --git a/crates/tower-api/Cargo.toml b/crates/tower-api/Cargo.toml index 9e3f300c..fc43f0a0 100644 --- a/crates/tower-api/Cargo.toml +++ b/crates/tower-api/Cargo.toml @@ -12,4 +12,4 @@ serde_with = { version = "^3.8", default-features = false, features = ["base64", serde_json = "^1.0" serde_repr = "^0.1" url = "^2.5" -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "rustls-tls"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "rustls-tls-webpki-roots-no-provider"] } diff --git a/crates/tower-cmd/Cargo.toml b/crates/tower-cmd/Cargo.toml index 701bf6e3..cb6a5f4f 100644 --- a/crates/tower-cmd/Cargo.toml +++ b/crates/tower-cmd/Cargo.toml @@ -21,6 +21,7 @@ reqwest = { workspace = true } reqwest-eventsource = { workspace = true } rpassword = { workspace = true } rsa = { workspace = true } +rustls = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } snafu = { workspace = true } diff --git a/crates/tower-cmd/src/lib.rs b/crates/tower-cmd/src/lib.rs index 5873aac8..bc9212f0 100644 --- a/crates/tower-cmd/src/lib.rs +++ b/crates/tower-cmd/src/lib.rs @@ -30,6 +30,8 @@ pub struct App { impl App { pub fn new() -> Self { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let cmd = root_cmd(); // When TOWER_API_KEY is set, skip session entirely — the API key is self-contained diff --git a/crates/tower-uv/Cargo.toml b/crates/tower-uv/Cargo.toml index bd6f1381..fc41a6f0 100644 --- a/crates/tower-uv/Cargo.toml +++ b/crates/tower-uv/Cargo.toml @@ -15,6 +15,7 @@ futures-lite = { workspace = true } hex = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } +rustls = { workspace = true } seahash = { workspace = true } tokio = { workspace = true } tokio-tar = { workspace = true } diff --git a/crates/tower-uv/src/install.rs b/crates/tower-uv/src/install.rs index 3f0c7147..e8a5820f 100644 --- a/crates/tower-uv/src/install.rs +++ b/crates/tower-uv/src/install.rs @@ -211,6 +211,8 @@ async fn download_uv_archive(path: &PathBuf, archive: String) -> Result Date: Thu, 14 May 2026 12:06:23 +0100 Subject: [PATCH 09/10] refactor(tower-runtime): structured AppCompletion / Status::Failed plumbing (#274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LocalApp::start` previously spawned `execute_local_app` with a `oneshot::Sender`; every `?` early-return in that task dropped the sender silently, and `LocalApp::status()` reported the resulting closed channel as `Error::WaiterClosed` — losing the actual cause (env join errors, `tower_uv::Error` variants, panics, etc.). Replace the channel payload with an internal `AppCompletion` enum and a new `Status::Failed(AppFailure)` shape that preserves the structured error. Highlights: - `AppCompletion` (internal) has `Exit(i32)`, `Cancelled`, `Failed(AppFailure)` variants. The spawn wrapper in `LocalApp::start` runs `inner_execute_local_app` inside `AssertUnwindSafe(...).catch_unwind()` and always sends an `AppCompletion` before exiting, so a closed waiter channel can no longer mask a real failure. - `Status::Failed(AppFailure)` replaces the old `Status::Failed { error_code, error_message }` strings. `AppFailure` has: * `Runtime(Error)` — structured runtime error * `Panic(String)` — `catch_unwind` payload * `Platform { error_code, error_message }` — opaque strings for callers that construct `Status::Failed` from outside `tower_runtime` (e.g. Kubernetes pod-status reconciliation). Stringification (`error_code` / `error_message`) is the consumer's responsibility, not the runtime's. - `Status::Cancelled` is a new terminal variant for explicit cancellation via the `CancellationToken`. Previously cancellation was represented as `Crashed { code: -1 }`, indistinguishable from an app that legitimately exits with code 255 / -1 or an IO error in `wait_for_process`. `inner_execute_local_app` now returns `Err(Error::Cancelled)` on every cancellation path (4 prologue checks + 3 post-`wait_for_process` checks that disambiguate cancellation from a child's genuine non-zero exit), and the spawn wrapper special-cases that into `AppCompletion::Cancelled`. - `Error` gains `Clone + PartialEq + Eq` (all variants are unit, so this is free) so `Status` can keep deriving `Clone + PartialEq`, which is required by status caching in `LocalApp::status()` and by existing integration tests. - `tower-cmd` (the only other in-crate consumer of `Status::Failed`) is updated for the new variant shape and prints a dedicated "cancelled" message for `Status::Cancelled`. - The integration test `test_abort_on_dependency_installation_failure` keeps asserting that `uv sync` non-zero exits surface as `Status::Crashed { code }` — `Status::Failed` is reserved for platform failures where the child never ran. `Error::WaiterClosed` now only fires when the spawn was aborted or the runtime was dropped — both real bugs worth surfacing as-is. --- Cargo.lock | 1 + crates/tower-cmd/src/run.rs | 21 ++- crates/tower-runtime/Cargo.toml | 1 + crates/tower-runtime/src/errors.rs | 2 +- crates/tower-runtime/src/lib.rs | 32 ++++- crates/tower-runtime/src/local.rs | 170 +++++++++++++++++------ crates/tower-runtime/tests/local_test.rs | 3 + 7 files changed, 181 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf4ffe33..d86231b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3726,6 +3726,7 @@ dependencies = [ "async-trait", "chrono", "config", + "futures", "nix 0.30.1", "snafu", "tmpdir", diff --git a/crates/tower-cmd/src/run.rs b/crates/tower-cmd/src/run.rs index 9ad5a189..be8050e6 100644 --- a/crates/tower-cmd/src/run.rs +++ b/crates/tower-cmd/src/run.rs @@ -251,13 +251,22 @@ where output::error(&format!("Your local run crashed with exit code: {}", code)); return Err(Error::AppCrashed); } - Status::Failed { - error_code, - error_message, - } => { + Status::Cancelled => { + output::error("Your local run was cancelled."); + return Err(Error::AppCrashed); + } + Status::Failed(failure) => { + let detail = match failure { + tower_runtime::AppFailure::Runtime(e) => format!("{:?}", e), + tower_runtime::AppFailure::Panic(msg) => format!("panic: {}", msg), + tower_runtime::AppFailure::Platform { + error_code, + error_message, + } => format!("{} ({})", error_message, error_code), + }; output::error(&format!( - "Your local run failed due to a platform error (code: {}, message: {})", - error_code, error_message + "Your local run failed due to a platform error: {}", + detail )); return Err(Error::AppCrashed); } diff --git a/crates/tower-runtime/Cargo.toml b/crates/tower-runtime/Cargo.toml index 37b47f62..e28fab94 100644 --- a/crates/tower-runtime/Cargo.toml +++ b/crates/tower-runtime/Cargo.toml @@ -9,6 +9,7 @@ license = { workspace = true } [dependencies] async-trait = { workspace = true } chrono = { workspace = true } +futures = { workspace = true } nix = { workspace = true } snafu = { workspace = true } tmpdir = { workspace = true } diff --git a/crates/tower-runtime/src/errors.rs b/crates/tower-runtime/src/errors.rs index 0326cd27..75a6b09c 100644 --- a/crates/tower-runtime/src/errors.rs +++ b/crates/tower-runtime/src/errors.rs @@ -1,6 +1,6 @@ use snafu::prelude::*; -#[derive(Debug, Snafu)] +#[derive(Clone, Debug, PartialEq, Eq, Snafu)] pub enum Error { #[snafu(display("failed to RPC server"))] RuntimeStartFailed, diff --git a/crates/tower-runtime/src/lib.rs b/crates/tower-runtime/src/lib.rs index 1be53ce8..446264fe 100644 --- a/crates/tower-runtime/src/lib.rs +++ b/crates/tower-runtime/src/lib.rs @@ -45,9 +45,34 @@ pub enum Status { Crashed { code: i32, }, + /// The app was explicitly terminated via the `CancellationToken` — a + /// deliberate stop, not a failure or a crash. Distinct from `Crashed` + /// because there's no meaningful exit code to report. + Cancelled, /// A platform-level failure (not the user's app). For example, pod scheduling /// failures, image pull errors, or other infrastructure issues. - Failed { + Failed(AppFailure), +} + +/// A platform-level failure — distinct from a child-process crash +/// (`Status::Crashed`) because the user's app never ran to completion. +/// Consumers (currently `tower-runtime-entrypoint` and the runner-side k8s +/// backend) translate this into the `error_code` / `error_message` strings +/// that flow through the termination-log → control-plane log pipeline. +#[derive(Clone, Debug, PartialEq)] +pub enum AppFailure { + /// A structured runtime error from `tower_runtime` (e.g. + /// `UnsupportedPlatform`, `SpawnFailed`, `DependencyInstallationFailed`). + Runtime(Error), + /// A panic inside the spawned task. `catch_unwind` only returns + /// `Box`, so we surface the best-effort textual payload. + Panic(String), + /// A platform failure surfaced from outside `tower_runtime` (e.g. the + /// kubelet refusing to pull an image, an init container failing, or a + /// parsed entrypoint termination message). The `error_code` is opaque + /// to `tower_runtime` — the consumer that constructed this variant owns + /// the vocabulary. + Platform { error_code: String, error_message: String, }, @@ -58,7 +83,10 @@ impl Status { pub fn is_terminal(&self) -> bool { matches!( self, - Status::Exited | Status::Crashed { .. } | Status::Failed { .. } + Status::Exited + | Status::Crashed { .. } + | Status::Cancelled + | Status::Failed(_) ) } } diff --git a/crates/tower-runtime/src/local.rs b/crates/tower-runtime/src/local.rs index e388fb04..07b368cf 100644 --- a/crates/tower-runtime/src/local.rs +++ b/crates/tower-runtime/src/local.rs @@ -6,7 +6,7 @@ use std::process::Stdio; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; -use crate::{errors::Error, OutputSender, StartOptions, Status}; +use crate::{errors::Error, AppFailure, OutputSender, StartOptions, Status}; use tokio::{ fs, @@ -35,18 +35,51 @@ use tower_uv::Uv; use crate::{App, Channel, Output, FD}; +/// Result of running an app to completion. The spawned execution task always +/// sends one of these on its waiter channel before exiting, so `status()` can +/// distinguish a real platform failure from a closed channel. +/// +/// `Failed(AppFailure)` preserves the structured `Error` (or panic payload) +/// so consumers can act on the variant directly — `error_code` / `error_message` +/// stringification is the consumer's responsibility. +#[derive(Clone, Debug)] +enum AppCompletion { + /// Child process exited (0 = clean, non-zero = crashed). + Exit(i32), + /// The cancellation token fired — explicit termination, not a failure. + /// Distinct from `Exit(non_zero)` so consumers can tell a deliberate stop + /// apart from a real crash. + Cancelled, + /// Platform-level failure inside the spawned task before/around the child + /// process (e.g. `tower_uv::Error`, env join failure, panic). + Failed(AppFailure), +} + +/// Best-effort extraction of a panic message from a `catch_unwind` payload. +fn panic_payload_message(payload: &Box) -> String { + if let Some(s) = payload.downcast_ref::<&'static str>() { + (*s).to_string() + } else if let Some(s) = payload.downcast_ref::() { + s.clone() + } else { + "execute_local_app panicked with non-string payload".to_string() + } +} + pub struct LocalApp { status: Mutex>, // waiter is what we use to communicate that the overall process is finished by the execution - // handle. - waiter: Mutex>, + // handle. The spawned task always sends an `AppCompletion` before it ends; if the channel + // ever closes without a send, that's a real bug (task aborted, runtime dropped) and surfaces + // as `Error::WaiterClosed`. + waiter: Mutex>, // terminator is what we use to flag that we want to terminate the child process. terminator: CancellationToken, // execute_handle keeps track of the current state of the execution lifecycle. - execute_handle: Option>>, + execute_handle: Option>, } // Helper function to check if a file is executable @@ -99,11 +132,18 @@ async fn find_bash() -> Result { } } -async fn execute_local_app( +/// Run the app to completion, returning the child's exit code on success or an +/// `Error` for any platform failure. Cancellation is reported as `Ok(-1)` to +/// preserve the historical `Status::Crashed { code: -1 }` semantic. +/// +/// The caller is responsible for delivering the result on the waiter channel — +/// see `LocalApp::start`, which wraps this in `catch_unwind` and an unconditional +/// `sx.send(AppCompletion::...)` so that no failure path can silently close the +/// channel. +async fn inner_execute_local_app( opts: StartOptions, - sx: oneshot::Sender, cancel_token: CancellationToken, -) -> Result<(), Error> { +) -> Result { let ctx = opts.ctx.clone(); let package = opts.package; let environment = opts.environment; @@ -157,9 +197,6 @@ async fn execute_local_app( // We do this before instantiating `Uv` because that can be somewhat time consuming. Likewise // this stops us from instantiating a bash process. if cancel_token.is_cancelled() { - // if there's a waiter, we want them to know that the process was cancelled so we have - // to return something on the relevant channel. - let _ = sx.send(-1); return Err(Error::Cancelled); } @@ -176,7 +213,14 @@ async fn execute_local_app( ) .await?; - let _ = sx.send(wait_for_process(ctx.clone(), &cancel_token, child).await); + let code = wait_for_process(ctx.clone(), &cancel_token, child).await; + // `wait_for_process` returns -1 on both cancellation and IO error; + // disambiguate so a cancellation surfaces as `Status::Cancelled` + // rather than `Status::Crashed { code: -1 }`. + if cancel_token.is_cancelled() { + return Err(Error::Cancelled); + } + Ok(code) } else { // we put Uv in to protected mode when there's no caching configured/enabled. let protected_mode = opts.cache_dir.is_none(); @@ -197,8 +241,6 @@ async fn execute_local_app( // Quickly do a check to see if there was a cancellation before we do a subprocess spawn to // ensure everything is in place. if cancel_token.is_cancelled() { - // again tell any waiters that we cancelled. - let _ = sx.send(-1); return Err(Error::Cancelled); } @@ -224,17 +266,21 @@ async fn execute_local_app( // Wait for venv to finish up. let res = wait_for_process(ctx.clone(), &cancel_token, child).await; + // Distinguish cancellation from a real uv-venv non-zero exit. + if cancel_token.is_cancelled() { + return Err(Error::Cancelled); + } + if res != 0 { - // If the venv process failed, we want to return an error. - let _ = sx.send(res); - return Err(Error::VirtualEnvCreationFailed); + // `uv venv` exited non-zero — surface as a crash with the child's + // exit code. `Status::Failed` is reserved for platform errors + // where the child never ran at all. + return Ok(res); } // Check once more if the process was cancelled before we do a uv sync. The sync itself, // once started, will take a while and we have logic for checking for cancellation. if cancel_token.is_cancelled() { - // again tell any waiters that we cancelled. - let _ = sx.send(-1); return Err(Error::Cancelled); } @@ -259,6 +305,13 @@ async fn execute_local_app( Ok(child) => { let mut res = run_setup_child(&ctx, &cancel_token, &opts.output_sender, child).await; + // If sync was cancelled, don't bother retrying — bail out + // cleanly so the receiver sees `Status::Cancelled` instead of + // `Status::Crashed { code: -1 }`. + if cancel_token.is_cancelled() { + return Err(Error::Cancelled); + } + // If the install failed, retry with the legacy setuptools<82 // pin. Some apps (those whose transitive deps rely on // pkg_resources) need that pin to install successfully; we don't @@ -276,21 +329,22 @@ async fn execute_local_app( .sync_with_legacy_setuptools_pin(&working_dir, &env_vars) .await?; res = run_setup_child(&ctx, &cancel_token, &opts.output_sender, retry_child).await; + if cancel_token.is_cancelled() { + return Err(Error::Cancelled); + } } if res != 0 { - // If the sync process failed, we want to return an error. - let _ = sx.send(res); - return Err(Error::DependencyInstallationFailed); + // `uv sync` exited non-zero — surface as a crash with the + // child's exit code. `Status::Failed` is reserved for + // platform errors where the child never ran at all. + return Ok(res); } } } // Check once more to see if the process was cancelled, this will bail us out early. if cancel_token.is_cancelled() { - // if there's a waiter, we want them to know that the process was cancelled so we have - // to return something on the relevant channel. - let _ = sx.send(-1); return Err(Error::Cancelled); } @@ -313,11 +367,12 @@ async fn execute_local_app( BufReader::new(stderr), )); - let _ = sx.send(wait_for_process(ctx.clone(), &cancel_token, child).await); + let code = wait_for_process(ctx.clone(), &cancel_token, child).await; + if cancel_token.is_cancelled() { + return Err(Error::Cancelled); + } + Ok(code) } - - // Everything was properly executed I suppose. - return Ok(()); } impl Drop for LocalApp { @@ -338,12 +393,36 @@ impl Drop for LocalApp { impl App for LocalApp { async fn start(opts: StartOptions) -> Result { + use futures::FutureExt; + use std::panic::AssertUnwindSafe; + let terminator = CancellationToken::new(); - let (sx, rx) = oneshot::channel::(); + let (sx, rx) = oneshot::channel::(); let waiter = Mutex::new(rx); - let handle = tokio::spawn(execute_local_app(opts, sx, terminator.clone())); + let task_terminator = terminator.clone(); + let handle = tokio::spawn(async move { + // `catch_unwind` turns a panic inside the spawned task into a normal + // `Err(payload)` so we can still report it on the waiter channel + // instead of leaving the receiver to observe a silently-closed + // channel (the historical `WaiterClosed` bug). + // + // `Err(Error::Cancelled)` is special-cased: cancellation is a + // deliberate stop, not a failure, so it surfaces as + // `AppCompletion::Cancelled` → `Status::Cancelled` rather than + // `AppCompletion::Failed(AppFailure::Runtime(Error::Cancelled))`. + let completion = match AssertUnwindSafe(inner_execute_local_app(opts, task_terminator)) + .catch_unwind() + .await + { + Ok(Ok(code)) => AppCompletion::Exit(code), + Ok(Err(Error::Cancelled)) => AppCompletion::Cancelled, + Ok(Err(e)) => AppCompletion::Failed(AppFailure::Runtime(e)), + Err(panic) => AppCompletion::Failed(AppFailure::Panic(panic_payload_message(&panic))), + }; + let _ = sx.send(completion); + }); let execute_handle = Some(handle); Ok(Self { @@ -377,17 +456,28 @@ impl App for LocalApp { match res { Err(TryRecvError::Empty) => Ok(Status::Running), + // The spawned task always sends an `AppCompletion` before + // exiting, so a closed channel here means the task was + // aborted or the runtime dropped — a real bug worth + // surfacing as-is. Err(TryRecvError::Closed) => Err(Error::WaiterClosed), - Ok(t) => { - // We save this for the next time this gets called. - if t == 0 { - *status = Some(Status::Exited); - Ok(Status::Exited) - } else { - let next_status = Status::Crashed { code: t }; - *status = Some(next_status.clone()); - Ok(next_status) - } + Ok(AppCompletion::Exit(0)) => { + *status = Some(Status::Exited); + Ok(Status::Exited) + } + Ok(AppCompletion::Exit(code)) => { + let next_status = Status::Crashed { code }; + *status = Some(next_status.clone()); + Ok(next_status) + } + Ok(AppCompletion::Cancelled) => { + *status = Some(Status::Cancelled); + Ok(Status::Cancelled) + } + Ok(AppCompletion::Failed(failure)) => { + let next_status = Status::Failed(failure); + *status = Some(next_status.clone()); + Ok(next_status) } } } diff --git a/crates/tower-runtime/tests/local_test.rs b/crates/tower-runtime/tests/local_test.rs index 5dda4d9d..b0ab679a 100644 --- a/crates/tower-runtime/tests/local_test.rs +++ b/crates/tower-runtime/tests/local_test.rs @@ -373,6 +373,9 @@ async fn test_abort_on_dependency_installation_failure() { Status::None => { panic!("App should have a status"); } + Status::Cancelled => { + panic!("App should have crashed, not been cancelled"); + } Status::Failed { .. } => { panic!("App should have crashed, not failed with a platform error"); } From 275b15604ab65d5ee9b481a76f83f9d640810fcc Mon Sep 17 00:00:00 2001 From: Ben Lovell Date: Thu, 14 May 2026 13:31:52 +0200 Subject: [PATCH 10/10] fix: align pagination with API semantics and make it configurable (#279) * fix: align pagination with API semantics and make it configurable The CLI pagination loop added in #275 was 0-indexed but the API treats page=0 (or null) as 'return everything in one response' and uses 1-indexed pages otherwise. The loop happened to work against real prod only because the first request asked for 'all' and immediately exited the loop. The mock server was 0-indexed and so diverged from the real API's semantics. Changes: - Add four hidden global flags for paginated list commands: --page-size, --page, --no-paginate, --max-items. Modeled on the AWS CLI's pagination knobs but matched to our page-number API (--starting-token would be cursor-shaped, which our API does not use). - Thread page_size / paginate / page / max_items through Config. - Fix fetch_all_pages: page=0 means 'all in one response and stop'; page>=1 iterates 1..=num_pages. - Align the mock /v1/apps/etc. pagination to the same semantics so the mock and prod behave the same way. - Rewrite cli_pagination.feature to create apps via the real CLI and delete them in after_scenario. With --page 1 --page-size 2 against three apps, the test forces a real two-page traversal against any backend. - Drop the mock-only /test/seed-apps and /test/reset endpoints that the old pagination feature relied on. * chore: Reformat the things * chore: Clean up old, broken rust that comes with Homebrew * chore: Go the other way with it--clobber the Homebrew rustup * chore: One more attempt to fix this broken rust toolchain in macos15 images * chore: Another attempt at resolving the issue with the upstream --------- Co-authored-by: Brad Heller --- .github/workflows/build-binaries.yml | 38 +++++++ crates/config/src/lib.rs | 51 +++++++++ crates/tower-cmd/src/api.rs | 44 ++++++-- crates/tower-cmd/src/lib.rs | 31 +++++ .../features/cli_pagination.feature | 24 ++-- tests/integration/features/environment.py | 18 +++ tests/integration/features/steps/cli_steps.py | 77 ++++++++----- tests/mock-api-server/main.py | 106 +++--------------- 8 files changed, 248 insertions(+), 141 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 7c0ae1b0..f71952b1 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -74,7 +74,26 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive + - name: "Install fresh upstream rustup" + # The macos-15-arm64 runner image (20260511.0048+) ships a broken + # Homebrew rustup whose bundled rustup-init rejects standard argv + # forwarded from the rustup proxy, breaking `rustup component add` + # and other commands maturin-action invokes. Upstream: + # https://github.com/actions/runner-images/issues/14097 + # Scrub Homebrew's rustup AND any stale upstream rustup, then + # reinstall cleanly with 1.88 as the default so setup-rust-toolchain + # has nothing left to do but verify. + run: | + brew uninstall --ignore-dependencies rustup || true + rm -f "$HOME/.cargo/bin/rustup" "$HOME/.cargo/bin/rustup-init" + curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL https://sh.rustup.rs | sh -s -- --default-toolchain 1.88 --profile minimal -y + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 + with: + # Don't restore ~/.cargo/bin from cache — it'd clobber the + # freshly-installed upstream rustup with a stale (and possibly + # Homebrew-linked) binary from a previous run. + cache-bin: false - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -102,7 +121,26 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive + - name: "Install fresh upstream rustup" + # The macos-15-arm64 runner image (20260511.0048+) ships a broken + # Homebrew rustup whose bundled rustup-init rejects standard argv + # forwarded from the rustup proxy, breaking `rustup component add` + # and other commands maturin-action invokes. Upstream: + # https://github.com/actions/runner-images/issues/14097 + # Scrub Homebrew's rustup AND any stale upstream rustup, then + # reinstall cleanly with 1.88 as the default so setup-rust-toolchain + # has nothing left to do but verify. + run: | + brew uninstall --ignore-dependencies rustup || true + rm -f "$HOME/.cargo/bin/rustup" "$HOME/.cargo/bin/rustup-init" + curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL https://sh.rustup.rs | sh -s -- --default-toolchain 1.88 --profile minimal -y + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 + with: + # Don't restore ~/.cargo/bin from cache — it'd clobber the + # freshly-installed upstream rustup with a stale (and possibly + # Homebrew-linked) binary from a previous run. + cache-bin: false - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index cca17e88..2e4a48a2 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -26,8 +26,27 @@ pub struct Config { // cache_dir is the directory that we should cache uv artifacts within. pub cache_dir: Option, + + // Override for the page size used when fetching paginated list endpoints. + // None means use the built-in default. + #[serde(skip_serializing, skip_deserializing)] + pub page_size: Option, + + // When false, only the first page of paginated endpoints is fetched. + #[serde(skip_serializing, skip_deserializing)] + pub paginate: bool, + + // Hard upper bound on the number of items returned by a paginated list call. + #[serde(skip_serializing, skip_deserializing)] + pub max_items: Option, + + // Starting page (0-indexed) for paginated list calls. + #[serde(skip_serializing, skip_deserializing)] + pub page: Option, } +pub const DEFAULT_PAGE_SIZE: i64 = 100; + impl Config { pub fn default() -> Self { Self { @@ -37,9 +56,17 @@ impl Config { session: None, api_key: None, cache_dir: Some(default_cache_dir()), + page_size: None, + paginate: true, + max_items: None, + page: None, } } + pub fn page_size(&self) -> i64 { + self.page_size.unwrap_or(DEFAULT_PAGE_SIZE) + } + pub fn from_env() -> Self { let debug = std::env::var("TOWER_DEBUG").is_ok(); let tower_url = if let Some(url) = std::env::var("TOWER_URL").ok() { @@ -56,6 +83,10 @@ impl Config { session: None, api_key, cache_dir: Some(default_cache_dir()), + page_size: None, + paginate: true, + max_items: None, + page: None, } } @@ -74,6 +105,22 @@ impl Config { config.tower_url = Url::parse(tower_url).unwrap(); } + if let Some(page_size) = matches.get_one::("page_size") { + config.page_size = Some(*page_size); + } + + if matches.get_flag("no_paginate") { + config.paginate = false; + } + + if let Some(max_items) = matches.get_one::("max_items") { + config.max_items = Some(*max_items); + } + + if let Some(page) = matches.get_one::("page") { + config.page = Some(*page); + } + config } @@ -85,6 +132,10 @@ impl Config { session: Some(sess), api_key: self.api_key, cache_dir: Some(default_cache_dir()), + page_size: self.page_size, + paginate: self.paginate, + max_items: self.max_items, + page: self.page, } } diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index a09ce26f..07f0dca8 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -64,24 +64,46 @@ impl PaginatedResponse for tower_api::models::ListSchedulesResponse { fn into_items(self) -> Vec { self.schedules } } -/// Fetches all pages from a paginated API endpoint. -async fn fetch_all_pages(fetch: F) -> Result, Error> +/// Fetches pages from a paginated API endpoint, honoring the caller's +/// page_size / paginate / page / max_items settings on Config. +/// +/// Page numbers follow the API convention: pages are 1-indexed, and `page = 0` +/// (or unset on the server side) means "no pagination, return everything in one +/// response". The loop sends `page = 0` exactly once if the caller did not opt +/// into a starting page, otherwise it iterates 1..=num_pages. +async fn fetch_all_pages(config: &Config, fetch: F) -> Result, Error> where R: PaginatedResponse, F: Fn(i64, i64) -> Fut, Fut: Future>>, { - let page_size: i64 = 100; + let page_size = config.page_size(); let mut all_items = Vec::new(); - let mut page: i64 = 0; + let mut page: i64 = config.page.unwrap_or(0); loop { let response = fetch(page, page_size).await?; let num_pages = response.pagination().num_pages; all_items.extend(response.into_items()); + if let Some(max) = config.max_items { + if all_items.len() as i64 >= max { + all_items.truncate(max as usize); + break; + } + } + + // page == 0 means the server returned all items in a single response. + if page == 0 { + break; + } + + if !config.paginate { + break; + } + page += 1; - if page >= num_pages || page >= 100 { + if page > num_pages || page > 100 { break; } } @@ -122,7 +144,7 @@ pub async fn list_apps( let api_config: configuration::Configuration = config.into(); let environment = environment.map(|s| s.to_string()); - fetch_all_pages(|page, page_size| { + fetch_all_pages(config, |page, page_size| { let api_config = &api_config; let environment = &environment; async move { @@ -309,7 +331,7 @@ pub async fn list_catalogs( let api_config: configuration::Configuration = config.into(); let env = env.to_string(); - fetch_all_pages(|page, page_size| { + fetch_all_pages(config, |page, page_size| { let api_config = &api_config; let env = &env; async move { @@ -358,7 +380,7 @@ pub async fn list_secrets( let api_config: configuration::Configuration = config.into(); let env = env.to_string(); - fetch_all_pages(|page, page_size| { + fetch_all_pages(config, |page, page_size| { let api_config = &api_config; let env = &env; async move { @@ -497,7 +519,7 @@ pub async fn list_teams( ) -> Result, Error> { let api_config: configuration::Configuration = config.into(); - fetch_all_pages(|page, page_size| { + fetch_all_pages(config, |page, page_size| { let api_config = &api_config; async move { let params = tower_api::apis::default_api::ListTeamsParams { @@ -975,7 +997,7 @@ pub async fn list_environments( > { let api_config: configuration::Configuration = config.into(); - fetch_all_pages(|page, page_size| { + fetch_all_pages(config, |page, page_size| { let api_config = &api_config; async move { let params = tower_api::apis::default_api::ListEnvironmentsParams { @@ -1022,7 +1044,7 @@ pub async fn list_schedules( let api_config: configuration::Configuration = config.into(); let environment = environment.map(String::from); - fetch_all_pages(|page, page_size| { + fetch_all_pages(config, |page, page_size| { let api_config = &api_config; let environment = &environment; async move { diff --git a/crates/tower-cmd/src/lib.rs b/crates/tower-cmd/src/lib.rs index bc9212f0..3560d3c4 100644 --- a/crates/tower-cmd/src/lib.rs +++ b/crates/tower-cmd/src/lib.rs @@ -251,6 +251,37 @@ fn root_cmd() -> Command { .value_parser(value_parser!(String)) .action(clap::ArgAction::Set), ) + .arg( + Arg::new("page_size") + .long("page-size") + .hide(true) + .value_parser(value_parser!(i64)) + .action(clap::ArgAction::Set) + .global(true), + ) + .arg( + Arg::new("no_paginate") + .long("no-paginate") + .help("Fetch only the first page of paginated results") + .action(clap::ArgAction::SetTrue) + .global(true), + ) + .arg( + Arg::new("max_items") + .long("max-items") + .help("Maximum number of items to return from paginated list commands") + .value_parser(value_parser!(i64)) + .action(clap::ArgAction::Set) + .global(true), + ) + .arg( + Arg::new("page") + .long("page") + .help("Page number to start at for paginated list commands; 0 (the default) requests all results without pagination") + .value_parser(value_parser!(i64)) + .action(clap::ArgAction::Set) + .global(true), + ) .subcommand_required(false) .arg_required_else_help(false) .subcommand(session::login_cmd()) diff --git a/tests/integration/features/cli_pagination.feature b/tests/integration/features/cli_pagination.feature index ada10629..f71262e8 100644 --- a/tests/integration/features/cli_pagination.feature +++ b/tests/integration/features/cli_pagination.feature @@ -1,16 +1,16 @@ -@serial -Feature: CLI Pagination +@serial @pagination +Feature: CLI Apps List Pagination As a developer using Tower CLI - I want all items to be returned from list commands - So that I don't miss apps or resources due to pagination limits + I want all of my apps to be returned from list commands + So that I don't miss apps due to pagination limits - Scenario: CLI apps list fetches all pages when results exceed page size - Given the mock API has 105 seeded apps - When I run "tower apps list" via CLI - Then the output should contain all 105 seeded app names + Scenario: CLI apps list returns every app across multiple pages + Given I have created 3 apps via CLI + When I run "tower --page 1 --page-size 2 apps list" via CLI + Then the output should contain all created app names - Scenario: CLI apps list in JSON mode returns all paginated results - Given the mock API has 105 seeded apps - When I run "tower apps list --json" via CLI + Scenario: CLI apps list in JSON mode returns every app across multiple pages + Given I have created 3 apps via CLI + When I run "tower --page 1 --page-size 2 apps list --json" via CLI Then the output should be valid JSON - And the JSON should contain at least 105 apps + And the JSON should contain all created app names diff --git a/tests/integration/features/environment.py b/tests/integration/features/environment.py index 62fbffbb..b6fb2d71 100644 --- a/tests/integration/features/environment.py +++ b/tests/integration/features/environment.py @@ -26,6 +26,24 @@ def before_scenario(context, scenario): def after_scenario(context, scenario): import shutil + # Delete any apps created by the scenario via "I have created N apps via CLI". + if getattr(context, "created_app_names", None): + env = os.environ.copy() + env["TOWER_URL"] = context.tower_url + test_home = Path(__file__).parent.parent / "test-home" + env["HOME"] = str(test_home.absolute()) + for name in context.created_app_names: + try: + subprocess.run( + [context.tower_binary, "apps", "delete", name], + env=env, + capture_output=True, + timeout=30, + ) + except subprocess.TimeoutExpired: + pass + context.created_app_names = [] + # Clean up any MCP servers started by this scenario for attr in ["http_mcp_process", "sse_mcp_process"]: if hasattr(context, attr): diff --git a/tests/integration/features/steps/cli_steps.py b/tests/integration/features/steps/cli_steps.py index f2f82934..c4a38eb0 100644 --- a/tests/integration/features/steps/cli_steps.py +++ b/tests/integration/features/steps/cli_steps.py @@ -438,41 +438,62 @@ def step_app_description_should_be(context, expected_description): # Pagination test steps -@step("the mock API has {count:d} seeded apps") -def step_seed_apps(context, count): - """Seed the mock API with a number of apps to test pagination.""" - # Reset first to get a clean slate - requests.post(f"{context.tower_url}/test/reset") - # Seed apps (no page_size override — uses default 20, so >20 apps forces pagination) - resp = requests.post( - f"{context.tower_url}/test/seed-apps", - json={"count": count}, - ) - assert resp.status_code == 200, f"Failed to seed apps: {resp.text}" - context.seeded_app_count = count +def _pagination_env(context): + env = os.environ.copy() + env["TOWER_URL"] = context.tower_url + test_home = Path(__file__).parent.parent.parent / "test-home" + env["HOME"] = str(test_home.absolute()) + return env -@step("the output should contain all {count:d} seeded app names") -def step_output_should_contain_all_seeded_apps(context, count): - """Verify the CLI output contains all seeded app names.""" - # Strip ANSI codes for checking - output = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", context.cli_output) - missing = [] +@step("I have created {count:d} apps via CLI") +def step_create_apps_via_cli(context, count): + """Create a number of apps via the real CLI so the list command has something to return.""" + env = _pagination_env(context) + context.created_app_names = [] for i in range(count): - name = f"paginated-app-{i:03d}" - if name not in output: - missing.append(name) + name = f"pagination-test-app-{i:03d}" + # Best-effort delete in case a prior run left this name behind. + subprocess.run( + [context.tower_binary, "apps", "delete", name], + env=env, + capture_output=True, + ) + result = subprocess.run( + [context.tower_binary, "apps", "create", "--name", name], + env=env, + capture_output=True, + text=True, + timeout=60, + ) + assert ( + result.returncode == 0 + ), f"Failed to create {name}: {result.stdout}\n{result.stderr}" + context.created_app_names.append(name) + + +@step("the output should contain all created app names") +def step_output_should_contain_all_created_apps(context): + output = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", context.cli_output) + missing = [name for name in context.created_app_names if name not in output] assert not missing, ( - f"Missing {len(missing)} apps from output (first 5): {missing[:5]}\n" + f"Missing {len(missing)} apps from output: {missing}\n" f"Full output: {output[:2000]}" ) -@step("the JSON should contain at least {count:d} apps") -def step_json_should_contain_at_least_n_apps(context, count): - """Verify JSON output contains at least the expected number of apps.""" +@step("the JSON should contain all created app names") +def step_json_should_contain_all_created_apps(context): data = parse_cli_json(context) assert isinstance(data, list), f"Expected JSON array, got: {type(data)}" - assert ( - len(data) >= count - ), f"Expected at least {count} apps in JSON, got {len(data)}" + listed = set() + for entry in data: + if not isinstance(entry, dict): + continue + # apps list returns AppSummary objects shaped as {"app": {"name": ...}, "runs": [...]}. + if "app" in entry and isinstance(entry["app"], dict): + listed.add(entry["app"].get("name")) + else: + listed.add(entry.get("name")) + missing = [name for name in context.created_app_names if name not in listed] + assert not missing, f"Missing {len(missing)} apps from JSON: {missing}" diff --git a/tests/mock-api-server/main.py b/tests/mock-api-server/main.py index 5e86b7c7..ba386f7a 100644 --- a/tests/mock-api-server/main.py +++ b/tests/mock-api-server/main.py @@ -48,7 +48,6 @@ async def log_requests(request: Request, call_next): mock_runs_db = {} mock_schedules_db = {} mock_deployed_apps = set() # Track which apps have been deployed -mock_max_page_size: Optional[int] = None # Override max page size for testing # Pre-populate with test-app for CLI validation/spinner tests mock_apps_db["predeployed-test-app"] = { @@ -462,19 +461,26 @@ async def describe_secrets_key(): def paginate(items: list, page: Optional[int], page_size: Optional[int]) -> tuple: - """Apply pagination to a list of items. Returns (page_items, pages_metadata).""" - global mock_max_page_size - if page_size is None: + """Apply pagination to a list of items. Returns (page_items, pages_metadata). + + Matches the real API: page is 1-indexed; page=0 or page=None means + "no pagination, return everything". + """ + if page_size is None or page_size <= 0: page_size = 20 - # Cap page_size if mock override is set (simulates server-side limit) - if mock_max_page_size is not None: - page_size = min(page_size, mock_max_page_size) - if page is None: - page = 0 total = len(items) + + if page is None or page == 0: + return items, { + "page": 0, + "total": total, + "num_pages": 1, + "page_size": page_size, + } + num_pages = max(1, (total + page_size - 1) // page_size) - start = page * page_size + start = (page - 1) * page_size end = start + page_size page_items = items[start:end] @@ -732,83 +738,3 @@ async def delete_schedule(schedule_data: Dict[str, Any]): @app.get("/health") async def health_check(): return {"status": "healthy", "timestamp": datetime.datetime.now().isoformat()} - - -# Test helper endpoints (for seeding data in integration tests) -@app.post("/test/seed-apps") -async def seed_apps(data: Dict[str, Any]): - """Seed the mock apps DB with a specified number of apps.""" - global mock_max_page_size - count = data.get("count", 10) - page_size_override = data.get("page_size_override") - if page_size_override is not None: - mock_max_page_size = page_size_override - - for i in range(count): - name = f"paginated-app-{i:03d}" - mock_apps_db[name] = { - "name": name, - "owner": "mock_owner", - "short_description": f"App number {i}", - "version": None, - "schedule": None, - "created_at": now_iso(), - "next_run_at": None, - "health_status": "healthy", - "pending_timeout": 300, - "running_timeout": 0, - "run_results": { - "cancelled": 0, - "crashed": 0, - "errored": 0, - "exited": 0, - "pending": 0, - "retrying": 0, - "running": 0, - }, - "subdomain": None, - "is_externally_accessible": False, - "status": "active", - } - - return {"seeded": count} - - -@app.post("/test/reset") -async def reset_test_data(): - """Reset all mock data stores to initial state.""" - global mock_max_page_size - mock_max_page_size = None - mock_apps_db.clear() - mock_secrets_db.clear() - mock_teams_db.clear() - mock_runs_db.clear() - mock_schedules_db.clear() - mock_deployed_apps.clear() - - # Re-add the pre-populated test app - mock_apps_db["predeployed-test-app"] = { - "name": "predeployed-test-app", - "owner": "mock_owner", - "short_description": "Pre-existing test app for CLI tests", - "version": None, - "schedule": None, - "created_at": now_iso(), - "next_run_at": None, - "health_status": "healthy", - "pending_timeout": 300, - "running_timeout": 0, - "run_results": { - "cancelled": 0, - "crashed": 0, - "errored": 0, - "exited": 0, - "pending": 0, - "retrying": 0, - "running": 0, - }, - "subdomain": None, - "is_externally_accessible": False, - } - mock_deployed_apps.add("predeployed-test-app") - return {"status": "reset"}