14 from typing
import List, Union, IO
23 def get_api_config() -> ApiConfig:
25 Set the API configuration based on CI the job is running on
27 :return: ApiConfig object
29 api_config = ApiConfig()
30 if 'GITLAB_CI' in os.environ:
31 api_config.running_on = Runner.GITLAB
32 api_config.api_url = os.environ[
'CI_API_V4_URL']
if 'CI_API_V4_URL' in \
34 api_config.project_id = os.environ[
'CI_PROJECT_ID']
if 'CI_PROJECT_ID' in \
36 api_config.mr_iid = os.environ[
'CI_MERGE_REQUEST_IID']
if \
37 'CI_MERGE_REQUEST_IID' in os.environ
else ''
38 api_config.api_token = os.environ[
'API_TOKEN']
if 'API_TOKEN' in \
40 api_config.project_name = os.environ[
'CI_PROJECT_NAME']
if \
41 'CI_PROJECT_NAME' in os.environ
else ''
42 api_config.project_desc = os.environ[
'CI_PROJECT_DESCRIPTION'].strip()
43 if api_config.project_desc ==
"":
44 api_config.project_desc =
None
45 api_config.project_orig = os.environ[
'CI_PROJECT_NAMESPACE']
46 api_config.project_url = os.environ[
'CI_PROJECT_URL']
47 elif 'TRAVIS' in os.environ
and os.environ[
'TRAVIS'] ==
'true':
48 api_config.running_on = Runner.TRAVIS
49 api_config.travis_repo_slug = os.environ[
'TRAVIS_REPO_SLUG']
50 api_config.travis_pull_request = os.environ[
'TRAVIS_PULL_REQUEST']
51 api_config.project_name = os.environ[
'TRAVIS_REPO_SLUG'].split(
"/")[-1]
52 api_config.project_orig =
"/".join(os.environ[
'TRAVIS_REPO_SLUG'].
54 api_config.project_url =
"https://github.com/" + \
55 os.environ[
'TRAVIS_REPO_SLUG']
56 elif 'GITHUB_ACTIONS' in os.environ
and \
57 os.environ[
'GITHUB_ACTIONS'] ==
'true':
58 api_config.running_on = Runner.GITHUB
59 api_config.api_url = os.environ[
'GITHUB_API']
if 'GITHUB_API' in \
60 os.environ
else 'https://api.github.com'
61 api_config.api_token = os.environ[
'GITHUB_TOKEN']
62 api_config.github_repo_slug = os.environ[
'GITHUB_REPOSITORY']
63 api_config.github_pull_request = os.environ[
'GITHUB_PULL_REQUEST']
64 api_config.project_name = os.environ[
'GITHUB_REPOSITORY'].split(
"/")[-1]
65 api_config.project_orig = os.environ[
'GITHUB_REPO_OWNER']
66 api_config.project_url = os.environ[
'GITHUB_REPO_URL']
70 def get_allow_list() -> dict:
72 Decode json from `allowlist.json`
74 :return: allowlist dictionary
76 if os.path.exists(
'whitelist.json'):
77 file_name =
'whitelist.json'
79 file_name =
'allowlist.json'
80 with open(file_name)
as f:
85 def print_results(name: str, failed_results: List[ScanResult],
88 Print the formatted scanner results
90 :param name: Name of the scanner
91 :param failed_results: formatted scanner results to be printed
92 :param result_file: File to write results to
94 for files
in failed_results:
95 print(f
"File: {files.file}")
96 result_file.write(f
"File: {files.file}\n")
98 if len(files.result) > 1:
100 print(f
"{name}{plural}:")
101 result_file.write(f
"{name}{plural}:\n")
102 for result
in files.result:
104 result_file.write(
"\t" + result +
"\n")
107 def print_log_message(filename: str,
108 failed_list: Union[bool, List[ScanResult]],
109 check_value: bool, failure_text: str,
110 acceptance_text: str, scan_type: str,
111 return_val: int) -> int:
113 Common helper function to print scan results.
115 :param filename: File where results are to be stored.
116 :param failed_list: Failed scan results.
117 :param check_value: Boolean value which failed_list should have.
118 :param failure_text: Message to print in case of failures.
119 :param acceptance_text: Message to print in case of no failures.
120 :param scan_type: Type of scan to print.
121 :param return_val: Return value for program
122 :return: New return value
124 report_file = open(filename,
'w')
125 if (isinstance(failed_list, bool)
and failed_list
is not check_value)
or \
126 (isinstance(failed_list, list)
and len(failed_list) != 0):
127 print(f
"\u2718 {failure_text}:")
128 report_file.write(f
"{failure_text}:\n")
129 print_results(scan_type, failed_list, report_file)
130 if scan_type ==
"License":
131 return_val = return_val | 2
132 elif scan_type ==
"Copyright":
133 return_val = return_val | 4
134 elif scan_type ==
"Keyword":
135 return_val = return_val | 8
137 print(f
"\u2714 {acceptance_text}")
138 report_file.write(f
"{acceptance_text}\n")
144 def text_report(cli_options: CliOptions, result_dir: str, return_val: int,
145 scanner: Scanners) -> int:
147 Run scanners and print results in text format.
149 :param cli_options: CLI options
150 :param result_dir: Result directory location
151 :param return_val: Return value of program
152 :param scanner: Scanner object
153 :return: Program's return value
155 if cli_options.nomos
or cli_options.ojo:
156 failed_licenses = scanner.results_are_allow_listed()
157 print_log_message(f
"{result_dir}/licenses.txt", failed_licenses,
True,
158 "Following licenses found which are not allow listed",
159 "No license violation found",
"License", return_val)
160 if cli_options.copyright:
161 copyright_results = scanner.get_copyright_list()
162 print_log_message(f
"{result_dir}/copyrights.txt", copyright_results,
False,
163 "Following copyrights found",
164 "No copyright violation found",
"Copyright", return_val)
165 if cli_options.keyword:
166 keyword_results = scanner.get_keyword_list()
167 print_log_message(f
"{result_dir}/keywords.txt", keyword_results,
False,
168 "Following keywords found",
169 "No keyword violation found",
"Keyword", return_val)
173 def bom_report(cli_options: CliOptions, result_dir: str, return_val: int,
174 scanner: Scanners, api_config: ApiConfig) -> int:
176 Run scanners and print results as an SBOM.
178 :param cli_options: CLI options
179 :param result_dir: Result directory location
180 :param return_val: Return value
181 :param scanner: Scanner object
182 :param api_config: API config options
183 :return: Program's return value
185 report_obj = SpdxReport(cli_options, api_config)
186 if cli_options.nomos
or cli_options.ojo:
187 scan_results = scanner.get_scanner_results()
188 report_obj.add_license_results(scan_results)
189 failed_licenses = scanner.get_non_allow_listed_results(scan_results)
190 return_val = print_log_message(f
"{result_dir}/licenses.txt",
191 failed_licenses,
True,
"Following licenses found which are not allow "
192 "listed",
"No license violation found",
193 "License", return_val)
194 if cli_options.copyright:
195 copyright_results = scanner.get_copyright_list(all_results=
True)
196 if copyright_results
is False:
197 copyright_results = []
198 report_obj.add_copyright_results(copyright_results)
199 failed_copyrights = scanner.get_non_allow_listed_copyrights(
201 return_val = print_log_message(f
"{result_dir}/copyrights.txt",
202 failed_copyrights,
False,
"Following copyrights found",
203 "No copyright violation found",
"Copyright", return_val)
204 if cli_options.keyword:
205 keyword_results = scanner.get_keyword_list()
206 return_val = print_log_message(f
"{result_dir}/keywords.txt",
207 keyword_results,
False,
"Following keywords found",
208 "No keyword violation found",
"Keyword", return_val)
209 report_obj.finalize_document()
210 report_name = f
"{result_dir}/sbom_"
211 if cli_options.report_format == ReportFormat.SPDX_JSON:
212 report_name +=
"spdx.json"
213 elif cli_options.report_format == ReportFormat.SPDX_RDF:
214 report_name +=
"spdx.rdf"
215 elif cli_options.report_format == ReportFormat.SPDX_TAG_VALUE:
216 report_name +=
"spdx.spdx"
217 elif cli_options.report_format == ReportFormat.SPDX_YAML:
218 report_name +=
"spdx.yaml"
219 report_obj.write_report(report_name)
220 print(f
"\u2714 Saved SBOM as {report_name}")
224 def main(parsed_args):
229 :return: 0 for success, error code on failure.
231 api_config = get_api_config()
233 cli_options.update_args(parsed_args)
236 cli_options.allowlist = get_allow_list()
237 except FileNotFoundError:
238 print(
"Unable to find allowlist.json in current dir\n"
239 "Continuing without it.", file=sys.stderr)
241 repo_setup = RepoSetup(cli_options, api_config)
242 if cli_options.repo
is False:
243 cli_options.diff_dir = repo_setup.get_diff_dir()
245 scanner = Scanners(cli_options)
249 result_dir =
"results"
250 os.makedirs(name=result_dir, exist_ok=
True)
252 if cli_options.report_format == ReportFormat.TEXT:
253 return_val = text_report(cli_options, result_dir, return_val, scanner)
255 return_val = bom_report(cli_options, result_dir, return_val, scanner,
260 if __name__ ==
"__main__":
261 parser = argparse.ArgumentParser(
262 description=textwrap.dedent(
"""fossology scanner designed for CI""")
265 "operation", type=str, help=
"Operations to run.", nargs=
'*',
266 choices=[
"nomos",
"copyright",
"keyword",
"ojo",
"repo"]
269 "--report", type=str, help=
"Type of report to generate. Default 'TEXT'.",
270 choices=[member.name
for member
in ReportFormat], default=ReportFormat.TEXT.name
272 args = parser.parse_args()
Store the options sent through the CLI.