Skip to content

Commit

Permalink
represent chal provide variants as explicit enum types
Browse files Browse the repository at this point in the history
Signed-off-by: Robert Detjens <[email protected]>
  • Loading branch information
detjensrobert committed Jan 31, 2025
1 parent 2f01285 commit 4f0545d
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 139 deletions.
58 changes: 39 additions & 19 deletions src/builder/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<PathBuf>> {
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),
}
}

Expand Down
98 changes: 49 additions & 49 deletions src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Result<Vec<_>>>()?;

// 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::<Result<Vec<_>>>()?;

// // 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);
}
Expand Down
110 changes: 61 additions & 49 deletions src/configparser/challenge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,

/// 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<PathBuf>,

/// List of files to read from the repo or container. If reading from the
/// repo source files, only relative paths are supported.
include: Vec<PathBuf>,
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<PathBuf>,
},
/// 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<PathBuf>,
#[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<PathBuf>,
},
/// 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<PathBuf>,
#[serde(rename = "as")]
archive_name: PathBuf,
},
}
impl FromStr for ProvideConfig {
type Err = Void;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(ProvideConfig {
from: None,
as_file: None,
include: vec![PathBuf::from(s)],
Ok(ProvideConfig::FromRepo {
files: vec![PathBuf::from(s)],
})
}
}
Expand Down
56 changes: 34 additions & 22 deletions src/tests/parsing/challenges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![],
Expand Down Expand Up @@ -212,10 +212,14 @@ fn challenge_provide() {
provide:
- foo.txt
- include:
- bar.txt
- baz.txt
- as: oranges
include: apples
- as: stuff.zip
include:
- ducks
Expand All @@ -225,6 +229,11 @@ fn challenge_provide() {
include:
- /foo/bar
- from: container
as: pears
include:
- /usr/lib/peaches
- from: container
as: shells.zip
include:
Expand All @@ -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(),
}
],
);
Expand Down

0 comments on commit 4f0545d

Please sign in to comment.