Fix security versions script
[integration.git] / test / security / check_versions / versions / reporting.py
1 #!/usr/bin/env python3
2
3 #   Copyright 2020 Orange, Ltd.
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #       http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 #
17 """
18 Generate result page
19 """
20 import logging
21 import pathlib
22 import json
23 from dataclasses import dataclass
24 import os
25 import statistics
26 import wget
27 import yaml
28
29 from packaging.version import Version
30
31 from jinja2 import (  # pylint: disable=import-error
32     Environment,
33     select_autoescape,
34     PackageLoader,
35 )
36
37 # Logger
38 LOG_LEVEL = "INFO"
39 logging.basicConfig()
40 LOGGER = logging.getLogger("onap-versions-status-reporting")
41 LOGGER.setLevel(LOG_LEVEL)
42
43 REPORTING_FILE = "/var/lib/xtesting/results/versions_reporting.html"
44 # REPORTING_FILE = "/tmp/versions_reporting.html"
45 RESULT_FILE = "/tmp/versions.json"
46 RECOMMENDED_VERSIONS_FILE = "/tmp/recommended_versions.yaml"
47 WAIVER_LIST_FILE = "/tmp/versions_xfail.txt"
48
49
50 @dataclass
51 class TestResult:
52     """Test results retrieved from xtesting."""
53
54     pod_name: str
55     container: str
56     image: str
57     python_version: str
58     python_status: int
59     java_version: str
60     java_status: int
61
62
63 @dataclass
64 class SerieResult:
65     """Serie of tests."""
66
67     serie_id: str
68     success_rate: int = 0
69     min: int = 0
70     max: int = 0
71     mean: float = 0.0
72     median: float = 0.0
73     nb_occurences: int = 0
74
75
76 class OnapVersionsReporting:
77     """Build html summary page."""
78
79     def __init__(self, result_file) -> None:
80         """Initialization of the report."""
81         version = os.getenv("ONAP_VERSION", "master")
82         base_url = "https://git.onap.org/integration/seccom/plain"
83         if pathlib.Path(WAIVER_LIST_FILE).is_file():
84             self._waiver_file = pathlib.Path(WAIVER_LIST_FILE)
85         else:
86             self._waiver_file = wget.download(
87                 base_url + "/waivers/versions/versions_xfail.txt?h=" + version,
88                 out=WAIVER_LIST_FILE,
89             )
90         if pathlib.Path(RECOMMENDED_VERSIONS_FILE).is_file():
91             self._recommended_versions_file = pathlib.Path(RECOMMENDED_VERSIONS_FILE)
92         else:
93             self._recommended_versions_file = wget.download(
94                 base_url + "/recommended_versions.yaml?h=" + version,
95                 out=RECOMMENDED_VERSIONS_FILE,
96             )
97
98     def get_versions_scan_results(self, result_file, waiver_list):
99         """Get all the versions from the scan."""
100         testresult = []
101         # Get the recommended version list for java and python
102         min_java_version = self.get_recommended_version(
103             RECOMMENDED_VERSIONS_FILE, "java11"
104         )
105         min_python_version = self.get_recommended_version(
106             RECOMMENDED_VERSIONS_FILE, "python3"
107         )
108
109         LOGGER.info("Min Java recommended version: %s", min_java_version)
110         LOGGER.info("Min Python recommended version: %s", min_python_version)
111
112         with open(result_file) as json_file:
113             data = json.load(json_file)
114         LOGGER.info("Number of pods: %s", len(data))
115         for component in data:
116             if component["container"] not in waiver_list:
117                 testresult.append(
118                     TestResult(
119                         pod_name=component["pod"],
120                         container=component["container"],
121                         image=component["extra"]["image"],
122                         python_version=component["versions"]["python"],
123                         java_version=component["versions"]["java"],
124                         python_status=self.get_version_status(
125                             component["versions"]["python"], min_python_version[0]
126                         ),
127                         java_status=self.get_version_status(
128                             component["versions"]["java"], min_java_version[0]
129                         ),
130                     )
131                 )
132         LOGGER.info("Nb of pods (after waiver filtering) %s", len(testresult))
133         return testresult
134
135     @staticmethod
136     def get_version_status(versions, min_version):
137         """Based on the min version set the status of the component version."""
138         # status_code
139         # 0: only recommended version found
140         # 1: recommended version found but not alone
141         # 2: recommended version not found but not far
142         # 3: recommended version not found but not far but not alone
143         # 4: recommended version not found
144         # we assume that versions are given accordign to usual java way
145         # X.Y.Z
146         LOGGER.debug("Version = %s", versions)
147         LOGGER.debug("Min Version = %s", min_version)
148         nb_versions_found = len(versions)
149         status_code = -1
150         LOGGER.debug("Nb versions found :%s", nb_versions_found)
151         # if no version found retrieved -1
152         if nb_versions_found > 0:
153             for version in versions:
154                 clean_version = Version(version.replace("_", "."))
155                 min_version_ok = str(min_version)
156
157                 if clean_version >= Version(min_version_ok):
158                     if nb_versions_found < 2:
159                         status_code = 0
160                     else:
161                         status_code = 2
162                 elif clean_version.major >= Version(min_version_ok).major:
163                     if nb_versions_found < 2:
164                         status_code = 1
165                     else:
166                         status_code = 3
167                 else:
168                     status_code = 4
169         LOGGER.debug("Version status code = %s", status_code)
170         return status_code
171
172     @staticmethod
173     def get_recommended_version(recommended_versions_file, component):
174         """Retrieve data from the json file."""
175         with open(recommended_versions_file) as stream:
176             data = yaml.safe_load(stream)
177             try:
178                 recommended_version = data[component]["recommended_versions"]
179             except KeyError:
180                 recommended_version = None
181         return recommended_version
182
183     @staticmethod
184     def get_waiver_list(waiver_file_path):
185         """Get the waiver list."""
186         pods_to_be_excluded = []
187         with open(waiver_file_path) as waiver_list:
188             for line in waiver_list:
189                 line = line.strip("\n")
190                 line = line.strip("\t")
191                 if not line.startswith("#"):
192                     pods_to_be_excluded.append(line)
193         return pods_to_be_excluded
194
195     @staticmethod
196     def get_score(component_type, scan_res):
197         # Look at the java and python results
198         # 0 = recommended version
199         # 1 = acceptable version
200         nb_good_versions = 0
201         nb_results = 0
202
203         for res in scan_res:
204             if component_type == "java":
205                 if res.java_status >= 0:
206                     nb_results += 1
207                     if res.java_status < 2:
208                         nb_good_versions += 1
209             elif component_type == "python":
210                 if res.python_status >= 0:
211                     nb_results += 1
212                     if res.python_status < 2:
213                         nb_good_versions += 1
214         try:
215             return round(nb_good_versions * 100 / nb_results, 1)
216         except ZeroDivisionError:
217             LOGGER.error("Impossible to calculate the success rate")
218         return 0
219
220     def generate_reporting(self, result_file):
221         """Generate HTML reporting page."""
222         LOGGER.info("Generate versions HTML report.")
223
224         # Get the waiver list
225         waiver_list = self.get_waiver_list(self._waiver_file)
226         LOGGER.info("Waiver list: %s", waiver_list)
227
228         # Get the Versions results
229         scan_res = self.get_versions_scan_results(result_file, waiver_list)
230
231         LOGGER.info("scan_res: %s", scan_res)
232
233         # Evaluate result
234         status_res = {"java": 0, "python": 0}
235         for component_type in "java", "python":
236             status_res[component_type] = self.get_score(component_type, scan_res)
237
238         LOGGER.info("status_res: %s", status_res)
239
240         # Calculate the average score
241         numbers = [status_res[key] for key in status_res]
242         mean_ = statistics.mean(numbers)
243
244         # Create reporting page
245         jinja_env = Environment(
246             autoescape=select_autoescape(["html"]),
247             loader=PackageLoader("onap_check_versions"),
248         )
249         page_info = {
250             "title": "ONAP Integration versions reporting",
251             "success_rate": status_res,
252             "mean": mean_,
253         }
254         jinja_env.get_template("versions.html.j2").stream(
255             info=page_info, data=scan_res
256         ).dump("{}".format(REPORTING_FILE))
257
258         return mean_
259
260
261 if __name__ == "__main__":
262     test = OnapVersionsReporting(
263         RESULT_FILE, WAIVER_LIST_FILE, RECOMMENDED_VERSIONS_FILE
264     )
265     test.generate_reporting(RESULT_FILE)