Skip to contentSkip to conversation

pkg-stats json output improvements

PATCH Superseded Heiko Thiery 2 participants 36 messages Feb 22, 2020 – Feb 24, 2020

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v3 cover pkg-stats json output improvements

This patchset adds several improvements for the json output of the pkg-stats script.

- add developers information to the packages
- add supported defconfigs to json
- add license information to json
- add more generic status field to the packages for easier post processing

---
v2 -> v3:
- keep variable latest_release but change format
- add check for license file hashes
- introduce a na status for the checks
- add a list of all possible checks to the json output
- add the cve check status

v1 -> v2:
- cleanup and recreation of patches
- remove pkg name from dumping to json
- use patch_files instead of combine count and files in dict
- include getdevelopers.py to reuse Developers class

---
Heiko Thiery (12):
support/scripts/pkg-stats: store latest version in a dict support/scripts/pkg-stats: store patch files for the package support/scripts/pkg-stats: set developers info support/scripts/pkg-stats: store licences of package support/scripts/pkg-stats: add package status support/scripts/pkg-stats: add package count to stats support/scripts/pkg-stats: store pkg dir path support/scripts/pkg-stats: add defconfig support support/scripts/pkg-stats: add support for license hash check support/scripts/pkg-stats: set status to 'na' for virtual packages support/scripts/pkg-stats: initialize all package status checks support/scripts/pkg-stats: add status for cve check

 support/scripts/pkg-stats | 272 +++++++++++++++++++++++++++++---------
 1 file changed, 212 insertions(+), 60 deletions(-)
Signature · 1 line
2.20.1
#1 lore raw

PATCH v3 1/12 support/scripts/pkg-stats: store latest version in a dict

From: Heiko Thiery <[email protected]>

This patch changes the type of the latest_version variable to a dict. This is for better readability/usability of the data. With this the json output is more descriptive in later processing of the json output.

---
 support/scripts/pkg-stats | 33 +++++++++++++++++----------------
 1 file changed, 17 insertions(+), 16 deletions(-)
support/scripts/pkg-stats +17 -16
index 7721d98459..8cc78f2f66 100755
@@ -71,7 +71,7 @@ class Package:
         self.url_status = None
         self.url_worker = None
         self.cves = list()
-        self.latest_version = (RM_API_STATUS_ERROR, None, None)
+        self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
 
     def pkgvar(self):
         return self.name.upper().replace("-", "_")
@@ -460,9 +460,8 @@ def check_package_latest_version(packages):
     """
     Fills in the .latest_version field of all Package objects
 
-    This field has a special format:
-      (status, version, id)
-    with:
+    This field is a dict and has the following keys:
+
     - status: one of RM_API_STATUS_ERROR,
       RM_API_STATUS_FOUND_BY_DISTRO, RM_API_STATUS_FOUND_BY_PATTERN,
       RM_API_STATUS_NOT_FOUND
@@ -478,7 +477,9 @@ def check_package_latest_version(packages):
     worker_pool = Pool(processes=64)
     results = worker_pool.map(check_package_latest_version_worker, (pkg.name for pkg in packages))
     for pkg, r in zip(packages, results):
-        pkg.latest_version = r
+        pkg.latest_version['status'] = r[0]
+        pkg.latest_version['version'] = r[1]
+        pkg.latest_version['id'] = r[2]
     del http_pool
 
 
@@ -516,13 +517,13 @@ def calculate_stats(packages):
             stats["hash"] += 1
         else:
             stats["no-hash"] += 1
-        if pkg.latest_version[0] == RM_API_STATUS_FOUND_BY_DISTRO:
+        if pkg.latest_version['status'] == RM_API_STATUS_FOUND_BY_DISTRO:
             stats["rmo-mapping"] += 1
         else:
             stats["rmo-no-mapping"] += 1
-        if not pkg.latest_version[1]:
+        if not pkg.latest_version['version']:
             stats["version-unknown"] += 1
-        elif pkg.latest_version[1] == pkg.current_version:
+        elif pkg.latest_version['version'] == pkg.current_version:
             stats["version-uptodate"] += 1
         else:
             stats["version-not-uptodate"] += 1
@@ -688,29 +689,29 @@ def dump_html_pkg(f, pkg):
     f.write("  <td class=\"centered\">%s</td>\n" % current_version)
 
     # Latest version
-    if pkg.latest_version[0] == RM_API_STATUS_ERROR:
+    if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
         td_class.append("version-error")
-    if pkg.latest_version[1] is None:
+    if pkg.latest_version['version'] is None:
         td_class.append("version-unknown")
-    elif pkg.latest_version[1] != pkg.current_version:
+    elif pkg.latest_version['version'] != pkg.current_version:
         td_class.append("version-needs-update")
     else:
         td_class.append("version-good")
 
-    if pkg.latest_version[0] == RM_API_STATUS_ERROR:
+    if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
         latest_version_text = "<b>Error</b>"
-    elif pkg.latest_version[0] == RM_API_STATUS_NOT_FOUND:
+    elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
         latest_version_text = "<b>Not found</b>"
     else:
-        if pkg.latest_version[1] is None:
+        if pkg.latest_version['version'] is None:
             latest_version_text = "<b>Found, but no version</b>"
         else:
             latest_version_text = "<a href=\"https://release-monitoring.org/project/%s\"><b>%s</b></a>" % \
-                (pkg.latest_version[2], str(pkg.latest_version[1]))
+                (pkg.latest_version['id'], str(pkg.latest_version['version']))
 
         latest_version_text += "<br/>"
 
-        if pkg.latest_version[0] == RM_API_STATUS_FOUND_BY_DISTRO:
+        if pkg.latest_version['status'] == RM_API_STATUS_FOUND_BY_DISTRO:
             latest_version_text += "found by <a href=\"https://release-monitoring.org/distro/Buildroot/\">distro</a>"
         else:
             latest_version_text += "found by guess"
Signature · 1 line
2.20.1
#2 lore raw

PATCH v3 1/12 support/scripts/pkg-stats: store latest version in a dict

Hello Heiko and all,

Quoted 8 lines · Heiko Thiery

From: Heiko Thiery <[email protected]>

This patch changes the type of the latest_version variable to a dict. This is for better readability/usability of the data. With this the json output is more descriptive in later processing of the json output.

You signed off with both your personal and work email addresses, was this intended ?

Quoted 30 lines
---
  support/scripts/pkg-stats | 33 +++++++++++++++++----------------
  1 file changed, 17 insertions(+), 16 deletions(-)
support/scripts/pkg-stats +3 -4
index 7721d98459..8cc78f2f66 100755
@@ -71,7 +71,7 @@ class Package:
          self.url_status = None
          self.url_worker = None
          self.cves = list()
-        self.latest_version = (RM_API_STATUS_ERROR, None, None)
+        self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
 
      def pkgvar(self):
          return self.name.upper().replace("-", "_")
@@ -460,9 +460,8 @@ def check_package_latest_version(packages):
      """
      Fills in the .latest_version field of all Package objects
 
-    This field has a special format:
-      (status, version, id)
-    with:
+    This field is a dict and has the following keys:
+
      - status: one of RM_API_STATUS_ERROR,
        RM_API_STATUS_FOUND_BY_DISTRO, RM_API_STATUS_FOUND_BY_PATTERN,
        RM_API_STATUS_NOT_FOUND

support/scripts/pkg-stats def check_package_latest_version(packages): · line 482

Quoted 7 lines
support/scripts/pkg-stats +3 -1
@@ -478,7 +477,9 @@ def check_package_latest_version(packages):
      worker_pool = Pool(processes=64)
      results = worker_pool.map(check_package_latest_version_worker, (pkg.name for pkg in packages))
      for pkg, r in zip(packages, results):
-        pkg.latest_version = r
+        pkg.latest_version['status'] = r[0]
+        pkg.latest_version['version'] = r[1]
+        pkg.latest_version['id'] = r[2]

Bikeshedding, but maybe more elegant:

pkg.latest_version = dict(zip(['status', 'version', 'id'], r))

#3 lore raw

PATCH v3 1/12 support/scripts/pkg-stats: store latest version in a dict

Hi Titouan and all,

Am So., 23. Feb. 2020 um 14:26 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 10 lines

Hello Heiko and all,

Quoted 6 lines · Heiko Thiery

From: Heiko Thiery <[email protected]>

This patch changes the type of the latest_version variable to a dict. This is for better readability/usability of the data. With this the json output is more descriptive in later processing of the json output.

Quoted 5 lines
Quoted 2 lines

You signed off with both your personal and work email addresses, was this intended ?

No that was not intended. Thank you.

Quoted 40 lines
Quoted 30 lines
---
  support/scripts/pkg-stats | 33 +++++++++++++++++----------------
  1 file changed, 17 insertions(+), 16 deletions(-)
support/scripts/pkg-stats +3 -4
index 7721d98459..8cc78f2f66 100755
@@ -71,7 +71,7 @@ class Package:
          self.url_status = None
          self.url_worker = None
          self.cves = list()
-        self.latest_version = (RM_API_STATUS_ERROR, None, None)
+        self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
 
      def pkgvar(self):
          return self.name.upper().replace("-", "_")
@@ -460,9 +460,8 @@ def check_package_latest_version(packages):
      """
      Fills in the .latest_version field of all Package objects
 
-    This field has a special format:
-      (status, version, id)
-    with:
+    This field is a dict and has the following keys:
+
      - status: one of RM_API_STATUS_ERROR,
        RM_API_STATUS_FOUND_BY_DISTRO, RM_API_STATUS_FOUND_BY_PATTERN,
        RM_API_STATUS_NOT_FOUND

support/scripts/pkg-stats def check_package_latest_version(packages): · line 482

Quoted 7 lines
support/scripts/pkg-stats +3 -1
@@ -478,7 +477,9 @@ def check_package_latest_version(packages):
      worker_pool = Pool(processes=64)
      results = worker_pool.map(check_package_latest_version_worker, (pkg.name for pkg in packages))
      for pkg, r in zip(packages, results):
-        pkg.latest_version = r
+        pkg.latest_version['status'] = r[0]
+        pkg.latest_version['version'] = r[1]
+        pkg.latest_version['id'] = r[2]

Bikeshedding, but maybe more elegant:

Quoted 1 line

pkg.latest_version = dict(zip(['status', 'version', 'id'], r))

Yes, this is bikeshedding ;-) but a good one! I will change that.

Thank you
--
Heiko

#4 lore raw

PATCH v3 2/12 support/scripts/pkg-stats: store patch files for the package

In reply to Heiko Thiery

From: Heiko Thiery <[email protected]>

---
 support/scripts/pkg-stats | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +3 -2
index 8cc78f2f66..4c963cef0f 100755
@@ -65,6 +65,7 @@ class Package:
         self.has_license_files = False
         self.has_hash = False
         self.patch_count = 0
+        self.patch_files = []
         self.warnings = 0
         self.current_version = None
         self.url = None
@@ -131,10 +132,10 @@ class Package:
         """
         Fills in the .patch_count field
         """
-        self.patch_count = 0
         pkgdir = os.path.dirname(self.path)
         for subdir, _, _ in os.walk(pkgdir):
-            self.patch_count += len(fnmatch.filter(os.listdir(subdir), '*.patch'))
+            self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
+            self.patch_count = len(self.patch_files)
 
     def set_current_version(self):
         """
Signature · 1 line
2.20.1
#5 lore raw

PATCH v3 2/12 support/scripts/pkg-stats: store patch files for the package

Heiko,

Quoted 4 lines · Heiko Thiery

From: Heiko Thiery <[email protected]>

Same as the previous patch, signed off with 2 different email addresses.

Quoted 17 lines
---
  support/scripts/pkg-stats | 5 +++--
  1 file changed, 3 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +1 -0
index 8cc78f2f66..4c963cef0f 100755
@@ -65,6 +65,7 @@ class Package:
          self.has_license_files = False
          self.has_hash = False
          self.patch_count = 0
+        self.patch_files = []
          self.warnings = 0
          self.current_version = None
          self.url = None

support/scripts/pkg-stats class Package: · line 138

Quoted 9 lines
support/scripts/pkg-stats +2 -2
@@ -131,10 +132,10 @@ class Package:
          """
          Fills in the .patch_count field
          """
-        self.patch_count = 0
          pkgdir = os.path.dirname(self.path)
          for subdir, _, _ in os.walk(pkgdir):
-            self.patch_count += len(fnmatch.filter(os.listdir(subdir), '*.patch'))
+            self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
+            self.patch_count = len(self.patch_files)

We can compute the patch_count only once after looping through all the files, no need to update it at each step.

Maybe we can also entirely remove this patch_count attribute from the Package class, and define it as a property:

class Package:

     # ...
     @property
     def patch_count(self):
         return len(self.patch_files)

support/scripts/pkg-stats class Package:

Quoted 4 lines
support/scripts/pkg-stats +0 -0
      def set_current_version(self):
          """
 

Best regards,

Titouan

#6 lore raw

PATCH v3 2/12 support/scripts/pkg-stats: store patch files for the package

Hi Titouan and all,

Am So., 23. Feb. 2020 um 14:35 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 42 lines

Heiko,

Quoted 4 lines · Heiko Thiery

From: Heiko Thiery <[email protected]>

Same as the previous patch, signed off with 2 different email addresses.

Quoted 17 lines
---
  support/scripts/pkg-stats | 5 +++--
  1 file changed, 3 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +1 -0
index 8cc78f2f66..4c963cef0f 100755
@@ -65,6 +65,7 @@ class Package:
          self.has_license_files = False
          self.has_hash = False
          self.patch_count = 0
+        self.patch_files = []
          self.warnings = 0
          self.current_version = None
          self.url = None

support/scripts/pkg-stats class Package: · line 138

Quoted 9 lines
support/scripts/pkg-stats +2 -2
@@ -131,10 +132,10 @@ class Package:
          """
          Fills in the .patch_count field
          """
-        self.patch_count = 0
          pkgdir = os.path.dirname(self.path)
          for subdir, _, _ in os.walk(pkgdir):
-            self.patch_count += len(fnmatch.filter(os.listdir(subdir), '*.patch'))
+            self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
+            self.patch_count = len(self.patch_files)

We can compute the patch_count only once after looping through all the files, no need to update it at each step.

Quoted 2 lines

Maybe we can also entirely remove this patch_count attribute from the Package class, and define it as a property:

This is a good idea. Just to mention then we lose this field in the json output. I don't know if there is another user for that. I can change the fields in the buildroot-stats app to use the length of the patches list. Has anyone concerns about remove the attribute?

Quoted 7 lines

class Package:

     # ...
     @property
     def patch_count(self):
         return len(self.patch_files)
Quoted 1 line
Quoted 8 lines
Quoted 3 lines
      def set_current_version(self):
          """

Best regards,

Titouan

Thank you,
Heiko

#7 lore raw

PATCH v3 3/12 support/scripts/pkg-stats: set developers info

In reply to Heiko Thiery

Use the function 'parse_developers' function from getdeveloperlib that collect the information about the developers and the files they maintain. Then set the maintainer(s) to each package.

---
 support/scripts/pkg-stats | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
support/scripts/pkg-stats +16 -0
index 4c963cef0f..643272e9d2 100755
@@ -30,10 +30,14 @@ import certifi
 import distutils.version
 import time
 import gzip
+import sys
 from urllib3 import HTTPSConnectionPool
 from urllib3.exceptions import HTTPError
 from multiprocessing import Pool
 
+sys.path.append('utils/')
+from getdeveloperlib import parse_developers
+
 NVD_START_YEAR = 2002
 NVD_JSON_VERSION = "1.0"
 NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
@@ -169,6 +173,15 @@ class Package:
         """
         return cve in self.all_ignored_cves.get(self.pkgvar(), [])
 
+    def set_developers(self, developers):
+        """
+        Fills in the .developers field
+        """
+        self.developers = list()
+        for dev in developers:
+            if dev.hasfile(self.path):
+                self.developers.append((dev.name))
+
     def __eq__(self, other):
         return self.path == other.path
 
@@ -891,6 +904,8 @@ def __main__():
                                       'HEAD']).splitlines()[0]
     print("Build package list ...")
     packages = get_pkglist(args.npackages, package_list)
+    print("Getting developers ...")
+    developers = parse_developers()
     print("Getting package make info ...")
     package_init_make_info()
     print("Getting package details ...")
@@ -902,6 +917,7 @@ def __main__():
         pkg.set_check_package_warnings()
         pkg.set_current_version()
         pkg.set_url()
+        pkg.set_developers(developers)
     print("Checking URL status")
     check_package_urls(packages)
     print("Getting latest versions ...")
Signature · 1 line
2.20.1
#8 lore raw

PATCH v3 3/12 support/scripts/pkg-stats: set developers info

Heiko, all,

Quoted 29 lines · Heiko Thiery

Use the function 'parse_developers' function from getdeveloperlib that collect the information about the developers and the files they maintain. Then set the maintainer(s) to each package.

---
  support/scripts/pkg-stats | 16 ++++++++++++++++
  1 file changed, 16 insertions(+)
support/scripts/pkg-stats +4 -0
index 4c963cef0f..643272e9d2 100755
@@ -30,10 +30,14 @@ import certifi
  import distutils.version
  import time
  import gzip
+import sys
  from urllib3 import HTTPSConnectionPool
  from urllib3.exceptions import HTTPError
  from multiprocessing import Pool
 
+sys.path.append('utils/')
+from getdeveloperlib import parse_developers
+ >   NVD_START_YEAR = 2002
  NVD_JSON_VERSION = "1.0"
  NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
@@ -169,6 +173,15 @@ class Package:
          """

support/scripts/pkg-stats class Package: · line 183

Quoted 10 lines
support/scripts/pkg-stats +8 -0
          return cve in self.all_ignored_cves.get(self.pkgvar(), [])
 
+    def set_developers(self, developers):
+        """
+        Fills in the .developers field
+        """
+        self.developers = list()
+        for dev in developers:
+            if dev.hasfile(self.path):
+                self.developers.append((dev.name))

Bikeshedding again, but maybe more elegant like this:

self.developers = [
dev.name
for dev in developers
if dev.hasfile(self.path)
]

Quoted 22 lines
support/scripts/pkg-stats +4 -0
+
      def __eq__(self, other):
          return self.path == other.path
 
@@ -891,6 +904,8 @@ def __main__():
                                        'HEAD']).splitlines()[0]
      print("Build package list ...")
      packages = get_pkglist(args.npackages, package_list)
+    print("Getting developers ...")
+    developers = parse_developers()
      print("Getting package make info ...")
      package_init_make_info()
      print("Getting package details ...")
@@ -902,6 +917,7 @@ def __main__():
          pkg.set_check_package_warnings()
          pkg.set_current_version()
          pkg.set_url()
+        pkg.set_developers(developers)
      print("Checking URL status")
      check_package_urls(packages)
      print("Getting latest versions ...")
#9 lore raw

PATCH v3 3/12 support/scripts/pkg-stats: set developers info

Hi Titouan and all,

Am So., 23. Feb. 2020 um 14:45 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 34 lines

Heiko, all,

Quoted 31 lines · Heiko Thiery

Use the function 'parse_developers' function from getdeveloperlib that collect the information about the developers and the files they maintain. Then set the maintainer(s) to each package.

---
  support/scripts/pkg-stats | 16 ++++++++++++++++
  1 file changed, 16 insertions(+)
support/scripts/pkg-stats +4 -0
index 4c963cef0f..643272e9d2 100755
@@ -30,10 +30,14 @@ import certifi
  import distutils.version
  import time
  import gzip
+import sys
  from urllib3 import HTTPSConnectionPool
  from urllib3.exceptions import HTTPError
  from multiprocessing import Pool
 
+sys.path.append('utils/')
+from getdeveloperlib import parse_developers
+ >   NVD_START_YEAR = 2002
  NVD_JSON_VERSION = "1.0"
  NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
@@ -169,6 +173,15 @@ class Package:
          """
          return cve in self.all_ignored_cves.get(self.pkgvar(), [])
 
Quoted 10 lines

support/scripts/pkg-stats class Package: · line 183

Quoted 8 lines
support/scripts/pkg-stats +8 -0
+    def set_developers(self, developers):
+        """
+        Fills in the .developers field
+        """
+        self.developers = list()
+        for dev in developers:
+            if dev.hasfile(self.path):
+                self.developers.append((dev.name))

Bikeshedding again, but maybe more elegant like this:

Indeed this is more elegant. I will change that.

Quoted 28 lines

self.developers = [
dev.name
for dev in developers
if dev.hasfile(self.path)
]

Quoted 22 lines
support/scripts/pkg-stats +4 -0
+
      def __eq__(self, other):
          return self.path == other.path
 
@@ -891,6 +904,8 @@ def __main__():
                                        'HEAD']).splitlines()[0]
      print("Build package list ...")
      packages = get_pkglist(args.npackages, package_list)
+    print("Getting developers ...")
+    developers = parse_developers()
      print("Getting package make info ...")
      package_init_make_info()
      print("Getting package details ...")
@@ -902,6 +917,7 @@ def __main__():
          pkg.set_check_package_warnings()
          pkg.set_current_version()
          pkg.set_url()
+        pkg.set_developers(developers)
      print("Checking URL status")
      check_package_urls(packages)
      print("Getting latest versions ...")
#10 lore raw

PATCH v3 4/12 support/scripts/pkg-stats: store licences of package

In reply to Heiko Thiery
---
 support/scripts/pkg-stats | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +4 -2
index 643272e9d2..ebaf04465e 100755
@@ -56,7 +56,7 @@ http_pool = None
 
 
 class Package:
-    all_licenses = list()
+    all_licenses = dict()
     all_license_files = list()
     all_versions = dict()
     all_ignored_cves = dict()
@@ -65,6 +65,7 @@ class Package:
         self.name = name
         self.path = path
         self.infras = None
+        self.license = None
         self.has_license = False
         self.has_license_files = False
         self.has_hash = False
@@ -122,6 +123,7 @@ class Package:
         var = self.pkgvar()
         if var in self.all_licenses:
             self.has_license = True
+            self.license = self.all_licenses[var]
         if var in self.all_license_files:
             self.has_license_files = True
 
@@ -384,7 +386,7 @@ def package_init_make_info():
             if value == "unknown":
                 continue
             pkgvar = pkgvar[:-8]
-            Package.all_licenses.append(pkgvar)
+            Package.all_licenses[pkgvar] = value
 
         elif pkgvar.endswith("_LICENSE_FILES"):
             if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):
Signature · 1 line
2.20.1
#11 lore raw

PATCH v3 4/12 support/scripts/pkg-stats: store licences of package

Heiko and all,

Quoted 1 line · Heiko Thiery
Quoted 34 lines
---
  support/scripts/pkg-stats | 6 ++++--
  1 file changed, 4 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +3 -1
index 643272e9d2..ebaf04465e 100755
@@ -56,7 +56,7 @@ http_pool = None
 
 
  class Package:
-    all_licenses = list()
+    all_licenses = dict()
      all_license_files = list()
      all_versions = dict()
      all_ignored_cves = dict()
@@ -65,6 +65,7 @@ class Package:
          self.name = name
          self.path = path
          self.infras = None
+        self.license = None
          self.has_license = False
          self.has_license_files = False
          self.has_hash = False
@@ -122,6 +123,7 @@ class Package >           var = self.pkgvar()
          if var in self.all_licenses:
              self.has_license = True
+            self.license = self.all_licenses[var]
          if var in self.all_license_files:
              self.has_license_files = True
 

support/scripts/pkg-stats def package_init_make_info(): · line 392

Quoted 8 lines
support/scripts/pkg-stats +1 -1
@@ -384,7 +386,7 @@ def package_init_make_info():
              if value == "unknown":
                  continue
              pkgvar = pkgvar[:-8]
-            Package.all_licenses.append(pkgvar)
+            Package.all_licenses[pkgvar] = value
 
          elif pkgvar.endswith("_LICENSE_FILES"):
              if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):

Best regards,

Titouan

#12 lore raw

PATCH v3 5/12 support/scripts/pkg-stats: add package status

In reply to Heiko Thiery

Unify the status check information. The status is stored in a tuple. The first entry is the status that can be 'ok', 'warning' or 'error'. The second entry is a verbose message.

The following checks are performed:
- url: status of the URL check
- license: status of the license presence check
- license-files: status of the license file check
- hash: status of the hash file presence check
- patches: status of the patches count check
- pkg-check: status of the check-package script result
- developers: status if a package has developers in the DEVELOPERS file
- version: status of the version check

With that status information the following variables are replaced:
has_license, has_license_files, has_hash, url_status

---
 support/scripts/pkg-stats | 104 +++++++++++++++++++++++++-------------
 1 file changed, 69 insertions(+), 35 deletions(-)
support/scripts/pkg-stats +69 -35
index ebaf04465e..7c8cc81cf2 100755
@@ -66,18 +66,15 @@ class Package:
         self.path = path
         self.infras = None
         self.license = None
-        self.has_license = False
-        self.has_license_files = False
-        self.has_hash = False
         self.patch_count = 0
         self.patch_files = []
         self.warnings = 0
         self.current_version = None
         self.url = None
-        self.url_status = None
         self.url_worker = None
         self.cves = list()
         self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
+        self.status = {}
 
     def pkgvar(self):
         return self.name.upper().replace("-", "_")
@@ -86,17 +83,17 @@ class Package:
         """
         Fills in the .url field
         """
-        self.url_status = "No Config.in"
+        self.status['url'] = ("warning", "no Config.in")
         for filename in os.listdir(os.path.dirname(self.path)):
             if fnmatch.fnmatch(filename, 'Config.*'):
                 fp = open(os.path.join(os.path.dirname(self.path), filename), "r")
                 for config_line in fp:
                     if URL_RE.match(config_line):
                         self.url = config_line.strip()
-                        self.url_status = "Found"
+                        self.status['url'] = ("ok", "found")
                         fp.close()
                         return
-                self.url_status = "Missing"
+                self.status['url'] = ("warning", "missing")
                 fp.close()
 
     def set_infra(self):
@@ -118,31 +115,43 @@ class Package:
 
     def set_license(self):
         """
-        Fills in the .has_license and .has_license_files fields
+        Fills in the .status['license'] and .status['license-files'] fields
         """
         var = self.pkgvar()
+        self.status['license'] = ("error", "missing")
+        self.status['license-files'] = ("error", "missing")
         if var in self.all_licenses:
-            self.has_license = True
             self.license = self.all_licenses[var]
+            self.status['license'] = ("ok", "found")
         if var in self.all_license_files:
-            self.has_license_files = True
+            self.status['license-files'] = ("ok", "found")
 
     def set_hash_info(self):
         """
-        Fills in the .has_hash field
+        Fills in the .status['hash'] field
         """
         hashpath = self.path.replace(".mk", ".hash")
-        self.has_hash = os.path.exists(hashpath)
+        if os.path.exists(hashpath):
+            self.status['hash'] = ("ok", "found")
+        else:
+            self.status['hash'] = ("error", "missing")
 
     def set_patch_count(self):
         """
-        Fills in the .patch_count field
+        Fills in the .patch_count, .patch_files and .status['patches'] fields
         """
         pkgdir = os.path.dirname(self.path)
         for subdir, _, _ in os.walk(pkgdir):
             self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
             self.patch_count = len(self.patch_files)
 
+        if self.patch_count == 0:
+            self.status['patches'] = ("ok", "no patches")
+        elif self.patch_count < 5:
+            self.status['patches'] = ("warning", "some patches")
+        else:
+            self.status['patches'] = ("error", "lots of patches")
+
     def set_current_version(self):
         """
         Fills in the .current_version field
@@ -153,10 +162,11 @@ class Package:
 
     def set_check_package_warnings(self):
         """
-        Fills in the .warnings field
+        Fills in the .warnings and .status['pkg-check'] fields
         """
         cmd = ["./utils/check-package"]
         pkgdir = os.path.dirname(self.path)
+        self.status['pkg-check'] = ("error", "Missing")
         for root, dirs, files in os.walk(pkgdir):
             for f in files:
                 if f.endswith(".mk") or f.endswith(".hash") or f == "Config.in" or f == "Config.in.host":
@@ -167,6 +177,10 @@ class Package:
             m = re.match("^([0-9]*) warnings generated", line)
             if m:
                 self.warnings = int(m.group(1))
+                if self.warnings == 0:
+                    self.status['pkg-check'] = ("ok", "no warnings")
+                else:
+                    self.status['pkg-check'] = ("error", "{} warnings".format(self.warnings))
                 return
 
     def is_cve_ignored(self, cve):
@@ -177,13 +191,20 @@ class Package:
 
     def set_developers(self, developers):
         """
-        Fills in the .developers field
+        Fills in the .developers and .status['developers'] field
         """
         self.developers = list()
+        self.status['developers'] = ("warning", "no developers")
         for dev in developers:
             if dev.hasfile(self.path):
                 self.developers.append((dev.name))
 
+        if self.developers:
+            self.status['developers'] = ("ok", "{} developers".format(len(self.developers)))
+
+    def is_status_ok(self, name):
+        return self.status[name][0] == 'ok'
+
     def __eq__(self, other):
         return self.path == other.path
 
@@ -192,7 +213,7 @@ class Package:
 
     def __str__(self):
         return "%s (path='%s', license='%s', license_files='%s', hash='%s', patches=%d)" % \
-            (self.name, self.path, self.has_license, self.has_license_files, self.has_hash, self.patch_count)
+            (self.name, self.path, self.is_status_ok('license'), self.is_status_ok('license-files'), self.status['hash'], self.patch_count)
 
 
 class CVE:
@@ -406,23 +427,23 @@ def package_init_make_info():
 
 
 def check_url_status_worker(url, url_status):
-    if url_status != "Missing" and url_status != "No Config.in":
+    if url_status[0] == 'ok':
         try:
             url_status_code = requests.head(url, timeout=30).status_code
             if url_status_code >= 400:
-                return "Invalid(%s)" % str(url_status_code)
+                return ("error", "invalid {}".format(url_status_code))
         except requests.exceptions.RequestException:
-            return "Invalid(Err)"
-        return "Ok"
+            return ("error", "invalid (err)")
+        return ("ok", "valid")
     return url_status
 
 
 def check_package_urls(packages):
     Package.pool = Pool(processes=64)
     for pkg in packages:
-        pkg.url_worker = pkg.pool.apply_async(check_url_status_worker, (pkg.url, pkg.url_status))
+        pkg.url_worker = pkg.pool.apply_async(check_url_status_worker, (pkg.url, pkg.status['url']))
     for pkg in packages:
-        pkg.url_status = pkg.url_worker.get(timeout=3600)
+        pkg.status['url'] = pkg.url_worker.get(timeout=3600)
 
 
 def release_monitoring_get_latest_version_by_distro(pool, name):
@@ -496,6 +517,18 @@ def check_package_latest_version(packages):
         pkg.latest_version['status'] = r[0]
         pkg.latest_version['version'] = r[1]
         pkg.latest_version['id'] = r[2]
+
+        if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
+            pkg.status['version'] = ('warning', 'RM API error')
+        elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
+            pkg.status['version'] = ('warning', 'RM package not found')
+
+        if pkg.latest_version['version'] is None:
+            pkg.status['version'] = ('warning', 'no upstream version available')
+        elif pkg.latest_version['version'] != pkg.current_version:
+            pkg.status['version'] = ('error', 'package needs update')
+        else:
+            pkg.status['version'] = ('ok', 'up-to-date')
     del http_pool
 
 
@@ -521,15 +554,15 @@ def calculate_stats(packages):
             stats["infra-%s" % infra] += 1
         else:
             stats["infra-unknown"] += 1
-        if pkg.has_license:
+        if pkg.is_status_ok('license'):
             stats["license"] += 1
         else:
             stats["no-license"] += 1
-        if pkg.has_license_files:
+        if pkg.is_status_ok('license-files'):
             stats["license-files"] += 1
         else:
             stats["no-license-files"] += 1
-        if pkg.has_hash:
+        if pkg.is_status_ok('hash'):
             stats["hash"] += 1
         else:
             stats["no-hash"] += 1
@@ -672,30 +705,30 @@ def dump_html_pkg(f, pkg):
 
     # License
     td_class = ["centered"]
-    if pkg.has_license:
+    if pkg.is_status_ok('license'):
         td_class.append("correct")
     else:
         td_class.append("wrong")
     f.write("  <td class=\"%s\">%s</td>\n" %
-            (" ".join(td_class), boolean_str(pkg.has_license)))
+            (" ".join(td_class), boolean_str(pkg.is_status_ok('license'))))
 
     # License files
     td_class = ["centered"]
-    if pkg.has_license_files:
+    if pkg.is_status_ok('license-files'):
         td_class.append("correct")
     else:
         td_class.append("wrong")
     f.write("  <td class=\"%s\">%s</td>\n" %
-            (" ".join(td_class), boolean_str(pkg.has_license_files)))
+            (" ".join(td_class), boolean_str(pkg.is_status_ok('license-files'))))
 
     # Hash
     td_class = ["centered"]
-    if pkg.has_hash:
+    if pkg.is_status_ok('hash'):
         td_class.append("correct")
     else:
         td_class.append("wrong")
     f.write("  <td class=\"%s\">%s</td>\n" %
-            (" ".join(td_class), boolean_str(pkg.has_hash)))
+            (" ".join(td_class), boolean_str(pkg.is_status_ok('hash'))))
 
     # Current version
     if len(pkg.current_version) > 20:
@@ -746,12 +779,13 @@ def dump_html_pkg(f, pkg):
 
     # URL status
     td_class = ["centered"]
-    url_str = pkg.url_status
-    if pkg.url_status == "Missing" or pkg.url_status == "No Config.in":
+    url_str = pkg.status['url'][1]
+    if pkg.status['url'][0] == "warning":
+        td_class.append("missing_url")
+    elif pkg.status['url'][0] == "error":
         td_class.append("missing_url")
-    elif pkg.url_status.startswith("Invalid"):
         td_class.append("invalid_url")
-        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.url_status)
+        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.status['url'][1])
     else:
         td_class.append("good_url")
         url_str = "<a href=%s>Link</a>" % pkg.url
Signature · 1 line
2.20.1
#13 lore raw

PATCH v3 5/12 support/scripts/pkg-stats: add package status

Heiko, all,

First of all, I really like this idea, as it makes the status checks quite easier to extend !

Quoted 54 lines · Heiko Thiery

Unify the status check information. The status is stored in a tuple. The first entry is the status that can be 'ok', 'warning' or 'error'. The second entry is a verbose message.

The following checks are performed:
- url: status of the URL check
- license: status of the license presence check
- license-files: status of the license file check
- hash: status of the hash file presence check
- patches: status of the patches count check
- pkg-check: status of the check-package script result
- developers: status if a package has developers in the DEVELOPERS file
- version: status of the version check

With that status information the following variables are replaced:
has_license, has_license_files, has_hash, url_status

---
  support/scripts/pkg-stats | 104 +++++++++++++++++++++++++-------------
  1 file changed, 69 insertions(+), 35 deletions(-)
support/scripts/pkg-stats +2 -5
index ebaf04465e..7c8cc81cf2 100755
@@ -66,18 +66,15 @@ class Package:
          self.path = path
          self.infras = None
          self.license = None
-        self.has_license = False
-        self.has_license_files = False
-        self.has_hash = False
          self.patch_count = 0
          self.patch_files = []
          self.warnings = 0
          self.current_version = None
          self.url = None
-        self.url_status = None
          self.url_worker = None
          self.cves = list()
          self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
+        self.status = {}
 
      def pkgvar(self):
          return self.name.upper().replace("-", "_")
@@ -86,17 +83,17 @@ class Package:
          """
          Fills in the .url field
          """
-        self.url_status = "No Config.in"
+        self.status['url'] = ("warning", "no Config.in")
          for filename in os.listdir(os.path.dirname(self.path)):
              if fnmatch.fnmatch(filename, 'Config.*'):

support/scripts/pkg-stats class Package: · line 96

Quoted 10 lines
support/scripts/pkg-stats +2 -2
                  fp = open(os.path.join(os.path.dirname(self.path), filename), "r")
                  for config_line in fp:
                      if URL_RE.match(config_line):
                          self.url = config_line.strip()
-                        self.url_status = "Found"
+                        self.status['url'] = ("ok", "found")
                          fp.close()
                          return
-                self.url_status = "Missing"
+                self.status['url'] = ("warning", "missing")

I would set ("error", "missing") for consistency with what is done elsewhere

support/scripts/pkg-stats class Package: · line 99

Quoted 3 lines
support/scripts/pkg-stats +0 -0
                  fp.close()
 
      def set_infra(self):

[-- SNIP --]

Quoted 4 lines
support/scripts/pkg-stats +0 -0
@@ -496,6 +517,18 @@ def check_package_latest_version(packages):
          pkg.latest_version['status'] = r[0]
          pkg.latest_version['version'] = r[1]
          pkg.latest_version['id'] = r[2]

support/scripts/pkg-stats def check_package_latest_version(packages): · line 529

Quoted 10 lines
support/scripts/pkg-stats +10 -0
+
+        if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
+            pkg.status['version'] = ('warning', 'RM API error')
+        elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
+            pkg.status['version'] = ('warning', 'RM package not found')
+
+        if pkg.latest_version['version'] is None:
+            pkg.status['version'] = ('warning', 'no upstream version available')
+        elif pkg.latest_version['version'] != pkg.current_version:
+            pkg.status['version'] = ('error', 'package needs update')

We can make the messages a bit more explicit:
* "Release Monitoring API error"
* "Package not found on Release Monitoring"
* "The newer version {} is available
upstream".format(pkg.latest_version['version'])

support/scripts/pkg-stats def check_package_latest_version(packages): · line 534

Quoted 5 lines
support/scripts/pkg-stats +2 -0
+        else:
+            pkg.status['version'] = ('ok', 'up-to-date')
      del http_pool
 
 

[-- SNIP --]

support/scripts/pkg-stats def dump_html_pkg(f, pkg): · line 786

Quoted 11 lines
support/scripts/pkg-stats +4 -2
@@ -746,12 +779,13 @@ def dump_html_pkg(f, pkg):
 
      # URL status
      td_class = ["centered"]
-    url_str = pkg.url_status
-    if pkg.url_status == "Missing" or pkg.url_status == "No Config.in":
+    url_str = pkg.status['url'][1]
+    if pkg.status['url'][0] == "warning":
+        td_class.append("missing_url")
+    elif pkg.status['url'][0] == "error":
          td_class.append("missing_url")

The above 2 conditions can be simplified like:

+ if pkg.status['url'][0] in ("error", "warning"):
+ td_class.append("missing_url")

support/scripts/pkg-stats def dump_html_pkg(f, pkg): · line 791

Quoted 8 lines
support/scripts/pkg-stats +1 -2
-    elif pkg.url_status.startswith("Invalid"):
          td_class.append("invalid_url")
-        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.url_status)
+        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.status['url'][1])
      else:
          td_class.append("good_url")
          url_str = "<a href=%s>Link</a>" % pkg.url

Best regards,

Titouan

#14 lore raw

PATCH v3 5/12 support/scripts/pkg-stats: add package status

Am So., 23. Feb. 2020 um 16:19 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 65 lines

Heiko, all,

First of all, I really like this idea, as it makes the status checks quite easier to extend !

Quoted 58 lines · Heiko Thiery

Unify the status check information. The status is stored in a tuple. The first entry is the status that can be 'ok', 'warning' or 'error'. The second entry is a verbose message.

The following checks are performed:
- url: status of the URL check
- license: status of the license presence check
- license-files: status of the license file check
- hash: status of the hash file presence check
- patches: status of the patches count check
- pkg-check: status of the check-package script result
- developers: status if a package has developers in the DEVELOPERS file
- version: status of the version check

With that status information the following variables are replaced:
has_license, has_license_files, has_hash, url_status

---
  support/scripts/pkg-stats | 104 +++++++++++++++++++++++++-------------
  1 file changed, 69 insertions(+), 35 deletions(-)
support/scripts/pkg-stats +2 -5
index ebaf04465e..7c8cc81cf2 100755
@@ -66,18 +66,15 @@ class Package:
          self.path = path
          self.infras = None
          self.license = None
-        self.has_license = False
-        self.has_license_files = False
-        self.has_hash = False
          self.patch_count = 0
          self.patch_files = []
          self.warnings = 0
          self.current_version = None
          self.url = None
-        self.url_status = None
          self.url_worker = None
          self.cves = list()
          self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
+        self.status = {}
 
      def pkgvar(self):
          return self.name.upper().replace("-", "_")
@@ -86,17 +83,17 @@ class Package:
          """
          Fills in the .url field
          """
-        self.url_status = "No Config.in"
+        self.status['url'] = ("warning", "no Config.in")
          for filename in os.listdir(os.path.dirname(self.path)):
              if fnmatch.fnmatch(filename, 'Config.*'):
                  fp = open(os.path.join(os.path.dirname(self.path), filename), "r")
                  for config_line in fp:
                      if URL_RE.match(config_line):
                          self.url = config_line.strip()
Quoted 8 lines

support/scripts/pkg-stats class Package: · line 96

Quoted 6 lines
support/scripts/pkg-stats +2 -2
-                        self.url_status = "Found"
+                        self.status['url'] = ("ok", "found")
                          fp.close()
                          return
-                self.url_status = "Missing"
+                self.status['url'] = ("warning", "missing")

I would set ("error", "missing") for consistency with what is done elsewhere

Yes I will change that.

By the way I have no glue what is correct. What is an error and what is only a warning? I think this has to be a separate threat.

e.g. for the version check. Is it an error when a newer package is available and "only" a warning if the version cannot be checked against release monitoring version?

Quoted 19 lines

support/scripts/pkg-stats class Package: · line 99

Quoted 4 lines
support/scripts/pkg-stats +0 -0
                  fp.close()
 
      def set_infra(self):

[-- SNIP --]

Quoted 12 lines
support/scripts/pkg-stats +8 -0
@@ -496,6 +517,18 @@ def check_package_latest_version(packages):
          pkg.latest_version['status'] = r[0]
          pkg.latest_version['version'] = r[1]
          pkg.latest_version['id'] = r[2]
+
+        if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
+            pkg.status['version'] = ('warning', 'RM API error')
+        elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
+            pkg.status['version'] = ('warning', 'RM package not found')
+
+        if pkg.latest_version['version'] is None:
+            pkg.status['version'] = ('warning', 'no upstream version available')
Quoted 9 lines

support/scripts/pkg-stats def check_package_latest_version(packages): · line 529

Quoted 2 lines
support/scripts/pkg-stats +2 -0
+        elif pkg.latest_version['version'] != pkg.current_version:
+            pkg.status['version'] = ('error', 'package needs update')

We can make the messages a bit more explicit:
* "Release Monitoring API error"
* "Package not found on Release Monitoring"
* "The newer version {} is available
upstream".format(pkg.latest_version['version'])

ok

Quoted 22 lines

support/scripts/pkg-stats def check_package_latest_version(packages): · line 534

Quoted 5 lines
support/scripts/pkg-stats +2 -0
+        else:
+            pkg.status['version'] = ('ok', 'up-to-date')
      del http_pool
 
 

[-- SNIP --]

support/scripts/pkg-stats def dump_html_pkg(f, pkg): · line 786

Quoted 11 lines
support/scripts/pkg-stats +4 -2
@@ -746,12 +779,13 @@ def dump_html_pkg(f, pkg):
 
      # URL status
      td_class = ["centered"]
-    url_str = pkg.url_status
-    if pkg.url_status == "Missing" or pkg.url_status == "No Config.in":
+    url_str = pkg.status['url'][1]
+    if pkg.status['url'][0] == "warning":
+        td_class.append("missing_url")
+    elif pkg.status['url'][0] == "error":
          td_class.append("missing_url")

The above 2 conditions can be simplified like:

Review

Quoted 2 lines
(quoted diff) +2 -0
+    if pkg.status['url'][0] in ("error", "warning"):
+        td_class.append("missing_url")

But then I have to change this to:

@@ -778,10 +778,9 @@ def dump_html_pkg(f, pkg):

     # URL status
     td_class = ["centered"]
     url_str = pkg.status['url'][1]

- if pkg.status['url'][0] == "warning":
- td_class.append("missing_url")
- elif pkg.status['url'][0] == "error":
+ if pkg.status['url'][0] in ("error", "warning"):
td_class.append("missing_url")
+ if pkg.status['url'][0] == "error":

         td_class.append("invalid_url")
         url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.status['url'][1])
     else:

So the invalid_url class is set only in case of status 'error'. Right?

Quoted 10 lines

support/scripts/pkg-stats def dump_html_pkg(f, pkg): · line 791

Quoted 8 lines
support/scripts/pkg-stats +1 -2
-    elif pkg.url_status.startswith("Invalid"):
          td_class.append("invalid_url")
-        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.url_status)
+        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.status['url'][1])
      else:
          td_class.append("good_url")
          url_str = "<a href=%s>Link</a>" % pkg.url
Quoted 3 lines

Best regards,

Titouan

Thank you,
Heiko

#15 lore raw

PATCH v3 6/12 support/scripts/pkg-stats: add package count to stats

In reply to Heiko Thiery
---
 support/scripts/pkg-stats | 1 +
 1 file changed, 1 insertion(+)
support/scripts/pkg-stats +1 -0
index 7c8cc81cf2..90afcc2332 100755
@@ -544,6 +544,7 @@ def check_package_cves(nvd_path, packages):
 
 def calculate_stats(packages):
     stats = defaultdict(int)
+    stats['packages'] = len(packages)
     for pkg in packages:
         # If packages have multiple infra, take the first one. For the
         # vast majority of packages, the target and host infra are the
Signature · 1 line
2.20.1
#16 lore raw

PATCH v3 6/12 support/scripts/pkg-stats: add package count to stats

Heiko, all,

Quoted 1 line · Heiko Thiery
Signed-off-by Heiko Thiery <heiko.thiery at gmail.
Quoted 17 lines
---
  support/scripts/pkg-stats | 1 +
  1 file changed, 1 insertion(+)
support/scripts/pkg-stats +1 -0
index 7c8cc81cf2..90afcc2332 100755
@@ -544,6 +544,7 @@ def check_package_cves(nvd_path, packages):
 
  def calculate_stats(packages):
      stats = defaultdict(int)
+    stats['packages'] = len(packages)
      for pkg in packages:
          # If packages have multiple infra, take the first one. For the
          # vast majority of packages, the target and host infra are the
#17 lore raw

PATCH v3 7/12 support/scripts/pkg-stats: store pkg dir path

In reply to Heiko Thiery

This value can be used for later processing.

In the buildroot-stats application this is used to create links pointing to the git repo of buildroot.

---
 support/scripts/pkg-stats | 1 +
 1 file changed, 1 insertion(+)
support/scripts/pkg-stats +1 -0
index 90afcc2332..36b33586ef 100755
@@ -64,6 +64,7 @@ class Package:
     def __init__(self, name, path):
         self.name = name
         self.path = path
+        self.pkg_path = os.path.dirname(path)
         self.infras = None
         self.license = None
         self.patch_count = 0
Signature · 1 line
2.20.1
#18 lore raw

PATCH v3 7/12 support/scripts/pkg-stats: store pkg dir path

Hello Heiko,

Quoted 6 lines · Heiko Thiery

This value can be used for later processing.

In the buildroot-stats application this is used to create links pointing to the git repo of buildroot.

Quoted 10 lines
---
  support/scripts/pkg-stats | 1 +
  1 file changed, 1 insertion(+)
support/scripts/pkg-stats +0 -0
index 90afcc2332..36b33586ef 100755

support/scripts/pkg-stats class Package: · line 70

Quoted 7 lines
support/scripts/pkg-stats +1 -0
@@ -64,6 +64,7 @@ class Package:
      def __init__(self, name, path):
          self.name = name
          self.path = path
+        self.pkg_path = os.path.dirname(path)
          self.infras = None
          self.license = None
          self.patch_count = 0

Regards,

Titouan

#19 lore raw

PATCH v3 7/12 support/scripts/pkg-stats: store pkg dir path

HI Titouan and all,

Am So., 23. Feb. 2020 um 16:20 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 33 lines

Hello Heiko,

Quoted 6 lines · Heiko Thiery

This value can be used for later processing.

In the buildroot-stats application this is used to create links pointing to the git repo of buildroot.

Quoted 10 lines
---
  support/scripts/pkg-stats | 1 +
  1 file changed, 1 insertion(+)
support/scripts/pkg-stats +0 -0
index 90afcc2332..36b33586ef 100755

support/scripts/pkg-stats class Package: · line 70

Quoted 7 lines
support/scripts/pkg-stats +1 -0
@@ -64,6 +64,7 @@ class Package:
      def __init__(self, name, path):
          self.name = name
          self.path = path
+        self.pkg_path = os.path.dirname(path)
          self.infras = None
          self.license = None
          self.patch_count = 0

Regards,

Quoted 1 line

Titouan

Thank you,
Heiko

#20 lore raw

PATCH v3 8/12 support/scripts/pkg-stats: add defconfig support

In reply to Heiko Thiery

Scan configs directory and create Defconfig objects.

---
 support/scripts/pkg-stats | 42 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +40 -2
index 36b33586ef..ae70f90485 100755
@@ -54,6 +54,33 @@ RM_API_STATUS_NOT_FOUND = 4
 # because it's used by sub-processes.
 http_pool = None
 
+class Defconfig:
+    def __init__(self, name, path):
+        self.name = name
+        self.path = path
+        self.developers = None
+
+    def set_developers(self, developers):
+        """
+        Fills in the .developers field
+        """
+        self.developers = list()
+        for dev in developers:
+            if dev.hasfile(self.path):
+                self.developers.append(dev.name)
+
+def get_defconfig_list():
+     """
+     Builds the list of Buildroot defconfigs, returning a list of Defconfig
+     objects.
+     """
+     defconfigs = list()
+     files = [f for f in os.listdir('configs')]
+     for name in files:
+         d = Defconfig(name[:-10], os.path.join('configs', name))
+         defconfigs.append(d)
+     return defconfigs
+
 
 class Package:
     all_licenses = dict()
@@ -882,7 +909,7 @@ def dump_html(packages, stats, date, commit, output):
         f.write(html_footer)
 
 
-def dump_json(packages, stats, date, commit, output):
+def dump_json(packages, defconfigs, stats, date, commit, output):
     # Format packages as a dictionnary instead of a list
     # Exclude local field that does not contains real date
     excluded_fields = ['url_worker', 'name']
@@ -893,6 +920,12 @@ def dump_json(packages, stats, date, commit, output):
             if k not in excluded_fields
         } for pkg in packages
     }
+    defconfigs = {
+        d.name: {
+            k: v
+            for k, v in d.__dict__.items()
+        } for d in defconfigs
+    }
     # Aggregate infrastructures into a single dict entry
     statistics = {
         k: v
@@ -903,6 +936,7 @@ def dump_json(packages, stats, date, commit, output):
     # The actual structure to dump, add commit and date to it
     final = {'packages': pkgs,
              'stats': statistics,
+             'defconfigs': defconfigs,
              'commit': commit,
              'date': str(date)}
 
@@ -944,6 +978,10 @@ def __main__():
     packages = get_pkglist(args.npackages, package_list)
     print("Getting developers ...")
     developers = parse_developers()
+    print("Build defconfig list ...")
+    defconfigs = get_defconfig_list()
+    for d in defconfigs:
+        d.set_developers(developers)
     print("Getting package make info ...")
     package_init_make_info()
     print("Getting package details ...")
@@ -970,7 +1008,7 @@ def __main__():
         dump_html(packages, stats, date, commit, args.html)
     if args.json:
         print("Write JSON")
-        dump_json(packages, stats, date, commit, args.json)
+        dump_json(packages, defconfigs, stats, date, commit, args.json)
 
 
 __main__()
Signature · 1 line
2.20.1
#21 lore raw

PATCH v3 8/12 support/scripts/pkg-stats: add defconfig support

Heiko, all,

Nice to add the defconfigs to the stats !

Quoted 19 lines · Heiko Thiery

Scan configs directory and create Defconfig objects.

---
  support/scripts/pkg-stats | 42 +++++++++++++++++++++++++++++++++++++--
  1 file changed, 40 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +4 -0
index 36b33586ef..ae70f90485 100755
@@ -54,6 +54,33 @@ RM_API_STATUS_NOT_FOUND = 4
  # because it's used by sub-processes.
  http_pool = None
 
+class Defconfig:
+    def __init__(self, name, path):
+        self.name = name
+        self.path = path

support/scripts/pkg-stats RM_API_STATUS_NOT_FOUND = 4 · line 70

Quoted 10 lines
support/scripts/pkg-stats +10 -0
+        self.developers = None
+
+    def set_developers(self, developers):
+        """
+        Fills in the .developers field
+        """
+        self.developers = list()
+        for dev in developers:
+            if dev.hasfile(self.path):
+                self.developers.append(dev.name)

self.developers = [
dev.name
for dev in developers
if dev.hasfile(self.path)
]

support/scripts/pkg-stats RM_API_STATUS_NOT_FOUND = 4 · line 82

Quoted 12 lines
support/scripts/pkg-stats +12 -0
+
+def get_defconfig_list():
+     """
+     Builds the list of Buildroot defconfigs, returning a list of Defconfig
+     objects.
+     """
+     defconfigs = list()
+     files = [f for f in os.listdir('configs')]
+     for name in files:
+         d = Defconfig(name[:-10], os.path.join('configs', name))
+         defconfigs.append(d)
+     return defconfigs

return [
Defconfig(name[:-10], os.path.join('configs', name)) for name in os.listdir('configs')
]

Quoted 55 lines
support/scripts/pkg-stats +14 -2
+
 
  class Package:
      all_licenses = dict()
@@ -882,7 +909,7 @@ def dump_html(packages, stats, date, commit, output):
          f.write(html_footer)
 
 
-def dump_json(packages, stats, date, commit, output):
+def dump_json(packages, defconfigs, stats, date, commit, output):
      # Format packages as a dictionnary instead of a list
      # Exclude local field that does not contains real date
      excluded_fields = ['url_worker', 'name']
@@ -893,6 +920,12 @@ def dump_json(packages, stats, date, commit, output):
              if k not in excluded_fields
          } for pkg in packages
      }
+    defconfigs = {
+        d.name: {
+            k: v
+            for k, v in d.__dict__.items()
+        } for d in defconfigs
+    }
      # Aggregate infrastructures into a single dict entry
      statistics = {
          k: v
@@ -903,6 +936,7 @@ def dump_json(packages, stats, date, commit, output):
      # The actual structure to dump, add commit and date to it
      final = {'packages': pkgs,
               'stats': statistics,
+             'defconfigs': defconfigs,
               'commit': commit,
               'date': str(date)}
 
@@ -944,6 +978,10 @@ def __main__():
      packages = get_pkglist(args.npackages, package_list)
      print("Getting developers ...")
      developers = parse_developers()
+    print("Build defconfig list ...")
+    defconfigs = get_defconfig_list()
+    for d in defconfigs:
+        d.set_developers(developers)
      print("Getting package make info ...")
      package_init_make_info()
      print("Getting package details ...")
@@ -970,7 +1008,7 @@ def __main__():
          dump_html(packages, stats, date, commit, args.html)
      if args.json:
          print("Write JSON")
-        dump_json(packages, stats, date, commit, args.json)
+        dump_json(packages, defconfigs, stats, date, commit, args.json)
 
 
  __main__()
#22 lore raw

PATCH v3 9/12 support/scripts/pkg-stats: add support for license hash check

In reply to Heiko Thiery

Store the names of license files and check if they are in the hash file.

---
 support/scripts/pkg-stats | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +14 -2
index ae70f90485..e954dd125e 100755
@@ -84,7 +84,7 @@ def get_defconfig_list():
 
 class Package:
     all_licenses = dict()
-    all_license_files = list()
+    all_license_files = dict()
     all_versions = dict()
     all_ignored_cves = dict()
 
@@ -94,6 +94,7 @@ class Package:
         self.pkg_path = os.path.dirname(path)
         self.infras = None
         self.license = None
+        self.license_files = None
         self.patch_count = 0
         self.patch_files = []
         self.warnings = 0
@@ -152,6 +153,7 @@ class Package:
             self.license = self.all_licenses[var]
             self.status['license'] = ("ok", "found")
         if var in self.all_license_files:
+            self.license_files = self.all_license_files[var].split(' ')
             self.status['license-files'] = ("ok", "found")
 
     def set_hash_info(self):
@@ -159,8 +161,18 @@ class Package:
         Fills in the .status['hash'] field
         """
         hashpath = self.path.replace(".mk", ".hash")
+        self.status['hash-license'] = ("na", "no hash file")
         if os.path.exists(hashpath):
             self.status['hash'] = ("ok", "found")
+            self.status['hash-license'] = ("error", "no license in hash file")
+            # check if license files are in hash file
+            if self.license_files is not None:
+                self.status['hash-license'] = ("ok", "found")
+                with open(hashpath) as f:
+                    content = f.read()
+                    for license in self.license_files:
+                        if content.find(license) == -1:
+                            self.status['hash-license'] = ("error", "license missing in hash file")
         else:
             self.status['hash'] = ("error", "missing")
 
@@ -441,7 +453,7 @@ def package_init_make_info():
             if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):
                 continue
             pkgvar = pkgvar[:-14]
-            Package.all_license_files.append(pkgvar)
+            Package.all_license_files[pkgvar] = value
 
         elif pkgvar.endswith("_VERSION"):
             if pkgvar.endswith("_DL_VERSION"):
Signature · 1 line
2.20.1
#23 lore raw

PATCH v3 9/12 support/scripts/pkg-stats: add support for license hash check

Heiko, all,

Quoted 3 lines · Heiko Thiery

Store the names of license files and check if they are in the hash file.

Quoted 62 lines
---
  support/scripts/pkg-stats | 16 ++++++++++++++--
  1 file changed, 14 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +14 -2
index ae70f90485..e954dd125e 100755
@@ -84,7 +84,7 @@ def get_defconfig_list():
 
  class Package:
      all_licenses = dict()
-    all_license_files = list()
+    all_license_files = dict()
      all_versions = dict()
      all_ignored_cves = dict()
 
@@ -94,6 +94,7 @@ class Package:
          self.pkg_path = os.path.dirname(path)
          self.infras = None
          self.license = None
+        self.license_files = None
          self.patch_count = 0
          self.patch_files = []
          self.warnings = 0
@@ -152,6 +153,7 @@ class Package:
              self.license = self.all_licenses[var]
              self.status['license'] = ("ok", "found")
          if var in self.all_license_files:
+            self.license_files = self.all_license_files[var].split(' ')
              self.status['license-files'] = ("ok", "found")
 
      def set_hash_info(self):
@@ -159,8 +161,18 @@ class Package:
          Fills in the .status['hash'] field
          """
          hashpath = self.path.replace(".mk", ".hash")
+        self.status['hash-license'] = ("na", "no hash file")
          if os.path.exists(hashpath):
              self.status['hash'] = ("ok", "found")
+            self.status['hash-license'] = ("error", "no license in hash file")
+            # check if license files are in hash file
+            if self.license_files is not None:
+                self.status['hash-license'] = ("ok", "found")
+                with open(hashpath) as f:
+                    content = f.read()
+                    for license in self.license_files:
+                        if content.find(license) == -1:
+                            self.status['hash-license'] = ("error", "license missing in hash file")
          else:
              self.status['hash'] = ("error", "missing")
 
@@ -441,7 +453,7 @@ def package_init_make_info():
              if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):
                  continue
              pkgvar = pkgvar[:-14]
-            Package.all_license_files.append(pkgvar)
+            Package.all_license_files[pkgvar] = value
 
          elif pkgvar.endswith("_VERSION"):
              if pkgvar.endswith("_DL_VERSION"):
#24 lore raw

PATCH v3 10/12 support/scripts/pkg-stats: set status to 'na' for virtual packages

In reply to Heiko Thiery

If there is no infra set or infra is virtual the status is set to 'na'.

This is done for the follwing checks:
- license
- license-files
- hash
- hash-license
- patches
- version

---
 support/scripts/pkg-stats | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
support/scripts/pkg-stats +26 -0
index e954dd125e..be3b6d7e71 100755
@@ -125,6 +125,14 @@ class Package:
                 self.status['url'] = ("warning", "missing")
                 fp.close()
 
+    def is_valid_infra(self):
+        try:
+            if self.infras[0][1] == 'virtual':
+                return False
+        except IndexError:
+                return False
+        return True
+
     def set_infra(self):
         """
         Fills in the .infras field
@@ -146,6 +154,11 @@ class Package:
         """
         Fills in the .status['license'] and .status['license-files'] fields
         """
+        if self.is_valid_infra() == False:
+            self.status['license'] = ("na", "no valid package infra")
+            self.status['license-files'] = ("na", "no valid package infra")
+            return
+
         var = self.pkgvar()
         self.status['license'] = ("error", "missing")
         self.status['license-files'] = ("error", "missing")
@@ -160,6 +173,11 @@ class Package:
         """
         Fills in the .status['hash'] field
         """
+        if self.is_valid_infra() == False:
+            self.status['hash'] = ("na", "no valid package infra")
+            self.status['hash-license'] = ("na", "no valid package infra")
+            return
+
         hashpath = self.path.replace(".mk", ".hash")
         self.status['hash-license'] = ("na", "no hash file")
         if os.path.exists(hashpath):
@@ -180,6 +198,10 @@ class Package:
         """
         Fills in the .patch_count, .patch_files and .status['patches'] fields
         """
+        if self.is_valid_infra() == False:
+            self.status['patches'] = ("na", "no valid package infra")
+            return
+
         pkgdir = os.path.dirname(self.path)
         for subdir, _, _ in os.walk(pkgdir):
             self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
@@ -558,6 +580,10 @@ def check_package_latest_version(packages):
         pkg.latest_version['version'] = r[1]
         pkg.latest_version['id'] = r[2]
 
+        if pkg.is_valid_infra() == False:
+            pkg.status['version'] = ("na", "no valid package infra")
+            continue
+
         if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
             pkg.status['version'] = ('warning', 'RM API error')
         elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
Signature · 1 line
2.20.1
#25 lore raw

PATCH v3 10/12 support/scripts/pkg-stats: set status to 'na' for virtual packages

Hello Heiko and all,

Quoted 20 lines · Heiko Thiery

If there is no infra set or infra is virtual the status is set to 'na'.

This is done for the follwing checks:
- license
- license-files
- hash
- hash-license
- patches
- version

---
  support/scripts/pkg-stats | 26 ++++++++++++++++++++++++++
  1 file changed, 26 insertions(+)
support/scripts/pkg-stats +0 -0
index e954dd125e..be3b6d7e71 100755

support/scripts/pkg-stats class Package: · line 134

Quoted 10 lines
support/scripts/pkg-stats +7 -0
@@ -125,6 +125,14 @@ class Package:
                  self.status['url'] = ("warning", "missing")
                  fp.close()
 
+    def is_valid_infra(self):
+        try:
+            if self.infras[0][1] == 'virtual':
+                return False
+        except IndexError:
+                return False
+        return True

You might want to make a property of this (like CVE.identifier). I would also suggest has_valid_infra, because a package is not an infrastructure :-)

support/scripts/pkg-stats class Package: · line 157

Quoted 9 lines
support/scripts/pkg-stats +2 -0
+
      def set_infra(self):
          """
          Fills in the .infras field
@@ -146,6 +154,11 @@ class Package:
          """
          Fills in the .status['license'] and .status['license-files'] fields
          """
+        if self.is_valid_infra() == False:

if not self.is_valid_infra():

(or using a property, if not self.has_valid_infra:)

support/scripts/pkg-stats class Package: · line 176

Quoted 12 lines
support/scripts/pkg-stats +5 -0
+            self.status['license'] = ("na", "no valid package infra")
+            self.status['license-files'] = ("na", "no valid package infra")
+            return
+
          var = self.pkgvar()
          self.status['license'] = ("error", "missing")
          self.status['license-files'] = ("error", "missing")
@@ -160,6 +173,11 @@ class Package:
          """
          Fills in the .status['hash'] field
          """
+        if self.is_valid_infra() == False:

same

support/scripts/pkg-stats class Package: · line 201

Quoted 12 lines
support/scripts/pkg-stats +5 -0
+            self.status['hash'] = ("na", "no valid package infra")
+            self.status['hash-license'] = ("na", "no valid package infra")
+            return
+
          hashpath = self.path.replace(".mk", ".hash")
          self.status['hash-license'] = ("na", "no hash file")
          if os.path.exists(hashpath):
@@ -180,6 +198,10 @@ class Package:
          """
          Fills in the .patch_count, .patch_files and .status['patches'] fields
          """
+        if self.is_valid_infra() == False:

same

support/scripts/pkg-stats def check_package_latest_version(packages): · line 583

Quoted 11 lines
support/scripts/pkg-stats +4 -0
+            self.status['patches'] = ("na", "no valid package infra")
+            return
+
          pkgdir = os.path.dirname(self.path)
          for subdir, _, _ in os.walk(pkgdir):
              self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
@@ -558,6 +580,10 @@ def check_package_latest_version(packages):
          pkg.latest_version['version'] = r[1]
          pkg.latest_version['id'] = r[2]
 
+        if pkg.is_valid_infra() == False:

same

support/scripts/pkg-stats def check_package_latest_version(packages): · line 589

Quoted 7 lines
support/scripts/pkg-stats +3 -0
+            pkg.status['version'] = ("na", "no valid package infra")
+            continue
+
          if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
              pkg.status['version'] = ('warning', 'RM API error')
          elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:

Regards,

Titouan

#26 lore raw

PATCH v3 10/12 support/scripts/pkg-stats: set status to 'na' for virtual packages

Hi Titouan and all,

Am So., 23. Feb. 2020 um 17:11 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 93 lines

Hello Heiko and all,

Quoted 20 lines · Heiko Thiery

If there is no infra set or infra is virtual the status is set to 'na'.

This is done for the follwing checks:
- license
- license-files
- hash
- hash-license
- patches
- version

---
  support/scripts/pkg-stats | 26 ++++++++++++++++++++++++++
  1 file changed, 26 insertions(+)
support/scripts/pkg-stats +0 -0
index e954dd125e..be3b6d7e71 100755

support/scripts/pkg-stats class Package: · line 134

Quoted 10 lines
support/scripts/pkg-stats +7 -0
@@ -125,6 +125,14 @@ class Package:
                  self.status['url'] = ("warning", "missing")
                  fp.close()
 
+    def is_valid_infra(self):
+        try:
+            if self.infras[0][1] == 'virtual':
+                return False
+        except IndexError:
+                return False
+        return True

You might want to make a property of this (like CVE.identifier). I would also suggest has_valid_infra, because a package is not an infrastructure :-)

support/scripts/pkg-stats class Package: · line 157

Quoted 9 lines
support/scripts/pkg-stats +2 -0
+
      def set_infra(self):
          """
          Fills in the .infras field
@@ -146,6 +154,11 @@ class Package:
          """
          Fills in the .status['license'] and .status['license-files'] fields
          """
+        if self.is_valid_infra() == False:

if not self.is_valid_infra():

(or using a property, if not self.has_valid_infra:)

support/scripts/pkg-stats class Package: · line 176

Quoted 12 lines
support/scripts/pkg-stats +5 -0
+            self.status['license'] = ("na", "no valid package infra")
+            self.status['license-files'] = ("na", "no valid package infra")
+            return
+
          var = self.pkgvar()
          self.status['license'] = ("error", "missing")
          self.status['license-files'] = ("error", "missing")
@@ -160,6 +173,11 @@ class Package:
          """
          Fills in the .status['hash'] field
          """
+        if self.is_valid_infra() == False:

same

support/scripts/pkg-stats class Package: · line 201

Quoted 12 lines
support/scripts/pkg-stats +5 -0
+            self.status['hash'] = ("na", "no valid package infra")
+            self.status['hash-license'] = ("na", "no valid package infra")
+            return
+
          hashpath = self.path.replace(".mk", ".hash")
          self.status['hash-license'] = ("na", "no hash file")
          if os.path.exists(hashpath):
@@ -180,6 +198,10 @@ class Package:
          """
          Fills in the .patch_count, .patch_files and .status['patches'] fields
          """
+        if self.is_valid_infra() == False:

same

Quoted 10 lines
support/scripts/pkg-stats +3 -0
+            self.status['patches'] = ("na", "no valid package infra")
+            return
+
          pkgdir = os.path.dirname(self.path)
          for subdir, _, _ in os.walk(pkgdir):
              self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
@@ -558,6 +580,10 @@ def check_package_latest_version(packages):
          pkg.latest_version['version'] = r[1]
          pkg.latest_version['id'] = r[2]
 
Quoted 12 lines

support/scripts/pkg-stats def check_package_latest_version(packages): · line 583

Quoted 1 line
support/scripts/pkg-stats +1 -0
+        if pkg.is_valid_infra() == False:

same

Quoted 7 lines
support/scripts/pkg-stats +3 -0
+            pkg.status['version'] = ("na", "no valid package infra")
+            continue
+
          if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
              pkg.status['version'] = ('warning', 'RM API error')
          elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:

You're right with all of them .. I will change that for v4.

Quoted 4 lines

Regards,

Titouan

Thank you,
Heiko

#27 lore raw

PATCH v3 11/12 support/scripts/pkg-stats: initialize all package status checks

In reply to Heiko Thiery

Also the list of status checks is added to the json output.

---
 support/scripts/pkg-stats | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
support/scripts/pkg-stats +14 -0
index be3b6d7e71..ed22f6b650 100755
@@ -87,6 +87,11 @@ class Package:
     all_license_files = dict()
     all_versions = dict()
     all_ignored_cves = dict()
+    # This is the list of all possible checks. Add new checks to this list so
+    # a tool that post-processeds the json output knows the checks before
+    # iterating over the packages.
+    status_checks = ['cve', 'developers', 'hash', 'hash-license', 'license',
+                     'license-files', 'patches', 'pkg-check', 'url', 'version']
 
     def __init__(self, name, path):
         self.name = name
@@ -103,7 +108,15 @@ class Package:
         self.url_worker = None
         self.cves = list()
         self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
+        self.init_check_status()
+
+    def init_check_status(self):
+        """
+        Set the status of all check to 'na'.
+        """
         self.status = {}
+        for check in self.status_checks:
+            self.status[check] = ("na", "no status check done")
 
     def pkgvar(self):
         return self.name.upper().replace("-", "_")
@@ -975,6 +988,7 @@ def dump_json(packages, defconfigs, stats, date, commit, output):
     final = {'packages': pkgs,
              'stats': statistics,
              'defconfigs': defconfigs,
+             'package_status_checks': Package.status_checks,
              'commit': commit,
              'date': str(date)}
 
Signature · 1 line
2.20.1
#28 lore raw

PATCH v3 11/12 support/scripts/pkg-stats: initialize all package status checks

Heiko, all,

Quoted 25 lines · Heiko Thiery

Also the list of status checks is added to the json output.

  support/scripts/pkg-stats | 14 ++++++++++++++
  1 file changed, 14 insertions(+)
support/scripts/pkg-stats +5 -0
index be3b6d7e71..ed22f6b650 100755
@@ -87,6 +87,11 @@ class Package:
      all_license_files = dict()
      all_versions = dict()
      all_ignored_cves = dict()
+    # This is the list of all possible checks. Add new checks to this list so
+    # a tool that post-processeds the json output knows the checks before
+    # iterating over the packages.
+    status_checks = ['cve', 'developers', 'hash', 'hash-license', 'license',
+                     'license-files', 'patches', 'pkg-check', 'url', 'version']
 
      def __init__(self, name, path):
          self.name = name
@@ -103,7 +108,15 @@ class Package:
          self.url_worker = None
          self.cves = list()

support/scripts/pkg-stats class Package: · line 119

Quoted 10 lines
support/scripts/pkg-stats +8 -0
          self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
+        self.init_check_status()
+
+    def init_check_status(self):
+        """
+        Set the status of all check to 'na'.
+        """
          self.status = {}
+        for check in self.status_checks:
+            self.status[check] = ("na", "no status check done")

In my opinion, it would be better to leave this dictionary empty at initialization.

Because we know the list of status checks that have been run (from the 'package_status_checks' list you introduce below), we can tell that a particular status check has not been done if its name is missing from the status dictionary. This would make the outputted json a tad smaller.

Quoted 12 lines
support/scripts/pkg-stats +1 -0
      def pkgvar(self):
          return self.name.upper().replace("-", "_")
@@ -975,6 +988,7 @@ def dump_json(packages, defconfigs, stats, date, commit, output):
      final = {'packages': pkgs,
               'stats': statistics,
               'defconfigs': defconfigs,
+             'package_status_checks': Package.status_checks,
               'commit': commit,
               'date': str(date)}
 
#29 lore raw

PATCH v3 11/12 support/scripts/pkg-stats: initialize all package status checks

Hi Titouan,

Am So., 23. Feb. 2020 um 15:09 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 43 lines

Heiko, all,

Quoted 25 lines · Heiko Thiery

Also the list of status checks is added to the json output.

  support/scripts/pkg-stats | 14 ++++++++++++++
  1 file changed, 14 insertions(+)
support/scripts/pkg-stats +5 -0
index be3b6d7e71..ed22f6b650 100755
@@ -87,6 +87,11 @@ class Package:
      all_license_files = dict()
      all_versions = dict()
      all_ignored_cves = dict()
+    # This is the list of all possible checks. Add new checks to this list so
+    # a tool that post-processeds the json output knows the checks before
+    # iterating over the packages.
+    status_checks = ['cve', 'developers', 'hash', 'hash-license', 'license',
+                     'license-files', 'patches', 'pkg-check', 'url', 'version']
 
      def __init__(self, name, path):
          self.name = name
@@ -103,7 +108,15 @@ class Package:
          self.url_worker = None
          self.cves = list()

support/scripts/pkg-stats class Package: · line 119

Quoted 10 lines
support/scripts/pkg-stats +8 -0
          self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
+        self.init_check_status()
+
+    def init_check_status(self):
+        """
+        Set the status of all check to 'na'.
+        """
          self.status = {}
+        for check in self.status_checks:
+            self.status[check] = ("na", "no status check done")

In my opinion, it would be better to leave this dictionary empty at initialization.

Quoted 5 lines

Because we know the list of status checks that have been run (from the 'package_status_checks' list you introduce below), we can tell that a particular status check has not been done if its name is missing from the status dictionary. This would make the outputted json a tad smaller.

No problem. As for the cve check this can lead to wrong status.

Quoted 12 lines

support/scripts/pkg-stats def dump_json(packages, defconfigs, stats, date, commit, output): · line 994

Quoted 12 lines
support/scripts/pkg-stats +1 -0
      def pkgvar(self):
          return self.name.upper().replace("-", "_")
@@ -975,6 +988,7 @@ def dump_json(packages, defconfigs, stats, date, commit, output):
      final = {'packages': pkgs,
               'stats': statistics,
               'defconfigs': defconfigs,
+             'package_status_checks': Package.status_checks,
               'commit': commit,
               'date': str(date)}
 

Thank you,
Heiko

#30 lore raw

PATCH v3 12/12 support/scripts/pkg-stats: add status for cve check

In reply to Heiko Thiery
---
 support/scripts/pkg-stats | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +8 -2
index ed22f6b650..4efff8624f 100755
@@ -617,8 +617,14 @@ def check_package_cves(nvd_path, packages):
 
     for cve in CVE.read_nvd_dir(nvd_path):
         for pkg_name in cve.pkg_names:
-            if pkg_name in packages and cve.affects(packages[pkg_name]):
-                packages[pkg_name].cves.append(cve.identifier)
+            if pkg_name in packages:
+                if cve.affects(packages[pkg_name]):
+                    packages[pkg_name].cves.append(cve.identifier)
+                if len(packages[pkg_name].cves):
+                    packages[pkg_name].status['cve'] = ('error', 'affected by cve')
+                else:
+                    packages[pkg_name].status['cve'] = ('ok', 'no cve found')
+
 
 
 def calculate_stats(packages):
Signature · 1 line
2.20.1
#31 lore raw

PATCH v3 12/12 support/scripts/pkg-stats: add status for cve check

Heiko, all,

Quoted 17 lines · Heiko Thiery
---
  support/scripts/pkg-stats | 10 ++++++++--
  1 file changed, 8 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +1 -2
index ed22f6b650..4efff8624f 100755
@@ -617,8 +617,14 @@ def check_package_cves(nvd_path, packages):
 
      for cve in CVE.read_nvd_dir(nvd_path):
          for pkg_name in cve.pkg_names:
-            if pkg_name in packages and cve.affects(packages[pkg_name]):
-                packages[pkg_name].cves.append(cve.identifier)
+            if pkg_name in packages:

support/scripts/pkg-stats def check_package_cves(nvd_path, packages): · line 630

Quoted 10 lines
support/scripts/pkg-stats +7 -0
+                if cve.affects(packages[pkg_name]):
+                    packages[pkg_name].cves.append(cve.identifier)
+                if len(packages[pkg_name].cves):
+                    packages[pkg_name].status['cve'] = ('error', 'affected by cve')
+                else:
+                    packages[pkg_name].status['cve'] = ('ok', 'no cve found')
+
 
 
  def calculate_stats(packages):

In the current state, that would give:

* If a CVE applies to a br package -> error
* If a CVE does not applies to a br package -> ok
* If no CVE points to a br package -> na (no status check done)

I would argue that the latest case is not correct. The status should be ok, because we ran through the whole list of CVEs from the NVD feed, and we did not find any of them that applies to this package.

I would rather write it like this:

########################

support/scripts/pkg-stats +6 -0
index ed22f6b650..91477d583e 100755
@@ -620,6 +620,12 @@ def check_package_cves(nvd_path, packages):
              if pkg_name in packages and cve.affects(packages[pkg_name]):
                  packages[pkg_name].cves.append(cve.identifier)
 
+    for pkg_name, pkg in packages.items():
+        if len(pkg.cves) > 0:
+            pkg.status['cve'] = ('error', 'affected by CVE(s)')
+        else:
+            pkg.status['cve'] = ('ok', 'no CVE found')
+
 
  def calculate_stats(packages):
      stats = defaultdict(int)

########################

Best regards,

Titouan

#32 lore raw

PATCH v3 12/12 support/scripts/pkg-stats: add status for cve check

Hi Titouan and all,

Am So., 23. Feb. 2020 um 15:24 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 34 lines

Heiko, all,

Quoted 17 lines · Heiko Thiery
---
  support/scripts/pkg-stats | 10 ++++++++--
  1 file changed, 8 insertions(+), 2 deletions(-)
support/scripts/pkg-stats +1 -2
index ed22f6b650..4efff8624f 100755
@@ -617,8 +617,14 @@ def check_package_cves(nvd_path, packages):
 
      for cve in CVE.read_nvd_dir(nvd_path):
          for pkg_name in cve.pkg_names:
-            if pkg_name in packages and cve.affects(packages[pkg_name]):
-                packages[pkg_name].cves.append(cve.identifier)
+            if pkg_name in packages:

support/scripts/pkg-stats def check_package_cves(nvd_path, packages): · line 630

Quoted 10 lines
support/scripts/pkg-stats +7 -0
+                if cve.affects(packages[pkg_name]):
+                    packages[pkg_name].cves.append(cve.identifier)
+                if len(packages[pkg_name].cves):
+                    packages[pkg_name].status['cve'] = ('error', 'affected by cve')
+                else:
+                    packages[pkg_name].status['cve'] = ('ok', 'no cve found')
+
 
 
  def calculate_stats(packages):

In the current state, that would give:

Quoted 7 lines

* If a CVE applies to a br package -> error
* If a CVE does not applies to a br package -> ok
* If no CVE points to a br package -> na (no status check done)

I would argue that the latest case is not correct. The status should be ok, because we ran through the whole list of CVEs from the NVD feed, and we did not find any of them that applies to this package.

I see .. I think you are right. This will lead to a wrong status. I will remove the initializer for the status.

Quoted 9 lines

I would rather write it like this:

########################

support/scripts/pkg-stats +0 -0
index ed22f6b650..91477d583e 100755

support/scripts/pkg-stats def check_package_cves(nvd_path, packages): · line 628

Quoted 9 lines
support/scripts/pkg-stats +6 -0
@@ -620,6 +620,12 @@ def check_package_cves(nvd_path, packages):
              if pkg_name in packages and cve.affects(packages[pkg_name]):
                  packages[pkg_name].cves.append(cve.identifier)
 
+    for pkg_name, pkg in packages.items():
+        if len(pkg.cves) > 0:
+            pkg.status['cve'] = ('error', 'affected by CVE(s)')
+        else:
+            pkg.status['cve'] = ('ok', 'no CVE found')
+

Isn't it right that we loop then (depending on the amount of nvd pathes) several thousend times?

e.g. packages ~2600, nvds ~ 20 => 20*2600=52000

On the other hand we loop over the list of packages all over the place ;-/

Quoted 9 lines
support/scripts/pkg-stats +0 -0
  def calculate_stats(packages):
      stats = defaultdict(int)
 ########################

Best regards,

Titouan

#33 lore raw

PATCH v3 12/12 support/scripts/pkg-stats: add status for cve check

Quoted 9 lines · Heiko Thiery

Hi Titouan and all,

Am So., 23. Feb. 2020 um 15:24 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 5 lines

Heiko, all,

Quoted 1 line · Heiko Thiery

[--SNIP--]

Quoted 23 lines
Quoted 10 lines

I would rather write it like this:

########################

support/scripts/pkg-stats +0 -0
index ed22f6b650..91477d583e 100755

support/scripts/pkg-stats def check_package_cves(nvd_path, packages): · line 628

Quoted 9 lines
support/scripts/pkg-stats +6 -0
@@ -620,6 +620,12 @@ def check_package_cves(nvd_path, packages):
               if pkg_name in packages and cve.affects(packages[pkg_name]):
                   packages[pkg_name].cves.append(cve.identifier)
 
+    for pkg_name, pkg in packages.items():
+        if len(pkg.cves) > 0:
+            pkg.status['cve'] = ('error', 'affected by CVE(s)')
+        else:
+            pkg.status['cve'] = ('ok', 'no CVE found')
+

Isn't it right that we loop then (depending on the amount of nvd pathes) several thousend times?

Quoted 1 line

e.g. packages ~2600, nvds ~ 20 => 20*2600=52000

Except that each NVD file contains a few thousands CVEs :).

Quoted 2 lines

On the other hand we loop over the list of packages all over the place ;-/

Looping over all CVEs in a single NVD file yields 5 to 10 more iterations than looping over all packages (for instance year 2018 alone has 16039 CVE items)

Quoted 10 lines
Quoted 10 lines
support/scripts/pkg-stats +0 -0
   def calculate_stats(packages):
       stats = defaultdict(int)
 ########################

Best regards,

Titouan

#34 lore raw

PATCH v3 12/12 support/scripts/pkg-stats: add status for cve check

Hi Titouan,

Am Mo., 24. Feb. 2020 um 10:35 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 13 lines · Heiko Thiery
Quoted 9 lines

Hi Titouan and all,

Am So., 23. Feb. 2020 um 15:24 Uhr schrieb Titouan Christophe <[email protected]>:

Quoted 5 lines

Heiko, all,

Quoted 1 line · Heiko Thiery

[--SNIP--]

Quoted 5 lines
Quoted 4 lines

Isn't it right that we loop then (depending on the amount of nvd pathes) several thousend times?

e.g. packages ~2600, nvds ~ 20 => 20*2600=52000

Quoted 9 lines

Except that each NVD file contains a few thousands CVEs :).

Quoted 2 lines

On the other hand we loop over the list of packages all over the place ;-/

Looping over all CVEs in a single NVD file yields 5 to 10 more iterations than looping over all packages (for instance year 2018 alone has 16039 CVE items)

you're right ... compared to this it doesn't matter.

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

def calculate_stats(packages):
stats = defaultdict(int)
########################

Best regards,

Titouan

Thank you,
Heiko

#35 lore raw

v3 cover pkg-stats json output improvements

In reply to Heiko Thiery

Hello Heiko,

I tested your patchset, along with your buildroot-stat application, and overall this is great ! The generated HTML is nice and very comfortable to navigate.

I replied to individual patches, most of the time for cosmetic reasons:

* Using list/dict comprehensions in place of for-append loops
* Make use of Python properties
* if xxx == False >>> if not xxx

As this is mostly a matter of taste, if you (or others) don't agree with me, I will add a Reviewed-by anyway.

There are also a few required changes:

* You signed off with 2 distinct email addresses for 2 patches in the series
* Prefer to update the package status after entirely processing a status check (instead of updating at each change during a loop)
* Consistency in the generated status (warning vs. error), and improve the status messages

Best regards,

Titouan

Quoted 41 lines · Heiko Thiery

This patchset adds several improvements for the json output of the pkg-stats script.

- add developers information to the packages
- add supported defconfigs to json
- add license information to json
- add more generic status field to the packages for easier post processing

---
v2 -> v3:
- keep variable latest_release but change format
- add check for license file hashes
- introduce a na status for the checks
- add a list of all possible checks to the json output
- add the cve check status

v1 -> v2:
- cleanup and recreation of patches
- remove pkg name from dumping to json
- use patch_files instead of combine count and files in dict
- include getdevelopers.py to reuse Developers class

---
Heiko Thiery (12):
support/scripts/pkg-stats: store latest version in a dict support/scripts/pkg-stats: store patch files for the package support/scripts/pkg-stats: set developers info support/scripts/pkg-stats: store licences of package support/scripts/pkg-stats: add package status support/scripts/pkg-stats: add package count to stats support/scripts/pkg-stats: store pkg dir path support/scripts/pkg-stats: add defconfig support support/scripts/pkg-stats: add support for license hash check support/scripts/pkg-stats: set status to 'na' for virtual packages support/scripts/pkg-stats: initialize all package status checks support/scripts/pkg-stats: add status for cve check

  support/scripts/pkg-stats | 272 +++++++++++++++++++++++++++++---------
  1 file changed, 212 insertions(+), 60 deletions(-)