blob: 20e7e26b9ba88d090bebcdd63cae7c32f260a063 [file] [log] [blame]
Andrew Geissler1fe918a2020-05-15 14:16:47 -05001#
2# ISA_la_plugin.py - License analyzer plugin, part of ISA FW
3# Functionality is based on similar scripts from Clear linux project
4#
5# Copyright (c) 2015 - 2016, Intel Corporation
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# * Redistributions of source code must retain the above copyright notice,
11# this list of conditions and the following disclaimer.
12# * Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15# * Neither the name of Intel Corporation nor the names of its contributors
16# may be used to endorse or promote products derived from this software
17# without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
23# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30import subprocess
31import os, sys
32
33LicenseChecker = None
34
35flicenses = "/configs/la/licenses"
36fapproved_non_osi = "/configs/la/approved-non-osi"
37fexceptions = "/configs/la/exceptions"
38funwanted = "/configs/la/violations"
39
40
41class ISA_LicenseChecker():
42 initialized = False
43 rpm_present = False
44
45 def __init__(self, ISA_config):
46 self.logfile = ISA_config.logdir + "/isafw_lalog"
47 self.unwanted = []
48 self.report_name = ISA_config.reportdir + "/la_problems_report_" + \
49 ISA_config.machine + "_" + ISA_config.timestamp
50 self.image_pkg_list = ISA_config.reportdir + "/pkglist"
51 self.image_pkgs = []
52 self.la_plugin_image_whitelist = ISA_config.la_plugin_image_whitelist
53 self.la_plugin_image_blacklist = ISA_config.la_plugin_image_blacklist
54 self.initialized = True
55 with open(self.logfile, 'a') as flog:
56 flog.write("\nPlugin ISA_LA initialized!\n")
57 # check that rpm is installed (supporting only rpm packages for now)
58 DEVNULL = open(os.devnull, 'wb')
59 rc = subprocess.call(["which", "rpm"], stdout=DEVNULL, stderr=DEVNULL)
60 DEVNULL.close()
61 if rc == 0:
62 self.rpm_present = True
63 else:
64 with open(self.logfile, 'a') as flog:
65 flog.write("rpm tool is missing! Licence info is expected from build system\n")
66
67 def process_package(self, ISA_pkg):
68 if (self.initialized):
69 if ISA_pkg.name:
70 if (not ISA_pkg.licenses):
71 # need to determine licenses first
72 # for this we need rpm tool to be present
73 if (not self.rpm_present):
74 with open(self.logfile, 'a') as flog:
75 flog.write("rpm tool is missing and licence info is not provided. Cannot proceed.\n")
76 return;
77 if (not ISA_pkg.source_files):
78 if (not ISA_pkg.path_to_sources):
79 self.initialized = False
80 with open(self.logfile, 'a') as flog:
81 flog.write(
82 "No path to sources or source file list is provided!")
83 flog.write(
84 "\nNot able to determine licenses for package: " + ISA_pkg.name)
85 return
86 # need to build list of source files
87 ISA_pkg.source_files = self.find_files(
88 ISA_pkg.path_to_sources)
89 for i in ISA_pkg.source_files:
90 if (i.endswith(".spec")):# supporting rpm only for now
91 args = ("rpm", "-q", "--queryformat",
92 "%{LICENSE} ", "--specfile", i)
93 try:
94 popen = subprocess.Popen(
95 args, stdout=subprocess.PIPE)
96 popen.wait()
97 ISA_pkg.licenses = popen.stdout.read().split()
98 except:
99 self.initialized = False
100 with open(self.logfile, 'a') as flog:
101 flog.write(
102 "Error in executing rpm query: " + str(sys.exc_info()))
103 flog.write(
104 "\nNot able to process package: " + ISA_pkg.name)
105 return
106 for l in ISA_pkg.licenses:
107 if (not self.check_license(l, flicenses) and
108 not self.check_license(l, fapproved_non_osi) and
109 not self.check_exceptions(ISA_pkg.name, l, fexceptions)):
110 # log the package as not following correct license
111 with open(self.report_name, 'a') as freport:
112 freport.write(l + "\n")
113 if (self.check_license(l, funwanted)):
114 # log the package as having license that should not be
115 # used
116 with open(self.report_name + "_unwanted", 'a') as freport:
117 freport.write(l + "\n")
118 else:
119 self.initialized = False
120 with open(self.logfile, 'a') as flog:
121 flog.write(
122 "Mandatory argument package name is not provided!\n")
123 flog.write("Not performing the call.\n")
124 else:
125 with open(self.logfile, 'a') as flog:
126 flog.write(
127 "Plugin hasn't initialized! Not performing the call.")
128
129 def process_report(self):
130 if (self.initialized):
131 with open(self.logfile, 'a') as flog:
132 flog.write("Creating report with violating licenses.\n")
133 self.process_pkg_list()
134 self.write_report_unwanted()
135 with open(self.logfile, 'a') as flog:
136 flog.write("Creating report in XML format.\n")
137 self.write_report_xml()
138
139 def process_pkg_list(self):
140 if os.path.isfile (self.image_pkg_list):
141 img_name = ""
142 with open(self.image_pkg_list, 'r') as finput:
143 for line in finput:
144 line = line.strip()
145 if not line:
146 continue
147 if line.startswith("Packages "):
148 img_name = line.split()[3]
149 with open(self.logfile, 'a') as flog:
150 flog.write("img_name: " + img_name + "\n")
151 continue
152 package_info = line.split()
153 pkg_name = package_info[0]
154 orig_pkg_name = package_info[2]
155 if (not self.image_pkgs) or ((pkg_name + " from " + img_name) not in self.image_pkgs):
156 self.image_pkgs.append(pkg_name + " from " + img_name + " " + orig_pkg_name)
157
158 def write_report_xml(self):
159 try:
160 from lxml import etree
161 except ImportError:
162 try:
163 import xml.etree.cElementTree as etree
164 except ImportError:
165 import xml.etree.ElementTree as etree
166 num_tests = 0
167 root = etree.Element('testsuite', name='LA_Plugin', tests='2')
168 if os.path.isfile(self.report_name):
169 with open(self.report_name, 'r') as f:
170 class_name = "Non-approved-licenses"
171 for line in f:
172 line = line.strip()
173 if line == "":
174 continue
175 if line.startswith("Packages that "):
176 class_name = "Violating-licenses"
177 continue
178 num_tests += 1
179 tcase1 = etree.SubElement(
180 root, 'testcase', classname=class_name, name=line.split(':', 1)[0])
181 etree.SubElement(
182 tcase1, 'failure', message=line, type='violation')
183 else:
184 tcase1 = etree.SubElement(
185 root, 'testcase', classname='ISA_LAChecker', name='none')
186 num_tests = 1
187 root.set('tests', str(num_tests))
188 tree = etree.ElementTree(root)
189 output = self.report_name + '.xml'
190 try:
191 tree.write(output, encoding='UTF-8',
192 pretty_print=True, xml_declaration=True)
193 except TypeError:
194 tree.write(output, encoding='UTF-8', xml_declaration=True)
195
196 def write_report_unwanted(self):
197 if os.path.isfile(self.report_name + "_unwanted"):
198 with open(self.logfile, 'a') as flog:
199 flog.write("image_pkgs: " + str(self.image_pkgs) + "\n")
200 flog.write("self.la_plugin_image_whitelist: " + str(self.la_plugin_image_whitelist) + "\n")
201 flog.write("self.la_plugin_image_blacklist: " + str(self.la_plugin_image_blacklist) + "\n")
202 with open(self.report_name, 'a') as fout:
203 with open(self.report_name + "_unwanted", 'r') as f:
204 fout.write(
205 "\n\nPackages that violate mandatory license requirements:\n")
206 for line in f:
207 line = line.strip()
208 pkg_name = line.split(':',1)[0]
209 if (not self.image_pkgs):
210 fout.write(line + " from image name not available \n")
211 continue
212 for pkg_info in self.image_pkgs:
213 image_pkg_name = pkg_info.split()[0]
214 image_name = pkg_info.split()[2]
215 image_orig_pkg_name = pkg_info.split()[3]
216 if ((image_pkg_name == pkg_name) or (image_orig_pkg_name == pkg_name)):
217 if self.la_plugin_image_whitelist and (image_name not in self.la_plugin_image_whitelist):
218 continue
219 if self.la_plugin_image_blacklist and (image_name in self.la_plugin_image_blacklist):
220 continue
221 fout.write(line + " from image " + image_name)
222 if (image_pkg_name != image_orig_pkg_name):
223 fout.write(" binary_pkg_name " + image_pkg_name + "\n")
224 continue
225 fout.write("\n")
226 os.remove(self.report_name + "_unwanted")
227
228 def find_files(self, init_path):
229 list_of_files = []
230 for (dirpath, dirnames, filenames) in os.walk(init_path):
231 for f in filenames:
232 list_of_files.append(str(dirpath + "/" + f)[:])
233 return list_of_files
234
235 def check_license(self, license, file_path):
236 with open(os.path.dirname(__file__) + file_path, 'r') as f:
237 for line in f:
238 s = line.rstrip()
239 curr_license = license.split(':',1)[1]
240 if s == curr_license:
241 return True
242 return False
243
244 def check_exceptions(self, pkg_name, license, file_path):
245 with open(os.path.dirname(__file__) + file_path, 'r') as f:
246 for line in f:
247 s = line.rstrip()
248 curr_license = license.split(':',1)[1]
249 if s == pkg_name + " " + curr_license:
250 return True
251 return False
252
253# ======== supported callbacks from ISA ============= #
254
255def init(ISA_config):
256 global LicenseChecker
257 LicenseChecker = ISA_LicenseChecker(ISA_config)
258
259
260def getPluginName():
261 return "ISA_LicenseChecker"
262
263
264def process_package(ISA_pkg):
265 global LicenseChecker
266 return LicenseChecker.process_package(ISA_pkg)
267
268
269def process_report():
270 global LicenseChecker
271 return LicenseChecker.process_report()
272
273# ==================================================== #