!80 [sync] PR-79: update version to v1.4.0-2

From: @openeuler-sync-bot 
Reviewed-by: @Lostwayzxc 
Signed-off-by: @Lostwayzxc
This commit is contained in:
openeuler-ci-bot 2023-12-18 09:27:49 +00:00 committed by Gitee
commit a00ab9dfc4
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 615 additions and 2 deletions

View File

@ -0,0 +1,251 @@
From ee8e8cb1bbc3bb1ba27ee6f0e8acfc663cf10c12 Mon Sep 17 00:00:00 2001
From: rearcher <123781007@qq.com>
Date: Tue, 12 Dec 2023 09:47:12 +0800
Subject: [PATCH] Add interface for detecting host status
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
zeus/conf/constant.py | 2 +
zeus/database/proxy/host.py | 73 +++++++++++++++++++++++++++++++++++-
zeus/function/verify/host.py | 8 ++++
zeus/host_manager/view.py | 63 +++++++++++++++++++++++++++++++
zeus/url.py | 2 +
5 files changed, 147 insertions(+), 1 deletion(-)
diff --git a/zeus/conf/constant.py b/zeus/conf/constant.py
index 9305604..994dd90 100644
--- a/zeus/conf/constant.py
+++ b/zeus/conf/constant.py
@@ -42,6 +42,7 @@ ADD_HOST_BATCH = "/manage/host/add/batch"
GET_HOST_TEMPLATE_FILE = "/manage/host/file/template"
DELETE_HOST = "/manage/host/delete"
QUERY_HOST = "/manage/host/get"
+GET_HOST_STATUS = "/manage/host/status/get"
GET_HOST_COUNT = "/manage/host/count"
AUTH_REDIRECT_URL = "/manage/account/authredirecturl"
BIND_AUTH_ACCOUNT = "/manage/account/bindaccount"
@@ -116,3 +117,4 @@ class HostStatus:
ONLINE = 0
OFFLINE = 1
UNESTABLISHED = 2
+ SCANNING = 3
diff --git a/zeus/database/proxy/host.py b/zeus/database/proxy/host.py
index 1656c56..477c482 100644
--- a/zeus/database/proxy/host.py
+++ b/zeus/database/proxy/host.py
@@ -268,7 +268,6 @@ class HostProxy(MysqlProxy):
"host_group_name": host.host_group_name,
"host_ip": host.host_ip,
"management": host.management,
- "status": host.status,
"scene": host.scene,
"os_version": host.os_version,
"ssh_port": host.ssh_port,
@@ -340,6 +339,52 @@ class HostProxy(MysqlProxy):
LOGGER.error("query host %s basic info fail", host_list)
return DATABASE_QUERY_ERROR, result
+ def get_host_ssh_info(self, data):
+ """
+ Get host ssh info according to host id from table
+
+ Args:
+ data(dict): parameter, e.g.
+ {
+ "username": "admin"
+ "host_list": ["id1", "id2"]
+ }
+
+ Returns:
+ int: status code
+ dict: query result
+ """
+ username = data.get('username')
+ host_list = data.get('host_list')
+ result = []
+ query_fields = [
+ Host.host_id,
+ Host.host_ip,
+ Host.ssh_port,
+ Host.pkey,
+ Host.ssh_user,
+ ]
+ filters = {Host.user == username}
+ if host_list:
+ filters.add(Host.host_id.in_(host_list))
+ try:
+ hosts = self.session.query(*query_fields).filter(*filters).all()
+ for host in hosts:
+ host_info = {
+ "host_id": host.host_id,
+ "host_ip": host.host_ip,
+ "ssh_port": host.ssh_port,
+ "pkey": host.pkey,
+ "ssh_user": host.ssh_user,
+ }
+ result.append(host_info)
+ LOGGER.debug("query host %s ssh info succeed", host_list)
+ return SUCCEED, result
+ except sqlalchemy.exc.SQLAlchemyError as error:
+ LOGGER.error(error)
+ LOGGER.error("query host %s ssh info fail", host_list)
+ return DATABASE_QUERY_ERROR, result
+
def get_total_host_info_by_user(self, data):
"""
Get host basic info according to user from table
@@ -775,3 +820,29 @@ class HostProxy(MysqlProxy):
LOGGER.error(error)
self.session.rollback()
return DATABASE_UPDATE_ERROR
+
+
+ def update_host_status(self, host_info: list) -> str:
+ """
+ update host status to host table
+
+ Args:
+ host_info(list): e.g
+ {
+ "host_id": host_id,
+ "status": status
+ }
+
+ Returns:
+ str: SUCCEED or DATABASE_UPDATE_ERROR
+ """
+ try:
+ for host in host_info:
+ self.session.query(Host).filter(Host.host_id == host.get('host_id')).update(
+ {"status": host.get('status')})
+ self.session.commit()
+ return SUCCEED
+ except sqlalchemy.exc.SQLAlchemyError as error:
+ LOGGER.error(error)
+ self.session.rollback()
+ return DATABASE_UPDATE_ERROR
diff --git a/zeus/function/verify/host.py b/zeus/function/verify/host.py
index d09eedd..f746968 100644
--- a/zeus/function/verify/host.py
+++ b/zeus/function/verify/host.py
@@ -60,6 +60,14 @@ class GetHostSchema(Schema):
per_page = fields.Integer(required=False, validate=lambda s: 50 > s > 0)
+class GetHostStatusSchema(Schema):
+ """
+ validators for parameter of /manage/host/getstatus
+ """
+
+ host_list = fields.List(fields.Integer(), required=True)
+
+
class AddHostGroupSchema(Schema):
"""
validators for parameter of /manage/host/add_host_group
diff --git a/zeus/host_manager/view.py b/zeus/host_manager/view.py
index 10418d1..7ad133d 100644
--- a/zeus/host_manager/view.py
+++ b/zeus/host_manager/view.py
@@ -46,6 +46,7 @@ from zeus.function.verify.host import (
GetHostGroupSchema,
GetHostInfoSchema,
GetHostSchema,
+ GetHostStatusSchema,
UpdateHostSchema,
)
from zeus.host_manager.ssh import SSH, execute_command_and_parse_its_result, generate_key
@@ -118,6 +119,68 @@ class GetHostCount(BaseResponse):
return self.response(code=status_code, data=result)
+class GetHostStatus(BaseResponse):
+ """
+ Interface for get host status.
+ Restful API: POST
+ """
+
+ @BaseResponse.handle(schema=GetHostStatusSchema, proxy=HostProxy)
+ def post(self, callback: HostProxy, **params):
+ """
+ get host status
+
+ Args:
+ host_list (list): host id list
+ username: "admin"
+
+ Returns:
+ list: response body
+ """
+ status_code, host_infos = callback.get_host_ssh_info(params)
+
+ multi_thread_handler = MultiThreadHandler(lambda p: self.get_host_status(p), host_infos, None)
+ multi_thread_handler.create_thread()
+ result_list = multi_thread_handler.get_result()
+
+ callback.update_host_status(result_list)
+
+ return self.response(code=status_code, data=result_list)
+
+ @staticmethod
+ def get_host_status(host: dict) -> dict:
+ """
+ Get host status
+
+ Args:
+ host (dict): e.g
+ {
+ "host_id":"host id",
+ "ssh_user":"root",
+ "pkey":"pkey",
+ "host_ip":"host_ip",
+ "ssh_port":"port"
+ }
+
+ Returns:
+ """
+ status = verify_ssh_login_info(
+ ClientConnectArgs(
+ host.get("host_ip"), host.get("ssh_port"), host.get("ssh_user"), host.get("pkey")
+ )
+ )
+ if status == state.SUCCEED:
+ if status != HostStatus.SCANNING:
+ host['status'] = HostStatus.ONLINE
+ elif status == state.SSH_AUTHENTICATION_ERROR:
+ host['status'] = HostStatus.UNESTABLISHED
+ else:
+ host['status'] = HostStatus.OFFLINE
+
+ result = {"host_id": host.get("host_id"), "status": host.get("status")}
+ return result
+
+
class AddHostGroup(BaseResponse):
"""
Interface for add host group.
diff --git a/zeus/url.py b/zeus/url.py
index eb8a189..ad8cec9 100644
--- a/zeus/url.py
+++ b/zeus/url.py
@@ -52,6 +52,7 @@ from zeus.conf.constant import (
USER_LOGIN,
SYNC_CONFIG,
OBJECT_FILE_CONFIG,
+ GET_HOST_STATUS,
)
from zeus.config_manager import view as config_view
from zeus.host_manager import view as host_view
@@ -77,6 +78,7 @@ SPECIFIC_URLS = {
(host_view.DeleteHost, DELETE_HOST),
(host_view.UpdateHost, UPDATE_HOST),
(host_view.GetHost, QUERY_HOST),
+ (host_view.GetHostStatus, GET_HOST_STATUS),
(host_view.GetHostInfo, QUERY_HOST_DETAIL),
(host_view.GetHostCount, GET_HOST_COUNT),
(host_view.GetHostTemplateFile, GET_HOST_TEMPLATE_FILE),
--
2.33.0

View File

@ -0,0 +1,77 @@
From c10d1ff7ad3b74886911b719f50f4775120db789 Mon Sep 17 00:00:00 2001
From: rearcher <123781007@qq.com>
Date: Thu, 14 Dec 2023 19:58:19 +0800
Subject: [PATCH] add a new query method based on host name for the host list query interface.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...6\216\245\345\217\243\346\226\207\346\241\243.yaml" | 4 ++++
zeus/database/proxy/host.py | 10 +++++++++-
zeus/function/verify/host.py | 1 +
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git "a/doc/design/aops-zeus\346\216\245\345\217\243\346\226\207\346\241\243.yaml" "b/doc/design/aops-zeus\346\216\245\345\217\243\346\226\207\346\241\243.yaml"
index efadcc6..87dfe68 100644
--- "a/doc/design/aops-zeus\346\216\245\345\217\243\346\226\207\346\241\243.yaml"
+++ "b/doc/design/aops-zeus\346\216\245\345\217\243\346\226\207\346\241\243.yaml"
@@ -998,6 +998,10 @@ definitions:
type: string
description: 获取指定主机组里的主机信息,为空表示所有
example: '[]'
+ search_key:
+ type: string
+ description: 输入主机名称或主机host_ip获取指定主机信息
+ example: search_key
management:
type: boolean
description: 管理节点or监控节点不传表示所有
diff --git a/zeus/database/proxy/host.py b/zeus/database/proxy/host.py
index 477c482..441ef21 100644
--- a/zeus/database/proxy/host.py
+++ b/zeus/database/proxy/host.py
@@ -19,7 +19,7 @@ import math
from typing import Dict, List, Tuple
import sqlalchemy
-from sqlalchemy import func
+from sqlalchemy import func, or_
from sqlalchemy.sql.expression import asc, desc
from sqlalchemy.orm.collections import InstrumentedList
@@ -210,11 +210,19 @@ class HostProxy(MysqlProxy):
username = data['username']
host_group_list = data.get('host_group_list')
management = data.get('management')
+ search_key = data.get('search_key')
filters = {Host.user == username}
if host_group_list:
filters.add(Host.host_group_name.in_(host_group_list))
if management is not None:
filters.add(Host.management == management)
+ if search_key:
+ filters.add(
+ or_(
+ Host.host_name.like("%" + search_key + "%"),
+ Host.host_ip.like("%" + search_key + "%"),
+ )
+ )
if data.get('status'):
filters.add(Host.status.in_(data.get('status')))
diff --git a/zeus/function/verify/host.py b/zeus/function/verify/host.py
index f746968..3f8bab9 100644
--- a/zeus/function/verify/host.py
+++ b/zeus/function/verify/host.py
@@ -52,6 +52,7 @@ class GetHostSchema(Schema):
"""
host_group_list = fields.List(fields.String(), required=True)
+ search_key = fields.String(required=False, validate=lambda s: 50 > len(s) > 0)
management = fields.Boolean(required=False)
status = fields.List(fields.Integer(validate=lambda s: s >= 0), required=False)
sort = fields.String(required=False, validate=validate.OneOf(["host_name", "host_group_name", ""]))
--
2.33.0

View File

@ -0,0 +1,25 @@
From 06c0ffd136892bca685dfa036905ebc0ef46cf27 Mon Sep 17 00:00:00 2001
From: rearcher <123781007@qq.com>
Date: Fri, 15 Dec 2023 14:53:40 +0800
Subject: [PATCH] fix search_key validate
---
zeus/function/verify/host.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zeus/function/verify/host.py b/zeus/function/verify/host.py
index 3f8bab9..461fc12 100644
--- a/zeus/function/verify/host.py
+++ b/zeus/function/verify/host.py
@@ -52,7 +52,7 @@ class GetHostSchema(Schema):
"""
host_group_list = fields.List(fields.String(), required=True)
- search_key = fields.String(required=False, validate=lambda s: 50 > len(s) > 0)
+ search_key = fields.String(required=False, validate=lambda s: 50 >= len(s) > 0)
management = fields.Boolean(required=False)
status = fields.List(fields.Integer(validate=lambda s: s >= 0), required=False)
sort = fields.String(required=False, validate=validate.OneOf(["host_name", "host_group_name", ""]))
--
2.33.0

View File

@ -0,0 +1,250 @@
From e7e9871111a67d1aee5b7a7d68029b13894f8fae Mon Sep 17 00:00:00 2001
From: rabbitali <wenxin32@foxmail.com>
Date: Wed, 13 Dec 2023 10:11:22 +0800
Subject: [PATCH] add rollback task execution method and fix cve scan
callback error
---
zeus/conf/constant.py | 1 +
zeus/function/verify/vulnerability.py | 18 ++--
zeus/url.py | 1 +
zeus/vulnerability_manage/view.py | 135 +++++++++++++++++++++++++-
4 files changed, 143 insertions(+), 12 deletions(-)
diff --git a/zeus/conf/constant.py b/zeus/conf/constant.py
index 994dd90..1370d6e 100644
--- a/zeus/conf/constant.py
+++ b/zeus/conf/constant.py
@@ -32,6 +32,7 @@ CERES_HOST_INFO = "aops-ceres collect --host '%s'"
CERES_CVE_REPO_SET = "aops-ceres apollo --set-repo '%s'"
CERES_CVE_SCAN = "aops-ceres apollo --scan '%s'"
CERES_CVE_FIX = "aops-ceres apollo --fix '%s'"
+CERES_CVE_ROLLBACK = "aops-ceres apollo --rollback '%s'"
CERES_HOTPATCH_REMOVE = "aops-ceres apollo --remove-hotpatch '%s'"
CERES_SYNC_CONF = "aops-ceres sync --conf '%s'"
CERES_OBJECT_FILE_CONF = "aops-ceres ragdoll --list '%s'"
diff --git a/zeus/function/verify/vulnerability.py b/zeus/function/verify/vulnerability.py
index 07875e0..ff25c8d 100644
--- a/zeus/function/verify/vulnerability.py
+++ b/zeus/function/verify/vulnerability.py
@@ -86,14 +86,12 @@ class CveFixSchema(TaskGeneralSchema):
fix_type = fields.String(validate=validate.OneOf(["hotpatch", "coldpatch"]), required=True)
-class CveRollbackSingleInfoSchema(Schema):
- cve_id = fields.String(validate=lambda s: len(s) > 0)
- hotpatch = fields.Boolean(validate=validate.OneOf([True, False]))
-
-
-class CveRollbackTask(Schema):
+class CveRollbackTaskSchema(Schema):
host_id = fields.Integer(required=True, validate=lambda s: s > 0)
- cves = fields.List(fields.Nested(CveRollbackSingleInfoSchema()), required=True)
+ installed_rpm = fields.String(required=True, validate=lambda s: 100 >= len(s) > 0)
+ target_rpm = fields.String(required=True, validate=lambda s: 100 >= len(s) > 0)
+ dnf_event_start = fields.Integer(allow_none=True, required=True, validate=lambda s: s > 0)
+ dnf_event_end = fields.Integer(allow_none=True, required=True, validate=lambda s: s > 0)
class CveRollbackSchema(TaskGeneralSchema):
@@ -101,10 +99,12 @@ class CveRollbackSchema(TaskGeneralSchema):
validator for cve rollback
"""
- tasks = fields.List(fields.Nested(CveRollbackTask()), required=True, validate=lambda s: len(s) > 0)
+ tasks = fields.List(fields.Nested(CveRollbackTaskSchema()), required=True, validate=lambda s: len(s) > 0)
+ fix_task_id = fields.String(required=True, validate=lambda s: len(s) > 0)
+ rollback_type = fields.String(validate=validate.OneOf(["hotpatch", "coldpatch"]), required=True)
class Meta:
- fields = ("tasks", "task_id", "task_name", "total_hosts", "task_type", "callback")
+ exclude = ("total_hosts",)
class HotpatchRemoveTask(Schema):
diff --git a/zeus/url.py b/zeus/url.py
index ad8cec9..5f00ef9 100644
--- a/zeus/url.py
+++ b/zeus/url.py
@@ -101,6 +101,7 @@ SPECIFIC_URLS = {
],
'CVE_URLS': [
(vulnerability_view.ExecuteRepoSetTask, EXECUTE_REPO_SET),
+ (vulnerability_view.ExecuteCveRollbackTask, EXECUTE_CVE_ROLLBACK),
(vulnerability_view.ExecuteCveScanTask, EXECUTE_CVE_SCAN),
(vulnerability_view.ExecuteCveFixTask, EXECUTE_CVE_FIX),
(vulnerability_view.ExecuteHotpatchRemoveTask, EXECUTE_HOTPATCH_REMOVE),
diff --git a/zeus/vulnerability_manage/view.py b/zeus/vulnerability_manage/view.py
index be52e23..37ab633 100644
--- a/zeus/vulnerability_manage/view.py
+++ b/zeus/vulnerability_manage/view.py
@@ -26,6 +26,7 @@ from zeus.conf import configuration
from zeus.conf.constant import (
CERES_CVE_FIX,
CERES_CVE_REPO_SET,
+ CERES_CVE_ROLLBACK,
CERES_HOTPATCH_REMOVE,
CERES_CVE_SCAN,
CveTaskStatus,
@@ -34,7 +35,13 @@ from zeus.conf.constant import (
from zeus.database.proxy.host import HostProxy
from zeus.database.table import Host
from zeus.function.model import ClientConnectArgs
-from zeus.function.verify.vulnerability import CveFixSchema, CveScanSchema, HotpatchRemoveSchema, RepoSetSchema
+from zeus.function.verify.vulnerability import (
+ CveFixSchema,
+ CveRollbackSchema,
+ CveScanSchema,
+ HotpatchRemoveSchema,
+ RepoSetSchema,
+)
from zeus.host_manager.ssh import execute_command_and_parse_its_result
@@ -283,7 +290,7 @@ class ExecuteCveScanTask(BaseResponse, BaseExcuteTask):
CERES_CVE_SCAN % json.dumps({"check_items": self._check_items}),
)
if status != state.SUCCEED:
- request_body["status"] = CveTaskStatus.FAIL
+ request_body.update({"status":CveTaskStatus.FAIL, "reboot":False})
else:
request_body.update(json.loads(cve_scan_result))
@@ -500,7 +507,7 @@ class ExecuteHotpatchRemoveTask(BaseResponse, BaseExcuteTask):
),
command,
)
-
+
if status == state.SUCCEED:
request_body.update(json.loads(hotpatch_remove_result))
else:
@@ -552,3 +559,125 @@ class ExecuteHotpatchRemoveTask(BaseResponse, BaseExcuteTask):
]
threading.Thread(target=lambda: gevent.joinall(wait_execute_tasks)).start()
return self.response(code=state.SUCCEED)
+
+
+class ExecuteCveRollbackTask(BaseResponse, BaseExcuteTask):
+ """
+ Interface for cve rollback.
+ Restful API: POST
+ """
+
+ def _execute_task(self, host_info: dict, task_info: dict) -> None:
+ """
+ Execute cve rollback task
+
+ Args:
+ host_info(dict): e.g
+ {
+ "host_id": 1,
+ "host_ip": "127.0.0.1",
+ "host_name": "test_host",
+ "ssh_port": 22,
+ "ssh_user": "root",
+ "pkey": "RSA-KEY-string",
+ }
+ task_info (dict): e.g
+ {
+ "host_id": "id1",
+ "check_items":[],
+ "rollback_type": "hotpatch",
+ "installed_kernel": "kernel-5.1.10",
+ "target_kernel": "kernel-5.1.9",
+ "dnf_event_start": 1,
+ "dnf_event_end": 2,
+ }
+ Returns:
+ None
+ """
+ request_body = {
+ "execution_time": int(time.time()),
+ "task_id": self._task_id,
+ "host_id": host_info.get("host_id"),
+ "host_ip": host_info.get("host_ip"),
+ "host_name": host_info.get("host_name"),
+ }
+
+ task_info.pop("host_id")
+ command = CERES_CVE_ROLLBACK % json.dumps(task_info)
+ status, cve_rollback_result = execute_command_and_parse_its_result(
+ ClientConnectArgs(
+ host_info.get("host_ip"),
+ host_info.get("ssh_port"),
+ host_info.get("ssh_user"),
+ host_info.get("pkey"),
+ 60 * 10,
+ ),
+ command,
+ )
+ if status != state.SUCCEED:
+ request_body.update(
+ {
+ "status": CveTaskStatus.FAIL,
+ "log": cve_rollback_result,
+ "check_items": [
+ {"item": item, "result": CveTaskStatus.FAIL} for item in task_info.get("check_items")
+ ],
+ }
+ )
+ else:
+ request_body.update(json.loads(cve_rollback_result))
+
+ url = f'http://{configuration.apollo.get("IP")}:{ configuration.apollo.get("PORT")}{self._callback_url}'
+ self.get_response("post", url, request_body, self._header, timeout=10)
+
+ @BaseResponse.handle(schema=CveRollbackSchema)
+ def post(self, **params) -> Response:
+ """
+ execute cve rollback task
+
+ Args:
+ params (dict): e.g
+ {
+ "task_id": "c6714973c9b342a380fd01fdf7f90ef5",
+ "task_name": "cve rollback task",
+ "fix_task_id": "string",
+ "task_type": "cve rollback",
+ "rollback_type": "coldpatch",
+ "check_items": ["network"],
+ "tasks": [
+ {
+ "host_id": 74,
+ "installed_rpm": "kernel-5.1.10",
+ "target_rpm": "kernel-5.1.9",
+ "dnf_event_start": 1,
+ "dnf_event_end": 2
+ }
+ ],
+ "callback": "/vulnerability/task/callback/cve/rollback"
+ }
+ Returns:
+ response body
+ """
+ total_host = [task_info["host_id"] for task_info in params.get("tasks")]
+ status_code, host_infos = query_host_basic_info(total_host, params.get('username'))
+ if status_code != state.SUCCEED:
+ return self.response(code=status_code)
+ # parse args
+ self._task_id = params.get("task_id")
+ self._task_name = params.get("task_name")
+ self._task_type = params.get("task_type")
+ self._header["local_account"] = params.get("username")
+ self._callback_url = params.get('callback')
+ # Execute task
+ tasks = generate_tasks(
+ params.get('tasks'),
+ host_infos,
+ **{
+ "check_items": params.get('check_items'),
+ "rollback_type": params.get('rollback_type'),
+ },
+ )
+ threading.Thread(
+ target=lambda: gevent.joinall([gevent.spawn(self._execute_task, *task) for task in tasks])
+ ).start()
+ return self.response(code=state.SUCCEED)
--
2.33.0

View File

@ -1,10 +1,14 @@
Name: aops-zeus
Version: v1.4.0
Release: 1
Release: 2
Summary: A host and user manager service which is the foundation of aops.
License: MulanPSL2
URL: https://gitee.com/openeuler/%{name}
Source0: %{name}-%{version}.tar.gz
Patch0001: 0001-add-interface-for-detecting-host-status.patch
Patch0002: 0002-update-the-query-host-list-api.patch
Patch0003: 0003-fix-search_key-validate.patch
Patch0004: 0004-add-rollback-task-execution-method.patch
BuildRequires: python3-setuptools
@ -21,7 +25,7 @@ A host and user manager service which is the foundation of aops.
%prep
%autosetup -n %{name}-%{version}
%autosetup -n %{name}-%{version} -p1
# build for aops-zeus
@ -45,6 +49,12 @@ cp -r database %{buildroot}/opt/aops/
%changelog
* Mon Dec 18 2023 wenxin<wenxin32@foxmail.com> - v1.4.0-2
- Add interface for detecting host status.
- Update query host list api, add a new query method based on host name for it.
- Add rollback task execution method.
- Fix cve scan callback error.
* Tue Dec 12 2023 wenxin<wenxin32@foxmail.com> - v1.4.0-1
- Change CVE rollback task to hotpatch remove