diff --git a/src/builder/artifacts.rs b/src/builder/artifacts.rs index 64530b1..67daddc 100644 --- a/src/builder/artifacts.rs +++ b/src/builder/artifacts.rs @@ -10,37 +10,57 @@ use tempfile::tempdir_in; use zip; use crate::builder::docker; +use crate::clients::docker; use crate::configparser::challenge::{ChallengeConfig, ProvideConfig}; -/// extract assets from given container name and provide config to challenge directory, return file path(s) extracted +/// extract assets from provide config and possible container to challenge directory, return file path(s) extracted #[tokio::main(flavor = "current_thread")] // make this a sync function pub async fn extract_asset( chal: &ChallengeConfig, provide: &ProvideConfig, - container: &docker::ContainerInfo, + // pod_containers: ) -> Result> { - debug!("extracting assets from container {}", &container.name); - // This needs to handle three cases: - // - single or multiple files without renaming (no as: field) - // - single file with rename (one item with as:) - // - multiple files as archive (multiple items with as:) + // This needs to handle three cases * 2 sources: + // - single or multiple files without renaming (no as: field) + // - single file with rename (one item with as:) + // - multiple files as archive (multiple items with as:) + // and whether the file is coming from + // - the repo + // - or a container // TODO: since this puts artifacts in the repo source folder, this should // try to not overwrite any existing files. - match &provide.as_file { - // no renaming, copy out all as-is - None => extract_files(chal, container, &provide.include).await, - // (as is keyword, so add underscore) - Some(as_) => { - if provide.include.len() == 1 { - // single file, rename - extract_rename(chal, container, &provide.include[0], as_).await - } else { - // multiple files, zip as archive - extract_archive(chal, container, &provide.include, as_).await - } + // debug!("extracting assets from container {}", &container.name); + + let docker = docker().await?; + + match provide { + // No action necessary, return path as-is + ProvideConfig::FromRepo { files } => Ok(files.clone()), + ProvideConfig::FromRepoRename { from, to } => { + std::fs::rename(from, to)?; + Ok(vec![to.clone()]) } + ProvideConfig::FromRepoArchive { + files, + archive_name, + } => { + zip_files(archive_name, files)?; + Ok(vec![archive_name.clone()]) + } + + ProvideConfig::FromContainer { container, files } => extract_files(chal, container, files), + ProvideConfig::FromContainerRename { + container, + from, + to, + } => extract_rename(chal, container, from, to), + ProvideConfig::FromContainerArchive { + container, + files, + archive_name, + } => extract_archive(chal, container, files, archive_name), } } diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 83bd44c..0a0809c 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -123,55 +123,55 @@ fn build_challenge( if extract_artifacts { info!("extracting build artifacts for chal {:?}", chal.directory); - let (provide_container, provide_static): (Vec<_>, Vec<_>) = - chal.provide.iter().partition(|p| p.from.is_some()); - - let extracted_files = provide_container - .iter() - // associate container `Provide` entries with their corresponding container image - .map(|provide| { - ( - provide, - chal.container_tag_for_pod(profile_name, provide.from.as_ref().unwrap()), - ) - }) - // extract each container provide entry - .map(|(p, tag)| { - let tag = tag?; - - let name = format!( - "asset-container-{}-{}", - chal.directory.to_string_lossy().replace("/", "-"), - p.from.clone().unwrap() - ); - let container = docker::create_container(&tag, &name)?; - - let asset_result = - artifacts::extract_asset(chal, p, &container).with_context(|| { - format!( - "failed to extract build artifacts for chal {:?} container {:?}", - chal.directory, - p.from.clone().unwrap() - ) - }); - - // clean up container even if it failed - docker::remove_container(container)?; - - asset_result - }) - .flatten_ok() - .collect::>>()?; - - // handle potentially zipping up local files as well - let local_files = provide_static.iter().map(|provide| { - match provide.as_file.as_ref() { - // no archiving needed, pass files as-is - None => Ok(provide.include.clone()), - // need to archive multiple files into zip - Some(as_) => artifacts::zip_files(as_, provide.include.as_ref()).map(|z| vec![z]), - } - }); + // let (provide_container, provide_static): (Vec<_>, Vec<_>) = + // chal.provide.iter().partition(|p| p.from.is_some()); + + // let extracted_files = provide_container + // .iter() + // // associate container `Provide` entries with their corresponding container image + // .map(|provide| { + // ( + // provide, + // chal.container_tag_for_pod(profile_name, provide.from.as_ref().unwrap()), + // ) + // }) + // // extract each container provide entry + // .map(|(p, tag)| { + // let tag = tag?; + + // let name = format!( + // "asset-container-{}-{}", + // chal.directory.to_string_lossy().replace("/", "-"), + // p.from.clone().unwrap() + // ); + // let container = docker::create_container(&tag, &name)?; + + // let asset_result = + // artifacts::extract_asset(chal, p, &container).with_context(|| { + // format!( + // "failed to extract build artifacts for chal {:?} container {:?}", + // chal.directory, + // p.from.clone().unwrap() + // ) + // }); + + // // clean up container even if it failed + // docker::remove_container(container)?; + + // asset_result + // }) + // .flatten_ok() + // .collect::>>()?; + + // // handle potentially zipping up local files as well + // let local_files = provide_static.iter().map(|provide| { + // match provide.as_file.as_ref() { + // // no archiving needed, pass files as-is + // None => Ok(provide.include.clone()), + // // need to archive multiple files into zip + // Some(as_) => artifacts::zip_files(as_, provide.include.as_ref()).map(|z| vec![z]), + // } + // }); info!("extracted artifacts: {:?}", built.assets); } diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index 472c1d0..e482e08 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -173,63 +173,75 @@ fn default_difficulty() -> i64 { #[fully_pub] enum FlagType { RawString(String), - File(FilePath), - Text(FileText), - Regex(FileRegex), - Verifier(FileVerifier), + File { file: PathBuf }, + Text { text: String }, + Regex { regex: String }, + Verifier { verifier: String }, } +// Parse each distinct kind of Provide action as a separate enum variant #[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] #[fully_pub] -struct FilePath { - file: PathBuf, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[fully_pub] -struct FileText { - text: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[fully_pub] -struct FileRegex { - regex: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[fully_pub] -struct FileVerifier { - verifier: String, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[fully_pub] -struct ProvideConfig { - /// The challenge container image where the file should be fetched from, - /// based on the names in `pods`. If omitted, the default value is the repo - /// challenge directory. - #[serde(default)] - from: Option, - - /// Rename or create zip file from included files. If only one file is - /// included, it is renamed to this value. If multiple files are included, - /// they are zipped into an archive with this filename. If this is omitted, - /// each file(s) are listed individually with no renaming. - #[serde(default, rename = "as")] - as_file: Option, - - /// List of files to read from the repo or container. If reading from the - /// repo source files, only relative paths are supported. - include: Vec, +enum ProvideConfig { + /// Upload file(s) as-is. + /// Single or multiple files with no as: or from: + /// Default if only a string is given. + FromRepo { + #[serde(rename = "include")] + files: Vec, + }, + /// Rename single file before uploading. + /// Single file with as: field without from: + FromRepoRename { + #[serde(rename = "include")] + from: PathBuf, + #[serde(rename = "as")] + to: PathBuf, + }, + /// Upload multiple files in zip archive + /// Multiple files with as: field without from: + FromRepoArchive { + #[serde(rename = "include")] + files: Vec, + #[serde(rename = "as")] + archive_name: PathBuf, + }, + + /// Upload file(s) from container as-is. + /// Single or multiple files with no as: + FromContainer { + #[serde(rename = "from")] + container: String, + #[serde(rename = "include")] + files: Vec, + }, + /// Rename single file from container before uploading. + /// Single file with as: field + FromContainerRename { + #[serde(rename = "from")] + container: String, + #[serde(rename = "include")] + from: PathBuf, + #[serde(rename = "as")] + to: PathBuf, + }, + /// Upload multiple files from container in zip archive + /// Multiple files with as: field + FromContainerArchive { + #[serde(rename = "from")] + container: String, + #[serde(rename = "include")] + files: Vec, + #[serde(rename = "as")] + archive_name: PathBuf, + }, } impl FromStr for ProvideConfig { type Err = Void; fn from_str(s: &str) -> std::result::Result { - Ok(ProvideConfig { - from: None, - as_file: None, - include: vec![PathBuf::from(s)], + Ok(ProvideConfig::FromRepo { + files: vec![PathBuf::from(s)], }) } } diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index 585f3e6..e83796e 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -88,9 +88,9 @@ fn challenge_two_levels() { category: "foo".to_string(), directory: PathBuf::from("foo/test"), - flag: FlagType::Text(FileText { + flag: FlagType::Text { text: "test{it-works}".to_string() - }), + }, provide: vec![], pods: vec![], @@ -212,10 +212,14 @@ fn challenge_provide() { provide: - foo.txt + - include: - bar.txt - baz.txt + - as: oranges + include: apples + - as: stuff.zip include: - ducks @@ -225,6 +229,11 @@ fn challenge_provide() { include: - /foo/bar + - from: container + as: pears + include: + - /usr/lib/peaches + - from: container as: shells.zip include: @@ -238,30 +247,33 @@ fn challenge_provide() { assert_eq!( chals[0].provide, vec![ - ProvideConfig { - from: None, - as_file: None, - include: vec!["foo.txt".into()] + ProvideConfig::FromRepo { + files: vec!["foo.txt".into()] + }, + ProvideConfig::FromRepo { + files: vec!["bar.txt".into(), "baz.txt".into()] + }, + ProvideConfig::FromRepoRename { + from: "apples".into(), + to: "oranges".into() }, - ProvideConfig { - from: None, - as_file: None, - include: vec!["bar.txt".into(), "baz.txt".into()] + ProvideConfig::FromRepoArchive { + files: vec!["ducks".into(), "beavers".into()], + archive_name: "stuff.zip".into() }, - ProvideConfig { - from: None, - as_file: Some("stuff.zip".into()), - include: vec!["ducks".into(), "beavers".into()] + ProvideConfig::FromContainer { + container: "container".to_string(), + files: vec!["/foo/bar".into()] }, - ProvideConfig { - from: Some("container".into()), - as_file: None, - include: vec!["/foo/bar".into()] + ProvideConfig::FromContainerRename { + container: "container".to_string(), + from: "/usr/lib/peaches".into(), + to: "pears".into(), }, - ProvideConfig { - from: Some("container".to_string()), - as_file: Some("shells.zip".into()), - include: vec!["/usr/bin/bash".into(), "/usr/bin/zsh".into()] + ProvideConfig::FromContainerArchive { + container: "container".to_string(), + files: vec!["/usr/bin/bash".into(), "/usr/bin/zsh".into()], + archive_name: "shells.zip".into(), } ], );