Skip to content

Commit

Permalink
Coerce all challenge pod env lists to maps
Browse files Browse the repository at this point in the history
Signed-off-by: Robert Detjens <[email protected]>
  • Loading branch information
detjensrobert committed Nov 16, 2024
1 parent 7458f0a commit f9f88ea
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 16 deletions.
46 changes: 43 additions & 3 deletions src/configparser/challenge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,46 @@ pub fn parse_one(path: &PathBuf) -> Result<ChallengeConfig> {
.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::<Result<_>>()?;
// 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)
Expand Down Expand Up @@ -149,7 +182,9 @@ struct Pod {
#[serde(flatten)]
image_source: ImageSource,

env: Option<ListOrMap>,
#[serde(default)]
env: ListOrMap,

resources: Option<Resource>,
replicas: i64,
ports: Vec<PortConfig>,
Expand Down Expand Up @@ -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<String>),
Map(Map<String, String>),
}
impl Default for ListOrMap {
fn default() -> Self {
ListOrMap::Map(Map::new())
}
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[fully_pub]
Expand Down
3 changes: 0 additions & 3 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@ mod parsing {
mod challenges;
mod config;
}

use anyhow;
use figment;
108 changes: 98 additions & 10 deletions src/tests/parsing/challenges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -325,7 +375,7 @@ fn challenge_pod_build() {
])
}),
replicas: 1,
env: None,
env: ListOrMap::Map(HashMap::new()),
resources: None,
ports: vec![PortConfig {
internal: 80,
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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(())
})
}

0 comments on commit f9f88ea

Please sign in to comment.