diff --git a/crates/config/src/towerfile.rs b/crates/config/src/towerfile.rs index aa385e1a..703c999d 100644 --- a/crates/config/src/towerfile.rs +++ b/crates/config/src/towerfile.rs @@ -12,6 +12,9 @@ pub struct Parameter { #[serde(default)] pub default: String, + + #[serde(default)] + pub hidden: bool, } #[derive(Deserialize, Serialize, Debug)] @@ -122,6 +125,7 @@ impl Towerfile { name, description, default, + hidden: false, }); } } @@ -272,6 +276,26 @@ mod test { assert_eq!(towerfile.parameters.len(), 2); assert_eq!(towerfile.parameters[0].name, "my_first_param"); assert_eq!(towerfile.parameters[1].name, "my_second_param"); + assert!(!towerfile.parameters[0].hidden); + } + + #[test] + fn test_parses_secret_parameters() { + let toml = r#" + [app] + name = "my-app" + script = "./script.py" + source = ["*.py"] + + [[parameters]] + name = "MY_PARAMETER" + hidden = true + "#; + + let towerfile = crate::Towerfile::from_toml(toml).unwrap(); + assert_eq!(towerfile.parameters.len(), 1); + assert_eq!(towerfile.parameters[0].name, "MY_PARAMETER"); + assert!(towerfile.parameters[0].hidden); } #[test] diff --git a/crates/tower-api/src/models/parameter.rs b/crates/tower-api/src/models/parameter.rs index ce85288d..f53c7b45 100644 --- a/crates/tower-api/src/models/parameter.rs +++ b/crates/tower-api/src/models/parameter.rs @@ -23,6 +23,9 @@ pub struct Parameter { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "name")] pub name: String, + #[serde(default)] + #[serde(rename = "hidden")] + pub hidden: bool, } impl Parameter { @@ -31,6 +34,7 @@ impl Parameter { default, description, name, + hidden: false, } } } diff --git a/crates/tower-api/src/models/run_parameter.rs b/crates/tower-api/src/models/run_parameter.rs index 7e9164d4..f45cc23d 100644 --- a/crates/tower-api/src/models/run_parameter.rs +++ b/crates/tower-api/src/models/run_parameter.rs @@ -20,10 +20,17 @@ pub struct RunParameter { #[serde_as(as = "DefaultOnNull")] #[serde(rename = "value")] pub value: String, + #[serde(default)] + #[serde(rename = "hidden")] + pub hidden: bool, } impl RunParameter { pub fn new(name: String, value: String) -> RunParameter { - RunParameter { name, value } + RunParameter { + name, + value, + hidden: false, + } } } diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index f3ddc421..e4024606 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -840,7 +840,11 @@ pub async fn create_schedule( let run_parameters = parameters.map(|params| { params .into_iter() - .map(|(key, value)| RunParameter { name: key, value }) + .map(|(key, value)| RunParameter { + name: key, + value, + hidden: false, + }) .collect() }); @@ -874,7 +878,11 @@ pub async fn update_schedule( let run_parameters = parameters.map(|params| { params .into_iter() - .map(|(key, value)| RunParameter { name: key, value }) + .map(|(key, value)| RunParameter { + name: key, + value, + hidden: false, + }) .collect() }); diff --git a/crates/tower-cmd/src/mcp.rs b/crates/tower-cmd/src/mcp.rs index 95d9440d..efe7e2a0 100644 --- a/crates/tower-cmd/src/mcp.rs +++ b/crates/tower-cmd/src/mcp.rs @@ -889,7 +889,22 @@ IMPORTANT REMINDERS: async fn tower_schedules_list(&self) -> Result { match api::list_schedules(&self.config, None, None).await { Ok(response) => { - Self::json_success(serde_json::json!({"schedules": response.schedules})) + let schedules = response + .schedules + .into_iter() + .map(|mut schedule| { + if let Some(parameters) = schedule.parameters.as_mut() { + for parameter in parameters { + if parameter.hidden { + parameter.value = "[hidden]".to_string(); + } + } + } + schedule + }) + .collect::>(); + + Self::json_success(serde_json::json!({"schedules": schedules})) } Err(e) => Self::error_result("Failed to list schedules", e), } diff --git a/crates/tower/src/lib.rs b/crates/tower/src/lib.rs index 751f4ba6..1549e234 100644 --- a/crates/tower/src/lib.rs +++ b/crates/tower/src/lib.rs @@ -23,8 +23,8 @@ mod bindings { let spec = PackageSpec::from_towerfile(&towerfile); - let runtime = tokio::runtime::Runtime::new() - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + let runtime = + tokio::runtime::Runtime::new().map_err(|e| PyRuntimeError::new_err(e.to_string()))?; let output = PathBuf::from(output); @@ -40,8 +40,7 @@ mod bindings { .as_ref() .ok_or_else(|| PyRuntimeError::new_err("package build produced no output file"))?; - std::fs::copy(src, &output) - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + std::fs::copy(src, &output).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; Ok(()) }) @@ -53,8 +52,8 @@ mod bindings { /// args: Command line arguments (typically sys.argv). #[pyfunction] fn _run_cli(args: Vec) -> PyResult<()> { - let runtime = tokio::runtime::Runtime::new() - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + let runtime = + tokio::runtime::Runtime::new().map_err(|e| PyRuntimeError::new_err(e.to_string()))?; // App::new_from_args() must run inside block_on because // Session::from_config_dir() requires an active tokio reactor.