diff options
Diffstat (limited to 'src/manifest.rs')
| -rw-r--r-- | src/manifest.rs | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/src/manifest.rs b/src/manifest.rs new file mode 100644 index 0000000..908dcbf --- /dev/null +++ b/src/manifest.rs @@ -0,0 +1,151 @@ +use yaml_rust2::parser::Parser; +use yaml_rust2::Event; +use std::ops::Range; + +enum YContext { + InDocument, + InObject, + InSequence, + InValue(bool), +} + +#[derive(Debug, PartialEq)] +pub struct ManifestFile { + pub image_tags: Vec<Range<usize>>, +} + +impl ManifestFile { + pub fn parse(content: &str) -> Self { + let mut yaml = Parser::new_from_str(content); + + let mut images = vec!(); + let mut scope = vec!(); + + loop { + let (ev, mark) = yaml.next_token().unwrap(); + match ev { + Event::StreamStart => {} + Event::StreamEnd => { break; } + Event::DocumentStart => { scope.push(YContext::InDocument); } + Event::DocumentEnd => { + assert!(matches!(scope.pop().unwrap(), YContext::InDocument)); + scope.pop_if(|x| matches!(x, YContext::InValue(_))); + }, + Event::MappingStart(_, _) => { scope.push(YContext::InObject); }, + Event::MappingEnd => { + assert!(matches!(scope.pop().unwrap(), YContext::InObject)); + scope.pop_if(|x| matches!(x, YContext::InValue(_))); + }, + Event::SequenceStart(_, _) => { scope.push(YContext::InSequence); }, + Event::SequenceEnd => { + assert!(matches!(scope.pop().unwrap(), YContext::InSequence)); + scope.pop_if(|x| matches!(x, YContext::InValue(_))); + }, + + Event::Scalar(ref txt, _, _, _) => { + let parent = scope.last().unwrap(); + match parent { + YContext::InObject => { + // We are the key of a mapping, which means the next even is the value + scope.push(YContext::InValue(txt == "image")); + }, + YContext::InSequence => {}, + YContext::InValue(img) => { + if *img { + images.push(mark.index()..mark.index() + txt.len()); + } + scope.pop(); + }, + // This should only happen for entirely empty documents + YContext::InDocument => assert!(txt == ""), + } + }, + x => todo!("{:?}", x), + } + } + + return Self{ + image_tags: images, + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_single_image() { + let yaml = "image: nginx:1.21"; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 1); + assert_eq!(manifest.image_tags[0], 7..17); + } + + #[test] + fn parse_multiple_images() { + let yaml = r#" +containers: +- image: app:v1 +- image: sidecar:v2 +"#; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 2); + assert_eq!(manifest.image_tags[0], 22..28); + assert_eq!(manifest.image_tags[1], 38..48); + } + + #[test] + fn parse_no_images() { + let yaml = "key: value"; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 0); + } + + #[test] + fn parse_empty_document() { + let yaml = ""; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 0); + } + + #[test] + fn parse_nested_object() { + let yaml = r#" +spec: + container: + image: nginx:1.21 +"#; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 1); + assert_eq!(manifest.image_tags[0], 31..41); + } + + #[test] + fn parse_multiple_documents() { + let yaml = r#" +kind: Service +--- +image: nginx:1.21 +"#; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 1); + assert_eq!(manifest.image_tags[0], 26..36); + } + + #[test] + fn parse_image_with_digest() { + let yaml = "image: nginx@sha256:abc123"; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 1); + assert_eq!(manifest.image_tags[0], 7..26); + } + + #[test] + fn parse_image_without_tag() { + let yaml = "image: nginx"; + let manifest = ManifestFile::parse(yaml); + assert_eq!(manifest.image_tags.len(), 1); + assert_eq!(manifest.image_tags[0], 7..12); + } +} |
