33# VulnerableCode is a trademark of nexB Inc.
44# SPDX-License-Identifier: Apache-2.0
55# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6- # See https://github.com/nexB /vulnerablecode for support or download.
6+ # See https://github.com/aboutcode-org /vulnerablecode for support or download.
77# See https://aboutcode.org for more information about nexB OSS projects.
88#
9+
910import logging
1011from datetime import datetime
1112from datetime import timezone
13+ from traceback import format_exc as traceback_format_exc
14+ from typing import Iterable
1215
1316from aboutcode .pipeline import BasePipeline
17+ from aboutcode .pipeline import LoopProgress
1418
19+ from vulnerabilities .importer import AdvisoryData
20+ from vulnerabilities .improver import MAX_CONFIDENCE
21+ from vulnerabilities .models import Advisory
22+ from vulnerabilities .pipes .advisory import import_advisory
23+ from vulnerabilities .pipes .advisory import insert_advisory
1524from vulnerabilities .utils import classproperty
1625
1726module_logger = logging .getLogger (__name__ )
@@ -32,3 +41,90 @@ def qualified_name(cls):
3241 Fully qualified name prefixed with the module name of the pipeline used in logging.
3342 """
3443 return f"{ cls .__module__ } .{ cls .__qualname__ } "
44+
45+
46+ class VulnerableCodeBaseImporterPipeline (VulnerableCodePipeline ):
47+ """
48+ Base importer pipeline for importing advisories.
49+
50+ Uses:
51+ Subclass this Pipeline and implement ``advisories_count`` and ``collect_advisories`` method.
52+ Also override the ``steps`` and ``advisory_confidence`` as needed.
53+ """
54+
55+ license_url = None
56+ spdx_license_expression = None
57+ repo_url = None
58+ importer_name = None
59+ advisory_confidence = MAX_CONFIDENCE
60+
61+ @classmethod
62+ def steps (cls ):
63+ return (
64+ # Add step for downloading/cloning resource as required.
65+ cls .collect_and_store_advisories ,
66+ cls .import_new_advisories ,
67+ # Add step for removing downloaded/cloned resource as required.
68+ )
69+
70+ def collect_advisories (self ) -> Iterable [AdvisoryData ]:
71+ """
72+ Yield AdvisoryData for importer pipeline.
73+
74+ Populate the `self.collected_advisories_count` field and yield AdvisoryData
75+ """
76+ raise NotImplementedError
77+
78+ def advisories_count (self ) -> int :
79+ """
80+ Return the estimated AdvisoryData to be yielded by ``collect_advisories``.
81+
82+ Used by ``collect_and_store_advisories`` to log the progress of advisory collection.
83+ """
84+ raise NotImplementedError
85+
86+ def collect_and_store_advisories (self ):
87+ collected_advisory_count = 0
88+ progress = LoopProgress (total_iterations = self .advisories_count (), logger = self .log )
89+ for advisory in progress .iter (self .collect_advisories ()):
90+ if _obj := insert_advisory (
91+ advisory = advisory ,
92+ pipeline_name = self .qualified_name ,
93+ logger = self .log ,
94+ ):
95+ collected_advisory_count += 1
96+
97+ self .log (f"Successfully collected { collected_advisory_count :,d} advisories" )
98+
99+ def import_new_advisories (self ):
100+ new_advisories = Advisory .objects .filter (
101+ created_by = self .qualified_name ,
102+ date_imported__isnull = True ,
103+ )
104+
105+ new_advisories_count = new_advisories .count ()
106+
107+ self .log (f"Importing { new_advisories_count :,d} new advisories" )
108+
109+ imported_advisory_count = 0
110+ progress = LoopProgress (total_iterations = new_advisories_count , logger = self .log )
111+ for advisory in progress .iter (new_advisories .paginated ()):
112+ self .import_advisory (advisory = advisory )
113+ if advisory .date_imported :
114+ imported_advisory_count += 1
115+
116+ self .log (f"Successfully imported { imported_advisory_count :,d} new advisories" )
117+
118+ def import_advisory (self , advisory : Advisory ) -> int :
119+ try :
120+ import_advisory (
121+ advisory = advisory ,
122+ pipeline_name = self .qualified_name ,
123+ confidence = self .advisory_confidence ,
124+ logger = self .log ,
125+ )
126+ except Exception as e :
127+ self .log (
128+ f"Failed to import advisory: { advisory !r} with error { e !r} :\n { traceback_format_exc ()} " ,
129+ level = logging .ERROR ,
130+ )
0 commit comments