diff --git a/0001-fix-partial-succeed-bug.patch b/0001-fix-partial-succeed-bug.patch deleted file mode 100644 index 9b74158ae5301c604a33b964a71f67bc31657170..0000000000000000000000000000000000000000 --- a/0001-fix-partial-succeed-bug.patch +++ /dev/null @@ -1,252 +0,0 @@ -From ff5a842960179f8399434cfd36caeed23bb5c218 Mon Sep 17 00:00:00 2001 -From: young <954906362@qq.com> -Date: Wed, 14 Dec 2022 21:36:46 +0800 -Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8A=E4=BC=A0?= - =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=92=8C=E6=8E=A5=E5=8F=A3=E4=B8=8D=E4=B8=80?= - =?UTF-8?q?=E8=87=B4=E4=BD=86=E6=B2=A1=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE?= - =?UTF-8?q?=E9=A2=98?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - ---- - .../cve_handler/manager/parse_advisory.py | 9 ++++++--- - .../cve_handler/manager/parse_unaffected.py | 8 ++++++++ - apollo/handler/cve_handler/view.py | 18 ++++++++++++++---- - 3 files changed, 28 insertions(+), 7 deletions(-) - -diff --git a/apollo/handler/cve_handler/manager/parse_advisory.py b/apollo/handler/cve_handler/manager/parse_advisory.py -index 773f746..4848ea3 100644 ---- a/apollo/handler/cve_handler/manager/parse_advisory.py -+++ b/apollo/handler/cve_handler/manager/parse_advisory.py -@@ -15,12 +15,12 @@ Time: - Author: - Description: parse security advisory xml file, insert into database - """ -+from collections import defaultdict - from xml.etree import cElementTree as ET - from xml.etree.ElementTree import ParseError --from collections import defaultdict - --from vulcanus.log.log import LOGGER - from apollo.function.customize_exception import ParseAdvisoryError -+from vulcanus.log.log import LOGGER - - __all__ = ["parse_security_advisory"] - -@@ -99,7 +99,10 @@ def parse_cvrf_dict(cvrf_dict): - ParseXmlError - """ - # affected package of this security advisory. joined with ',' if have multiple packages -- cvrf_note = cvrf_dict["cvrfdoc"]["DocumentNotes"]["Note"] -+ cve_document_notes = cvrf_dict["cvrfdoc"].get("DocumentNotes", "") -+ if not cve_document_notes: -+ return [], [], [] -+ cvrf_note = cve_document_notes["Note"] - affected_pkgs = "" - for info in cvrf_note: - if info["Title"] == "Affected Component": -diff --git a/apollo/handler/cve_handler/manager/parse_unaffected.py b/apollo/handler/cve_handler/manager/parse_unaffected.py -index 9b4ae03..7212a5c 100644 ---- a/apollo/handler/cve_handler/manager/parse_unaffected.py -+++ b/apollo/handler/cve_handler/manager/parse_unaffected.py -@@ -76,7 +76,13 @@ def parse_cvrf_dict(cvrf_dict): - Raises: - ParseXmlError - """ -+ cvrf_note = cvrf_dict["cvrfdoc"].get("DocumentNotes", "") -+ if cvrf_note: -+ return [], [], [] -+ - cve_info_list = cvrf_dict["cvrfdoc"]["Vulnerability"] -+ if isinstance(cve_info_list, dict): -+ cve_info_list = [cve_info_list] - cve_table_rows = [] - cve_pkg_rows = [] - doc_list = [] -@@ -87,6 +93,8 @@ def parse_cvrf_dict(cvrf_dict): - remediation = cve_info["Remediations"]["Remediation"] - if isinstance(remediation, list): - remediation = remediation[0] -+ if remediation["Type"] != "Unaffected": -+ continue - cvss_score = cve_info["CVSSScoreSets"]["ScoreSet"]["BaseScore"] - severity = parse_cve_severity(cvss_score) - cve_row = { -diff --git a/apollo/handler/cve_handler/view.py b/apollo/handler/cve_handler/view.py -index 4bfde0f..f90bd8e 100644 ---- a/apollo/handler/cve_handler/view.py -+++ b/apollo/handler/cve_handler/view.py -@@ -229,8 +229,10 @@ class VulUploadAdvisory(BaseResponse): - def _save_single_advisory(proxy, file_path): - file_name = os.path.basename(file_path) - try: -- cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory( -- file_path) -+ cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path) -+ if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []: -+ os.remove(file_path) -+ return WRONG_FILE_FORMAT - os.remove(file_path) - except (KeyError, ParseAdvisoryError) as error: - os.remove(file_path) -@@ -264,8 +266,10 @@ class VulUploadAdvisory(BaseResponse): - for file_path in file_path_list: - file_name = os.path.basename(file_path) - try: -- cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory( -- file_path) -+ cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path) -+ if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []: -+ shutil.rmtree(folder_path) -+ return WRONG_FILE_FORMAT - except (KeyError, ParseAdvisoryError) as error: - fail_list.append(file_name) - LOGGER.error( -@@ -356,6 +360,9 @@ class VulUploadUnaffected(BaseResponse): - file_name = os.path.basename(file_path) - try: - cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path) -+ if cve_rows == [] and cve_pkg_rows == [] and doc_list == []: -+ os.remove(file_path) -+ return WRONG_FILE_FORMAT - os.remove(file_path) - except (KeyError, ParseAdvisoryError) as error: - os.remove(file_path) -@@ -387,6 +394,9 @@ class VulUploadUnaffected(BaseResponse): - file_name = os.path.basename(file_path) - try: - cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path) -+ if cve_rows == [] and cve_pkg_rows == [] and doc_list == []: -+ shutil.rmtree(folder_path) -+ return WRONG_FILE_FORMAT - except (KeyError, ParseAdvisoryError) as error: - fail_list.append(file_name) - LOGGER.error("Some error occurred when parsing unaffected cve advisory '%s'." % file_name) --- -Gitee - - -From 574bbe874c9f87f7e2fff223fb48da047be8b83c Mon Sep 17 00:00:00 2001 -From: young <954906362@qq.com> -Date: Thu, 15 Dec 2022 10:03:58 +0800 -Subject: [PATCH 2/3] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8E=E6=8E=A5?= - =?UTF-8?q?=E5=8F=A3=E4=B8=8D=E4=B8=80=E8=87=B4=E7=9A=84=E9=94=99=E8=AF=AF?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - ---- - apollo/handler/cve_handler/view.py | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/apollo/handler/cve_handler/view.py b/apollo/handler/cve_handler/view.py -index f90bd8e..cea00eb 100644 ---- a/apollo/handler/cve_handler/view.py -+++ b/apollo/handler/cve_handler/view.py -@@ -265,6 +265,9 @@ class VulUploadAdvisory(BaseResponse): - fail_list = [] - for file_path in file_path_list: - file_name = os.path.basename(file_path) -+ suffix = file_name.split('.')[-1] -+ if suffix != "xml": -+ return WRONG_FILE_FORMAT - try: - cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path) - if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []: -@@ -392,6 +395,9 @@ class VulUploadUnaffected(BaseResponse): - fail_list = [] - for file_path in file_path_list: - file_name = os.path.basename(file_path) -+ suffix = file_name.split('.')[-1] -+ if suffix != "xml": -+ return WRONG_FILE_FORMAT - try: - cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path) - if cve_rows == [] and cve_pkg_rows == [] and doc_list == []: --- -Gitee - - -From b5e456ab33a323c8156a024c64b5a2193883347d Mon Sep 17 00:00:00 2001 -From: young <954906362@qq.com> -Date: Thu, 15 Dec 2022 16:40:08 +0800 -Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= - =?UTF-8?q?=E6=A3=80=E8=A7=86=E9=97=AE=E9=A2=98?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - ---- - .../handler/cve_handler/manager/parse_unaffected.py | 2 -- - apollo/handler/cve_handler/view.py | 13 +++++++------ - 2 files changed, 7 insertions(+), 8 deletions(-) - -diff --git a/apollo/handler/cve_handler/manager/parse_unaffected.py b/apollo/handler/cve_handler/manager/parse_unaffected.py -index 7212a5c..6338cd1 100644 ---- a/apollo/handler/cve_handler/manager/parse_unaffected.py -+++ b/apollo/handler/cve_handler/manager/parse_unaffected.py -@@ -93,8 +93,6 @@ def parse_cvrf_dict(cvrf_dict): - remediation = cve_info["Remediations"]["Remediation"] - if isinstance(remediation, list): - remediation = remediation[0] -- if remediation["Type"] != "Unaffected": -- continue - cvss_score = cve_info["CVSSScoreSets"]["ScoreSet"]["BaseScore"] - severity = parse_cve_severity(cvss_score) - cve_row = { -diff --git a/apollo/handler/cve_handler/view.py b/apollo/handler/cve_handler/view.py -index cea00eb..8c161a3 100644 ---- a/apollo/handler/cve_handler/view.py -+++ b/apollo/handler/cve_handler/view.py -@@ -230,10 +230,9 @@ class VulUploadAdvisory(BaseResponse): - file_name = os.path.basename(file_path) - try: - cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path) -- if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []: -- os.remove(file_path) -- return WRONG_FILE_FORMAT - os.remove(file_path) -+ if not all([cve_rows, cve_pkg_rows, cve_pkg_docs]): -+ return WRONG_FILE_FORMAT - except (KeyError, ParseAdvisoryError) as error: - os.remove(file_path) - LOGGER.error( -@@ -267,10 +266,11 @@ class VulUploadAdvisory(BaseResponse): - file_name = os.path.basename(file_path) - suffix = file_name.split('.')[-1] - if suffix != "xml": -+ shutil.rmtree(folder_path) - return WRONG_FILE_FORMAT - try: - cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path) -- if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []: -+ if not all([cve_rows, cve_pkg_rows, cve_pkg_docs]): - shutil.rmtree(folder_path) - return WRONG_FILE_FORMAT - except (KeyError, ParseAdvisoryError) as error: -@@ -363,7 +363,7 @@ class VulUploadUnaffected(BaseResponse): - file_name = os.path.basename(file_path) - try: - cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path) -- if cve_rows == [] and cve_pkg_rows == [] and doc_list == []: -+ if not all([cve_rows, cve_pkg_rows, doc_list]): - os.remove(file_path) - return WRONG_FILE_FORMAT - os.remove(file_path) -@@ -397,10 +397,11 @@ class VulUploadUnaffected(BaseResponse): - file_name = os.path.basename(file_path) - suffix = file_name.split('.')[-1] - if suffix != "xml": -+ shutil.rmtree(folder_path) - return WRONG_FILE_FORMAT - try: - cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path) -- if cve_rows == [] and cve_pkg_rows == [] and doc_list == []: -+ if not all([cve_rows, cve_pkg_rows, doc_list]): - shutil.rmtree(folder_path) - return WRONG_FILE_FORMAT - except (KeyError, ParseAdvisoryError) as error: --- -Gitee - diff --git a/0002-add-dnf-hot-patch-list-plugin.patch b/0002-add-dnf-hot-patch-list-plugin.patch deleted file mode 100644 index 4138dca5e2421a6ffe88693efc404ba8a11a3742..0000000000000000000000000000000000000000 --- a/0002-add-dnf-hot-patch-list-plugin.patch +++ /dev/null @@ -1,749 +0,0 @@ -From b316b4ec37fdca20c314b9755a81416c1f10a68f Mon Sep 17 00:00:00 2001 -From: wang-guangge -Date: Fri, 24 Mar 2023 22:56:26 +0800 -Subject: [PATCH] add dnf hot patch list plugin - ---- - hotpatch/baseclass.py | 192 +++++++++++++++++++ - hotpatch/hotpatch.py | 201 ++++++++++++++++++++ - hotpatch/hotpatch_updateinfo.py | 321 ++++++++++++++++++++++++++++++++ - 3 files changed, 714 insertions(+) - create mode 100644 hotpatch/baseclass.py - create mode 100644 hotpatch/hotpatch.py - create mode 100644 hotpatch/hotpatch_updateinfo.py - -diff --git a/hotpatch/baseclass.py b/hotpatch/baseclass.py -new file mode 100644 -index 0000000..6dd1330 ---- /dev/null -+++ b/hotpatch/baseclass.py -@@ -0,0 +1,192 @@ -+class Hotpatch(object): -+ __slots__ = ['_name', '_version', '_cves', -+ '_advisory', '_arch', '_filename', '_state'] -+ -+ def __init__(self, -+ name, -+ version, -+ arch, -+ filename, -+ release=''): -+ """ -+ name: str -+ version: str -+ arch: str -+ filename: str -+ release: str -+ """ -+ self._name = name -+ self._version = version -+ self._arch = arch -+ self._filename = filename -+ self._cves = [] -+ self._advisory = None -+ self._state = '' -+ -+ @property -+ def state(self): -+ return self._state -+ -+ @state.setter -+ def state(self, value): -+ self._state = value -+ -+ @property -+ def name(self): -+ return self._name -+ -+ @property -+ def version(self): -+ return self._version -+ -+ @property -+ def src_pkg_nevre(self): -+ src_pkg = self.name[self.name.index('-')+1:self.name.rindex('-')] -+ release_pos = src_pkg.rindex('-') -+ version_pos = src_pkg.rindex('-', 0, release_pos) -+ src_pkg_name, src_pkg_version, src_pkg_release = src_pkg[ -+ 0:version_pos], src_pkg[version_pos+1:release_pos], src_pkg[release_pos+1:] -+ return src_pkg_name, src_pkg_version, src_pkg_release -+ -+ @property -+ def nevra(self): -+ """ -+ nevra: name-version-release.arch -+ """ -+ return self.filename[0:self.filename.rindex('.')] -+ -+ @property -+ def hotpatch_name(self): -+ hotpatch_name = self.name[self.name.rindex('-')+1:] -+ return hotpatch_name -+ -+ @property -+ def syscare_name(self): -+ src_pkg = '%s-%s-%s' % (self.src_pkg_nevre) -+ return '%s/%s' % (src_pkg, self.hotpatch_name) -+ -+ @property -+ def cves(self): -+ return self._cves -+ -+ @cves.setter -+ def cves(self, cves): -+ self._cves = cves -+ -+ @property -+ def advisory(self): -+ return self._advisory -+ -+ @advisory.setter -+ def advisory(self, advisory): -+ self._advisory = advisory -+ -+ @property -+ def arch(self): -+ return self._arch -+ -+ @property -+ def filename(self): -+ return self._filename -+ -+ -+class Cve(object): -+ __slots__ = ['_cve_id', '_hotpatch'] -+ -+ def __init__(self, -+ id, -+ href='', -+ title='', -+ type='cve'): -+ """ -+ id: str -+ href: str -+ title: str -+ type: str -+ """ -+ self._cve_id = id -+ self._hotpatch = None -+ -+ @property -+ def hotpatch(self): -+ return self._hotpatch -+ -+ @hotpatch.setter -+ def hotpatch(self, hotpatch: Hotpatch): -+ self._hotpatch = hotpatch -+ -+ @property -+ def cve_id(self): -+ return self._cve_id -+ -+ -+class Advisory(object): -+ __slots__ = ['_id', '_type', '_title', '_severity', -+ '_description', '_updated', '_hotpatches', '_cves'] -+ -+ def __init__(self, -+ id, -+ type, -+ title, -+ severity, -+ description, -+ updated="1970-01-01 08:00:00", -+ release="", -+ issued=""): -+ """ -+ id: str -+ type: str -+ title: str -+ severity: str -+ description: str -+ updated: str -+ release: str -+ issued: str -+ """ -+ self._id = id -+ self._type = type -+ self._title = title -+ self._severity = severity -+ self._description = description -+ self._updated = updated -+ self._cves = {} -+ self._hotpatches = [] -+ -+ @property -+ def id(self): -+ return self._id -+ -+ @property -+ def type(self): -+ return self._type -+ -+ @property -+ def title(self): -+ return self._title -+ -+ @property -+ def severity(self): -+ return self._severity -+ -+ @property -+ def description(self): -+ return self._description -+ -+ @property -+ def updated(self): -+ return self._updated -+ -+ @property -+ def cves(self): -+ return self._cves -+ -+ @cves.setter -+ def cves(self, advisory_cves): -+ self._cves = advisory_cves -+ -+ @property -+ def hotpatches(self): -+ return self._hotpatches -+ -+ def add_hotpatch(self, hotpatch: Hotpatch): -+ self._hotpatches.append(hotpatch) -diff --git a/hotpatch/hotpatch.py b/hotpatch/hotpatch.py -new file mode 100644 -index 0000000..80cc69b ---- /dev/null -+++ b/hotpatch/hotpatch.py -@@ -0,0 +1,201 @@ -+import dnf -+from dnf.i18n import _ -+from dnf.cli.commands.updateinfo import UpdateInfoCommand -+import hawkey -+from .hotpatch_updateinfo import HotpatchUpdateInfo -+ -+ -+class Versions: -+ """ -+ Version number processing -+ """ -+ -+ separator = (".", "-") -+ _connector = "&" -+ -+ def _order(self, version, separator=None): -+ """ -+ Version of the cutting -+ Args: -+ version: version -+ separator: separator -+ -+ Returns: -+ -+ """ -+ if not separator: -+ separator = self._connector -+ return tuple([int(v) for v in version.split(separator) if v.isdigit()]) -+ -+ def lgt(self, version, compare_version): -+ """ -+ Returns true if the size of the compared version is greater -+ than that of the compared version, or false otherwise -+ -+ """ -+ for separator in self.separator: -+ version = self._connector.join( -+ [v for v in version.split(separator)]) -+ compare_version = self._connector.join( -+ [v for v in compare_version.split(separator)] -+ ) -+ version = self._order(version) -+ compare_version = self._order(compare_version) -+ return version >= compare_version -+ -+ -+@dnf.plugin.register_command -+class HotpatchCommand(dnf.cli.Command): -+ aliases = ['hotpatch'] -+ summary = _('show hotpatch info') -+ -+ def __init__(self, cli): -+ """ -+ Initialize the command -+ """ -+ super(HotpatchCommand, self).__init__(cli) -+ -+ @staticmethod -+ def set_argparser(parser): -+ output_format = parser.add_mutually_exclusive_group() -+ output_format.add_argument("--list", dest='_spec_action', const='list', -+ action='store_const', -+ help=_('show list of cves')) -+ -+ def configure(self): -+ demands = self.cli.demands -+ demands.sack_activation = True -+ demands.available_repos = True -+ -+ self.filter_cves = self.opts.cves if self.opts.cves else None -+ -+ def run(self): -+ self.hp_hawkey = HotpatchUpdateInfo(self.cli.base, self.cli) -+ -+ if self.opts._spec_action == 'list': -+ self.display() -+ -+ def get_mapping_nevra_cve(self) -> dict: -+ """ -+ Get cve nevra mapping based on the UpdateInfoCommand of 'dnf updateinfo list cves' -+ -+ Returns: -+ { -+ (nevra, advisory.updated): -+ cve_id: { -+ (advisory.type, advisory.severity), -+ ... -+ } -+ ... -+ } -+ """ -+ # configure UpdateInfoCommand with 'dnf updateinfo list cves' -+ updateinfo = UpdateInfoCommand(self.cli) -+ updateinfo.opts = self.opts -+ -+ updateinfo.opts.spec_action = 'list' -+ updateinfo.opts.with_cve = True -+ updateinfo.opts.spec = '*' -+ updateinfo.opts._advisory_types = set() -+ updateinfo.opts.availability = 'available' -+ self.updateinfo = updateinfo -+ -+ apkg_adv_insts = updateinfo.available_apkg_adv_insts( -+ updateinfo.opts.spec) -+ -+ mapping_nevra_cve = dict() -+ for apkg, advisory, _ in apkg_adv_insts: -+ nevra = (apkg.name, apkg.evr, apkg.arch) -+ for ref in advisory.references: -+ if ref.type != hawkey.REFERENCE_CVE: -+ continue -+ mapping_nevra_cve.setdefault((nevra, advisory.updated), dict())[ -+ ref.id] = (advisory.type, advisory.severity) -+ -+ return mapping_nevra_cve -+ -+ def _filter_and_format_list_output(self, echo_lines: list, fixed_cve_id: set, fixed_coldpatches: set): -+ """ -+ Only show specified cve information that have not been fixed, and format output -+ """ -+ def is_patch_fixed(coldpatch, fixed_coldpatches): -+ """ -+ Check whether the coldpatch is fixed -+ """ -+ for fixed_coldpatch in fixed_coldpatches: -+ pkg_name, pkg_evr, _ = coldpatch -+ fixed_pkg_name, fixed_pkg_evr, _ = fixed_coldpatch -+ if pkg_name != fixed_pkg_name: -+ continue -+ version = Versions() -+ if version.lgt(fixed_pkg_evr, pkg_evr): -+ return True -+ return False -+ -+ idw = tiw = ciw = 0 -+ format_lines = set() -+ for echo_line in echo_lines: -+ cve_id, type, coldpatch, hotpatch = echo_line[0], echo_line[1], echo_line[2], echo_line[3] -+ if self.filter_cves is not None and cve_id not in self.filter_cves: -+ continue -+ if cve_id in fixed_cve_id: -+ continue -+ if not isinstance(coldpatch, str): -+ if is_patch_fixed(coldpatch, fixed_coldpatches): -+ continue -+ else: -+ pkg_name, pkg_evr, pkg_arch = coldpatch -+ coldpatch = '%s-%s.%s' % (pkg_name, pkg_evr, pkg_arch) -+ -+ idw = max(idw, len(cve_id)) -+ tiw = max(tiw, len(type)) -+ ciw = max(ciw, len(coldpatch)) -+ format_lines.add((cve_id, type, coldpatch, hotpatch)) -+ for format_line in sorted(format_lines, key=lambda x: x[2]): -+ print('%-*s %-*s %-*s %s' % -+ (idw, format_line[0], tiw, format_line[1], ciw, format_line[2], format_line[3])) -+ -+ def display(self): -+ """ -+ Append hotpatch information according to the output of 'dnf updateinfo list cves' -+ -+ echo lines: -+ [ -+ [cve_id, type, coldpatch, hotpatch] -+ ] -+ """ -+ -+ def type2label(updateinfo, typ, sev): -+ if typ == hawkey.ADVISORY_SECURITY: -+ return updateinfo.SECURITY2LABEL.get(sev, _('Unknown/Sec.')) -+ else: -+ return updateinfo.TYPE2LABEL.get(typ, _('unknown')) -+ -+ mapping_nevra_cve = self.get_mapping_nevra_cve() -+ echo_lines = [] -+ fixed_cve_id = set() -+ fixed_coldpatches = set() -+ iterated_cve_id = set() -+ for ((nevra), aupdated), id2type in sorted(mapping_nevra_cve.items(), key=lambda x: x[0]): -+ pkg_name, pkg_evr, pkg_arch = nevra -+ for cve_id, atypesev in id2type.items(): -+ iterated_cve_id.add(cve_id) -+ label = type2label(self.updateinfo, *atypesev) -+ echo_line = [cve_id, label, nevra, '-'] -+ if cve_id in self.hp_hawkey.hotpatch_cves: -+ hotpatch = self.hp_hawkey.hotpatch_cves[cve_id].hotpatch -+ if hotpatch is not None and hotpatch.src_pkg_nevre[0] == pkg_name: -+ if hotpatch.state == self.hp_hawkey.INSTALLED: -+ # record the fixed cves -+ for cve_id in hotpatch.cves: -+ fixed_cve_id.add(cve_id) -+ # record the fixed coldpatch to filter the cves of the corresponding coldpatch with the lower version -+ fixed_coldpatches.add((nevra)) -+ continue -+ elif hotpatch.state == self.hp_hawkey.INSTALLABLE: -+ echo_line[3] = hotpatch.nevra -+ -+ echo_lines.append(echo_line) -+ -+ self._filter_and_format_list_output( -+ echo_lines, fixed_cve_id, fixed_coldpatches) -diff --git a/hotpatch/hotpatch_updateinfo.py b/hotpatch/hotpatch_updateinfo.py -new file mode 100644 -index 0000000..4e0b702 ---- /dev/null -+++ b/hotpatch/hotpatch_updateinfo.py -@@ -0,0 +1,321 @@ -+from .baseclass import Hotpatch, Cve, Advisory -+from .syscare import Syscare -+import os -+from typing import Optional -+import gzip -+import xml.etree.ElementTree as ET -+import datetime -+ -+ -+class HotpatchUpdateInfo(object): -+ """ -+ Hotpatch relevant updateinfo processing -+ """ -+ -+ UNINSTALLABLE = 0 -+ INSTALLED = 1 -+ INSTALLABLE = 2 -+ -+ def __init__(self, base, cli): -+ self.base = base -+ self.cli = cli -+ # dict {advisory_id: Advisory} -+ self._hotpatch_advisories = {} -+ # dict {cve_id: Cve} -+ self._hotpatch_cves = {} -+ # list [{'Uuid': uuid, 'Name':name, 'Status': status}] -+ self._hotpatch_status = [] -+ -+ self.init_hotpatch_info() -+ -+ def init_hotpatch_info(self): -+ """ -+ Initialize hotpatch information -+ """ -+ self._get_installed_pkgs() -+ self._parse_and_store_hotpatch_info_from_updateinfo() -+ self._init_hotpatch_status_from_syscare() -+ self._init_hotpatch_state() -+ -+ @property -+ def hotpatch_cves(self): -+ return self._hotpatch_cves -+ -+ @property -+ def hotpatch_status(self): -+ return self._hotpatch_status -+ -+ def _get_installed_pkgs(self): -+ """ -+ Get installed packages by setting the hawkey -+ """ -+ sack = self.base.sack -+ # the latest installed packages -+ q = sack.query().installed().latest(1) -+ # plus packages of the running kernel -+ kernel_q = sack.query().filterm(empty=True) -+ kernel = sack.get_running_kernel() -+ if kernel: -+ kernel_q = kernel_q.union( -+ sack.query().filterm(sourcerpm=kernel.sourcerpm)) -+ q = q.union(kernel_q.installed()) -+ q = q.apply() -+ -+ self._inst_pkgs_query = q -+ -+ def _parse_and_store_hotpatch_info_from_updateinfo(self): -+ """ -+ Initialize hotpatch information from repos -+ """ -+ # get xxx-hotpatch.xml.gz file paths by traversing the system_cachedir(/var/cache/dnf) -+ system_cachedir = self.cli.base.conf.system_cachedir -+ all_repos = self.cli.base.repos -+ map_repo_updateinfoxml = {} -+ -+ for file in os.listdir(system_cachedir): -+ file_path = os.path.join(system_cachedir, file) -+ if os.path.isdir(file_path): -+ repodata_path = os.path.join(file_path, "repodata") -+ if not os.path.isdir(repodata_path): -+ continue -+ -+ for xml_file in os.listdir(repodata_path): -+ # the hotpatch relevant updateinfo is recorded in xxx-hotpatch.xml.gz -+ if "hotpatch" in xml_file: -+ repo_name = file.split("-")[0] -+ cache_updateinfo_xml_path = os.path.join( -+ repodata_path, xml_file) -+ map_repo_updateinfoxml[repo_name] = cache_updateinfo_xml_path -+ -+ # only hotpatch relevant updateinfo from enabled repos are parsed and stored -+ for repo in all_repos.iter_enabled(): -+ repo_id = repo.id -+ if repo_id in map_repo_updateinfoxml: -+ updateinfoxml_path = map_repo_updateinfoxml[repo_id] -+ self._parse_and_store_from_xml(updateinfoxml_path) -+ -+ def _parse_pkglist(self, pkglist): -+ """ -+ Parse the pkglist information, filter the hotpatches with different arches -+ """ -+ hotpatches = [] -+ hot_patch_collection = pkglist.find('collection') -+ arches = self.base.sack.list_arches() -+ if not hot_patch_collection: -+ return hotpatches -+ for package in hot_patch_collection.iter('package'): -+ hotpatch = {key: value for key, value in package.items()} -+ if hotpatch['arch'] not in arches: -+ continue -+ hotpatch['filename'] = package.find('filename').text -+ hotpatches.append(hotpatch) -+ return hotpatches -+ -+ def _parse_references(self, reference): -+ """ -+ Parse the reference information, check whether the 'id' is missing -+ """ -+ cves = [] -+ for ref in reference: -+ cve = {key: value for key, value in ref.items()} -+ if 'id' not in cve: -+ continue -+ cves.append(cve) -+ return cves -+ -+ def _verify_date_str_lawyer(self, datetime_str: str) -> str: -+ """ -+ Check whether the 'datetime' field is legal, if not return default value -+ """ -+ if datetime_str.isdigit() and len(datetime_str) == 10: -+ datetime_str = int(datetime_str) -+ datetime_str = datetime.datetime.fromtimestamp( -+ datetime_str).strftime("%Y-%m-%d %H:%M:%S") -+ try: -+ datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S') -+ return datetime_str -+ except ValueError: -+ return "1970-01-01 08:00:00" -+ -+ def _parse_advisory(self, update): -+ """ -+ Parse the advisory information: check whether the 'datetime' field is legal, parse the 'references' -+ field and the 'pkglist' field, save 'type' information -+ """ -+ advisory = {} -+ for node in update: -+ if node.tag == 'datetime': -+ advisory[node.tag] = self._verify_date_str_lawyer( -+ update.find(node.tag).text) -+ elif node.tag == 'references': -+ advisory[node.tag] = self._parse_references(node) -+ elif node.tag == 'pkglist': -+ advisory['hotpatches'] = self._parse_pkglist(node) -+ else: -+ advisory[node.tag] = update.find(node.tag).text -+ advisory['type'] = update.get('type') -+ return advisory -+ -+ def _store_advisory_info(self, advisory_kwargs: dict()): -+ """ -+ Instantiate Cve, Hotpatch and Advisory object according to the advisory kwargs -+ """ -+ advisory_references = advisory_kwargs.pop('references') -+ advisory_hotpatches = advisory_kwargs.pop('hotpatches') -+ advisory = Advisory(**advisory_kwargs) -+ advisory_cves = {} -+ for cve_kwargs in advisory_references: -+ cve = Cve(**cve_kwargs) -+ self._hotpatch_cves[cve.cve_id] = cve -+ advisory_cves[cve.cve_id] = cve -+ advisory.cves = advisory_cves -+ -+ for hotpatch_kwargs in advisory_hotpatches: -+ hotpatch = Hotpatch(**hotpatch_kwargs) -+ hotpatch.advisory = advisory -+ hotpatch.cves = advisory_cves.keys() -+ -+ advisory.add_hotpatch(hotpatch) -+ -+ for cve in advisory_cves.values(): -+ cve.hotpatch = hotpatch -+ -+ self._hotpatch_advisories.setdefault( -+ advisory_kwargs['id'], list()).append(advisory) -+ -+ def _init_hotpatch_state(self): -+ """ -+ Initialize the hotpatch state -+ -+ each hotpatch has three states: -+ 1. UNINSTALLABLE: can not be installed due to the source package version mismatch -+ 2. INSTALLED: has been installed and actived in syscare -+ 3. INSTALLABLE: can be installed -+ -+ """ -+ for advisories in self._hotpatch_advisories.values(): -+ for advisory in advisories: -+ for hotpatch in advisory.hotpatches: -+ src_pkg_name, src_pkg_version, src_pkg_release = hotpatch.src_pkg_nevre -+ inst_pkgs = self._inst_pkgs_query.filter(name=src_pkg_name) -+ hotpatch.state = self.UNINSTALLABLE -+ # check whether the relevant source package is installed on this machine -+ if not inst_pkgs: -+ continue -+ for inst_pkg in inst_pkgs: -+ inst_pkg_vere = '%s-%s' % (inst_pkg.version, -+ inst_pkg.release) -+ hp_vere = '%s-%s' % (src_pkg_version, src_pkg_release) -+ if hp_vere != inst_pkg_vere: -+ continue -+ elif self._get_hotpatch_status_in_syscare(hotpatch) == 'ACTIVED': -+ hotpatch.state = self.INSTALLED -+ else: -+ hotpatch.state = self.INSTALLABLE -+ -+ def _parse_and_store_from_xml(self, updateinfoxml): -+ """ -+ Parse and store hotpatch update information from xxx-hotpatch.xml.gz -+ -+ xxx-hotpatch.xml.gz e.g. -+ -+ -+ -+ -+ openEuler-SA-2022-1 -+ An update for mariadb is now available for openEuler-22.03-LTS -+ Important -+ openEuler -+ -+ -+ -+ -+ patch-redis-6.2.5-1-HP001.(CVE-2022-24048) -+ -+ -+ openEuler -+ -+ patch-redis-6.2.5-1-HP001-0-1.aarch64.rpm -+ -+ -+ patch-redis-6.2.5-1-HP001-0-1.x86_64.rpm -+ -+ -+ -+ -+ ... -+ -+ """ -+ content = gzip.open(updateinfoxml) -+ tree = ET.parse(content) -+ root = tree.getroot() -+ for update in root.iter('update'): -+ advisory = self._parse_advisory(update) -+ self._store_advisory_info(advisory) -+ -+ def _init_hotpatch_status_from_syscare(self): -+ """ -+ Initialize hotpatch status from syscare -+ """ -+ self._hotpatch_status = Syscare().list() -+ -+ self._hotpatch_state = {} -+ for hotpatch_info in self._hotpatch_status: -+ self._hotpatch_state[hotpatch_info['Name'] -+ ] = hotpatch_info['Status'] -+ -+ def _get_hotpatch_status_in_syscare(self, hotpatch: Hotpatch) -> str: -+ """ -+ Get hotpatch status in syscare -+ """ -+ if hotpatch.syscare_name not in self._hotpatch_state: -+ return '' -+ return self._hotpatch_state[hotpatch.syscare_name] -+ -+ def get_hotpatches_from_cve(self, cves: Optional[list[str]] = []) -> dict(): -+ """ -+ Get hotpatches from specified cve -+ -+ Args: -+ cves: [cve_id_1, cve_id_2] -+ -+ Returns: -+ { -+ cve_id_1: [hotpatch1], -+ cve_id_2: [] -+ } -+ """ -+ mapping_cve_hotpatches = dict() -+ for cve_id in cves: -+ mapping_cve_hotpatches[cve_id] = [] -+ if cve_id not in self.hotpatch_cves: -+ continue -+ hotpatch = self.hotpatch_cves[cve_id].hotpatch -+ if hotpatch is not None and hotpatch.state == self.INSTALLABLE: -+ mapping_cve_hotpatches[cve_id].append(hotpatch.nevra) -+ return mapping_cve_hotpatches -+ -+ def get_hotpatches_from_advisories(self, advisories: Optional[list[str]] = []) -> dict(): -+ """ -+ Get hotpatches from specified advisories -+ -+ Args: -+ advisories: [advisory_id_1, advisory_id_2] -+ -+ Return: -+ { -+ advisory_id_1: [hotpatch1], -+ advisory_id_2: [] -+ } -+ """ -+ mapping_advisory_hotpatches = dict() -+ for advisory_id in advisories: -+ mapping_advisory_hotpatches[advisory_id] = [] -+ if advisory_id not in self._hotpatch_advisories: -+ continue -+ for advisory in self._hotpatch_advisories[advisory_id]: -+ for hotpatch in advisory.hotpatches: -+ if hotpatch.state == self.INSTALLABLE: -+ mapping_advisory_hotpatches[advisory_id].append( -+ hotpatch.nevra) -+ return mapping_advisory_hotpatches --- -2.33.0 - diff --git a/0003-add-dnf-hot-upgrade-plugin.patch b/0003-add-dnf-hot-upgrade-plugin.patch deleted file mode 100644 index 6b53481121552983a6722c853caddfbae66890f3..0000000000000000000000000000000000000000 --- a/0003-add-dnf-hot-upgrade-plugin.patch +++ /dev/null @@ -1,399 +0,0 @@ -From 20c09ffb55a630a4259b7c87cd88da2ce6f28a07 Mon Sep 17 00:00:00 2001 -From: zhu-yuncheng -Date: Sat, 25 Mar 2023 14:43:20 +0800 -Subject: [PATCH] add dnf hot upgrade plugin - ---- - hotpatch/hot-upgrade.py | 275 ++++++++++++++++++++++++++++++++++++++++ - hotpatch/syscare.py | 96 ++++++++++++++ - 2 files changed, 371 insertions(+) - create mode 100644 hotpatch/hot-upgrade.py - create mode 100644 hotpatch/syscare.py - -diff --git a/hotpatch/hot-upgrade.py b/hotpatch/hot-upgrade.py -new file mode 100644 -index 0000000..7a4c3c6 ---- /dev/null -+++ b/hotpatch/hot-upgrade.py -@@ -0,0 +1,275 @@ -+# supplies the dnf 'diff' command. -+# -+# Copyright (C) 2018 Red Hat, Inc. -+# Written by Pavel Raiskup . -+# -+# This copyrighted material is made available to anyone wishing to use, -+# modify, copy, or redistribute it subject to the terms and conditions of -+# the GNU General Public License v.2, or (at your option) any later version. -+# This program is distributed in the hope that it will be useful, but WITHOUT -+# ANY WARRANTY expressed or implied, including the implied warranties of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -+# Public License for more details. You should have received a copy of the -+# GNU General Public License along with this program; if not, write to the -+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the -+# source code or documentation are not subject to the GNU General Public -+# License and may only be used or replicated with the express permission of -+# Red Hat, Inc. -+ -+from __future__ import print_function -+ -+import dnf.base -+import dnf.exceptions -+import hawkey -+from dnf.cli import commands -+from dnf.cli.option_parser import OptionParser -+from dnf.cli.output import Output -+from dnfpluginscore import _, logger -+ -+from .syscare import Syscare -+from .hotpatch_updateinfo import HotpatchUpdateInfo -+ -+ -+@dnf.plugin.register_command -+class HotupgradeCommand(dnf.cli.Command): -+ aliases = ("hotupgrade",) -+ summary = "Hot upgrade package using hot patch." -+ usage = "" -+ syscare = Syscare() -+ hp_list = [] -+ -+ @staticmethod -+ def set_argparser(parser): -+ parser.add_argument('packages', nargs='*', help=_('Package to upgrade'), -+ action=OptionParser.ParseSpecGroupFileCallback, -+ metavar=_('PACKAGE')) -+ -+ def configure(self): -+ """Verify that conditions are met so that this command can run. -+ These include that there are enabled repositories with gpg -+ keys, and that this command is being run by the root user. -+ """ -+ demands = self.cli.demands -+ demands.sack_activation = True -+ demands.available_repos = True -+ demands.resolving = True -+ demands.root_user = True -+ -+ commands._checkGPGKey(self.base, self.cli) -+ if not self.opts.filenames: -+ commands._checkEnabledRepo(self.base) -+ -+ def run(self): -+ if self.opts.pkg_specs: -+ self.hp_list = self.opts.pkg_specs -+ elif self.opts.cves or self.opts.advisory: -+ cve_pkgs = self.get_hotpatch_based_on_cve(self.opts.cves) -+ advisory_pkgs = self.get_hotpatch_based_on_advisory(self.opts.advisory) -+ self.hp_list = cve_pkgs + advisory_pkgs -+ else: -+ raise dnf.exceptions.Error(_('No qualified rpm package name or cve/advisory id.')) -+ -+ hp_target_map = self._get_available_hotpatches(self.hp_list) -+ if not hp_target_map: -+ raise dnf.exceptions.Error(_('No hot patches marked for install.')) -+ -+ target_patch_map = self._get_applied_old_patch(list(hp_target_map.values())) -+ if target_patch_map: -+ self._remove_hot_patches(target_patch_map) -+ else: -+ self.syscare.save() -+ success = self._install_hot_patch(list(hp_target_map.keys())) -+ if not success: -+ output, status = self.syscare.restore() -+ if status: -+ raise dnf.exceptions.Error(_('Roll back failed.')) -+ raise dnf.exceptions.Error(_("Roll back succeed.")) -+ return -+ -+ def run_transaction(self) -> None: -+ """ -+ apply hot patches -+ Returns: -+ None -+ """ -+ logger.info(_('Applying hot patch')) -+ if not self.base.transaction: -+ for hp in self.hp_list: -+ self._apply_hp(hp) -+ return -+ -+ for ts_item in self.base.transaction: -+ if ts_item.action not in dnf.transaction.FORWARD_ACTIONS: -+ continue -+ self._apply_hp(str(ts_item.pkg)) -+ -+ def _apply_hp(self, hp_full_name): -+ pkg_info = self._parse_hp_name(hp_full_name) -+ hp_full_name = "-".join([pkg_info["name"], pkg_info["version"], pkg_info["release"]]) \ -+ + '/' + pkg_info["hp_name"] -+ output, status = self.syscare.apply(hp_full_name) -+ if status: -+ logger.info(_('Apply hot patch failed: %s.'), hp_full_name) -+ else: -+ logger.info(_('Apply hot patch succeed: %s.'), hp_full_name) -+ -+ def _get_available_hotpatches(self, pkg_specs: list) -> dict: -+ """ -+ check two conditions: -+ 1. the hot patch rpm package exists in repositories -+ 2. the hot patch's target package with specific version and release already installed -+ Args: -+ pkg_specs: full names of hot patches' rpm packages -+ -+ Returns: -+ dict: key is available hot patches' full name, value is target package's name-version-release -+ """ -+ hp_target_map = {} -+ installed_packages = self.base.sack.query().installed() -+ for pkg_spec in set(pkg_specs): -+ query = self.base.sack.query() -+ # check the package exist in repo or not -+ subj = dnf.subject.Subject(pkg_spec) -+ parsed_nevras = subj.get_nevra_possibilities(forms=[hawkey.FORM_NEVRA]) -+ if len(parsed_nevras) != 1: -+ logger.info(_('Cannot parse NEVRA for package "{nevra}"').format(nevra=pkg_spec)) -+ continue -+ -+ parsed_nevra = parsed_nevras[0] -+ available_hp = query.available().filter(name=parsed_nevra.name, version=parsed_nevra.version, -+ release=parsed_nevra.release, arch=parsed_nevra.arch) -+ if not available_hp: -+ logger.info(_('No match for argument: %s'), self.base.output.term.bold(pkg_spec)) -+ continue -+ -+ # check the hot patch's target package installed or not -+ pkg_info = self._parse_hp_name(pkg_spec) -+ installed_pkg = installed_packages.filter(name=pkg_info["name"], -+ version=pkg_info["version"], -+ release=pkg_info["release"]).run() -+ if not installed_pkg: -+ logger.info(_("The hot patch's target package is not installed: %s"), -+ self.base.output.term.bold(pkg_spec)) -+ continue -+ -+ if len(installed_pkg) != 1: -+ logger.info(_("The hot patch '%s' has multiple target packages, please check."), -+ self.base.output.term.bold(pkg_spec)) -+ continue -+ target = "-".join([pkg_info["name"], pkg_info["version"], pkg_info["release"]]) -+ hp_target_map[pkg_spec] = target -+ return hp_target_map -+ -+ def _get_applied_old_patch(self, targets: list): -+ """ -+ get targets' applied hot patches -+ Args: -+ targets: target RPMs' name-version-release. e.g. redis-1.0-1 -+ -+ Returns: -+ dict: targets' applied hot patches. e.g. {'redis-1.0-1': 'redis-1.0-1/HP001'} -+ """ -+ target_patch_map = {} -+ hps_info = Syscare.list() -+ for hp_info in hps_info: -+ target, hp_name = hp_info["Name"].split('/') -+ if target in targets and hp_info["Status"] != "NOT-APPLIED": -+ logger.info(_("The target package '%s' has a hotpatch '%s' applied"), -+ self.base.output.term.bold(target), -+ self.base.output.term.bold(hp_name)) -+ target_patch_map[target] = hp_info["Name"] -+ return target_patch_map -+ -+ def _remove_hot_patches(self, target_patch_map: dict) -> None: -+ output = Output(self.base, dnf.conf.Conf()) -+ logger.info(_("Gonna remove these hot patches: %s"), list(target_patch_map.values())) -+ remove_flag = output.userconfirm() -+ if not remove_flag: -+ raise dnf.exceptions.Error(_('Operation aborted.')) -+ -+ self.syscare.save() -+ for target, hp_name in target_patch_map.items(): -+ logger.info(_("Remove hot patch %s."), hp_name) -+ output, status = self.syscare.remove(hp_name) -+ if status: -+ logger.info(_("Remove hot patch '%s' failed, roll back to original status."), -+ self.base.output.term.bold(hp_name)) -+ output, status = self.syscare.restore() -+ if status: -+ raise dnf.exceptions.Error(_('Roll back failed.')) -+ raise dnf.exceptions.Error(_('Roll back succeed.')) -+ -+ @staticmethod -+ def _parse_hp_name(hp_filename: str) -> dict: -+ """ -+ parse hot patch's name, get target rpm's name, version, release and hp's name. -+ Args: -+ hp_filename: hot patch's name, in the format of -+ 'patch-{pkg_name}-{pkg_version}-{pkg_release}-{patchname}-{patch_version}-{patch_release}.rpm' -+ e.g. patch-kernel-5.10.0-60.66.0.91.oe2203-HP001-1-1.x86_64.rpm -+ pkg_name may have '-' in it, patch name cannot have '-'. -+ Returns: -+ dict: rpm info. {"name": "", "version": "", "release": "", "hp_name": ""} -+ """ -+ splitted_hp_filename = hp_filename.split('-') -+ try: -+ rpm_info = {"release": splitted_hp_filename[-4], "version": splitted_hp_filename[-5], -+ "name": "-".join(splitted_hp_filename[1:-5]), "hp_name": splitted_hp_filename[-3]} -+ except IndexError as e: -+ raise dnf.exceptions.Error(_('Parse hot patch name failed. Please insert correct hot patch name.')) -+ return rpm_info -+ -+ def _install_hot_patch(self, pkg_specs: list) -> bool: -+ """ -+ install hot patches -+ Args: -+ pkg_specs: hot patches' full name -+ -+ Returns: -+ bool -+ """ -+ success = True -+ for pkg_spec in pkg_specs: -+ try: -+ self.base.install(pkg_spec) -+ except dnf.exceptions.MarkingError as e: -+ logger.info(_('No match for argument: %s.'), -+ self.base.output.term.bold(pkg_spec)) -+ success = False -+ return success -+ -+ def get_hotpatch_based_on_cve(self, cves: list) -> list: -+ """ -+ Get the hot patches corresponding to CVEs -+ Args: -+ cves: cve id list -+ -+ Returns: -+ list: list of hot patches full name. e.g.["tmp2-tss-3.1.0-3.oe2203sp1"] -+ """ -+ updateinfo = HotpatchUpdateInfo(self.cli.base, self.cli) -+ hp_list = [] -+ cve_hp_dict = updateinfo.get_hotpatches_from_cve(cves) -+ for cve, hp in cve_hp_dict.items(): -+ if not hp: -+ logger.info(_("The cve doesn't exist: %s"), cve) -+ continue -+ hp_list += hp -+ return list(set(hp_list)) -+ -+ def get_hotpatch_based_on_advisory(self, advisories: list) -> list: -+ """ -+ Get the hot patches corresponding to advisories -+ Args: -+ advisories: advisory id list -+ -+ Returns: -+ list: list of hot patches full name. e.g.["tmp2-tss-3.1.0-3.oe2203sp1"] -+ """ -+ updateinfo = HotpatchUpdateInfo(self.cli.base, self.cli) -+ hp_list = [] -+ advisory_hp_dict = updateinfo.get_hotpatches_from_advisories(advisories) -+ for hp in advisory_hp_dict.values(): -+ hp_list += hp -+ return list(set(hp_list)) -diff --git a/hotpatch/syscare.py b/hotpatch/syscare.py -new file mode 100644 -index 0000000..b24b07e ---- /dev/null -+++ b/hotpatch/syscare.py -@@ -0,0 +1,96 @@ -+import subprocess -+from typing import List -+ -+ -+SUCCEED = 0 -+FAIL = 255 -+ -+def cmd_output(cmd): -+ try: -+ result = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -+ result.wait() -+ return result.stdout.read().decode('utf-8'), result.returncode -+ except Exception as e: -+ print("error: ", e) -+ return str(e), FAIL -+ -+class Syscare: -+ @classmethod -+ def list(cls, condition=None) -> List[dict]: -+ """ -+ Target Name Status -+ redis-6.2.5-1.oe2203 CVE-2021-23675 ACTIVED -+ kernel-5.10.0-60.80.0.104.oe2203 modify-proc-version ACTIVED -+ """ -+ cmd = ["syscare", "list"] -+ list_output, return_code = cmd_output(cmd) -+ if return_code != SUCCEED: -+ return [] -+ -+ content = list_output.split('\n') -+ if len(content) <= 2: -+ return [] -+ -+ header = content[0].split() -+ result = [] -+ for item in content[1:-1]: -+ tmp = dict(zip(header, item.split())) -+ if not condition or cls.judge(tmp, condition): -+ result.append(tmp) -+ return result -+ -+ @staticmethod -+ def judge(content: dict, condition: dict): -+ for key, value in condition.items(): -+ if content.get(key) != value: -+ return False -+ return True -+ -+ @staticmethod -+ def status(patch_name: str): -+ cmd = ["syscare", "status", patch_name] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -+ -+ @staticmethod -+ def active(patch_name: str): -+ cmd = ["syscare", "active", patch_name] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -+ -+ @staticmethod -+ def deactive(patch_name: str): -+ cmd = ["syscare", "deactive", patch_name] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -+ -+ @staticmethod -+ def remove(patch_name: str): -+ cmd = ["syscare", "remove", patch_name] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -+ -+ @staticmethod -+ def apply(patch_name: str): -+ cmd = ["syscare", "apply", patch_name] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -+ -+ @staticmethod -+ def save(): -+ cmd = ["syscare", "save"] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -+ -+ @staticmethod -+ def restore(): -+ cmd = ["syscare", "restore"] -+ output, return_code = cmd_output(cmd) -+ -+ return output, return_code -\ No newline at end of file --- -2.30.0 - diff --git a/0004-better-hotupgrade-command-output.patch b/0004-better-hotupgrade-command-output.patch deleted file mode 100644 index cfcce19b62b19d3203a999a7effc0267db9bb110..0000000000000000000000000000000000000000 --- a/0004-better-hotupgrade-command-output.patch +++ /dev/null @@ -1,25 +0,0 @@ -From a1ccf84bf28323ce613e0c1d1819c31d0d6cd13b Mon Sep 17 00:00:00 2001 -From: zhu-yuncheng -Date: Mon, 27 Mar 2023 23:45:18 +0800 -Subject: [PATCH] better hotupgrade command output - ---- - hotpatch/hot-upgrade.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/hotpatch/hot-upgrade.py b/hotpatch/hot-upgrade.py -index 7a4c3c6..06f32ad 100644 ---- a/hotpatch/hot-upgrade.py -+++ b/hotpatch/hot-upgrade.py -@@ -253,7 +253,7 @@ class HotupgradeCommand(dnf.cli.Command): - cve_hp_dict = updateinfo.get_hotpatches_from_cve(cves) - for cve, hp in cve_hp_dict.items(): - if not hp: -- logger.info(_("The cve doesn't exist: %s"), cve) -+ logger.info(_("The cve's hot patch doesn't exist: %s"), cve) - continue - hp_list += hp - return list(set(hp_list)) --- -2.30.0 - diff --git a/aops-apollo-v1.1.2.tar.gz b/aops-apollo-v1.1.2.tar.gz deleted file mode 100644 index bb868462665fec2140ef74c55b61547721468c5c..0000000000000000000000000000000000000000 Binary files a/aops-apollo-v1.1.2.tar.gz and /dev/null differ diff --git a/aops-apollo-v1.2.0.tar.gz b/aops-apollo-v1.2.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..12ae105618a5c02f4090e3557892b955f77e93f3 Binary files /dev/null and b/aops-apollo-v1.2.0.tar.gz differ diff --git a/aops-apollo.spec b/aops-apollo.spec index c903054b7077bf788d9cb2f1aa45af9a7977b2ec..4c065878f6dc91d85258e9351a3a2af5c437312e 100644 --- a/aops-apollo.spec +++ b/aops-apollo.spec @@ -1,20 +1,17 @@ Name: aops-apollo -Version: v1.1.2 -Release: 7 +Version: v1.2.0 +Release: 1 Summary: Cve management service, monitor machine vulnerabilities and provide fix functions. License: MulanPSL2 URL: https://gitee.com/openeuler/%{name} Source0: %{name}-%{version}.tar.gz -Patch0001: 0001-fix-partial-succeed-bug.patch -Patch0002: 0002-add-dnf-hot-patch-list-plugin.patch -Patch0003: 0003-add-dnf-hot-upgrade-plugin.patch -Patch0004: 0004-better-hotupgrade-command-output.patch BuildRequires: python3-setuptools -Requires: aops-vulcanus >= v1.0.0 +Requires: aops-vulcanus >= v1.2.0 Requires: python3-elasticsearch python3-flask-restful python3-marshmallow >= 3.13.0 Requires: python3-sqlalchemy python3-PyMySQL python3-Flask-APScheduler >= 1.11.0 Requires: python3-PyYAML python3-flask +Requires: python3-retrying python3-lxml Provides: aops-apollo @@ -29,7 +26,8 @@ Requires: python3-hawkey python3-dnf syscare dnf hotpatch plugin, it's about hotpatch query and fix %prep -%autosetup -n %{name}-%{version} -p1 +%autosetup -n %{name}-%{version} + # build for aops-apollo %py3_build @@ -41,10 +39,10 @@ dnf hotpatch plugin, it's about hotpatch query and fix #install for aops-dnf-plugin cp -r hotpatch %{buildroot}/%{python3_sitelib}/dnf-plugins/ - %files %doc README.* %attr(0644,root,root) %{_sysconfdir}/aops/apollo.ini +%attr(0644,root,root) %{_sysconfdir}/aops/apollo_crontab.ini %attr(0755,root,root) %{_bindir}/aops-apollo %attr(0755,root,root) /usr/lib/systemd/system/aops-apollo.service %{python3_sitelib}/aops_apollo*.egg-info @@ -53,25 +51,18 @@ cp -r hotpatch %{buildroot}/%{python3_sitelib}/dnf-plugins/ %files -n dnf-hotpatch-plugin %{python3_sitelib}/dnf-plugins/* - %changelog -* Mon Mar 27 2023 zhu-yuncheng - v1.1.2-7 -- better hotupgrade command output when cve exists but hot patch doesn't - -* Sat Mar 25 2023 wangguangge - v1.1.2-6 -- fix baseclass.py bug and add syscare require in spec - -* Sat Mar 25 2023 zhu-yuncheng - v1.1.2-5 -- add dnf hot upgrade plugin - -* Fri Mar 24 2023 wangguangge - v1.1.2-4 +* Mon Apr 17 2023 gongzhengtang - v1.2.0-1 +- add updated security advisory at regular time +- add execute the CVE scan command at regular time +- add correct abnormal data at regular time - add dnf hotpatch list plugin * Tue Dec 27 2022 wenxin - v1.1.2-3 - modify version for vulcanus * Thu Dec 15 2022 ptyang<1475324955@qq.com> - v1.1.2-2 -- fix "PARTIAL_SUCCEED" bug +- fix "PARTIAL_SUCCEED" bug * Wed Dec 07 2022 wenxin - v1.1.2-1 - modify status code for upload security advisories;fix cve query error