Skip to content

Commit

Permalink
Default meta key (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunklas authored Jul 7, 2022
1 parent 3806204 commit 94421bc
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 82 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
clap = { version = "3.2.8", features = ["derive"] }
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.82"
serde_json = { version = "1.0.82", features = ["preserve_order"] }
quick-xml = "0.23.0"
regex = "1.6.0"
lazy_static = "1.4.0"
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Scans a given directory for [software of unknown provinence (SOUP)](https://en.wikipedia.org/wiki/Software_of_unknown_pedigree) and writes them to a json-file.
The json-file contains name, version and a meta property for each SOUP.
The meta property may be populated with arbitrary metadata.
If you run souper after the version of a SOUP has been updated, the json-file will be updated with the new version, while preserving the arbitrary metadata.
The meta property is a json object which may be populated with arbitrary metadata.
If you run souper after the version of a SOUP has been updated, the json-file will be updated with the new version, while preserving content of the meta property.
If a SOUP has been added or removed, the json-file will be updated accordingly.

*Why*?
Expand Down Expand Up @@ -48,6 +48,18 @@ Alternatively, you can run souper from any directory:

`souper --directory /path/to/my/repo --output-file soups.json`

### Excluding directories

In case there's a directory that you'd like to skip, use the `--exclude-directory` argument.

`souper --output-file soups.json --exclude-directory ./test/`

### Default meta keys

If you know what properties that you'd like in the meta property, you can have them created automatically by using the `--meta-key` argument.

`souper --output-file soups.json --meta-key requirements --meta-key manufacturer`

## Create a release

1. On your feature branch, bump to a proper version number in [`Cargo.toml`](./Cargo.toml)
Expand Down
17 changes: 15 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use std::{
env,
path
};
use serde_json::{
json,
Map,
Value
};
use clap::Parser;

mod soup;
Expand All @@ -25,7 +30,11 @@ struct Cli {

/// Directory to exclude
#[clap(short = 'e', long = "exclude-directory", parse(from_os_str))]
exclude_dirs: Vec<path::PathBuf>
exclude_dirs: Vec<path::PathBuf>,

// Key to add in meta property
#[clap(short = 'm', long = "meta-key")]
meta_keys: Vec<String>
}

fn main() {
Expand All @@ -49,6 +58,10 @@ fn main() {
}
let exclude_dirs = args.exclude_dirs;

let default_meta = args.meta_keys.into_iter()
.map(|meta_key| (meta_key, json!("")))
.collect::<Map<String, Value>>();

let current_contexts = match output_path.is_file() {
true => match SoupContexts::from_output_file(&output_path) {
Ok(contexts) => contexts,
Expand All @@ -64,7 +77,7 @@ fn main() {
panic!("{}", e);
}
};
let scanned_contexts = match SoupContexts::from_paths(result, path) {
let scanned_contexts = match SoupContexts::from_paths(result, path, default_meta) {
Ok(contexts) => contexts,
Err(e) => {
panic!("{}", e);
Expand Down
22 changes: 12 additions & 10 deletions src/parse/csproj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ use std::{
collections::{BTreeSet, HashMap},
io
};
use serde_json::{
Map,
Value
};
use quick_xml::events::Event;
use quick_xml::Reader;
use serde_json::json;
use super::SoupSource;
use crate::soup::model::{Soup, SoupSourceParseError};

Expand All @@ -14,7 +17,7 @@ impl<R> SoupSource<R> for CsProj
where
R: io::BufRead,
{
fn soups(reader: R) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
fn soups(reader: R, default_meta: &Map<String, Value>) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
let mut reader = Reader::from_reader(reader);
reader.trim_text(true);
reader.expand_empty_elements(true);
Expand All @@ -33,7 +36,7 @@ where
soups.insert(Soup {
name,
version,
meta: json!({})
meta: default_meta.clone()
});
},
Ok(Event::Eof) => break,
Expand Down Expand Up @@ -71,7 +74,6 @@ fn attribute_value(attributes: &HashMap<Vec<u8>, Vec<u8>>, key: &str) -> Result<
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn single_dependency() {
Expand All @@ -83,14 +85,14 @@ mod tests {
</Project>
"#.as_bytes();

let result = CsProj::soups(content);
let result = CsProj::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(1, soups.len());
let expected_soup = Soup {
name: "Azure.Messaging.ServiceBus".to_owned(),
version: "7.2.1".to_owned(),
meta: json!({}),
meta: Map::new()
};
assert_eq!(true, soups.contains(&expected_soup));
}
Expand All @@ -106,13 +108,13 @@ mod tests {
</Project>
"#.as_bytes();

let result = CsProj::soups(content);
let result = CsProj::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(2, soups.len());
let expected_soups = vec![
Soup { name: "Azure.Messaging.ServiceBus".to_owned(), version: "7.2.1".to_owned(), meta: json!({}) },
Soup { name: "Swashbuckle.AspNetCore".to_owned(), version: "6.3.1".to_owned(), meta: json!({}) }
Soup { name: "Azure.Messaging.ServiceBus".to_owned(), version: "7.2.1".to_owned(), meta: Map::new() },
Soup { name: "Swashbuckle.AspNetCore".to_owned(), version: "6.3.1".to_owned(), meta: Map::new() }
].into_iter().collect::<BTreeSet<Soup>>();
assert_eq!(expected_soups, soups);
}
Expand All @@ -126,7 +128,7 @@ mod tests {
</Project>
"#.as_bytes();

let result = CsProj::soups(content);
let result = CsProj::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(0, soups.len());
Expand Down
24 changes: 13 additions & 11 deletions src/parse/docker_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::{
collections::BTreeSet,
io
};
use serde_json::json;
use serde_json::{
Map,
Value
};
use regex::Regex;
use lazy_static::lazy_static;
use super::SoupSource;
Expand All @@ -18,7 +21,7 @@ impl<R> SoupSource<R> for DockerBase
where
R: io::BufRead,
{
fn soups(reader: R) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
fn soups(reader: R, default_meta: &Map<String, Value>) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
Ok(reader.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
Expand All @@ -29,7 +32,7 @@ where
Some(Soup{
name: name.to_owned(),
version: version.to_owned(),
meta: json!({})
meta: default_meta.clone()
})
},
None => None
Expand All @@ -42,21 +45,20 @@ where
#[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);
let result = DockerBase::soups(content, &Map::new());
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!({})
meta: Map::new()
};
assert_eq!(true, soups.contains(&expected_soup));
}
Expand All @@ -67,13 +69,13 @@ FROM postgres:14.4
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
"#.as_bytes();

let result = DockerBase::soups(content);
let result = DockerBase::soups(content, &Map::new());
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!({})
meta: Map::new()
};
assert_eq!(true, soups.contains(&expected_soup));
}
Expand All @@ -84,13 +86,13 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
FROM --platform=linux/x86_64 mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
"#.as_bytes();

let result = DockerBase::soups(content);
let result = DockerBase::soups(content, &Map::new());
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!({})
meta: Map::new()
};
assert_eq!(true, soups.contains(&expected_soup));
}
Expand All @@ -100,7 +102,7 @@ FROM --platform=linux/x86_64 mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
let content = r#"
COPY --chown app:app . ./
"#.as_bytes();
let result = DockerBase::soups(content);
let result = DockerBase::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(0, soups.len());
Expand Down
6 changes: 5 additions & 1 deletion src/parse/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use std::collections::BTreeSet;
use std::io;
use serde_json::{
Map,
Value
};
use crate::soup::model::{Soup, SoupSourceParseError};

pub trait SoupSource<R: io::BufRead> {
fn soups(reader: R) -> Result<BTreeSet<Soup>, SoupSourceParseError>;
fn soups(reader: R, default_meta: &Map<String, Value>) -> Result<BTreeSet<Soup>, SoupSourceParseError>;
}

pub mod package_json;
Expand Down
21 changes: 12 additions & 9 deletions src/parse/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use std::{
collections::{HashMap, BTreeSet},
io,
};
use serde_json::{
Map,
Value
};
use serde::Deserialize;
use serde_json::json;
use crate::soup::model::{Soup, SoupSourceParseError};
use super::SoupSource;

Expand All @@ -15,7 +18,7 @@ struct Content {
}

impl <R> SoupSource<R> for PackageJson where R: io::BufRead {
fn soups(reader: R) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
fn soups(reader: R, default_meta: &Map<String, Value>) -> Result<BTreeSet<Soup>, SoupSourceParseError> {
let content: Content = match serde_json::from_reader(reader) {
Ok(content) => content,
Err(e) => {
Expand All @@ -29,7 +32,7 @@ impl <R> SoupSource<R> for PackageJson where R: io::BufRead {
.map(|(key, value)| Soup {
name: key,
version: value,
meta: json!({})
meta: default_meta.clone()
})
.collect::<BTreeSet<Soup>>())
}
Expand All @@ -46,14 +49,14 @@ mod tests {
"some-lib": "^1.0.0"
}
}"#.as_bytes();
let result = PackageJson::soups(content);
let result = PackageJson::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(1, soups.len());
let expected_soup = Soup {
name: "some-lib".to_owned(),
version: "^1.0.0".to_owned(),
meta: json!({})
meta: Map::new()
};
assert_eq!(true, soups.contains(&expected_soup));
}
Expand All @@ -66,13 +69,13 @@ mod tests {
"another-lib": "6.6.6"
}
}"#.as_bytes();
let result = PackageJson::soups(content);
let result = PackageJson::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(2, soups.len());
let expected_soups = vec![
Soup { name: "some-lib".to_owned(), version: "^1.0.0".to_owned(), meta: json!({}) },
Soup { name: "another-lib".to_owned(), version: "6.6.6".to_owned(), meta: json!({}) }
Soup { name: "some-lib".to_owned(), version: "^1.0.0".to_owned(), meta: Map::new() },
Soup { name: "another-lib".to_owned(), version: "6.6.6".to_owned(), meta: Map::new() }
].into_iter().collect::<BTreeSet<Soup>>();
assert_eq!(expected_soups, soups);
}
Expand All @@ -82,7 +85,7 @@ mod tests {
let content = r#"{
"dependencies": {}
}"#.as_bytes();
let result = PackageJson::soups(content);
let result = PackageJson::soups(content, &Map::new());
assert_eq!(true, result.is_ok());
let soups = result.unwrap();
assert_eq!(0, soups.len());
Expand Down
Loading

0 comments on commit 94421bc

Please sign in to comment.