Skip to content

Commit

Permalink
Bugfixes, update html report summary. (#18)
Browse files Browse the repository at this point in the history
Signed-off-by: Caroline Russell <caroline@appthreat.dev>
  • Loading branch information
cerrussell authored Jun 7, 2024
1 parent 318d617 commit 5d99187
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 47 deletions.
23 changes: 7 additions & 16 deletions custom_json_diff/bom_diff_template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,13 @@
Summary
</th>
</tr>
{% if not comp_only %}
{% for item in stats %}
<tr>
<td>{{ item[0] }}</td>
<td style="text-align: right">{{ item[1] }}</td>

</tr>
{% endfor %}
{% endif %}
{% if comp_only %}
{% for item in stats %}
<tr>
<td>{{ stats[0][0] }}</td>
<td style="text-align: right">{{ stats[0][1] }}</td>
<td>{{ item[0] }}</td>
<td style="text-align: right">{{ item[1] }}</td>

</tr>
{% endif %}
{% endfor %}
</table>

</div>
Expand Down Expand Up @@ -160,10 +151,10 @@
{% endfor %}</td>
</tr>
{% endif %}
{% if diff_other_comp_1 or diff_other_comp_2 %}
{% if diff_other_1 or diff_other_2 %}
<tr>
<th>other types</th>
<td>{% for item in diff_other_comp_1 %}
<td>{% for item in diff_other_1 %}
<details>
<summary>{{ item['name'] }}@{{ item['version'] }}</summary>
<ul>
Expand All @@ -175,7 +166,7 @@
</ul>
</details>
{% endfor %}</td>
<td>{% for item in diff_other_comp_2 %}
<td>{% for item in diff_other_2 %}
<details>
<summary>{{ item['name'] }}@{{ item['version'] }}</summary>
<ul>
Expand Down
12 changes: 5 additions & 7 deletions custom_json_diff/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,16 @@ def build_args() -> argparse.Namespace:

def main():
args = build_args()
if args.exclude:
args.exclude = args.exclude.split(",")
if args.include:
args.include = args.include.split(",")
exclude = args.exclude.split(",") if args.exclude else []
include = args.include.split(",") if args.include else []
options = Options(
allow_new_versions=args.allow_new_versions,
allow_new_data=args.allow_new_data,
config=args.config,
comp_only=args.components_only,
bom_diff=args.bom_diff,
include=args.include,
exclude=args.exclude,
include=include,
exclude=exclude,
file_1=args.input[0],
file_2=args.input[1],
output=args.output,
Expand All @@ -123,7 +121,7 @@ def main():
result_summary = perform_bom_diff(j1, j2)
else:
result_summary = get_diff(j1, j2, options)
report_results(result, result_summary, j1, options)
report_results(result, result_summary, options, j1, j2)


if __name__ == "__main__":
Expand Down
45 changes: 33 additions & 12 deletions custom_json_diff/custom_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,22 @@
from custom_json_diff.custom_diff_classes import BomDicts, FlatDicts, Options


def calculate_pcts(diff_stats: Dict, j1_counts: Dict) -> List[list[str]]:
return [
[f"Common {key} found: ", f"{value}/{j1_counts[key]}"]
for key, value in diff_stats.items()
def calculate_pcts(diff_stats: Dict, j1: BomDicts, j2: BomDicts) -> List[list[str]]:
j1_counts = j1.generate_counts()
j2_counts = j2.generate_counts()
result = [
[f"Common {key} matched: ", f"{value}"]
for key, value in diff_stats["common"].items()
]
result.extend([
[f"BOM 1 {key} not matched: ", f"{j1_counts[key] - value}/{j1_counts[key]}"]
for key, value in diff_stats["common"].items()
])
result.extend([
[f"BOM 2 {key} not matched: ", f"{j2_counts[key] - value}/{j2_counts[key]}"]
for key, value in diff_stats["common"].items()
])
return [i for i in result if i[1] not in ["0", "0/0"]]


def check_regex(regex_keys: Set[re.Pattern], key: str) -> bool:
Expand All @@ -33,7 +44,7 @@ def compare_dicts(options: Options) -> Tuple[int, FlatDicts | BomDicts, FlatDict
return 1, json_1_data, json_2_data


def export_html_report(outfile: str, diffs: Dict, j1: BomDicts, options: Options) -> None:
def export_html_report(outfile: str, diffs: Dict, j1: BomDicts, j2: BomDicts, options: Options) -> None:
if options.report_template:
template_file = options.report_template
else:
Expand All @@ -50,7 +61,7 @@ def export_html_report(outfile: str, diffs: Dict, j1: BomDicts, options: Options
diffs["diff_summary"][options.file_2].get("dependencies", []), purl_regex)
diffs["common_summary"]["dependencies"] = parse_purls(
diffs["common_summary"].get("dependencies", []), purl_regex)
stats_summary = calculate_pcts(generate_diff_counts(diffs), j1.generate_counts())
stats_summary = calculate_pcts(generate_diff_counts(diffs, j1.options.file_2), j1, j2)
report_result = jinja_tmpl.render(
common_lib=diffs["common_summary"].get("components", {}).get("libraries", []),
common_frameworks=diffs["common_summary"].get("components", {}).get("frameworks", []),
Expand Down Expand Up @@ -91,14 +102,24 @@ def filter_dict(data: Dict, options: Options) -> FlatDicts:
return FlatDicts(data).filter_out_keys(options.exclude)


def generate_diff_counts(diffs) -> Dict:
return {"components": len(
def generate_diff_counts(diffs, f2: str) -> Dict:
return {"common": {"components": len(
diffs["common_summary"].get("components", {}).get("libraries", [])) + len(
diffs["common_summary"].get("components", {}).get("frameworks", [])) + len(
diffs["common_summary"].get("components", {}).get("applications", [])) + len(
diffs["common_summary"].get("components", {}).get("other_types", [])),
"services": len(diffs["common_summary"].get("services", [])),
"dependencies": len(diffs["common_summary"].get("dependencies", []))}
"services": len(diffs["common_summary"].get("services", [])),
"dependencies": len(diffs["common_summary"].get("dependencies", []))},
"diff": {"components": len(
diffs["diff_summary"].get(f2, {}).get("components", {}).get("libraries",
[])) + len(
diffs["diff_summary"].get(f2, {}).get("components", {}).get("frameworks",
[])) + len(
diffs["diff_summary"].get(f2, {}).get("components", {}).get("applications",
[])) + len(
diffs["diff_summary"].get(f2, {}).get("components", {}).get("other_types", [])), },
"services": len(diffs["diff_summary"].get(f2, {}).get("services", [])),
"dependencies": len(diffs["diff_summary"].get(f2, {}).get("dependencies", []))}


def get_diff(j1: FlatDicts, j2: FlatDicts, options: Options) -> Dict:
Expand Down Expand Up @@ -153,15 +174,15 @@ def perform_bom_diff(bom_1: BomDicts, bom_2: BomDicts) -> Dict:
return output


def report_results(status: int, diffs: Dict, j1: BomDicts | FlatDicts, options: Options) -> None:
def report_results(status: int, diffs: Dict, options: Options, j1: BomDicts | None = None, j2: BomDicts | None = None) -> None:
if status == 0:
print("No differences found.")
else:
print("Differences found.")
handle_results(options.output, diffs)
if options.bom_diff and options.output:
report_file = options.output.replace(".json", "") + ".html"
export_html_report(report_file, diffs, j1, options) # type: ignore
export_html_report(report_file, diffs, j1, j2, options) # type: ignore


def sort_dict_lists(result: Dict, sort_keys: List[str]) -> Dict:
Expand Down
23 changes: 12 additions & 11 deletions custom_json_diff/custom_diff_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import toml
from json_flatten import unflatten # type: ignore


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -343,31 +344,31 @@ def create_search_key(key: str, value: str) -> str:


def get_cdxgen_excludes(includes: List[str], comp_only: bool, allow_new_versions: bool,
allow_new_data: bool) -> Tuple[List[str], Set[str], Set[str], bool]:
allow_new_data: bool) -> Tuple[List[str], List[str], List[str], bool]:

excludes = {'metadata.timestamp': 'metadata.timestamp', 'serialNumber': 'serialNumber',
'metadata.tools.components.[].version': 'metadata.tools.components.[].version',
'metadata.tools.components.[].purl': 'metadata.tools.components.[].purl',
'metadata.tools.components.[].bom-ref': 'metadata.tools.components.[].bom-ref',
'properties': 'components.[].properties', 'evidence': 'components.[].evidence',
'licenses': 'components.[].licenses', 'hashes': 'components.[].hashes'}
'licenses': 'components.[].licenses', 'hashes': 'components.[].hashes',
'externalReferences': 'components.[].externalReferences',
'externalreferences': 'components.[].externalReferences'}
if comp_only:
excludes |= {'services': 'services', 'dependencies': 'dependencies'}
if allow_new_data:
component_keys = set()
service_keys = set()
component_keys = []
service_keys = []
else:
component_keys = {'name', 'author', 'publisher', 'group', 'type', 'scope', 'description'}
service_keys = {'name', 'authenticated', 'x-trust-boundary', 'endpoints'}
component_keys = ['name', 'author', 'publisher', 'group', 'type', 'scope', 'description']
service_keys = ['name', 'authenticated', 'x-trust-boundary', 'endpoints']
if not allow_new_versions:
component_keys.add('version')
component_keys.add('bom-ref')
component_keys.add('purl')
component_keys.extend([i for i in ('version', 'purl', 'bom-ref', 'version') if i not in excludes])

return (
[v for k, v in excludes.items() if k not in includes],
component_keys,
service_keys,
[v for v in component_keys if v not in excludes],
[v for v in service_keys if v not in excludes],
allow_new_data,
)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "custom-json-diff"
version = "1.2.0"
version = "1.2.1"
description = "Custom JSON and CycloneDx BOM diffing and comparison tool."
authors = [
{ name = "Caroline Russell", email = "caroline@appthreat.dev" },
Expand Down

0 comments on commit 5d99187

Please sign in to comment.