Skip to content
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

Merged
merged 37 commits into from Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d080cb6
moved GenerateAsBuilt to CommonScripts
samuelFain Jan 2, 2023
13010e5
Updated code standards and doc strings
samuelFain Jan 2, 2023
8f1bb18
Added doc strings
samuelFain Jan 2, 2023
5615d6e
added UT's
samuelFain Jan 3, 2023
9383a95
moved main function logic to helper functions
samuelFain Jan 4, 2023
79df035
removed UT coverage from __init__ functions
samuelFain Jan 4, 2023
8e89be3
removed UT coverage from __init__ functions
samuelFain Jan 4, 2023
d30b4ab
Added UT doc-strings, Linting
samuelFain Jan 4, 2023
ffad517
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 4, 2023
9b99b2f
Updated dockerimage
samuelFain Jan 4, 2023
9aa4f6f
Updates release notes
samuelFain Jan 4, 2023
1074132
Updated release notes
samuelFain Jan 4, 2023
78c3a2d
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 4, 2023
6ed156d
Updated release notes
samuelFain Jan 5, 2023
f4297e7
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 5, 2023
5915a7e
fixes for CR issues
samuelFain Jan 5, 2023
fc7fa0a
deprecated original pack
samuelFain Jan 5, 2023
4460c6d
deprecated original pack
samuelFain Jan 5, 2023
f17630b
Release notes misspell fix
samuelFain Jan 5, 2023
7141b23
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 5, 2023
9daf312
Updated UT
samuelFain Jan 5, 2023
243371b
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 5, 2023
85c6948
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 8, 2023
ae069a7
Updated release notes
samuelFain Jan 8, 2023
a2fae9f
Added id_set json
samuelFain Jan 8, 2023
d0274cc
Updated release notes
samuelFain Jan 10, 2023
921ef23
Updated release notes
samuelFain Jan 10, 2023
68703fe
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 10, 2023
10f1958
Updated RN
samuelFain Jan 12, 2023
5ba9a30
Removed constants
samuelFain Jan 12, 2023
c638667
Updated RN
samuelFain Jan 12, 2023
062cec5
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 12, 2023
b06c5c7
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 12, 2023
cf5f31d
UT coverage
samuelFain Jan 12, 2023
b73fa67
Fixed linting error
samuelFain Jan 12, 2023
e09afea
Merge branch 'master' into sf_GenerateAsBuilt
samuelFain Jan 12, 2023
64339b1
Updated RN
samuelFain Jan 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion Packs/CommonScripts/.secrets-ignore
Expand Up @@ -79,10 +79,12 @@ email@domain.com
domain.com
www.w3schools.com
sailpoint
https://cdn.jsdelivr.net
bootstrap@5.0.0-beta3
https://invalid.url
https://longurl.in
https://longurl.in/api/expand-url
https://short.url/b
https://short.url/a
https://unshorten.me/json/
https://xsoar.pan.dev
https://xsoar.pan.dev
4 changes: 4 additions & 0 deletions Packs/CommonScripts/ReleaseNotes/1_10_44.md
@@ -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).
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -1,8 +1,6 @@
import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *

import sys
import jinja2

"""
Expand All @@ -21,8 +19,6 @@
DEMISTO_DEPENDENCIES_PATH = "/itemsdependencies"
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelFain Still don't see anything regarding this.
We don't need those constants

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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">
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -482,8 +480,10 @@ def __init__(
closed_incidents,
playbook_stats,
reports,
dashboards
):
dashboards,
author,
customer
): # pragma: no cover
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the pragma no cover here? remove this

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is not the best practice to put this.
We only use it in specific cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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": {
Expand All @@ -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": {
Expand All @@ -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": {
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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 = []
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand All @@ -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()