From f9f88eaa145f40ac5796386197128c8861cba3a2 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sat, 16 Nov 2024 14:32:34 -0800 Subject: [PATCH] Coerce all challenge pod env lists to maps Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 46 +++++++++++++- src/tests/mod.rs | 3 - src/tests/parsing/challenges.rs | 108 +++++++++++++++++++++++++++++--- 3 files changed, 141 insertions(+), 16 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index 5ea5062..62b9339 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -64,13 +64,46 @@ pub fn parse_one(path: &PathBuf) -> Result { .to_str() .unwrap(); - let parsed = Figment::new() + let mut parsed: ChallengeConfig = Figment::new() .merge(Yaml::file(path.clone())) // merge in generated data from file path .merge(Serialized::default("directory", chal_dir)) .merge(Serialized::default("category", category)) .extract()?; + // coerce pod env lists to maps + // TODO: do this in serde deserialize? + for pod in parsed.pods.iter_mut() { + pod.env = match pod.env.clone() { + ListOrMap::Map(m) => ListOrMap::Map(m), + ListOrMap::List(l) => { + // split NAME=VALUE list into separate name and value + let split: Vec<(String, String)> = l + .into_iter() + .map(|var| { + // error if envvar is malformed + let split = var.splitn(2, '=').collect_vec(); + if split.len() == 2 { + Ok((split[0].to_string(), split[1].to_string())) + } else { + Err(anyhow!("Cannot split envvar {var:?}")) + } + }) + .collect::>()?; + // build hashmap from split name and value iteratively. this + // can't use HashMap::from() here since the values are dynamic + // and from() only works for Vec constants + let map = split + .into_iter() + .fold(Map::new(), |mut map, (name, value)| { + map.insert(name, value); + map + }); + ListOrMap::Map(map) + } + } + } + trace!("got challenge config: {parsed:#?}"); Ok(parsed) @@ -149,7 +182,9 @@ struct Pod { #[serde(flatten)] image_source: ImageSource, - env: Option, + #[serde(default)] + env: ListOrMap, + resources: Option, replicas: i64, ports: Vec, @@ -189,13 +224,18 @@ fn default_dockerfile() -> String { "Dockerfile".to_string() } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[serde(untagged)] #[fully_pub] enum ListOrMap { List(Vec), Map(Map), } +impl Default for ListOrMap { + fn default() -> Self { + ListOrMap::Map(Map::new()) + } +} #[derive(Debug, PartialEq, Serialize, Deserialize)] #[fully_pub] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f8a18d2..530a7ad 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -2,6 +2,3 @@ mod parsing { mod challenges; mod config; } - -use anyhow; -use figment; diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index 22df184..7f8c8f8 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -117,6 +117,56 @@ fn challenge_three_levels() { }) } +#[test] +fn challenge_missing_fields() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("test/noflag")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + "#, + )?; + + let dir = jail.create_dir("test/noauthor")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + description: just a test challenge + difficulty: 1 + + flag: + text: test{asdf} + "#, + )?; + + let dir = jail.create_dir("test/nodescrip")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + difficulty: 1 + + flag: + text: test{asdf} + "#, + )?; + + let chals = parse_all(); + assert!(chals.is_err()); + let errs = chals.unwrap_err(); + + assert_eq!(errs.len(), 3); + + Ok(()) + }) +} + #[test] /// Challenges can omit both provides and pods fields if needed fn challenge_no_provides_or_pods() { @@ -221,7 +271,7 @@ fn challenge_pods() { name: "foo".to_string(), image_source: ImageSource::Image("nginx".to_string()), replicas: 2, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 80, @@ -237,7 +287,7 @@ fn challenge_pods() { args: HashMap::new() }), replicas: 1, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 8000, @@ -306,7 +356,7 @@ fn challenge_pod_build() { args: HashMap::new() }), replicas: 1, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 80, @@ -325,7 +375,7 @@ fn challenge_pod_build() { ]) }), replicas: 1, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 80, @@ -391,10 +441,10 @@ fn challenge_pod_env() { image_source: ImageSource::Image("nginx".to_string()), replicas: 1, - env: Some(ListOrMap::Map(HashMap::from([ + env: ListOrMap::Map(HashMap::from([ ("FOO".to_string(), "this".to_string()), - ("BAR".to_string(), "that".to_string()) - ]))), + ("BAR".to_string(), "that".to_string()), + ])), resources: None, ports: vec![PortConfig { internal: 80, @@ -406,9 +456,9 @@ fn challenge_pod_env() { name: "bar".to_string(), image_source: ImageSource::Image("nginx".to_string()), replicas: 1, - env: Some(ListOrMap::List(vec![ - "FOO=this".to_string(), - "BAR=that".to_string(), + env: ListOrMap::Map(HashMap::from([ + ("FOO".to_string(), "this".to_string()), + ("BAR".to_string(), "that".to_string()), ])), resources: None, ports: vec![PortConfig { @@ -423,3 +473,41 @@ fn challenge_pod_env() { Ok(()) }) } + +#[test] +/// Challenge pod envvar strings error if malformed +fn challenge_pod_bad_env() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + pods: + - name: foo + image: nginx + env: + - FOO + replicas: 1 + ports: + - internal: 80 + expose: + http: test.chals.example.com + "#, + )?; + + let chals = parse_all(); + assert!(chals.is_err()); + let errs = chals.unwrap_err(); + assert_eq!(errs.len(), 1); + + Ok(()) + }) +}