use std::ops::Range; #[derive(Debug, Clone, PartialEq)] pub struct DockerRef { pub registry: Option>, pub image: Range, pub tag: Option>, pub digest: Option>, } impl DockerRef { pub fn parse(content: &str, chunk: &Range) -> DockerRef { let mut string_range = chunk.clone(); let mut digest = None; if let Some(idx) = content[string_range.clone()].rfind("@") { digest = Some(string_range.start+idx+1..string_range.end); string_range.end = string_range.start+idx; } let mut tag = None; if let Some(idx) = content[string_range.clone()].rfind(":") { tag = Some(string_range.start+idx+1..string_range.end); string_range.end = string_range.start+idx; } let mut registry = None; let image; if let Some(idx) = content[string_range.clone()].find("/") { let head = &content[string_range.clone()][..idx]; if head.contains(":") || head.contains(".") { registry = Some(string_range.start..string_range.start+idx); image = string_range.start+idx+1..string_range.end; } else { registry = None; image = string_range; } } else { image = string_range; } return DockerRef { registry, image, tag, digest, }; } } #[cfg(test)] mod tests { use super::*; #[test] fn image_only() { let input = "nginx"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(r.registry, None); assert_eq!(&input[r.image], "nginx"); assert_eq!(r.tag, None); assert_eq!(r.digest, None); } #[test] fn image_with_tag() { let input = "nginx:1.21"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(r.registry, None); assert_eq!(&input[r.image], "nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(r.digest, None); } #[test] fn image_with_org() { let input = "myorg/nginx:1.21"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(r.registry, None); assert_eq!(&input[r.image], "myorg/nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(r.digest, None); } #[test] fn registry_detected_by_dot() { let input = "registry.io/nginx:1.21"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(&input[r.registry.unwrap()], "registry.io"); assert_eq!(&input[r.image], "nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(r.digest, None); } #[test] fn registry_detected_by_port() { let input = "localhost:5000/nginx:1.21"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(&input[r.registry.unwrap()], "localhost:5000"); assert_eq!(&input[r.image], "nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(r.digest, None); } #[test] fn digest_without_tag() { let input = "nginx@sha256:abc123"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(r.registry, None); assert_eq!(&input[r.image], "nginx"); assert_eq!(r.tag, None); assert_eq!(&input[r.digest.unwrap()], "sha256:abc123"); } #[test] fn tag_and_digest() { let input = "nginx:1.21@sha256:abc123"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(r.registry, None); assert_eq!(&input[r.image], "nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(&input[r.digest.unwrap()], "sha256:abc123"); } #[test] fn full_reference() { let input = "registry.io/myorg/nginx:1.21@sha256:abc123"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(&input[r.registry.unwrap()], "registry.io"); assert_eq!(&input[r.image], "myorg/nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(&input[r.digest.unwrap()], "sha256:abc123"); } #[test] fn deep_image_path() { let input = "registry.io/org/team/project/nginx:1.21"; let r = DockerRef::parse(input, &(0..input.len())); assert_eq!(&input[r.registry.unwrap()], "registry.io"); assert_eq!(&input[r.image], "org/team/project/nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(r.digest, None); } #[test] fn non_zero_offset() { let input = "prefix registry.io/nginx:1.21 suffix"; let r = DockerRef::parse(input, &(7..29)); assert_eq!(&input[r.registry.unwrap()], "registry.io"); assert_eq!(&input[r.image], "nginx"); assert_eq!(&input[r.tag.unwrap()], "1.21"); assert_eq!(r.digest, None); } }