diff --git a/README.md b/README.md index 3037682..da09e0c 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,21 @@ Here is an example JSON config: } } ``` + +For flake-based jobsets, `nixexprinput` can be omitted, but the config must set `type` to `1` and include `flake`: + +``` +{ + "type": 1, + "description": "hydra-cli flake jobset", + "checkinterval": 60, + "enabled": 1, + "visible": true, + "keepnr": 3, + "flake": "github:nlewo/hydra-cli" +} +``` + #### jobset-eval The `jobset-eval` command starts the evaluation of a jobset. diff --git a/hydra-cli/src/hydra/example.rs b/hydra-cli/src/hydra/example.rs index ca641b3..46dbf97 100644 --- a/hydra-cli/src/hydra/example.rs +++ b/hydra-cli/src/hydra/example.rs @@ -10,9 +10,9 @@ pub fn jobset_config() -> JobsetConfig { enabled: JobsetEnabled::Enabled, visible: true, keepnr: 3, - nixexprinput: "src".to_string(), - nixexprpath: "default.nix".to_string(), - inputs: { + nixexprinput: Some("src".to_string()), + nixexprpath: Some("default.nix".to_string()), + inputs: Some({ let mut map = HashMap::::new(); let input = Input { value: Some("https://github.com/nlewo/hydra-cli.git master".to_string()), @@ -22,6 +22,8 @@ pub fn jobset_config() -> JobsetConfig { }; map.insert("src".to_string(), input); map - }, + }), + jobset_type: None, + flake: None, } } diff --git a/hydra-cli/src/hydra/reqwest_client.rs b/hydra-cli/src/hydra/reqwest_client.rs index f0b2104..978fedc 100644 --- a/hydra-cli/src/hydra/reqwest_client.rs +++ b/hydra-cli/src/hydra/reqwest_client.rs @@ -294,10 +294,12 @@ mod tests { checkinterval: 100, enabled: JobsetEnabled::Enabled, visible: true, - nixexprinput: "input".to_string(), - nixexprpath: "path".to_string(), + nixexprinput: Some("input".to_string()), + nixexprpath: Some("path".to_string()), keepnr: 10, - inputs: HashMap::new(), + inputs: Some(HashMap::new()), + jobset_type: None, + flake: None, }; let _m = mock("PUT", "/jobset/foo-project/foo-jobset") .with_status(200) @@ -315,10 +317,12 @@ mod tests { checkinterval: 100, enabled: JobsetEnabled::Enabled, visible: true, - nixexprinput: "input".to_string(), - nixexprpath: "path".to_string(), + nixexprinput: Some("input".to_string()), + nixexprpath: Some("path".to_string()), keepnr: 10, - inputs: HashMap::new(), + inputs: Some(HashMap::new()), + jobset_type: None, + flake: None, }; let _m = mock("PUT", "/jobset/foo-project/foo-jobset") .with_status(500) diff --git a/hydra-cli/src/hydra/types.rs b/hydra-cli/src/hydra/types.rs index 927fe9a..fe9f902 100644 --- a/hydra-cli/src/hydra/types.rs +++ b/hydra-cli/src/hydra/types.rs @@ -164,9 +164,16 @@ pub struct JobsetConfig { #[serde(skip_serializing_if = "is_not_visible")] pub visible: bool, pub keepnr: i64, - pub nixexprinput: String, - pub nixexprpath: String, - pub inputs: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub nixexprinput: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub nixexprpath: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub inputs: Option>, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub jobset_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub flake: Option, } fn is_not_visible(visible: &bool) -> bool { diff --git a/hydra-cli/src/ops/jobset_create.rs b/hydra-cli/src/ops/jobset_create.rs index beeb719..4f26330 100644 --- a/hydra-cli/src/ops/jobset_create.rs +++ b/hydra-cli/src/ops/jobset_create.rs @@ -1,10 +1,92 @@ use crate::hydra::client::{Creds, HydraClient, JobsetConfig}; -use crate::ops::{ok_msg, OpResult}; +use crate::ops::{ok_msg, OpError, OpResult}; use std::fs::read_to_string; -fn load_config(config_path: &str) -> JobsetConfig { - let cfg = read_to_string(config_path).expect("Failed to read config file"); - serde_json::from_str(&cfg).expect("Failed to parse jobset configuration") +fn validate_config(jobset_cfg: &JobsetConfig) -> Result<(), OpError> { + if jobset_cfg.nixexprinput.is_none() { + if jobset_cfg.jobset_type != Some(1) { + return Err(OpError::Error( + "Missing nixexprinput requires `type` set to 1".to_string(), + )); + } + + if jobset_cfg.flake.is_none() { + return Err(OpError::Error( + "Missing nixexprinput requires a `flake` key".to_string(), + )); + } + } + + Ok(()) +} + +fn load_config(config_path: &str) -> Result { + let cfg = read_to_string(config_path) + .map_err(|e| OpError::Error(format!("Failed to read config file: {}", e)))?; + let jobset_cfg: JobsetConfig = serde_json::from_str(&cfg) + .map_err(|e| OpError::Error(format!("Failed to parse jobset configuration: {}", e)))?; + + validate_config(&jobset_cfg)?; + Ok(jobset_cfg) +} + +#[cfg(test)] +mod test { + use super::validate_config; + use crate::hydra::client::JobsetConfig; + + #[test] + fn accepts_legacy_nixexpr_config() { + let cfg: JobsetConfig = serde_json::from_str( + r#"{ + "description": "desc", + "checkinterval": 60, + "enabled": 1, + "visible": true, + "keepnr": 3, + "nixexprinput": "src", + "nixexprpath": "default.nix", + "inputs": {} +}"#, + ) + .unwrap(); + + assert!(validate_config(&cfg).is_ok()); + } + + #[test] + fn accepts_flake_config_without_nixexprinput() { + let cfg: JobsetConfig = serde_json::from_str( + r#"{ + "type": 1, + "description": "desc", + "checkinterval": 60, + "enabled": 1, + "visible": true, + "keepnr": 3, + "flake": "github:org/repo" +}"#, + ) + .unwrap(); + + assert!(validate_config(&cfg).is_ok()); + } + + #[test] + fn rejects_missing_nixexprinput_without_flake_type() { + let cfg: JobsetConfig = serde_json::from_str( + r#"{ + "description": "desc", + "checkinterval": 60, + "enabled": 1, + "visible": true, + "keepnr": 3 +}"#, + ) + .unwrap(); + + assert!(validate_config(&cfg).is_err()); + } } pub fn run( @@ -15,7 +97,7 @@ pub fn run( user: &str, password: &str, ) -> OpResult { - let jobset_cfg = load_config(config_path); + let jobset_cfg = load_config(config_path)?; let creds = Creds { username: String::from(user), password: String::from(password),