def generate_playbook_doc(input, output, examples, id_set, verbose=False): try: playbook = get_yaml(input) errors = [] description = playbook.get('description', '') if not description: errors.append('Error! You are missing description for the playbook') doc = [description, '', '## Dependencies', 'This playbook uses the following sub-playbooks, integrations, and scripts.', ''] playbooks, integrations, scripts, commands = get_playbook_dependencies(playbook) inputs, inputs_errors = get_inputs(playbook) outputs, outputs_errors = get_outputs(playbook) errors.extend(inputs_errors) errors.extend(outputs_errors) doc.extend(generate_list_section('Sub-playbooks', playbooks, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any sub-playbooks.')) doc.extend(generate_list_section('Integrations', integrations, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any integrations.')) doc.extend(generate_list_section('Scripts', scripts, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any scripts.')) doc.extend(generate_list_section('Commands', commands, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any commands.')) doc.extend(generate_table_section(inputs, 'Playbook Inputs', 'There are no inputs for this playbook.')) doc.extend(generate_table_section(outputs, 'Playbook Outputs', 'There are no outputs for this playbook.')) doc.append('<!-- Playbook PNG image comes here -->') doc_text = '\n'.join(doc) save_output(output, 'README.md', doc_text) if errors: print_warning('Possible Errors:') for error in errors: print_warning(error) except Exception as ex: if verbose: raise else: print_error(f'Error: {str(ex)}') return
def generate_setup_section(yaml_data: dict): section = [ '1. Navigate to **Settings** > **Integrations** > **Servers & Services**.', '2. Search for {}.'.format(yaml_data['name']), '3. Click **Add instance** to create and configure a new integration instance.' ] access_data = [] for conf in yaml_data['configuration']: access_data.append({ 'Parameter': conf.get('display'), 'Description': string_escape_md(conf.get('additionalinfo', '')), 'Required': conf.get('required', '') }) # Check if at least one parameter has additional info field. # If not, remove the description column from the access data table section. access_data_with_description = list( filter(lambda x: x.get('Description', '') != '', access_data)) if len(access_data_with_description) == 0: list(map(lambda x: x.pop('Description'), access_data)) section.extend( generate_table_section(access_data, '', horizontal_rule=False, numbered_section=True)) section.append( '4. Click **Test** to validate the URLs, token, and connection.') return section
def test_generate_table_section_with_newlines(): """Unit test Given - generate_table_section command - inputs as a list of dicts When - running the command on an input including \n (PcapFilter) Then - Validate That the \n is escaped correctly in a markdown format. """ from demisto_sdk.commands.generate_docs.common import \ generate_table_section section = generate_table_section([{ 'Name': 'RsaDecryptKeyEntryID', 'Description': 'This input specifies the file entry id for the RSA decrypt key if the user provided the key' ' in the incident.', 'Default Value': 'File.EntryID', 'Required': 'Optional'}, {'Name': 'PcapFileEntryID', 'Description': 'This input specifies the file entry id for the PCAP file if the user provided the file in the' ' incident. One PCAP file can run per incident.', 'Default Value': 'File.EntryID', 'Required': 'Optional'}, {'Name': 'WpaPassword', 'Description': 'This input value is used to provide a WPA \\(Wi\\-Fi Protected Access\\) password to decrypt' ' encrypted 802.11 Wi\\-FI traffic.', 'Default Value': '', 'Required': 'Optional'}, {'Name': 'PcapFilter', 'Description': 'This input specifies a search filter to be used on the PCAP file. Filters can be used to' ' search only for a specific IP, protocols and other examples. The syntax is the same as in' ' Wireshark which can be found here:' ' https://www.wireshark.org/docs/man-pages/wireshark-filter.html \nFor this playbook, using' ' a PCAP filter will generate a new smaller PCAP file based on the provided filter therefor' ' thus reducing the extraction of non relevant files.', 'Default Value': '', 'Required': 'Optional'}, {'Name': 'ExtractedFilesLimit', 'Description': 'This input limits the number of files to be extracted from the PCAP file.' ' Default value is 5.', 'Default Value': '5', 'Required': 'Optional'} ], 'Playbook Inputs', 'There are no inputs for this playbook.') expected_section = [ '## Playbook Inputs', '---', '', '| **Name** | **Description** | **Default Value** | **Required** |', '| --- | --- | --- | --- |', '| RsaDecryptKeyEntryID | This input specifies the file entry id for the RSA decrypt key if the user provided' ' the key in the incident. | File.EntryID | Optional |', '| PcapFileEntryID | This input specifies the file entry id for the PCAP file if the user provided the file in' ' the incident. One PCAP file can run per incident. | File.EntryID | Optional |', '| WpaPassword | This input value is used to provide a WPA \\(Wi\\-Fi Protected Access\\) password' ' to decrypt encrypted 802.11 Wi\\-FI traffic. | | Optional |', '| PcapFilter | This input specifies a search filter to be used on the PCAP file. Filters can be used to' ' search only for a specific IP, protocols and other examples. The syntax is the same as in Wireshark which' ' can be found here: https://www.wireshark.org/docs/man-pages/wireshark-filter.html <br/>For this' ' playbook, using a PCAP filter will generate a new smaller PCAP file based on the provided filter therefor' ' thus reducing the extraction of non relevant files. | | Optional |', '| ExtractedFilesLimit | This input limits the number of files to be extracted from the PCAP file. ' 'Default value is 5. | 5 | Optional |', '' ] assert section == expected_section
def test_generate_table_section_numbered_section(): """ Given - A table that should be part of a numbered section (like the setup section of integration README). When - Running the generate_table_section command. Then - Validate that the generated table has \t at the beginning of each line. """ from demisto_sdk.commands.generate_docs.common import generate_table_section expected_section = [ '', ' | **Type** | **Docker Image** |', ' | --- | --- |', ' | python2 | demisto/python2 |', '' ] section = generate_table_section(data=[{ 'Type': 'python2', 'Docker Image': 'demisto/python2' }], title='', horizontal_rule=False, numbered_section=True) assert section == expected_section
def test_generate_table_section(): """Unit test Given - generate_table_section command - script metadata as a list of dicts When - running the command on the inputs including a docker image Then - Validate That the script metadata was created correctly. """ from demisto_sdk.commands.generate_docs.common import \ generate_table_section section = generate_table_section([{ 'Type': 'python2', 'Docker Image': 'demisto/python2' }], 'Script Data', 'No data found.', 'This is the metadata of the script.') expected_section = [ '## Script Data', '---', 'This is the metadata of the script.', '| **Type** | **Docker Image** |', '| --- | --- |', '| python2 | demisto/python2 |', '' ] assert section == expected_section
def test_generate_table_section_empty(): from demisto_sdk.commands.generate_docs.common import generate_table_section section = generate_table_section([], 'Script Data', 'No data found.', 'This is the metadata of the script.') expected_section = ['## Script Data', '---', 'No data found.', ''] assert section == expected_section
def test_generate_table_section(): from demisto_sdk.commands.generate_docs.common import generate_table_section section = generate_table_section([{ 'Type': 'python2', 'Docker Image': 'demisto/python2' }], 'Script Data', 'No data found.', 'This is the metadata of the script.') expected_section = [ '## Script Data', '---', 'This is the metadata of the script.', '| **Type** | **Docker Image** |', '| --- | --- |', '| python2 | demisto/python2 |', '' ] assert section == expected_section
def generate_setup_section(yaml_data: dict): section = [ '1. Navigate to **Settings** > **Integrations** > **Servers & Services**.', '2. Search for {}.'.format(yaml_data['name']), '3. Click **Add instance** to create and configure a new integration instance.' ] access_data = [] for conf in yaml_data['configuration']: access_data.append( {'Parameter': conf.get('name', ''), 'Description': conf.get('display', ''), 'Required': conf.get('required', '')}) section.extend(generate_table_section(access_data, '', horizontal_rule=False)) section.append('4. Click **Test** to validate the URLs, token, and connection.') return section
def test_generate_table_section_empty(): """Unit test Given - generate_table_section command - script empty metadata When - running the command on the inputs Then - Validate That the script metadata was created correctly. """ from demisto_sdk.commands.generate_docs.common import \ generate_table_section section = generate_table_section([], 'Script Data', 'No data found.', 'This is the metadata of the script.') expected_section = [ '## Script Data', '---', 'No data found.', ''] assert section == expected_section
def generate_script_doc(input_path, examples, output: str = None, permissions: str = None, limitations: str = None, insecure: bool = False, verbose: bool = False): try: doc: list = [] errors: list = [] example_section: list = [] if not output: # default output dir will be the dir of the input file output = os.path.dirname(os.path.realpath(input_path)) if examples: if os.path.isfile(examples): with open(examples, 'r') as examples_file: examples = examples_file.read().splitlines() else: examples = examples.split(',') for i, example in enumerate(examples): if not example.startswith('!'): examples[i] = f'!{examples}' example_dict, build_errors = build_example_dict(examples, insecure) script_name = list( example_dict.keys())[0] if example_dict else None example_section, example_errors = generate_script_example( script_name, example_dict) errors.extend(build_errors) errors.extend(example_errors) else: errors.append( 'Note: Script example was not provided. For a more complete documentation,run with the -e ' 'option with an example command. For example: -e "!ConvertFile entry_id=<entry_id>".' ) script = get_yaml(input_path) # get script data script_info = get_script_info(input_path) script_id = script.get('commonfields')['id'] # get script dependencies dependencies, _ = get_depends_on(script) # get the script usages by the id set if not os.path.isfile(DEFAULT_ID_SET_PATH): id_set_creator = IDSetCreator(output='', print_logs=False) id_set, _, _ = id_set_creator.create_id_set() else: id_set = open_id_set_file(DEFAULT_ID_SET_PATH) used_in = get_used_in(id_set, script_id) description = script.get('comment', '') # get inputs/outputs inputs, inputs_errors = get_inputs(script) outputs, outputs_errors = get_outputs(script) errors.extend(inputs_errors) errors.extend(outputs_errors) if not description: errors.append( 'Error! You are missing a description for the Script') doc.append(description + '\n') doc.extend(generate_table_section(script_info, 'Script Data')) if dependencies: doc.extend( generate_list_section( 'Dependencies', dependencies, True, text='This script uses the following commands and scripts.' )) # Script global permissions if permissions == 'general': doc.extend(generate_section('Permissions', '')) if used_in: if len(used_in) <= 10: doc.extend( generate_list_section( 'Used In', used_in, True, text= 'This script is used in the following playbooks and scripts.' )) else: # if we have more than 10 use a sample print_warning( f'"Used In" section found too many scripts/playbooks ({len(used_in)}). Will use a sample of 10.' ' Full list is available as a comment in the README file.') sample_used_in = random.sample(used_in, 10) doc.extend( generate_list_section( 'Used In', sorted(sample_used_in), True, text= 'Sample usage of this script can be found in the following playbooks and scripts.' )) used_in_str = '\n'.join(used_in) doc.append( f"<!--\nUsed In: list was truncated. Full list commented out for reference:\n\n{used_in_str}\n -->\n" ) doc.extend( generate_table_section(inputs, 'Inputs', 'There are no inputs for this script.')) doc.extend( generate_table_section(outputs, 'Outputs', 'There are no outputs for this script.')) if example_section: doc.extend(example_section) # Known limitations if limitations: doc.extend( generate_numbered_section('Known Limitations', limitations)) doc_text = '\n'.join(doc) save_output(output, 'README.md', doc_text) if errors: print_warning('Possible Errors:') for error in errors: print_warning(error) except Exception as ex: if verbose: raise else: print_error(f'Error: {str(ex)}') return
def generate_playbook_doc(input, output: str = None, permissions: str = None, limitations: str = None, verbose: bool = False): try: playbook = get_yaml(input) if not output: # default output dir will be the dir of the input file output = os.path.dirname(os.path.realpath(input)) errors = [] description = playbook.get('description', '') _name = playbook.get('name', 'Unknown') if not description: errors.append( 'Error! You are missing description for the playbook') doc = [ description, '', '## Dependencies', 'This playbook uses the following sub-playbooks, integrations, and scripts.', '' ] playbooks, integrations, scripts, commands = get_playbook_dependencies( playbook, input) inputs, inputs_errors = get_inputs(playbook) outputs, outputs_errors = get_outputs(playbook) playbook_filename = os.path.basename(input).replace('.yml', '') errors.extend(inputs_errors) errors.extend(outputs_errors) # Playbooks general permissions if permissions == 'general': doc.extend(generate_section('Permissions', '')) doc.extend( generate_list_section( 'Sub-playbooks', playbooks, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any sub-playbooks.')) doc.extend( generate_list_section( 'Integrations', integrations, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any integrations.')) doc.extend( generate_list_section( 'Scripts', scripts, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any scripts.')) doc.extend( generate_list_section( 'Commands', commands, header_type=HEADER_TYPE.H3, empty_message='This playbook does not use any commands.')) doc.extend( generate_table_section(inputs, 'Playbook Inputs', 'There are no inputs for this playbook.')) doc.extend( generate_table_section(outputs, 'Playbook Outputs', 'There are no outputs for this playbook.')) # Known limitations if limitations: doc.extend( generate_numbered_section('Known Limitations', limitations)) doc.append('## Playbook Image\n---') doc.append(f'![{_name}](Insert the link to your image here)') doc_text = '\n'.join(doc) save_output(output, f'{playbook_filename}_README.md', doc_text) if errors: print_warning('Possible Errors:') for error in errors: print_warning(error) except Exception as ex: if verbose: raise else: print_error(f'Error: {str(ex)}') return
def generate_script_doc(input, output, examples, id_set='', verbose=False): try: doc = [] errors = [] used_in = [] example_section = [] if examples: if not examples.startswith('!'): examples = f'!{examples}' example_dict, build_errors = build_example_dict([examples]) script_name = examples.split(' ')[0][1:] example_section, example_errors = generate_script_example( script_name, example_dict) errors.extend(build_errors) errors.extend(example_errors) else: errors.append( f'Note: Script example was not provided. For a more complete documentation,run with the -e ' f'option with an example command. For example: -e "!ConvertFile entry_id=<entry_id>".' ) script = get_yaml(input) # get script data secript_info = get_script_info(input) script_id = script.get('commonfields')['id'] # get script dependencies dependencies, _ = get_depends_on(script) if not id_set: errors.append(f'id_set.json file is missing') elif not os.path.isfile(id_set): errors.append(f'id_set.json file {id_set} was not found') else: used_in = get_used_in(id_set, script_id) description = script.get('comment', '') deprecated = script.get('deprecated', False) # get inputs/outputs inputs, inputs_errors = get_inputs(script) outputs, outputs_errors = get_outputs(script) errors.extend(inputs_errors) errors.extend(outputs_errors) if not description: errors.append( 'Error! You are missing description for the playbook') if deprecated: doc.append('`Deprecated`') doc.append(description) doc.extend(generate_table_section(secript_info, 'Script Data')) if dependencies: doc.extend( generate_list_section( 'Dependencies', dependencies, True, text='This script uses the following commands and scripts.' )) if used_in: doc.extend( generate_list_section( 'Used In', used_in, True, text= 'This script is used in the following playbooks and scripts.' )) doc.extend( generate_table_section(inputs, 'Inputs', 'There are no inputs for this script.')) doc.extend( generate_table_section(outputs, 'Outputs', 'There are no outputs for this script.')) if example_section: doc.extend(example_section) doc_text = '\n'.join(doc) save_output(output, 'README.md', doc_text) if errors: print_warning('Possible Errors:') for error in errors: print_warning(error) except Exception as ex: if verbose: raise else: print_error(f'Error: {str(ex)}') return
def generate_script_doc(input, examples, output: str = None, permissions: str = None, limitations: str = None, insecure: bool = False, verbose: bool = False): try: doc = [] errors = [] used_in = [] example_section = [] if not output: # default output dir will be the dir of the input file output = os.path.dirname(os.path.realpath(input)) if examples: if not examples.startswith('!'): examples = f'!{examples}' example_dict, build_errors = build_example_dict([examples], insecure) script_name = examples.split(' ')[0][1:] example_section, example_errors = generate_script_example( script_name, example_dict) errors.extend(build_errors) errors.extend(example_errors) else: errors.append( f'Note: Script example was not provided. For a more complete documentation,run with the -e ' f'option with an example command. For example: -e "!ConvertFile entry_id=<entry_id>".' ) script = get_yaml(input) # get script data secript_info = get_script_info(input) script_id = script.get('commonfields')['id'] # get script dependencies dependencies, _ = get_depends_on(script) # get the script usages by the id set id_set_creator = IDSetCreator() id_set = id_set_creator.create_id_set() used_in = get_used_in(id_set, script_id) description = script.get('comment', '') deprecated = script.get('deprecated', False) # get inputs/outputs inputs, inputs_errors = get_inputs(script) outputs, outputs_errors = get_outputs(script) errors.extend(inputs_errors) errors.extend(outputs_errors) if not description: errors.append( 'Error! You are missing description for the playbook') if deprecated: doc.append('`Deprecated`') doc.append(description) doc.extend(generate_table_section(secript_info, 'Script Data')) if dependencies: doc.extend( generate_list_section( 'Dependencies', dependencies, True, text='This script uses the following commands and scripts.' )) # Script global permissions if permissions == 'general': doc.extend(generate_section('Permissions', '')) if used_in: doc.extend( generate_list_section( 'Used In', used_in, True, text= 'This script is used in the following playbooks and scripts.' )) doc.extend( generate_table_section(inputs, 'Inputs', 'There are no inputs for this script.')) doc.extend( generate_table_section(outputs, 'Outputs', 'There are no outputs for this script.')) if example_section: doc.extend(example_section) # Known limitations if limitations: doc.extend( generate_numbered_section('Known Limitations', limitations)) doc_text = '\n'.join(doc) save_output(output, 'README.md', doc_text) if errors: print_warning('Possible Errors:') for error in errors: print_warning(error) except Exception as ex: if verbose: raise else: print_error(f'Error: {str(ex)}') return