New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GenerateAsBuilt [Common Scripts Utility Consolidations] #23471
Changes from 28 commits
d080cb6
13010e5
8f1bb18
5615d6e
9383a95
79df035
8e89be3
d30b4ab
ffad517
9b99b2f
9aa4f6f
1074132
78c3a2d
6ed156d
f4297e7
5915a7e
fc7fa0a
4460c6d
f17630b
7141b23
9daf312
243371b
85c6948
ae069a7
a2fae9f
d0274cc
921ef23
68703fe
10f1958
5ba9a30
c638667
062cec5
b06c5c7
cf5f31d
b73fa67
e09afea
64339b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
#### Scripts | ||
##### New: GenerateAsBuilt | ||
- Generate an as built document, as HTML, based on the running XSOAR instance. Requires an instance of the Demisto API integration configured. (Available from Cortex XSOAR 6.0.0). | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
import demistomock as demisto | ||
from CommonServerPython import * | ||
from CommonServerUserPython import * | ||
|
||
import sys | ||
import jinja2 | ||
|
||
""" | ||
|
@@ -21,8 +19,6 @@ | |
DEMISTO_DEPENDENCIES_PATH = "/itemsdependencies" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can remove all these constants and add the URLs to the right commands There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @samuelFain Still don't see anything regarding this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the constants are used in multiple places in the script, and also i think it makes the code more clear and understandable, should I delete the constants anyway? |
||
DEMISTO_REPORTS_PATH = "/reports" | ||
DEMISTO_DASHBOARDS_PATH = "/dashboards" | ||
MAX_REQUEST_SIZE = demisto.args().get("size", 1000) | ||
MAX_DAYS = demisto.args().get("days", 7) | ||
HTML_TABLE_TEMPLATE = """ | ||
<h3>{{ name }}</h3> | ||
<table class="table"> | ||
|
@@ -430,24 +426,26 @@ def as_html(self, *args, **kwargs): # noqa: F841 | |
|
||
class UseCaseDocument: | ||
""" | ||
Generates a "use case" document, that is, a document that collates the dependencies and requiremnts of a | ||
Generates a "use case" document, that is, a document that collates the dependencies and requirements of a | ||
given playbook ("use case") within a running XSOAR environment. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
playbook_name, | ||
dependencies | ||
): | ||
dependencies, | ||
author, | ||
customer | ||
): # pragma: no cover | ||
self.playbook_name = playbook_name | ||
self.automations = dependencies.get("automation", NoneTableData()) | ||
self.integrations = dependencies.get("integration", NoneTableData()) | ||
self.playbooks = dependencies.get("playbook", NoneTableData()) | ||
self.incidentfields = dependencies.get("incidentfield", NoneTableData()) | ||
self.incidenttypes = dependencies.get("incidenttype", NoneTableData()) | ||
self.author = demisto.args().get("author") | ||
self.author = author | ||
self.date = datetime.now().strftime("%m/%d/%Y") | ||
self.customer = demisto.args().get("customer") | ||
self.customer = customer | ||
|
||
def markdown(self): | ||
template = jinja2.Template(USECASE_MD_DOCUMENT_TEMPLATE) | ||
|
@@ -482,8 +480,10 @@ def __init__( | |
closed_incidents, | ||
playbook_stats, | ||
reports, | ||
dashboards | ||
): | ||
dashboards, | ||
author, | ||
customer | ||
): # pragma: no cover | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need the pragma no cover here? remove this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's for the test coverage. The init functions of the classes are kind of big but contain no logic. But If it's not "best practice" i'll remove this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is not the best practice to put this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
self.template = template | ||
self.integrations_table = integrations_table | ||
self.installed_packs_table = installed_packs_table | ||
|
@@ -493,9 +493,9 @@ def __init__( | |
self.open_incidents = open_incidents | ||
self.closed_incidents = closed_incidents | ||
self.playbook_stats = playbook_stats | ||
self.author = demisto.args().get("author") | ||
self.author = author | ||
self.date = datetime.now().strftime("%m/%d/%Y") | ||
self.customer = demisto.args().get("customer") | ||
self.customer = customer | ||
self.reports = reports | ||
self.dashboards = dashboards | ||
|
||
|
@@ -552,6 +552,15 @@ def build_document( | |
|
||
|
||
def post_api_request(url, body): | ||
"""Post API request. | ||
|
||
Args: | ||
url (str): request url path | ||
body (Dict): integration command / script arguments | ||
|
||
Returns: | ||
Dict: dictionary representation of the response | ||
""" | ||
api_args = { | ||
"uri": url, | ||
"body": body | ||
|
@@ -563,10 +572,18 @@ def post_api_request(url, body): | |
except KeyError: | ||
return_error(f'API Request failed, no response from API call to {url}') | ||
except TypeError: | ||
return_error(f'API Request failed, failedto {raw_res}') | ||
return_error(f'API Request failed, failed to {raw_res}') | ||
|
||
|
||
def get_api_request(url): | ||
"""Get API request. | ||
|
||
Args: | ||
url (str): request url path | ||
|
||
Returns: | ||
Dict: dictionary representation of the response | ||
""" | ||
api_args = { | ||
"uri": url | ||
} | ||
|
@@ -583,6 +600,15 @@ def get_api_request(url): | |
|
||
|
||
def get_all_incidents(days=7, size=1000): | ||
"""Get all incidents from API request. | ||
|
||
Args: | ||
days (int): number of days. Defaults to 7. | ||
size (int): number of incidents. Defaults to 1000. | ||
|
||
Returns: | ||
Dict: incidents returned from the API request | ||
""" | ||
body = { | ||
"userFilter": False, | ||
"filter": { | ||
|
@@ -606,6 +632,15 @@ def get_all_incidents(days=7, size=1000): | |
|
||
|
||
def get_open_incidents(days=7, size=1000): | ||
"""Get open incidents from API. | ||
|
||
Args: | ||
days (int): number of days. Defaults to 7. | ||
size (int): number of incidents. Defaults to 1000. | ||
|
||
Returns: | ||
SingleFieldData: SingleFieldData object representing open incidents | ||
""" | ||
body = { | ||
"userFilter": False, | ||
"filter": { | ||
|
@@ -631,6 +666,15 @@ def get_open_incidents(days=7, size=1000): | |
|
||
|
||
def get_closed_incidents(days=7, size=1000): | ||
"""Get closed incidents from API. | ||
|
||
Args: | ||
days (int): number of days. Defaults to 7. | ||
size (int): number of incidents. Defaults to 1000. | ||
|
||
Returns: | ||
SingleFieldData: SingleFieldData object representing closed incidents | ||
""" | ||
body = { | ||
"userFilter": False, | ||
"filter": { | ||
|
@@ -655,12 +699,16 @@ def get_closed_incidents(days=7, size=1000): | |
return rd | ||
|
||
|
||
def get_enabled_integrations(): | ||
""" | ||
Retrieve all the running instances. | ||
:return: TableData | ||
def get_enabled_integrations(max_request_size: int): | ||
"""Retrieve all the running instances. | ||
|
||
Args: | ||
max_request_size (int): maximum number of instances to retrieve. | ||
|
||
Returns: | ||
SortedTableData: TableData object with the enabled instances. | ||
""" | ||
r = post_api_request(DEMISTO_INTEGRATIONS_PATH, {"size": MAX_REQUEST_SIZE}) | ||
r = post_api_request(DEMISTO_INTEGRATIONS_PATH, {"size": max_request_size}) | ||
instances = r.get("instances") | ||
enabled_instances = [] | ||
for instance in instances: | ||
|
@@ -673,9 +721,10 @@ def get_enabled_integrations(): | |
|
||
|
||
def get_installed_packs(): | ||
""" | ||
Get all the installed Content Packs | ||
:return: TableData | ||
"""Get all the installed Content Packs | ||
|
||
Returns: | ||
SortedTableData: TableData object with the installed Content Packs. | ||
""" | ||
# if tis doesn't work, return nothing. | ||
r = get_api_request(DEMISTO_INSTALLED_PATH) | ||
|
@@ -686,9 +735,10 @@ def get_installed_packs(): | |
|
||
|
||
def get_custom_playbooks(): | ||
""" | ||
Return all the custom playbooks installed in XSOAR> | ||
:return: TableData | ||
"""Return all the custom playbooks installed in XSOAR | ||
|
||
Returns: | ||
SortedTableData: TableData object with the custom playbooks. | ||
""" | ||
r = post_api_request(DEMISTO_PLAYBOOKS_PATH, {"query": "system:F AND hidden:F"}).get("playbooks") | ||
for pb in r: | ||
|
@@ -713,9 +763,10 @@ def get_custom_reports(): | |
|
||
|
||
def get_custom_dashboards(): | ||
""" | ||
Return all the custom dashboards configured in XSOAR | ||
:return: TableData | ||
"""Return all the custom dashboards configured in XSOAR | ||
|
||
Returns: | ||
TableData: TableData object with the custom dashboards. | ||
""" | ||
r = get_api_request(DEMISTO_DASHBOARDS_PATH) | ||
dashboards = [] | ||
|
@@ -728,9 +779,10 @@ def get_custom_dashboards(): | |
|
||
|
||
def get_all_playbooks(): | ||
""" | ||
Return all the custom playbooks installed in XSOAR> | ||
:return: TableData | ||
"""Return all the custom playbooks installed in XSOAR | ||
|
||
Returns: | ||
TableData: TableData object with the custom playbooks. | ||
""" | ||
r = post_api_request(DEMISTO_PLAYBOOKS_PATH, {"query": "hidden:F"}).get("playbooks") | ||
for pb in r: | ||
|
@@ -740,11 +792,16 @@ def get_all_playbooks(): | |
|
||
|
||
def get_playbook_stats(playbooks, days=7, size=1000): | ||
""" | ||
Pull all the incident types and assoociated playbooks, | ||
"""Pull all the incident types and associated playbooks, | ||
then join this with the incident stats to determine how often each playbook has been used. | ||
|
||
:param playbooks (TableData): Table Data of Playbooks | ||
Args: | ||
playbooks (SortedTableData): TableData object with the custom playbooks. | ||
days (int, optional): max number of days. Defaults to 7. | ||
size (int, optional): max request size. Defaults to 1000. | ||
|
||
Returns: | ||
Dict: Dictionary of playbook stats. | ||
""" | ||
# incident_types = get_api_request(DEMISTO_INCIDENT_TYPE_PATH) | ||
incidents = get_all_incidents(days, size) | ||
|
@@ -776,14 +833,18 @@ def get_playbook_stats(playbooks, days=7, size=1000): | |
|
||
|
||
def get_playbook_dependencies(playbook_name): | ||
""" | ||
Given a playbook name, searches for all dependencies. | ||
"""Given a playbook name, searches for all dependencies. | ||
|
||
Args: | ||
playbook_name (string): name of the playbook. | ||
|
||
Returns: | ||
Dict[SortedTableData]: Dictionary of TableData objects. | ||
""" | ||
playbooks = get_all_playbooks() | ||
playbook = playbooks.search("name", playbook_name) | ||
if not playbook: | ||
return_error(f"Playbook {playbook_name} not found.") | ||
sys.exit() | ||
playbook_id = playbook.get("id") | ||
body = { | ||
"items": [ | ||
|
@@ -835,31 +896,44 @@ def get_system_config(): | |
return rd | ||
|
||
|
||
def main(): | ||
if demisto.args().get("playbook"): | ||
# If we get a playbook, we generate a use case document, instead of teh platform as build | ||
r = get_playbook_dependencies(demisto.args().get("playbook")) | ||
doc = UseCaseDocument( | ||
playbook_name=demisto.args().get("playbook"), | ||
dependencies=r | ||
) | ||
fr = fileResult("usecase.html", doc.html(), file_type=EntryType.ENTRY_INFO_FILE) | ||
return_results(fr) | ||
return_results(CommandResults( | ||
readable_output=doc.markdown(), | ||
)) | ||
return | ||
def playbook_use_case(playbook: str, author: str, customer: str): | ||
"""Given a playbook, generate a use case document. | ||
|
||
# If no playbook is passed, we generate a platform as built. | ||
open_incidents = get_open_incidents(MAX_DAYS, MAX_REQUEST_SIZE) | ||
closed_incidents = get_closed_incidents(MAX_DAYS, MAX_REQUEST_SIZE) | ||
Args: | ||
playbook (str): playbook name | ||
author (str): author name | ||
customer (str): company name | ||
""" | ||
r = get_playbook_dependencies(playbook) | ||
doc = UseCaseDocument( | ||
playbook_name=playbook, | ||
dependencies=r, | ||
author=author, | ||
customer=customer, | ||
) | ||
fr = fileResult("usecase.html", doc.html(), file_type=EntryType.ENTRY_INFO_FILE) | ||
return_results(fr) | ||
return_results(CommandResults(readable_output=doc.markdown(),)) | ||
|
||
|
||
def platform_as_built_use_case(max_request_size: int, max_days: int, author: str, customer: str): | ||
"""Generate a platform as built use case document. | ||
|
||
Args: | ||
max_request_size (int): max request size | ||
max_days (int): max number of days | ||
author (str): author name | ||
customer (str): company name | ||
""" | ||
open_incidents = get_open_incidents(max_days, max_request_size) | ||
closed_incidents = get_closed_incidents(max_days, max_request_size) | ||
|
||
system_config = get_system_config() | ||
integrations = get_enabled_integrations() | ||
integrations = get_enabled_integrations(max_request_size) | ||
installed_packs = get_installed_packs() | ||
playbooks = get_custom_playbooks() | ||
automations = get_custom_automations() | ||
playbook_stats = get_playbook_stats(playbooks, MAX_DAYS, MAX_REQUEST_SIZE) | ||
playbook_stats = get_playbook_stats(playbooks, max_days, max_request_size) | ||
|
||
reports = get_custom_reports() | ||
dashboards = get_custom_dashboards() | ||
|
@@ -875,7 +949,10 @@ def main(): | |
closed_incidents=closed_incidents, | ||
playbook_stats=playbook_stats, | ||
reports=reports, | ||
dashboards=dashboards | ||
dashboards=dashboards, | ||
author=author, | ||
customer=customer | ||
|
||
) | ||
fr = fileResult("asbuilt.html", d.html(), file_type=EntryType.ENTRY_INFO_FILE) | ||
return_results(CommandResults( | ||
|
@@ -884,5 +961,21 @@ def main(): | |
return_results(fr) | ||
|
||
|
||
def main(): # pragma: no cover | ||
args = demisto.args() | ||
author = args.get("author") | ||
customer = args.get("customer") | ||
|
||
# Given a playbook is passed, we generate a use case document, instead of the platform as build. | ||
if playbook := args.get("playbook"): | ||
playbook_use_case(playbook, author, customer) | ||
|
||
else: | ||
# If no playbook is passed, we generate a platform as built. | ||
max_request_size = args.get("size", 1000) | ||
max_days = args.get("days", 7) | ||
platform_as_built_use_case(max_request_size, max_days, author, customer) | ||
|
||
|
||
if __name__ in ('__builtin__', 'builtins'): | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add here also the update docker image note
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.