github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/test/compare/compare.py (about)

     1  #!/usr/bin/env python3
     2  import sys
     3  import json
     4  import collections
     5  
     6  INDENT = "    "
     7  
     8  
     9  Metadata = collections.namedtuple("Metadata", "metadata sources")
    10  Package = collections.namedtuple("Package", "name type version")
    11  
    12  
    13  class Syft:
    14      def __init__(self, report_path):
    15          self.report_path = report_path
    16  
    17      def _enumerate_section(self, section):
    18          with open(self.report_path) as json_file:
    19              data = json.load(json_file)
    20              for entry in data[section]:
    21                  yield entry
    22  
    23      def packages(self):
    24          packages = set()
    25          metadata = collections.defaultdict(dict)
    26          for entry in self._enumerate_section(section="artifacts"):
    27              package = Package(
    28                  name=entry["name"], type=entry["type"], version=entry["version"]
    29              )
    30  
    31              packages.add(package)
    32              metadata[package.type][package] = Metadata(
    33                  # note: the metadata entry is optional
    34                  metadata=repr(entry.get("metadata", "")), sources=repr(entry["locations"])
    35              )
    36          return packages, metadata
    37  
    38  
    39  def print_rows(rows):
    40      if not rows:
    41          return
    42      widths = []
    43      for col, _ in enumerate(rows[0]):
    44          width = max(len(row[col]) for row in rows) + 2  # padding
    45          widths.append(width)
    46      for row in rows:
    47          print("".join(word.ljust(widths[col_idx]) for col_idx, word in enumerate(row)))
    48  
    49  
    50  def main(baseline_report, new_report):
    51      report1_obj = Syft(report_path=baseline_report)
    52      report1_packages, report1_metadata = report1_obj.packages()
    53  
    54      report2_obj = Syft(report_path=new_report)
    55      report2_packages, report2_metadata = report2_obj.packages()
    56  
    57      if len(report2_packages) == 0 or len(report1_packages) == 0:
    58          # we are purposefully selecting test images that are guaranteed to have packages, so this should never happen
    59          print(colors.bold + colors.fg.red + "no packages found!", colors.reset)
    60          return 1
    61  
    62      same_packages = report2_packages & report1_packages
    63      percent_overlap_packages = (
    64          float(len(same_packages)) / float(len(report1_packages))
    65      ) * 100.0
    66  
    67      extra_packages = report2_packages - report1_packages
    68      missing_packages = report1_packages - report2_packages
    69  
    70      report1_metadata_set = set()
    71      for package in report1_packages:
    72          metadata = report1_metadata[package.type][package]
    73          report1_metadata_set.add((package, metadata))
    74  
    75      report2_metadata_set = set()
    76      for package in report2_packages:
    77          metadata = report2_metadata[package.type][package]
    78          report2_metadata_set.add((package, metadata))
    79  
    80      same_metadata = report2_metadata_set & report1_metadata_set
    81      percent_overlap_metadata = 0
    82      if len(report1_metadata_set) > 0:
    83          percent_overlap_metadata = (
    84              float(len(same_metadata)) / float(len(report1_metadata_set))
    85          ) * 100.0
    86  
    87      if extra_packages:
    88          rows = []
    89          print(colors.bold + "Extra packages:", colors.reset)
    90          for package in sorted(list(extra_packages)):
    91              rows.append([INDENT, repr(package)])
    92          print_rows(rows)
    93          print()
    94  
    95      if missing_packages:
    96          rows = []
    97          print(colors.bold + "Missing packages:", colors.reset)
    98          for package in sorted(list(missing_packages)):
    99              rows.append([INDENT, repr(package)])
   100          print_rows(rows)
   101          print()
   102  
   103      print(colors.bold+"Summary:", colors.reset)
   104      print("   Baseline Packages: %d" % len(report1_packages))
   105      print("   New Packages:      %d" % len(report2_packages))
   106      print(
   107          "   Baseline Packages Matched: %.2f %% (%d/%d packages)"
   108          % (percent_overlap_packages, len(same_packages), len(report1_packages))
   109      )
   110      print(
   111          "   Baseline Metadata Matched: %.2f %% (%d/%d metadata)"
   112          % (percent_overlap_metadata, len(same_metadata), len(report1_metadata_set))
   113      )
   114  
   115      if len(report1_packages) != len(report2_packages):
   116          print(colors.bold + "   Quality Gate: " + colors.fg.red + "FAILED (requires exact name & version match)\n", colors.reset)
   117          return 1
   118      else:
   119          print(colors.bold + "   Quality Gate: " + colors.fg.green + "pass\n", colors.reset)
   120      return 0
   121  
   122  
   123  class colors:
   124      reset='\033[0m'
   125      bold='\033[01m'
   126      disable='\033[02m'
   127      underline='\033[04m'
   128      reverse='\033[07m'
   129      strikethrough='\033[09m'
   130      invisible='\033[08m'
   131      class fg:
   132          black='\033[30m'
   133          red='\033[31m'
   134          green='\033[32m'
   135          orange='\033[33m'
   136          blue='\033[34m'
   137          purple='\033[35m'
   138          cyan='\033[36m'
   139          lightgrey='\033[37m'
   140          darkgrey='\033[90m'
   141          lightred='\033[91m'
   142          lightgreen='\033[92m'
   143          yellow='\033[93m'
   144          lightblue='\033[94m'
   145          pink='\033[95m'
   146          lightcyan='\033[96m'
   147      class bg:
   148          black='\033[40m'
   149          red='\033[41m'
   150          green='\033[42m'
   151          orange='\033[43m'
   152          blue='\033[44m'
   153          purple='\033[45m'
   154          cyan='\033[46m'
   155          lightgrey='\033[47m'
   156  
   157  
   158  if __name__ == "__main__":
   159      print("\nComparing two Syft reports...\n")
   160      if len(sys.argv) != 3:
   161          sys.exit("please provide two Syft json files")
   162  
   163      rc = main(sys.argv[1], sys.argv[2])
   164      sys.exit(rc)