mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
Remove host insights view
This commit is contained in:
parent
b6ced2b8dc
commit
f460f70513
@ -1753,10 +1753,9 @@ class HostSerializer(BaseSerializerWithVariables):
|
||||
'has_inventory_sources',
|
||||
'last_job',
|
||||
'last_job_host_summary',
|
||||
'insights_system_id',
|
||||
'ansible_facts_modified',
|
||||
)
|
||||
read_only_fields = ('last_job', 'last_job_host_summary', 'insights_system_id', 'ansible_facts_modified')
|
||||
read_only_fields = ('last_job', 'last_job_host_summary', 'ansible_facts_modified')
|
||||
|
||||
def build_relational_field(self, field_name, relation_info):
|
||||
field_class, field_kwargs = super(HostSerializer, self).build_relational_field(field_name, relation_info)
|
||||
@ -1780,7 +1779,6 @@ class HostSerializer(BaseSerializerWithVariables):
|
||||
smart_inventories=self.reverse('api:host_smart_inventories_list', kwargs={'pk': obj.pk}),
|
||||
ad_hoc_commands=self.reverse('api:host_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
|
||||
ad_hoc_command_events=self.reverse('api:host_ad_hoc_command_events_list', kwargs={'pk': obj.pk}),
|
||||
insights=self.reverse('api:host_insights', kwargs={'pk': obj.pk}),
|
||||
ansible_facts=self.reverse('api:host_ansible_facts_detail', kwargs={'pk': obj.pk}),
|
||||
)
|
||||
)
|
||||
|
||||
@ -16,7 +16,6 @@ from awx.api.views import (
|
||||
HostSmartInventoriesList,
|
||||
HostAdHocCommandsList,
|
||||
HostAdHocCommandEventsList,
|
||||
HostInsights,
|
||||
)
|
||||
|
||||
|
||||
@ -33,7 +32,6 @@ urls = [
|
||||
url(r'^(?P<pk>[0-9]+)/smart_inventories/$', HostSmartInventoriesList.as_view(), name='host_smart_inventories_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', HostAdHocCommandsList.as_view(), name='host_ad_hoc_commands_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_command_events/$', HostAdHocCommandEventsList.as_view(), name='host_ad_hoc_command_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/insights/$', HostInsights.as_view(), name='host_insights'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
|
||||
@ -90,17 +90,14 @@ from awx.main import models
|
||||
from awx.main.utils import (
|
||||
camelcase_to_underscore,
|
||||
extract_ansible_vars,
|
||||
get_awx_http_client_headers,
|
||||
get_object_or_400,
|
||||
getattrd,
|
||||
get_pk_from_dict,
|
||||
schedule_task_manager,
|
||||
ignore_inventory_computed_fields,
|
||||
set_environ,
|
||||
)
|
||||
from awx.main.utils.encryption import encrypt_value
|
||||
from awx.main.utils.filters import SmartFilter
|
||||
from awx.main.utils.insights import filter_insights_api_response
|
||||
from awx.main.redact import UriCleaner
|
||||
from awx.api.permissions import (
|
||||
JobTemplateCallbackPermission,
|
||||
@ -1700,106 +1697,6 @@ class GatewayTimeout(APIException):
|
||||
default_code = 'gateway_timeout'
|
||||
|
||||
|
||||
class HostInsights(GenericAPIView):
|
||||
|
||||
model = models.Host
|
||||
serializer_class = serializers.EmptySerializer
|
||||
|
||||
def _call_insights_api(self, url, session, headers):
|
||||
try:
|
||||
with set_environ(**settings.AWX_TASK_ENV):
|
||||
res = session.get(url, headers=headers, timeout=120)
|
||||
except requests.exceptions.SSLError:
|
||||
raise BadGateway(_('SSLError while trying to connect to {}').format(url))
|
||||
except requests.exceptions.Timeout:
|
||||
raise GatewayTimeout(_('Request to {} timed out.').format(url))
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise BadGateway(_('Unknown exception {} while trying to GET {}').format(e, url))
|
||||
|
||||
if res.status_code == 401:
|
||||
raise BadGateway(_('Unauthorized access. Please check your Insights Credential username and password.'))
|
||||
elif res.status_code != 200:
|
||||
raise BadGateway(
|
||||
_('Failed to access the Insights API at URL {}.' ' Server responded with {} status code and message {}').format(
|
||||
url, res.status_code, res.content
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
return res.json()
|
||||
except ValueError:
|
||||
raise BadGateway(_('Expected JSON response from Insights at URL {}' ' but instead got {}').format(url, res.content))
|
||||
|
||||
def _get_session(self, username, password):
|
||||
session = requests.Session()
|
||||
session.auth = requests.auth.HTTPBasicAuth(username, password)
|
||||
|
||||
return session
|
||||
|
||||
def _get_platform_info(self, host, session, headers):
|
||||
url = '{}/api/inventory/v1/hosts?insights_id={}'.format(settings.INSIGHTS_URL_BASE, host.insights_system_id)
|
||||
res = self._call_insights_api(url, session, headers)
|
||||
try:
|
||||
res['results'][0]['id']
|
||||
except (IndexError, KeyError):
|
||||
raise NotFound(_('Could not translate Insights system ID {}' ' into an Insights platform ID.').format(host.insights_system_id))
|
||||
|
||||
return res['results'][0]
|
||||
|
||||
def _get_reports(self, platform_id, session, headers):
|
||||
url = '{}/api/insights/v1/system/{}/reports/'.format(settings.INSIGHTS_URL_BASE, platform_id)
|
||||
|
||||
return self._call_insights_api(url, session, headers)
|
||||
|
||||
def _get_remediations(self, platform_id, session, headers):
|
||||
url = '{}/api/remediations/v1/remediations?system={}'.format(settings.INSIGHTS_URL_BASE, platform_id)
|
||||
|
||||
remediations = []
|
||||
|
||||
# Iterate over all of the pages of content.
|
||||
while url:
|
||||
data = self._call_insights_api(url, session, headers)
|
||||
remediations.extend(data['data'])
|
||||
|
||||
url = data['links']['next'] # Will be `None` if this is the last page.
|
||||
|
||||
return remediations
|
||||
|
||||
def _get_insights(self, host, session, headers):
|
||||
platform_info = self._get_platform_info(host, session, headers)
|
||||
platform_id = platform_info['id']
|
||||
reports = self._get_reports(platform_id, session, headers)
|
||||
remediations = self._get_remediations(platform_id, session, headers)
|
||||
|
||||
return {'insights_content': filter_insights_api_response(platform_info, reports, remediations)}
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
host = self.get_object()
|
||||
cred = None
|
||||
|
||||
if host.insights_system_id is None:
|
||||
return Response(dict(error=_('This host is not recognized as an Insights host.')), status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if host.inventory and host.inventory.insights_credential:
|
||||
cred = host.inventory.insights_credential
|
||||
else:
|
||||
return Response(dict(error=_('The Insights Credential for "{}" was not found.').format(host.inventory.name)), status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
username = cred.get_input('username', default='')
|
||||
password = cred.get_input('password', default='')
|
||||
session = self._get_session(username, password)
|
||||
headers = get_awx_http_client_headers()
|
||||
|
||||
data = self._get_insights(host, session, headers)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
def handle_exception(self, exc):
|
||||
# Continue supporting the slightly different way we have handled error responses on this view.
|
||||
response = super().handle_exception(exc)
|
||||
response.data['error'] = response.data.pop('detail')
|
||||
return response
|
||||
|
||||
|
||||
class GroupList(ListCreateAPIView):
|
||||
|
||||
model = models.Group
|
||||
|
||||
@ -503,13 +503,6 @@ class Host(CommonModelNameNotUnique, RelatedJobsMixin):
|
||||
null=True,
|
||||
help_text=_('The date and time ansible_facts was last modified.'),
|
||||
)
|
||||
insights_system_id = models.TextField(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
db_index=True,
|
||||
help_text=_('Red Hat Insights host unique identifier.'),
|
||||
)
|
||||
|
||||
objects = HostManager()
|
||||
|
||||
|
||||
@ -845,23 +845,6 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
|
||||
continue
|
||||
host.ansible_facts = ansible_facts
|
||||
host.ansible_facts_modified = now()
|
||||
ansible_local = ansible_facts.get('ansible_local', {}).get('insights', {})
|
||||
ansible_facts = ansible_facts.get('insights', {})
|
||||
ansible_local_system_id = ansible_local.get('system_id', None) if isinstance(ansible_local, dict) else None
|
||||
ansible_facts_system_id = ansible_facts.get('system_id', None) if isinstance(ansible_facts, dict) else None
|
||||
if ansible_local_system_id:
|
||||
print("Setting local {}".format(ansible_local_system_id))
|
||||
logger.debug(
|
||||
"Insights system_id {} found for host <{}, {}> in"
|
||||
" ansible local facts".format(ansible_local_system_id, host.inventory.id, host.name)
|
||||
)
|
||||
host.insights_system_id = ansible_local_system_id
|
||||
elif ansible_facts_system_id:
|
||||
logger.debug(
|
||||
"Insights system_id {} found for host <{}, {}> in"
|
||||
" insights facts".format(ansible_local_system_id, host.inventory.id, host.name)
|
||||
)
|
||||
host.insights_system_id = ansible_facts_system_id
|
||||
host.save()
|
||||
system_tracking_logger.info(
|
||||
'New fact for inventory {} host {}'.format(smart_str(host.inventory.name), smart_str(host.name)),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,14 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
with open(os.path.join(dir_path, 'insights_hosts.json')) as data_file:
|
||||
TEST_INSIGHTS_HOSTS = json.load(data_file)
|
||||
|
||||
with open(os.path.join(dir_path, 'insights.json')) as data_file:
|
||||
TEST_INSIGHTS_PLANS = json.load(data_file)
|
||||
|
||||
with open(os.path.join(dir_path, 'insights_remediations.json')) as data_file:
|
||||
TEST_INSIGHTS_REMEDIATIONS = json.load(data_file)['data']
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"total": 1,
|
||||
"count": 1,
|
||||
"page": 1,
|
||||
"per_page": 50,
|
||||
"results": [
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"insights_id": "22222222-2222-2222-2222-222222222222",
|
||||
"updated": "2019-03-19T21:59:09.213151-04:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "9197ba55-0abc-4028-9bbe-269e530f8bd5",
|
||||
"name": "Fix Critical CVEs",
|
||||
"created_by": {
|
||||
"username": "jharting@redhat.com",
|
||||
"first_name": "Jozef",
|
||||
"last_name": "Hartinger"
|
||||
},
|
||||
"created_at": "2018-12-05T08:19:36.641Z",
|
||||
"updated_by": {
|
||||
"username": "jharting@redhat.com",
|
||||
"first_name": "Jozef",
|
||||
"last_name": "Hartinger"
|
||||
},
|
||||
"updated_at": "2018-12-05T08:19:36.641Z",
|
||||
"issue_count": 0,
|
||||
"system_count": 0,
|
||||
"needs_reboot": true
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"count": 0,
|
||||
"total": 0
|
||||
},
|
||||
"links": {
|
||||
"first": null,
|
||||
"last": null,
|
||||
"next": null,
|
||||
"previous": null
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestHostInsights:
|
||||
def test_insights_bad_host(self, get, hosts, user, mocker):
|
||||
mocker.patch.object(requests.Session, 'get')
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'] == 'This host is not recognized as an Insights host.'
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_insights_host_missing_from_insights(self, get, hosts, insights_credential, user, mocker):
|
||||
class Response:
|
||||
status_code = 200
|
||||
content = "{'results': []}"
|
||||
|
||||
def json(self):
|
||||
return {'results': []}
|
||||
|
||||
mocker.patch.object(requests.Session, 'get', return_value=Response())
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
host.insights_system_id = '123e4567-e89b-12d3-a456-426655440000'
|
||||
host.inventory.insights_credential = insights_credential
|
||||
host.inventory.save()
|
||||
host.save()
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'] == ('Could not translate Insights system ID 123e4567-e89b-12d3-a456-426655440000' ' into an Insights platform ID.')
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_insights_no_credential(self, get, hosts, user, mocker):
|
||||
mocker.patch.object(requests.Session, 'get')
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
host.insights_system_id = '123e4567-e89b-12d3-a456-426655440000'
|
||||
host.save()
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'] == 'The Insights Credential for "test-inv" was not found.'
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"status_code, exception, error, message",
|
||||
[
|
||||
(
|
||||
502,
|
||||
requests.exceptions.SSLError,
|
||||
'SSLError while trying to connect to https://myexample.com/whocares/me/',
|
||||
None,
|
||||
),
|
||||
(
|
||||
504,
|
||||
requests.exceptions.Timeout,
|
||||
'Request to https://myexample.com/whocares/me/ timed out.',
|
||||
None,
|
||||
),
|
||||
(502, requests.exceptions.RequestException, 'booo!', 'Unknown exception booo! while trying to GET https://myexample.com/whocares/me/'),
|
||||
],
|
||||
)
|
||||
def test_insights_exception(self, get, hosts, insights_credential, user, mocker, status_code, exception, error, message):
|
||||
mocker.patch.object(requests.Session, 'get', side_effect=exception(error))
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
host.insights_system_id = '123e4567-e89b-12d3-a456-426655440000'
|
||||
host.inventory.insights_credential = insights_credential
|
||||
host.inventory.save()
|
||||
host.save()
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'] == message or error
|
||||
assert response.status_code == status_code
|
||||
|
||||
def test_insights_unauthorized(self, get, hosts, insights_credential, user, mocker):
|
||||
Response = namedtuple('Response', 'status_code content')
|
||||
mocker.patch.object(requests.Session, 'get', return_value=Response(401, 'mock 401 err msg'))
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
host.insights_system_id = '123e4567-e89b-12d3-a456-426655440000'
|
||||
host.inventory.insights_credential = insights_credential
|
||||
host.inventory.save()
|
||||
host.save()
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'] == ("Unauthorized access. Please check your Insights Credential username and password.")
|
||||
assert response.status_code == 502
|
||||
|
||||
def test_insights_bad_status(self, get, hosts, insights_credential, user, mocker):
|
||||
Response = namedtuple('Response', 'status_code content')
|
||||
mocker.patch.object(requests.Session, 'get', return_value=Response(500, 'mock 500 err msg'))
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
host.insights_system_id = '123e4567-e89b-12d3-a456-426655440000'
|
||||
host.inventory.insights_credential = insights_credential
|
||||
host.inventory.save()
|
||||
host.save()
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'].startswith("Failed to access the Insights API at URL")
|
||||
assert "Server responded with 500 status code and message mock 500 err msg" in response.data['error']
|
||||
assert response.status_code == 502
|
||||
|
||||
def test_insights_bad_json(self, get, hosts, insights_credential, user, mocker):
|
||||
class Response:
|
||||
status_code = 200
|
||||
content = 'booo!'
|
||||
|
||||
def json(self):
|
||||
raise ValueError("we do not care what this is")
|
||||
|
||||
mocker.patch.object(requests.Session, 'get', return_value=Response())
|
||||
|
||||
host = hosts(host_count=1)[0]
|
||||
host.insights_system_id = '123e4567-e89b-12d3-a456-426655440000'
|
||||
host.inventory.insights_credential = insights_credential
|
||||
host.inventory.save()
|
||||
host.save()
|
||||
|
||||
url = reverse('api:host_insights', kwargs={'pk': host.pk})
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.data['error'].startswith("Expected JSON response from Insights at URL")
|
||||
assert 'insights_id=123e4567-e89b-12d3-a456-426655440000' in response.data['error']
|
||||
assert response.data['error'].endswith("but instead got booo!")
|
||||
assert response.status_code == 502
|
||||
@ -71,7 +71,7 @@ def test_finish_job_fact_cache_with_existing_data(job, hosts, inventory, mocker,
|
||||
for h in hosts:
|
||||
h.save = mocker.Mock()
|
||||
|
||||
ansible_facts_new = {"foo": "bar", "insights": {"system_id": "updated_by_scan"}}
|
||||
ansible_facts_new = {"foo": "bar"}
|
||||
filepath = os.path.join(fact_cache, hosts[1].name)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(json.dumps(ansible_facts_new))
|
||||
@ -90,31 +90,9 @@ def test_finish_job_fact_cache_with_existing_data(job, hosts, inventory, mocker,
|
||||
assert host.ansible_facts == {"a": 1, "b": 2}
|
||||
assert host.ansible_facts_modified is None
|
||||
assert hosts[1].ansible_facts == ansible_facts_new
|
||||
assert hosts[1].insights_system_id == "updated_by_scan"
|
||||
hosts[1].save.assert_called_once_with()
|
||||
|
||||
|
||||
def test_finish_job_fact_cache_with_malformed_fact(job, hosts, inventory, mocker, tmpdir):
|
||||
fact_cache = os.path.join(tmpdir, 'facts')
|
||||
modified_times = {}
|
||||
job.start_job_fact_cache(fact_cache, modified_times, 0)
|
||||
|
||||
for h in hosts:
|
||||
h.save = mocker.Mock()
|
||||
|
||||
for h in hosts:
|
||||
filepath = os.path.join(fact_cache, h.name)
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump({'ansible_local': {'insights': 'this is an unexpected error from ansible'}}, f)
|
||||
new_modification_time = time.time() + 3600
|
||||
os.utime(filepath, (new_modification_time, new_modification_time))
|
||||
|
||||
job.finish_job_fact_cache(fact_cache, modified_times)
|
||||
|
||||
for h in hosts:
|
||||
assert h.insights_system_id is None
|
||||
|
||||
|
||||
def test_finish_job_fact_cache_with_bad_data(job, hosts, inventory, mocker, tmpdir):
|
||||
fact_cache = os.path.join(tmpdir, 'facts')
|
||||
modified_times = {}
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
# Copyright (c) 2017 Ansible Tower by Red Hat
|
||||
# All Rights Reserved.
|
||||
|
||||
|
||||
from awx.main.utils.insights import filter_insights_api_response
|
||||
from awx.main.tests.data.insights import TEST_INSIGHTS_HOSTS, TEST_INSIGHTS_PLANS, TEST_INSIGHTS_REMEDIATIONS
|
||||
|
||||
|
||||
def test_filter_insights_api_response():
|
||||
actual = filter_insights_api_response(TEST_INSIGHTS_HOSTS['results'][0], TEST_INSIGHTS_PLANS, TEST_INSIGHTS_REMEDIATIONS)
|
||||
|
||||
assert actual['last_check_in'] == '2019-03-19T21:59:09.213151-04:00'
|
||||
assert len(actual['reports']) == 5
|
||||
assert len(actual['reports'][0]['maintenance_actions']) == 1
|
||||
assert actual['reports'][0]['maintenance_actions'][0]['name'] == "Fix Critical CVEs"
|
||||
rule = actual['reports'][0]['rule']
|
||||
|
||||
assert rule['severity'] == 'WARN'
|
||||
assert rule['description'] == ("Kernel vulnerable to side-channel attacks in modern microprocessors (CVE-2017-5715/Spectre)")
|
||||
assert rule['category'] == 'Security'
|
||||
assert rule['summary'] == (
|
||||
"A vulnerability was discovered in modern microprocessors supported by the kernel,"
|
||||
" whereby an unprivileged attacker can use this flaw to bypass restrictions to gain read"
|
||||
" access to privileged memory.\nThe issue was reported as [CVE-2017-5715 / Spectre]"
|
||||
"(https://access.redhat.com/security/cve/CVE-2017-5715).\n"
|
||||
)
|
||||
@ -1,39 +0,0 @@
|
||||
# Copyright (c) 2017 Ansible Tower by Red Hat
|
||||
# All Rights Reserved.
|
||||
|
||||
|
||||
# Old Insights API -> New API
|
||||
#
|
||||
# last_check_in is missing entirely, is now provided by a different endpoint
|
||||
# reports[] -> []
|
||||
# reports[].rule.{description,summary} -> [].rule.{description,summary}
|
||||
# reports[].rule.category -> [].rule.category.name
|
||||
# reports[].rule.severity (str) -> [].rule.total_risk (int)
|
||||
|
||||
# reports[].rule.{ansible,ansible_fix} appears to be unused
|
||||
# reports[].maintenance_actions[] missing entirely, is now provided
|
||||
# by a different Insights endpoint
|
||||
|
||||
|
||||
def filter_insights_api_response(platform_info, reports, remediations):
|
||||
severity_mapping = {1: 'INFO', 2: 'WARN', 3: 'ERROR', 4: 'CRITICAL'}
|
||||
|
||||
new_json = {
|
||||
'platform_id': platform_info['id'],
|
||||
'last_check_in': platform_info.get('updated'),
|
||||
'reports': [],
|
||||
}
|
||||
for rep in reports:
|
||||
new_report = {'rule': {}, 'maintenance_actions': remediations}
|
||||
rule = rep.get('rule') or {}
|
||||
for k in ['description', 'summary']:
|
||||
if k in rule:
|
||||
new_report['rule'][k] = rule[k]
|
||||
if 'category' in rule:
|
||||
new_report['rule']['category'] = rule['category']['name']
|
||||
if rule.get('total_risk') in severity_mapping:
|
||||
new_report['rule']['severity'] = severity_mapping[rule['total_risk']]
|
||||
|
||||
new_json['reports'].append(new_report)
|
||||
|
||||
return new_json
|
||||
Loading…
x
Reference in New Issue
Block a user