Skip to content

Commit

Permalink
Add support for docker base images (#32)
Browse files Browse the repository at this point in the history
Fixes #28
  • Loading branch information
Dunklas authored Jul 6, 2022
1 parent dd8f4c9 commit 3806204
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 19 deletions.
36 changes: 35 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "souper"
version = "0.4.0"
version = "0.4.1"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -10,3 +10,5 @@ clap = { version = "3.2.8", features = ["derive"] }
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.82"
quick-xml = "0.23.0"
regex = "1.6.0"
lazy_static = "1.4.0"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ Below is an example of how the output looks like, with some arbitrary metadata.
}
```

At the time of writing, souper will attempt to identify SOUPs from the following sources:
Souper will attempt to identify SOUPs from the following sources:
- package.json (npm)
- *.csproj (ASP.NET)
- docker base images

## Installation

Expand Down
9 changes: 5 additions & 4 deletions src/dir_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ pub fn scan(dir: &path::PathBuf, exclude_dirs: &Vec<path::PathBuf>) -> Result<Ve
Some("package.json") => {
files.push(path);
},
Some(file_name_str) => {
if file_name_str.contains(".csproj") {
files.push(path);
}
Some(file_name_str) if file_name_str.contains(".csproj") => {
files.push(path);
},
Some(file_name_str) if file_name_str.contains("Dockerfile") => {
files.push(path);
},
_ => {}
}
Expand Down
20 changes: 9 additions & 11 deletions src/parse/csproj.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::SoupSource;
use crate::soup::model::{Soup, SoupSourceParseError};
use quick_xml::events::Event;
use quick_xml::Reader;
use serde_json::json;
use std::{
collections::{BTreeSet, HashMap},
io
};
use quick_xml::events::Event;
use quick_xml::Reader;
use serde_json::json;
use super::SoupSource;
use crate::soup::model::{Soup, SoupSourceParseError};

pub struct CsProj {}

Expand Down Expand Up @@ -75,14 +75,13 @@ mod tests {

#[test]
fn single_dependency() {
let content: &[u8] = r#"
let content = r#"
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.2.1" />
</ItemGroup>
</Project>
"#
.as_bytes();
"#.as_bytes();

let result = CsProj::soups(content);
assert_eq!(true, result.is_ok());
Expand All @@ -98,15 +97,14 @@ mod tests {

#[test]
fn multiple_dependencies() {
let content: &[u8] = r#"
let content = r#"
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
</ItemGroup>
</Project>
"#
.as_bytes();
"#.as_bytes();

let result = CsProj::soups(content);
assert_eq!(true, result.is_ok());
Expand Down
108 changes: 108 additions & 0 deletions src/parse/docker_base.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::{
collections::BTreeSet,
io
};
use serde_json::json;
use regex::Regex;
use lazy_static::lazy_static;
use super::SoupSource;
use crate::soup::model::{Soup, SoupSourceParseError};

pub struct DockerBase {}

lazy_static! {
static ref BASE_PATTERN: Regex = Regex::new(r"^ *FROM +(?:--platform=[\w/]+ +)?(?P<name>[\w\-\./]+):(?P<version>[\w\.-]+) *(?:AS +[\w\-]+)? *$").unwrap();
}

impl<R> SoupSource<R> for DockerBase
where
R: io::BufRead,
{
fn soups(reader: R) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
Ok(reader.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
match BASE_PATTERN.captures(&line) {
Some(captures) => {
let name = captures.name("name").unwrap().as_str();
let version = captures.name("version").unwrap().as_str();
Some(Soup{
name: name.to_owned(),
version: version.to_owned(),
meta: json!({})
})
},
None => None
}
})
.collect::<BTreeSet<Soup>>())
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn simple_base() {
let content = r#"
FROM postgres:14.4
"#.as_bytes();

let result = DockerBase::soups(content);
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
let expected_soup = Soup {
name: "postgres".to_owned(),
version: "14.4".to_owned(),
meta: json!({})
};
assert_eq!(true, soups.contains(&expected_soup));
}

#[test]
fn named_base() {
let content = r#"
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
"#.as_bytes();

let result = DockerBase::soups(content);
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
let expected_soup = Soup {
name: "mcr.microsoft.com/dotnet/sdk".to_owned(),
version: "6.0".to_owned(),
meta: json!({})
};
assert_eq!(true, soups.contains(&expected_soup));
}

#[test]
fn with_platform() {
let content = r#"
FROM --platform=linux/x86_64 mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
"#.as_bytes();

let result = DockerBase::soups(content);
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
let expected_soup = Soup{
name: "mcr.microsoft.com/dotnet/sdk".to_owned(),
version: "6.0".to_owned(),
meta: json!({})
};
assert_eq!(true, soups.contains(&expected_soup));
}

#[test]
fn no_from_statement() {
let content = r#"
COPY --chown app:app . ./
"#.as_bytes();
let result = DockerBase::soups(content);
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(0, soups.len());
}
}
1 change: 1 addition & 0 deletions src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ pub trait SoupSource<R: io::BufRead> {

pub mod package_json;
pub mod csproj;
pub mod docker_base;
4 changes: 3 additions & 1 deletion src/soup/contexts_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use crate::soup::model::{Soup, SoupContexts, SouperIoError};
use crate::parse::{
SoupSource,
package_json::PackageJson,
csproj::CsProj
csproj::CsProj,
docker_base::DockerBase
};
use crate::utils;

Expand Down Expand Up @@ -36,6 +37,7 @@ impl SoupContexts {
let parse_result = match filename.to_str() {
Some("package.json") => PackageJson::soups(reader),
Some(x) if x.contains(".csproj") => CsProj::soups(reader),
Some(x) if x.contains("Dockerfile") => DockerBase::soups(reader),
_ => {
panic!("No parser found for: {:?}", filename)
}
Expand Down

0 comments on commit 3806204

Please sign in to comment.