├── .python-version ├── yamllint_config.yml ├── clay584 └── genie │ ├── colored_diff.png │ ├── learn_genie_params.png │ ├── clay584-genie-0.1.7.tar.gz │ ├── plugins │ ├── modules │ │ ├── args.json │ │ └── learn_genie.py │ └── filter │ │ └── parse_genie.py │ ├── galaxy.yml │ └── README.md ├── inventory ├── NOTICE ├── Pipfile ├── ansible.cfg ├── test.yml ├── .gitignore ├── README.md ├── LICENSE └── Pipfile.lock /.python-version: -------------------------------------------------------------------------------- 1 | 3.6.8 2 | -------------------------------------------------------------------------------- /yamllint_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ignore: | 4 | README.md 5 | ansible.cfg 6 | LICENSE 7 | -------------------------------------------------------------------------------- /clay584/genie/colored_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clay584/genie_collection/HEAD/clay584/genie/colored_diff.png -------------------------------------------------------------------------------- /clay584/genie/learn_genie_params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clay584/genie_collection/HEAD/clay584/genie/learn_genie_params.png -------------------------------------------------------------------------------- /clay584/genie/clay584-genie-0.1.7.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clay584/genie_collection/HEAD/clay584/genie/clay584-genie-0.1.7.tar.gz -------------------------------------------------------------------------------- /inventory: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | sbx-nxos-mgmt.cisco.com: 4 | ansible_connection: local 5 | ansible_python_interpreter: "{{ ansible_playbook_python }}" -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | genie_collection: 2 | 3 | learn_genie 4 | parse_genie 5 | 6 | Copyright (c) 2020 Presidio, Inc. and/or its affiliates. 7 | 8 | This project includes software developed at Presidio, Inc. and/or its affiliates. -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | ansible = "*" 10 | pyats = "*" 11 | genie = "*" 12 | 13 | [requires] 14 | python_version = "3.6" 15 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | stdout_callback = yaml 3 | max_diff_size = 9999999 4 | deprecation_warnings = False 5 | library = ~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules:./clay584/genie/plugins/modules 6 | ;display_skipped_hosts = False 7 | ;ask_vault_pass = True 8 | ;inventory = inventory 9 | 10 | [always] 11 | deprecation_warnings = False 12 | -------------------------------------------------------------------------------- /clay584/genie/plugins/modules/args.json: -------------------------------------------------------------------------------- 1 | { 2 | "ANSIBLE_MODULE_ARGS": { 3 | "host": "sbx-nxos-mgmt.cisco.com", 4 | "port": 8181, 5 | "protocol": "ssh", 6 | "username": "admin", 7 | "password": "Admin_1234!", 8 | "os": "nxos", 9 | "feature": "arp", 10 | "exclude": ["test123"], 11 | "no_default_exclusion": true 12 | } 13 | } -------------------------------------------------------------------------------- /test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: all 4 | gather_facts: false 5 | connection: local 6 | tasks: 7 | - name: Learn Genie - 1st Run 8 | learn_genie: 9 | host: "{{ ansible_host }}" 10 | port: 8181 11 | protocol: ssh 12 | username: admin 13 | password: Admin_1234! 14 | os: nxos 15 | feature: ospf 16 | register: genie1 17 | 18 | # - name: Debug Genie 19 | # debug: 20 | # msg: "{{ genie_arp1 }}" 21 | 22 | - name: pause for changes 23 | pause: 24 | minutes: 1 25 | 26 | - name: Learn Genie with Diff 27 | learn_genie: 28 | host: "{{ ansible_host }}" 29 | port: 8181 30 | protocol: ssh 31 | username: admin 32 | password: Admin_1234! 33 | os: nxos 34 | feature: ospf 35 | compare_to: "{{ genie1 }}" 36 | diff: true 37 | 38 | # - name: Debug2 39 | # debug: 40 | # msg: "{{ genie2 }}" 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | #.python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .idea 131 | 132 | *.tar.gz 133 | -------------------------------------------------------------------------------- /clay584/genie/galaxy.yml: -------------------------------------------------------------------------------- 1 | ### REQUIRED 2 | 3 | # The namespace of the collection. This can be a company/brand/organization or product namespace under which all 4 | # content lives. May only contain alphanumeric characters and underscores. Additionally namespaces cannot start with 5 | # underscores or numbers and cannot contain consecutive underscores 6 | namespace: clay584 7 | 8 | # The name of the collection. Has the same character restrictions as 'namespace' 9 | name: genie 10 | 11 | # The version of the collection. Must be compatible with semantic versioning 12 | version: 0.1.11 13 | 14 | # The path to the Markdown (.md) readme file. This path is relative to the root of the collection 15 | readme: README.md 16 | 17 | # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) 18 | # @nicks:irc/im.site#channel' 19 | authors: 20 | - Clay Curtis 21 | 22 | 23 | ### OPTIONAL but strongly recommended 24 | 25 | # A short summary description of the collection 26 | description: Brings Cisco pyATS/Genie functionality to Ansible 27 | 28 | # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only 29 | # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' 30 | license: 31 | - GPL-3.0-or-later 32 | 33 | # The path to the license file for the collection. This path is relative to the root of the collection. This key is 34 | # mutually exclusive with 'license' 35 | license_file: '' 36 | 37 | # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character 38 | # requirements as 'namespace' and 'name' 39 | tags: ['cisco', 'genie', 'networking', 'parse', 'pyats'] 40 | 41 | # Collections that this collection requires to be installed for it to be usable. The key of the dict is the 42 | # collection label 'namespace.name'. The value is a version range 43 | # L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version 44 | # range specifiers can be set and are separated by ',' 45 | dependencies: {} 46 | 47 | # The URL of the originating SCM repository 48 | repository: https://github.com/clay584/genie_collection 49 | 50 | # The URL to any online docs 51 | documentation: https://github.com/clay584/genie_collection/blob/master/clay584/genie/README.md 52 | 53 | # The URL to the homepage of the collection/project 54 | homepage: https://github.com/clay584/genie_collection 55 | 56 | # The URL to the collection issue tracker 57 | issues: https://github.com/clay584/genie_collection/issues 58 | -------------------------------------------------------------------------------- /clay584/genie/plugins/filter/parse_genie.py: -------------------------------------------------------------------------------- 1 | # Make coding more python3-ish 2 | from __future__ import (absolute_import, division, print_function) 3 | __metaclass__ = type 4 | 5 | __author__ = "Clay Curtis" 6 | __license__ = "GPLv3" 7 | __email__ = "jccurtis@presidio.com" 8 | __version__ = "1.0" 9 | 10 | import sys 11 | 12 | from ansible.errors import AnsibleError, AnsibleFilterError 13 | from ansible.module_utils._text import to_native, to_text 14 | from ansible.module_utils.six import string_types 15 | from ansible.utils.display import Display 16 | 17 | try: 18 | from genie.conf.base import Device, Testbed 19 | from genie.libs.parser.utils import get_parser 20 | from genie import parsergen 21 | HAS_GENIE = True 22 | except ImportError: 23 | HAS_GENIE = False 24 | 25 | try: 26 | from pyats.datastructures import AttrDict 27 | HAS_PYATS = True 28 | except ImportError: 29 | HAS_PYATS = False 30 | 31 | 32 | display = Display() 33 | 34 | 35 | def parse_genie(cli_output, 36 | command=None, 37 | os=None, 38 | platform=None, 39 | generic_tabular=False, 40 | generic_tabular_name=None, 41 | generic_tabular_metadata=None): 42 | """ 43 | Uses the Cisco pyATS/Genie library to parse cli output into structured data. 44 | :param cli_output: (String) CLI output from Cisco device 45 | :param command: (String) CLI command that was used to generate the cli_output 46 | :param os: (String) Operating system of the device for which cli_output was obtained. 47 | :param platform: (String) Platform of the device. This is Optional. 48 | :param generic_tabular: (Boolean) If the output being passed in is generic tabular output for which 49 | we don't have a parser, we will try to parse with a generic tabular parser. 50 | :param generic_tabular_metadata: (dict) If it is generic tabular data, we will need metadata in order 51 | to parse the data. This dict contains tabular data table headers, indexes, and other data needed 52 | in order for Genie to parse the tabular data. 53 | :return: Dict object conforming to the defined genie parser schema. 54 | https://pubhub.devnetcloud.com/media/pyats-packages/docs/genie/genie_libs/#/parsers/show%20version 55 | """ 56 | 57 | # Does the user have the necessary packages installed in order to use this filter? 58 | if not HAS_GENIE: 59 | raise AnsibleFilterError("parse_genie: Genie package is not installed. To install, run 'pip install genie'.") 60 | 61 | if not HAS_PYATS: 62 | raise AnsibleFilterError("parse_genie: pyATS package is not installed. To install, run 'pip install pyats'.") 63 | 64 | # Does the user have the required version of Python 3.4 that Genie and pyATS requires? 65 | if sys.version_info[0] == 3 and sys.version_info[1] >= 4: 66 | pass 67 | else: 68 | raise AnsibleFilterError("parse_genie: pyATS/Genie package requires python 3.4 or greater.") 69 | 70 | # Input validation 71 | 72 | # Is the CLI output a string? 73 | if not isinstance(cli_output, string_types): 74 | raise AnsibleError( 75 | "The content provided to the parse_genie filter was not a string." 76 | ) 77 | 78 | # Is the command a string? 79 | if not isinstance(command, string_types): 80 | raise AnsibleFilterError( 81 | "The command provided to the parse_genie filter was not a string." 82 | ) 83 | 84 | # Is the OS a string? 85 | if not isinstance(os, string_types): 86 | raise AnsibleFilterError( 87 | "The network OS provided to the parse_genie filter was not a string." 88 | ) 89 | 90 | # Is the OS provided by the user a supported OS by Genie? 91 | # Supported Genie OSes: https://github.com/CiscoTestAutomation/genieparser/tree/master/src/genie/libs/parser 92 | supported_oses = ["ios", "iosxe", "iosxr", "junos", "nxos"] 93 | if os.lower() not in supported_oses: 94 | raise AnsibleFilterError( 95 | "The network OS provided ({0}) to the parse_genie filter is not a supported OS in Genie.".format( 96 | os 97 | ) 98 | ) 99 | 100 | def _parse(raw_cli_output, cmd, nos, platform): 101 | # Boilerplate code to get the parser functional 102 | # tb = Testbed() 103 | if platform: 104 | device = Device("new_device", os=nos, platform=platform) 105 | device.custom.setdefault("abstraction", {})["order"] = ["os", "platform"] 106 | else: 107 | device = Device("new_device", os=nos) 108 | device.custom.setdefault("abstraction", {})["order"] = ["os"] 109 | 110 | device.cli = AttrDict({"execute": None}) 111 | 112 | # User input checking of the command provided. Does the command have a Genie parser? 113 | try: 114 | get_parser(cmd, device) 115 | except Exception as e: 116 | raise AnsibleFilterError( 117 | "parse_genie: {0} - Available parsers: {1}".format( 118 | to_native(e), "https://pubhub.devnetcloud.com/media/pyats-packages/docs/genie/genie_libs/#/parsers" 119 | ) 120 | ) 121 | 122 | try: 123 | parsed_output = device.parse(cmd, output=raw_cli_output) 124 | return parsed_output 125 | except Exception as e: 126 | raise AnsibleFilterError( 127 | "parse_genie: {0} - Failed to parse command output.".format( 128 | to_native(e) 129 | ) 130 | ) 131 | 132 | def _parse_generic_tabular(cli_output, os, headers, key_index): 133 | # Boilerplate code to get the parser functional 134 | tb = Testbed() 135 | device = Device("new_device", os=os) 136 | 137 | device.custom.setdefault("abstraction", {})["order"] = ["os"] 138 | device.cli = AttrDict({"execute": None}) 139 | 140 | # Do the parsing 141 | # result = parsergen.oper_fill_tabular(device_output=cli_output, device_os=nos, header_ 142 | # fields=headers, index=[key]) 143 | result = parsergen.oper_fill_tabular(device_output=cli_output, 144 | device_os=os, 145 | header_fields=headers, 146 | index=key_index) 147 | 148 | # Structured data, but it has a blank entry because of the first line of the output 149 | # being blank under the headers. 150 | parsed_output = result.entries 151 | 152 | return parsed_output 153 | 154 | # If tabular data without a parser, we will try to parse with a generic parser 155 | if generic_tabular: 156 | # raise AnsibleFilterError(to_native(generic_tabular_metadata)) 157 | try: 158 | headers = generic_tabular_metadata["parse_genie"][os][command]["headers"] 159 | index = generic_tabular_metadata["parse_genie"][os][command]["index"] 160 | except Exception as e: 161 | raise AnsibleFilterError( 162 | "parse_genie: {0} - Failed to parse generic_tabular_metadata.".format( 163 | to_native(e) 164 | )) 165 | 166 | structured_data = _parse_generic_tabular(cli_output, os, headers, index) 167 | if structured_data: 168 | return _parse_generic_tabular(cli_output, os, headers, index) 169 | else: 170 | raise AnsibleFilterError( 171 | "parse_genie: - Failed to parse tabular command output from '{}' command '{}'.".format(os, command)) 172 | 173 | else: 174 | # If it is not generic output with no parser, we will parse it. 175 | # Try to parse the output 176 | # If OS is IOS, ansible could have passed in IOS, but the Genie device-type is actually IOS-XE, 177 | # so we will try to parse both. 178 | if os == "ios": 179 | try: 180 | return _parse(cli_output, command, "ios", platform) 181 | except Exception: 182 | return _parse(cli_output, command, "iosxe", platform) 183 | else: 184 | return _parse(cli_output, command, os, platform) 185 | 186 | 187 | class FilterModule(object): 188 | """ Cisco pyATS/Genie Parser Filter """ 189 | 190 | def filters(self): 191 | return { 192 | "parse_genie": parse_genie 193 | } 194 | -------------------------------------------------------------------------------- /clay584/genie/plugins/modules/learn_genie.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright: (c) 2020, Clay Curtis 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | ANSIBLE_METADATA = { 7 | "metadata_version": "0.1.0", 8 | "status": ["preview"], 9 | "supported_by": "community", 10 | } 11 | 12 | DOCUMENTATION = """ 13 | --- 14 | module: learn_genie 15 | 16 | short_description: This module utilizes the Cisco PyATS/Genie framework to "learn" features from network devices. 17 | 18 | version_added: "2.9" 19 | 20 | description: 21 | - "This module allows for the user of the Ansible module to reach out to a network device and pull back CLI data, parse it, and 'learn' the entirety of a given feature on a network device." 22 | 23 | options: 24 | host: 25 | description: 26 | - The network device that PyATS/Genie should connect to 27 | required: true 28 | port: 29 | description: 30 | - The port number for SSH on the device 31 | required: false 32 | username: 33 | description: 34 | - The username used to connect to the device 35 | required: true 36 | password: 37 | description: 38 | - The password used to connect to the device 39 | required: true 40 | os: 41 | description: 42 | - Network operating system of the device 43 | required: true 44 | feature: 45 | description: 46 | - The device feature to be learned from the device 47 | required: true 48 | compare_to: 49 | description: 50 | - Compare to prior genie learn data (diff) 51 | required: false 52 | exclude: 53 | description: 54 | - Exclude noisy keys such as packet counters, timestamps, uptime, etc. 55 | required: false 56 | no_default_exclusion: 57 | description: 58 | - Do not exclude any noisy keys in the Genie diff operation such as packet counters, timestamps, uptime, etc. 59 | required: false 60 | colors: 61 | description: 62 | - Turn on or off colored diff output. Defaults to on (colored output). Requires Python package "colorama". 63 | required: false 64 | 65 | # extends_documentation_fragment: 66 | # - azure 67 | 68 | author: 69 | - Clay Curtis (@ccurtis584) 70 | """ 71 | 72 | EXAMPLES = """ 73 | # Learn the state of BGP 74 | - name: Learn BGP Feature 75 | learn_genie: 76 | host: 10.1.1.1 77 | username: admin 78 | password: password1234 79 | os: iosxe 80 | feature: bgp 81 | """ 82 | 83 | RETURN = """ 84 | feature_data: 85 | description: An OS-agnostic data structure for the feature which will be the same regardless of device operating system. 86 | type: dict 87 | returned: always 88 | """ 89 | 90 | import json 91 | import importlib 92 | from pathlib import Path 93 | from ansible.module_utils.basic import AnsibleModule 94 | from ansible.errors import AnsibleError 95 | from ansible.module_utils.six import string_types 96 | from ansible.module_utils._text import to_native 97 | 98 | 99 | try: 100 | from genie.testbed import load 101 | # from pyats.topology import loader 102 | from genie.utils.diff import Diff 103 | HAS_GENIE = True 104 | except ImportError: 105 | HAS_GENIE = False 106 | 107 | if not HAS_GENIE: 108 | raise AnsibleError( 109 | "You must have PyATS/Genie packages installed on the Ansible control node!" 110 | ) 111 | 112 | try: 113 | from colorama import Fore, Back, Style, init 114 | init() 115 | except ImportError: # fallback so that the imported classes always exist 116 | class ColorFallback(): 117 | __getattr__ = lambda self, name: '' 118 | Fore = Back = Style = ColorFallback() 119 | 120 | 121 | def color_diff(diff): 122 | diff_list = diff.splitlines() 123 | for line in diff_list: 124 | if line.startswith('+++'): 125 | yield Fore.WHITE + line + Fore.RESET 126 | elif line.startswith('---'): 127 | yield Fore.WHITE + line + Fore.RESET 128 | elif line.startswith('+'): 129 | yield Fore.GREEN + line + Fore.RESET 130 | elif line.startswith('-'): 131 | yield Fore.RED + line + Fore.RESET 132 | elif line.startswith('@@'): 133 | yield Fore.BLUE + line + Fore.RESET 134 | else: 135 | yield line 136 | 137 | 138 | def run_module(): 139 | # define available arguments/parameters a user can pass to the module 140 | module_args = dict( 141 | host=dict(type="str", required=True), 142 | port=dict(type="int", required=False), 143 | protocol=dict(type="str", required=False), 144 | username=dict(type="str", required=True), 145 | password=dict(type="str", required=True, no_log=True), 146 | os=dict(type="str", required=True), 147 | feature=dict(type="str", required=True), 148 | compare_to=dict(type="raw", required=False), 149 | exclude=dict(type="list", required=False), 150 | no_default_exclusion=dict(type="bool", required=False), 151 | colors=dict(type="bool", required=False) 152 | ) 153 | # TODO: Add protocol so Unicon can use anything 154 | # print(type(module_args['compare_to'])) 155 | # seed the result dict in the object 156 | # we primarily care about changed and state 157 | # change is if this module effectively modified the target 158 | # state will include any data that you want your module to pass back 159 | # for consumption, for example, in a subsequent task 160 | result = dict(changed=False) 161 | 162 | # the AnsibleModule object will be our abstraction working with Ansible 163 | # this includes instantiation, a couple of common attr would be the 164 | # args/params passed to the execution, as well as if the module 165 | # supports check mode 166 | module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) 167 | 168 | host = module.params["host"] 169 | if module.params.get("port") is not None: 170 | port = module.params["port"] 171 | else: 172 | port = 22 173 | username = module.params["username"] 174 | password = module.params["password"] 175 | os = module.params["os"] 176 | feature = module.params["feature"] 177 | if module.params.get("compare_to"): 178 | # compare_to = json.loads(module.params.get("compare_to")) 179 | compare_to = module.params.get("compare_to") 180 | if module.params.get("exclude"): 181 | excluded_keys = module.params.get("exclude") 182 | if module.params.get("no_default_exclusion") is False: 183 | no_default_exclusion = False 184 | elif module.params.get("no_default_exclusion") is None: 185 | no_default_exclusion = False 186 | else: 187 | no_default_exclusion = True 188 | if module.params.get("colors") is False: 189 | colors = False 190 | elif module.params.get("colors") is not None: 191 | colors = True 192 | else: 193 | colors = True 194 | if module.params.get("protocol") == "telnet": 195 | protocol = "telnet" 196 | else: 197 | protocol = "ssh" 198 | 199 | # if the user is working with this module in only check mode we do not 200 | # want to make any changes to the environment, just return the current 201 | # state with no modifications 202 | if module.check_mode: 203 | module.exit_json(**result) 204 | 205 | # Check user input 206 | for k, v in module.params.items(): 207 | if k == "port" and v is not None: 208 | if not isinstance(v, int) and v not in range(1-65535): 209 | raise AnsibleError( 210 | "The {} parameter must be an integer between 0-65535".format(k) 211 | ) 212 | elif k == "compare_to": 213 | pass 214 | elif k == "port": 215 | pass 216 | elif k == "exclude": 217 | pass 218 | elif k == "colors": 219 | pass 220 | elif k == "no_default_exclusion": 221 | pass 222 | elif k == "protocol": 223 | pass 224 | else: 225 | if not isinstance(v, string_types): 226 | raise AnsibleError( 227 | "The {} parameter must be a string such as a hostname or IP address.".format( 228 | k 229 | ) 230 | ) 231 | 232 | # Did the user pass in a feature that is supported on a given platform 233 | genie_ops = importlib.util.find_spec("genie.libs.ops") 234 | ops_file_obj = Path(genie_ops.origin).parent.joinpath("ops.json") 235 | with open(ops_file_obj, "r") as f: 236 | ops_json = json.load(f) 237 | supported_features = [k for k, _ in ops_json.items()] 238 | 239 | # Load in default exclusions for diffs for all features for genie learn 240 | # genie_yamls = importlib.util.find_spec("genie.libs.sdk.genie_yamls") 241 | # genie_excludes = Path(genie_yamls.origin).parent.joinpath("pts_datafile.yaml") 242 | # with open(genie_excludes, "r") as f: 243 | # diff_excludes = json.load(f) 244 | # supported_features = [k for k, _ in ops_json.items()] 245 | 246 | default_excludes = {} 247 | from importlib import import_module 248 | for i in supported_features: 249 | modulename = "genie.libs.ops.{}.{}".format(i, i) 250 | package_name = i.capitalize() 251 | try: 252 | this_module = import_module(modulename, package_name) 253 | this_class = getattr(this_module, package_name) 254 | this_excludes = this_class.exclude 255 | default_excludes.update({i: this_excludes}) 256 | except AttributeError: 257 | default_excludes.update({i: []}) 258 | 259 | # this_module = __import__(modulename) 260 | # default_excludes.append({i: this_module.i) 261 | # from genie.libs.ops.i.i import Interface 262 | 263 | # Is the feature even supported? 264 | if feature not in supported_features: 265 | raise AnsibleError( 266 | "The feature entered is not supported on the current version of Genie.\nCurrently supported features: {0}\n{1}".format( 267 | to_native(supported_features), 268 | "https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/models", 269 | ) 270 | ) 271 | 272 | # Is the feature supported on the OS that was provided from the user? 273 | for f in ops_json.items(): 274 | if feature == f[0]: 275 | if os not in [k for k, _ in f[1].items()]: 276 | raise AnsibleError( 277 | "The {0} feature entered is not supported on {1}.\nCurrently supported features & platforms:\n{2}".format( 278 | feature, os, 279 | "https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/models", 280 | ) 281 | ) 282 | 283 | testbed = { 284 | "devices": { 285 | host: { 286 | "ip": host, 287 | "port": port, 288 | "protocol": protocol, 289 | "username": username, 290 | "password": password, 291 | "os": os, 292 | } 293 | } 294 | } 295 | 296 | tb = load(testbed) 297 | dev = tb.devices[host] 298 | dev.connect(log_stdout=False, learn_hostname=True) 299 | output = dev.learn(feature) 300 | 301 | # Do diff if compare_to was provided 302 | if module.params.get("compare_to"): 303 | # do genie diff 304 | # print(type(compare_to['genie'][feature])) 305 | # print(type(output.info)) 306 | # dd = Diff({"a": "yes"}, {"a": "no"}) 307 | # with open('/tmp/compare_to.txt', 'w') as f: 308 | # f.write(json.dumps(compare_to['genie'][feature])) 309 | # with open('/tmp/output.txt', 'w') as f: 310 | # f.write(json.dumps(output.info)) 311 | 312 | before = compare_to['genie'][feature] 313 | current = json.dumps(output.info) 314 | current = json.loads(current) 315 | # current = eval(str(output.info)) 316 | try: 317 | excluded_keys 318 | if no_default_exclusion: 319 | merged_exclusions = excluded_keys 320 | else: 321 | merged_exclusions = list(set().union(excluded_keys, default_excludes[feature])) 322 | dd = Diff(before, current, exclude=merged_exclusions) 323 | except NameError: 324 | if len(default_excludes[feature]) > 0: 325 | if no_default_exclusion: 326 | dd = Diff(before, current) 327 | else: 328 | dd = Diff(before, current, exclude=default_excludes[feature]) 329 | else: 330 | dd = Diff(before, current) 331 | dd.findDiff() 332 | if colors: 333 | result.update({"diff": {"prepared": '\n'.join(color_diff(str(dd)))}}) 334 | else: 335 | result.update({"diff": {"prepared": str(dd)}}) 336 | module._diff = True 337 | if len(str(dd)) > 0: 338 | result['changed'] = True 339 | 340 | feature_data = { 341 | feature: output.info 342 | } 343 | 344 | result.update({"genie": feature_data}) 345 | 346 | # use whatever logic you need to determine whether or not this module 347 | # made any modifications to your target 348 | # if module.params['new']: 349 | # result['changed'] = True 350 | 351 | # during the execution of the module, if there is an exception or a 352 | # conditional state that effectively causes a failure, run 353 | # AnsibleModule.fail_json() to pass in the message and the result 354 | # if module.params['name'] == 'fail me': 355 | # module.fail_json(msg='You requested this to fail', **result) 356 | 357 | # in the event of a successful module execution, you will want to 358 | # simple AnsibleModule.exit_json(), passing the key/value results 359 | module.exit_json(**result) 360 | 361 | 362 | def main(): 363 | run_module() 364 | 365 | 366 | if __name__ == "__main__": 367 | main() 368 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cisco Genie Ansible Collection (Unofficial) 2 | 3 | [![Published](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-published.svg)](https://developer.cisco.com/codeexchange/github/repo/clay584/genie_collection) 4 | ![Ansible Version](https://img.shields.io/badge/ansible-%3E%3D2.9-blue.svg) 5 | ![Python Version](https://img.shields.io/badge/python-%3E%3D3.4-blue.svg) 6 | 7 | This is an Ansible collection that brings the functionality of Cisco's pyATS and Genie 8 | libraries to Ansible users. 9 | 10 | For more information on the Cisco pyATS project, see [https://developer.cisco.com/docs/pyats/ 11 | ](https://developer.cisco.com/docs/pyats/) 12 | 13 | - [Cisco Genie Ansible Collection (Unofficial)](#cisco-genie-ansible-collection--unofficial-) 14 | * [What's in this Collection?](#what-s-in-this-collection-) 15 | + [Learn Genie Ansible Module](#learn-genie-ansible-module) 16 | + [Parse Genie Ansible Filter Plugin](#parse-genie-ansible-filter-plugin) 17 | * [Prerequisites](#prerequisites) 18 | * [Installation](#installation) 19 | - [Usage](#usage) 20 | * [Learn Genie Module](#learn-genie-module) 21 | - [Module Parameters](#module-parameters) 22 | - [Examples](#examples) 23 | * [Parse Genie Filter Plugin](#parse-genie-filter-plugin) 24 | + [Ansible to Genie OS Mappings](#ansible-to-genie-os-mappings) 25 | - [Usage](#usage-1) 26 | - [Short Example](#short-example) 27 | - [Full Example #1](#full-example--1) 28 | - [Full Example #2](#full-example--2) 29 | + [Generic Tabular Parsing](#generic-tabular-parsing) 30 | - [How Tabular Parsing Works](#how-tabular-parsing-works) 31 | - [Preparing to Use the Tabular Parser](#preparing-to-use-the-tabular-parser) 32 | - [Calling the Tabular Parser in a Playbook](#calling-the-tabular-parser-in-a-playbook) 33 | - [Full Example #1](#full-example--1-1) 34 | + [Development](#development) 35 | + [Testing](#testing) 36 | + [Pushing](#pushing) 37 | 38 | ## What's in this Collection? 39 | 40 | ### Learn Genie Ansible Module 41 | 42 | This is an Ansible module that allows you to `learn` a feature on a device in your Ansible playbook. 43 | This is the equivalent of running `genie learn ` from the Genie CLI tool. 44 | Using Cisco's Genie libraries, this will connect to the device, run a series of commands, and return 45 | a data structure that conforms to an OS-agnostic data model, meaning that you could run the `learn_genie` 46 | module against an IOS, NXOS, IOS-XR, or IOS-XE device for a given feature, and the data returned will be 47 | in the same format. This allows for much more simple automation logic as the data structures are 48 | identical regardless of device OS. 49 | 50 | The second part of this module allows you to, again, `learn` a feature, but then also compare it 51 | against a previous run. For example, if you have a playbook, you could learn a feature, make some 52 | device configuration changes, and then the final task of the playbook, you could learn the feature again 53 | and compare the two using `genie diff`. 54 | 55 | ### Parse Genie Ansible Filter Plugin 56 | 57 | This is an Ansible filter plugin that can take raw CLI output and return structured data. This is the 58 | same filter plugin located [here](https://galaxy.ansible.com/clay584/parse_genie). Since Ansible has 59 | created Ansible Collections, this has been added to this collection. The other project will remain unchanged for 60 | backward-compatibility, but no further updates will be done to that project. All future work on `parse_genie` 61 | will be in this collection. 62 | 63 | ## Prerequisites 64 | 65 | This collection will require the following on the Ansible control machine: 66 | 67 | - Python 3.4+ 68 | - Ansible 2.9+ 69 | - pyATS 70 | - Genie 71 | - colorama 72 | 73 | ## Installation 74 | 75 | Please follow these instructions to ensure that the filter plugin will function with your playbooks: 76 | 77 | 1. Create a directory for your playbook and go into it. 78 | - `mkdir my_playbook && cd my_playbook` 79 | 2. Create a virtual environment. 80 | - `python3 -m venv .venv` 81 | 3. Activate the virtual environment. 82 | - `source .venv/bin/activate` 83 | 4. Install the required Python packages. 84 | - `pip install ansible pyats genie colorama` 85 | 5. Deactivate and reactivate the Python virtual environment. 86 | - `deactivate && source .venv/bin/activate` 87 | 6. Install Ansible Collection 88 | - `ansible-galaxy collection install clay584.genie` 89 | 90 | 91 | # Usage 92 | 93 | ## Learn Genie Module 94 | 95 | This module is very similar to invoking the `genie learn` and `genie diff` CLI tool. In your Ansible 96 | playbook, you would call the module `learn_genie` and pass in parameters as described in the following 97 | section. The returned data from Genie is returned, and when using this module in your playbooks, it is 98 | appropriate the `register` the output from the task in your playbook. That registered output can then be 99 | used in subsequent tasks or plays. Also, you can use the registered output in later runs of the `learn_genie` 100 | module in order to take advantage of the `genie diff` functionality. 101 | 102 | #### A Note About Ansible Interpreter 103 | 104 | There are some silly Ansible settings regarding localhost settings and `ansible_python_interpreter` 105 | and what you have to do in order to use your virtual environment on localhost when `connection: local`. 106 | 107 | You will have to set the `ansible_python_interpreter` to the current playbook interpreter, either via 108 | the ansible.cfg file or via the inventory file. 109 | 110 | Here is an example: 111 | 112 | ``` 113 | all: 114 | hosts: 115 | sbx-nxos-mgmt.cisco.com: 116 | ansible_connection: local 117 | ansible_python_interpreter: "{{ ansible_playbook_python }}" 118 | ``` 119 | 120 | 121 | [Here](https://docs.ansible.com/ansible/latest/inventory/implicit_localhost.html) is the documentation 122 | around this setting. 123 | 124 | #### Module Parameters 125 | 126 | ![Learn Genie Parameters](https://github.com/clay584/genie_collection/raw/master/clay584/genie/learn_genie_params.png) 127 | 128 | #### Examples 129 | 130 | ``` 131 | --- 132 | 133 | - hosts: all 134 | gather_facts: false 135 | connection: local 136 | collections: 137 | - clay584.genie 138 | tasks: 139 | - name: Learn Genie - ARP 140 | learn_genie: 141 | host: "{{ ansible_host }}" 142 | port: 8181 143 | protocol: ssh 144 | username: admin 145 | password: Admin_1234! 146 | os: nxos 147 | feature: arp 148 | register: genie_arp1 149 | 150 | - name: Debug Genie 151 | debug: 152 | msg: "{{ genie_arp1 }}" 153 | ``` 154 | 155 | The above playbook would yield the following: 156 | 157 | ``` 158 | $ ansible-playbook -i inventory test.yml 159 | 160 | PLAY [all] ****************************************************************** 161 | 162 | TASK [Learn Genie - ARP] **************************************************** 163 | ok: [sbx-nxos-mgmt.cisco.com] 164 | 165 | TASK [Debug Genie] ********************************************************** 166 | ok: [sbx-nxos-mgmt.cisco.com] => 167 | msg: 168 | changed: false 169 | failed: false 170 | genie: 171 | arp: 172 | interfaces: 173 | Ethernet1/5: 174 | arp_dynamic_learning: 175 | local_proxy_enable: false 176 | proxy_enable: false 177 | Vlan100: 178 | arp_dynamic_learning: 179 | local_proxy_enable: false 180 | proxy_enable: false 181 | Vlan101: 182 | arp_dynamic_learning: 183 | local_proxy_enable: false 184 | proxy_enable: false 185 | Vlan102: 186 | arp_dynamic_learning: 187 | local_proxy_enable: false 188 | proxy_enable: false 189 | Vlan103: 190 | arp_dynamic_learning: 191 | local_proxy_enable: false 192 | proxy_enable: false 193 | Vlan104: 194 | arp_dynamic_learning: 195 | local_proxy_enable: false 196 | proxy_enable: false 197 | Vlan105: 198 | arp_dynamic_learning: 199 | local_proxy_enable: false 200 | proxy_enable: false 201 | loopback1: 202 | arp_dynamic_learning: 203 | local_proxy_enable: false 204 | proxy_enable: false 205 | mgmt0: 206 | arp_dynamic_learning: 207 | local_proxy_enable: false 208 | proxy_enable: false 209 | statistics: 210 | entries_total: 0 211 | in_drops: 3387630 212 | in_replies_pkts: 153 213 | in_requests_pkts: 3387463 214 | in_total: 0 215 | incomplete_total: 0 216 | out_drops: 0 217 | out_gratuitous_pkts: 14 218 | out_replies_pkts: 0 219 | out_requests_pkts: 14 220 | out_total: 28 221 | 222 | PLAY RECAP ****************************************************************** 223 | sbx-nxos-mgmt.cisco.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 224 | 225 | ``` 226 | 227 | Here is an example of using the diff functionality... 228 | 229 | ``` 230 | --- 231 | 232 | - hosts: all 233 | gather_facts: false 234 | connection: local 235 | tasks: 236 | - name: Learn Genie 1st Run 237 | learn_genie: 238 | host: "{{ ansible_host }}" 239 | port: 8181 240 | protocol: ssh 241 | username: admin 242 | password: Admin_1234! 243 | os: nxos 244 | feature: ospf 245 | register: genie1 246 | 247 | - name: Learn Genie with Diff 248 | learn_genie: 249 | host: "{{ ansible_host }}" 250 | port: 8181 251 | protocol: ssh 252 | username: admin 253 | password: Admin_1234! 254 | os: nxos 255 | feature: ospf 256 | compare_to: "{{ genie1 }}" 257 | diff: true 258 | ``` 259 | 260 | The above play outputs the following: 261 | 262 | ``` 263 | $ ansible-playbook -i inventory test.yml 264 | 265 | PLAY [all] ****************************************************************** 266 | 267 | TASK [Learn Genie - 1st Run] ************************************************ 268 | ok: [sbx-nxos-mgmt.cisco.com] 269 | 270 | ... truncated - made some OSPF changes ... 271 | 272 | TASK [Learn Genie with Diff] ************************************************ 273 | vrf: 274 | default: 275 | address_family: 276 | ipv4: 277 | instance: 278 | 1: 279 | + router_id: 172.16.1.0 280 | - router_id: 172.16.0.1 281 | changed: [sbx-nxos-mgmt.cisco.com] 282 | 283 | PLAY RECAP ****************************************************************** 284 | sbx-nxos-mgmt.cisco.com : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 285 | 286 | ``` 287 | 288 | If the module parameter `colors` is not set to `false` and `colorama` is installed, the diff output 289 | will be colored. 290 | 291 | ![Colored Diff Output](https://github.com/clay584/genie_collection/raw/master/clay584/genie/colored_diff.png) 292 | 293 | ## Parse Genie Filter Plugin 294 | 295 | **ATTENTION!!! - If you run into an issue with a command failing to parse, it is possible that there is a bug in the parsing library which is maintained by Cisco. For those issues, you can open an issue [here](https://github.com/CiscoTestAutomation/genieparser/issues).** 296 | 297 | The network genie filter takes unstructured network CLI command output from all 298 | Cisco network operating systems, and outputs structured data. While similar to other 299 | network CLI parsers already available (parse_cli, parse_cli_textfsm), this parser is 300 | powered by a very mature and robust library written by Cisco called Genie (and underlying framework pyATS). 301 | This provides over 1200 parsers that transform configuration and CLI 302 | output to structured data that is normalized and conforms to standard, OS-agnostic data models. 303 | 304 | The Genie library can also serve as an engine to parse tabular and non-tabular free-form text 305 | using much less code than traditional parsing requires. Therefore, it can be used to 306 | parse any vendor output; not just that of Cisco devices. However, that would involve writing custom parsers. 307 | This release does not include the functionality to utilize custom parsers. The supported parsers are whatever 308 | is included in the release of Genie that the user has installed on the Ansible control machine. 309 | 310 | The list of supported operating systems and commands, as well 311 | as the data's schema definitions (data models) which describe exactly what fields and 312 | data types will be returned for any given command, is available from Cisco at the link below. 313 | 314 | https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers 315 | 316 | ### Ansible to Genie OS Mappings 317 | 318 | Below are the mappings from Ansible's `ansible_network_os` to Genie's `os`: 319 | 320 | | Ansible Network OS | Genie OS | 321 | | ------------------- | ------------- | 322 | | ios | ios, iosxe | 323 | | nxos | nxos | 324 | | iosxr | iosxr | 325 | | junos | junos | 326 | 327 | If you are working with IOS or IOS-XE there is ambiguity in that Ansible considers IOS and IOS-XE 328 | the same and therefore the `ansible_network_os = ios`, but Genie needs to know specifically if it is 329 | IOS or IOS-XE in order to parse the CLI output correctly. If you pass `ansible_network_os` to this filter plugin, 330 | and it is equal to `ios`, parse_genie will try to parse it with Genie using `os=ios` first, and if that fails, it will 331 | then try to parse it with `os=iosxe`. 332 | 333 | So keep that in mind when creating your playbooks. It may be best to pass the real OS to the parse_genie. 334 | You can do that by keeping another inventory variable or host_var to specify the Genie OS for each network device 335 | and using that variable as the OS for the parse_genie. 336 | 337 | #### Usage 338 | 339 | Make sure to read in the parse_genie role before you attempt to use it later in your playbook. 340 | 341 | ...trunctated... 342 | 343 | tasks: 344 | - name: Read in parse_genie role 345 | collections: 346 | - clay584.genie 347 | 348 | ...trunctated... 349 | 350 | **There is a stupid bug in Ansible, which the Ansible maintainers have listed as "by design", 351 | whereby modules and such can be referenced by their short-name, but filter plugins must be reference 352 | by their fully qualified name. So, when using the parse_genie filter plugin, you must use it in the following way.** 353 | 354 | ``` 355 | "{{ show_cli_output | clay584.genie.parse_genie(command='show version', os='iosxe') }}" 356 | ``` 357 | 358 | #### Short Example 359 | To convert the output of a network device CLI command, use the `parse_genie` filter as shown in this example 360 | (do not use abbreviated CLI commands). 361 | 362 | Converting CLI output of the `show version` command from a Cisco IOS-XE device to structured data:: 363 | 364 | {{ cli_output | clay584.genie.parse_genie(command='show version', os='iosxe') }} 365 | 366 | For deeper abstraction, you might want to add `platform` to `parse_parse`. 367 | 368 | {{ cli_output | clay584.genie.parse_genie(command='show environment all', os='iosxe', platform='asr1k') }} 369 | 370 | The above example would yield the following: 371 | 372 | { 373 | "version": { 374 | "chassis": "CSR1000V", 375 | "chassis_sn": "9TKUWGKX5MO", 376 | "curr_config_register": "0x2102", 377 | "disks": { 378 | "bootflash:.": { 379 | "disk_size": "7774207", 380 | "type_of_disk": "virtual hard disk" 381 | }, 382 | "webui:.": { 383 | "disk_size": "0", 384 | "type_of_disk": "WebUI ODM Files" 385 | } 386 | }, 387 | "hostname": "host-172-16-1-96", 388 | "image_id": "X86_64_LINUX_IOSD-UNIVERSALK9-M", 389 | "image_type": "production image", 390 | "last_reload_reason": "Reload Command", 391 | "license_level": "ax", 392 | "license_type": "Default. No valid license found.", 393 | "main_mem": "1126522", 394 | "mem_size": { 395 | "non-volatile configuration": "32768", 396 | "physical": "3018840" 397 | }, 398 | "next_reload_license_level": "ax", 399 | "number_of_intfs": { 400 | "Gigabit Ethernet": "2" 401 | }, 402 | "os": "IOS-XE", 403 | "platform": "Virtual XE", 404 | "processor_type": "VXE", 405 | "rom": "IOS-XE ROMMON", 406 | "rtr_type": "CSR1000V", 407 | "system_image": "bootflash:packages.conf", 408 | "uptime": "2 minutes", 409 | "uptime_this_cp": "3 minutes", 410 | "version": "16.5.1b,", 411 | "version_short": "16.5" 412 | } 413 | } 414 | 415 | #### Full Example #1 416 | 417 | Playbook: 418 | 419 | --- 420 | 421 | - hosts: localhost 422 | connection: local 423 | collections: 424 | - clay584.genie 425 | vars: 426 | show_version_output: | 427 | Cisco IOS XE Software, Version 16.05.01b 428 | Cisco IOS Software [Everest], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.5.1b, RELEASE SOFTWARE (fc1) 429 | Technical Support: http://www.cisco.com/techsupport 430 | Copyright (c) 1986-2017 by Cisco Systems, Inc. 431 | Compiled Tue 11-Apr-17 16:41 by mcpre 432 | 433 | 434 | Cisco IOS-XE software, Copyright (c) 2005-2017 by cisco Systems, Inc. 435 | All rights reserved. Certain components of Cisco IOS-XE software are 436 | licensed under the GNU General Public License ("GPL") Version 2.0. The 437 | software code licensed under GPL Version 2.0 is free software that comes 438 | with ABSOLUTELY NO WARRANTY. You can redistribute and/or modify such 439 | GPL code under the terms of GPL Version 2.0. For more details, see the 440 | documentation or "License Notice" file accompanying the IOS-XE software, 441 | or the applicable URL provided on the flyer accompanying the IOS-XE 442 | software. 443 | 444 | 445 | ROM: IOS-XE ROMMON 446 | 447 | host-172-16-1-96 uptime is 2 minutes 448 | Uptime for this control processor is 3 minutes 449 | System returned to ROM by reload 450 | System image file is "bootflash:packages.conf" 451 | Last reload reason: Reload Command 452 | 453 | 454 | 455 | This product contains cryptographic features and is subject to United 456 | States and local country laws governing import, export, transfer and 457 | use. Delivery of Cisco cryptographic products does not imply 458 | third-party authority to import, export, distribute or use encryption. 459 | Importers, exporters, distributors and users are responsible for 460 | compliance with U.S. and local country laws. By using this product you 461 | agree to comply with applicable laws and regulations. If you are unable 462 | to comply with U.S. and local laws, return this product immediately. 463 | 464 | A summary of U.S. laws governing Cisco cryptographic products may be found at: 465 | http://www.cisco.com/wwl/export/crypto/tool/stqrg.html 466 | 467 | If you require further assistance please contact us by sending email to 468 | export@cisco.com. 469 | 470 | License Level: ax 471 | License Type: Default. No valid license found. 472 | Next reload license Level: ax 473 | 474 | cisco CSR1000V (VXE) processor (revision VXE) with 1126522K/3075K bytes of memory. 475 | Processor board ID 9TKUWGKX5MO 476 | 2 Gigabit Ethernet interfaces 477 | 32768K bytes of non-volatile configuration memory. 478 | 3018840K bytes of physical memory. 479 | 7774207K bytes of virtual hard disk at bootflash:. 480 | 0K bytes of WebUI ODM Files at webui:. 481 | 482 | Configuration register is 0x2102 483 | 484 | - name: Debug Genie Filter 485 | debug: 486 | msg: "{{ show_version_output | clay584.genie.parse_genie(command='show version', os='iosxe') }}" 487 | delegate_to: localhost 488 | 489 | 490 | Output: 491 | 492 | $ ansible-playbook -i inventory debug.yml 493 | 494 | PLAY [localhost] ************************************************************************* 495 | 496 | TASK [Gathering Facts] ******************************************************************* 497 | ok: [localhost] 498 | 499 | TASK [Debug Genie Filter] **************************************************************** 500 | ok: [localhost -> localhost] => { 501 | "msg": { 502 | "version": { 503 | "chassis": "CSR1000V", 504 | "chassis_sn": "9TKUWGKX5MO", 505 | "curr_config_register": "0x2102", 506 | "disks": { 507 | "bootflash:.": { 508 | "disk_size": "7774207", 509 | "type_of_disk": "virtual hard disk" 510 | }, 511 | "webui:.": { 512 | "disk_size": "0", 513 | "type_of_disk": "WebUI ODM Files" 514 | } 515 | }, 516 | "hostname": "host-172-16-1-96", 517 | "image_id": "X86_64_LINUX_IOSD-UNIVERSALK9-M", 518 | "image_type": "production image", 519 | "last_reload_reason": "Reload Command", 520 | "license_level": "ax", 521 | "license_type": "Default. No valid license found.", 522 | "main_mem": "1126522", 523 | "mem_size": { 524 | "non-volatile configuration": "32768", 525 | "physical": "3018840" 526 | }, 527 | "next_reload_license_level": "ax", 528 | "number_of_intfs": { 529 | "Gigabit Ethernet": "2" 530 | }, 531 | "os": "IOS-XE", 532 | "platform": "Virtual XE", 533 | "processor_type": "VXE", 534 | "rom": "IOS-XE ROMMON", 535 | "rtr_type": "CSR1000V", 536 | "system_image": "bootflash:packages.conf", 537 | "uptime": "2 minutes", 538 | "uptime_this_cp": "3 minutes", 539 | "version": "16.5.1b,", 540 | "version_short": "16.5" 541 | } 542 | } 543 | } 544 | 545 | 546 | #### Full Example #2 547 | 548 | Playbook: 549 | 550 | --- 551 | 552 | - hosts: csr1000v 553 | gather_facts: False 554 | collections: 555 | - clay584.genie 556 | tasks: 557 | - name: Get Data From Device 558 | ios_command: 559 | commands: show arp vrf Mgmt-intf 560 | register: arp_output 561 | 562 | - name: Print Structured Data 563 | debug: 564 | msg: "{{ arp_output['stdout'][0] | clay584.genie.parse_genie(command='show arp vrf Mgmt-intf', os='iosxe') }}" 565 | delegate_to: localhost 566 | 567 | Output: 568 | 569 | $ ansible-playbook -i inventory playbook.yml 570 | 571 | PLAY [csr1000v] ************************************************************************** 572 | 573 | TASK [Get Data From Device] ************************************************************** 574 | ok: [csr1000v] 575 | 576 | TASK [Print Structured Data] ************************************************************* 577 | ok: [csr1000v -> localhost] => { 578 | "msg": { 579 | "interfaces": { 580 | "GigabitEthernet1": { 581 | "ipv4": { 582 | "neighbors": { 583 | "172.16.1.111": { 584 | "age": "0", 585 | "ip": "172.16.1.111", 586 | "link_layer_address": "5e00.4004.0000", 587 | "origin": "dynamic", 588 | "protocol": "Internet", 589 | "type": "ARPA" 590 | }, 591 | "172.16.1.114": { 592 | "age": "-", 593 | "ip": "172.16.1.114", 594 | "link_layer_address": "5e00.4001.0000", 595 | "origin": "static", 596 | "protocol": "Internet", 597 | "type": "ARPA" 598 | } 599 | } 600 | } 601 | } 602 | } 603 | } 604 | } 605 | 606 | ### Generic Tabular Parsing 607 | 608 | Cisco Genie has support for 1200 commands and counting, but for those show commands where there is not 609 | a parser that has been built by Cisco, there is the generic tabular parsing functionality. For more 610 | information on the Genie tabular parsing functionality, see their [oper_fill_tabular](https://pubhub.devnetcloud.com/media/pyats-packages/docs/parsergen/tabular/tabular.html) documentation. 611 | 612 | #### How Tabular Parsing Works 613 | 614 | In order to parse a command output when there is a parser that has been built, all that is required is the `command`, `command ouput`, and `os`. 615 | But if there is not a parser built, you must specify some additional information to help the parser determine how 616 | to parse the command output. This additional data is two-fold: 617 | 618 | 1. Headers - The column headers as shown in the command's output. 619 | 2. Index - The key of the dictionary items that the parser will return. 620 | 621 | Consider the following example: 622 | 623 | 1. Command: `show ip sla summary` 624 | 2. Command Output: 625 | 626 | ``` 627 | IPSLAs Latest Operation Summary 628 | Codes: * active, ^ inactive, ~ pending 629 | All Stats are in milliseconds. Stats with u are in microseconds 630 | 631 | ID Type Destination Stats Return Last 632 | Code Run 633 | ------------------------------------------------------------------------------------------------ 634 | *1 udp-jitter 10.0.0.2 RTT=900u OK 20 seconds ago 635 | *2 icmp-echo 10.0.0.2 RTT=1 OK 3 seconds ago 636 | ``` 637 | 3. Headers - `ID`, `Type`, `Destination`, `Stats`, `Return Code`, and `Last Run`. 638 | 4. Index - We want to use the `ID` column as the index for this data when we get it back from the parser. 639 | 5. Parser Output: 640 | 641 | ``` 642 | {'*1': {'Destination ': '10.0.0.2', 643 | 'ID ': '*1', 644 | 'Last Run': '20 seconds ago', 645 | 'Return Code': 'OK', 646 | 'Stats ': 'RTT=900u', 647 | 'Type ': 'udp-jitter'}, 648 | '*2': {'Destination ': '10.0.0.2', 649 | 'ID ': '*2', 650 | 'Last Run': '3 seconds ago', 651 | 'Return Code': 'OK', 652 | 'Stats ': 'RTT=1', 653 | 'Type ': 'icmp-echo'}} 654 | 655 | ``` 656 | 657 | #### Preparing to Use the Tabular Parser 658 | 659 | In order to use this tabular parser we must first construct the `headers` and `index` for a given command on 660 | a given OS in a format that can be read into an Ansible playbook, and subsequently fed into the parse_genie filter plugin. 661 | 662 | In order to do this, you must create a vars file in your playbook that is in the following format. It is 663 | organized by OS, then by command. Then under each command, the headers and index are defined. You can 664 | define as many commands as you like for each network OS as long as it is within this data structure. 665 | 666 | ``` 667 | parse_genie: 668 | ios: 669 | "show ip sla summary": 670 | headers: 671 | - - ID 672 | - Type 673 | - Destination 674 | - Stats 675 | - Return 676 | - Last 677 | - - '' 678 | - '' 679 | - '' 680 | - '' 681 | - Code 682 | - Run 683 | index: 684 | - 0 685 | iosxe: 686 | "show ip sla summary": 687 | headers: 688 | - - ID 689 | - Type 690 | - Destination 691 | - Stats 692 | - Return 693 | - Last 694 | - - '' 695 | - '' 696 | - '' 697 | - '' 698 | - Code 699 | - Run 700 | index: 701 | - 1 702 | 703 | ``` 704 | 705 | The python equivalent of the above yaml format is: 706 | 707 | ``` 708 | python_dict = { 709 | "parse_genie": { 710 | "ios": { 711 | "show ip sla summary": { 712 | "headers": [ 713 | [ 714 | "ID", 715 | "Type", 716 | "Destination", 717 | "Stats", 718 | "Return", 719 | "Last" 720 | ], 721 | [ 722 | "", 723 | "", 724 | "", 725 | "", 726 | "Code", 727 | "Run" 728 | ] 729 | ], 730 | "index": [ 731 | 0 732 | ] 733 | } 734 | }, 735 | "iosxe": { 736 | "show ip sla summary": { 737 | "headers": [ 738 | [ 739 | "ID", 740 | "Type", 741 | "Destination", 742 | "Stats", 743 | "Return", 744 | "Last" 745 | ], 746 | [ 747 | "", 748 | "", 749 | "", 750 | "", 751 | "Code", 752 | "Run" 753 | ] 754 | ], 755 | "index": [ 756 | 1 757 | ] 758 | } 759 | } 760 | } 761 | } 762 | ``` 763 | 764 | #### Calling the Tabular Parser in a Playbook 765 | 766 | Now that we have defined a generic tabular command and its headers and index, we can actually call 767 | it from a playbook. 768 | 769 | First, we read in the vars file that contains the tabular command parsing metadata. 770 | 771 | ``` 772 | - name: Include vars file with generic command metadata 773 | include_vars: 774 | file: parse_genie_generic_commands.yml 775 | name: parse_genie 776 | ``` 777 | 778 | Next, we pass the command output to `parse_genie` but with a couple of extra parameters. 779 | 780 | ``` 781 | - name: Parse generic tabular command output 782 | debug: 783 | msg: "{{ command_output | parse_genie(command='show ip sla summary', os='ios', generic_tabular=True, generic_tabular_metadata=parse_genie) }}" 784 | delegate_to: localhost 785 | ``` 786 | 787 | The resulting parsed output will show as follows: 788 | 789 | ``` 790 | ok: [localhost -> localhost] => { 791 | "msg": { 792 | "*1": { 793 | "Destination ": "10.0.0.2", 794 | "ID ": "*1", 795 | "Last Run": "20 seconds ago", 796 | "Return Code": "OK", 797 | "Stats ": "RTT=900u", 798 | "Type ": "udp-jitter" 799 | }, 800 | "*2": { 801 | "Destination ": "10.0.0.2", 802 | "ID ": "*2", 803 | "Last Run": "3 seconds ago", 804 | "Return Code": "OK", 805 | "Stats ": "RTT=1", 806 | "Type ": "icmp-echo" 807 | } 808 | } 809 | } 810 | ``` 811 | 812 | #### Full Example #1 813 | 814 | Playbook: 815 | 816 | ``` 817 | 818 | --- 819 | 820 | - hosts: localhost 821 | connection: local 822 | collections: 823 | - clay584.genie 824 | vars: 825 | out_ios_sla: | 826 | IPSLAs Latest Operation Summary 827 | Codes: * active, ^ inactive, ~ pending 828 | All Stats are in milliseconds. Stats with u are in microseconds 829 | 830 | ID Type Destination Stats Return Last 831 | Code Run 832 | ------------------------------------------------------------------------------------------------ 833 | *1 udp-jitter 10.0.0.2 RTT=900u OK 20 seconds ago 834 | *2 icmp-echo 10.0.0.2 RTT=1 OK 3 seconds ago 835 | 836 | tasks: 837 | - name: Include vars file that has generic tabular command metadata 838 | include_vars: 839 | file: parse_genie_generic_commands.yml 840 | name: parse_genie 841 | 842 | - name: Test Genie Filter for generic tabular data 843 | debug: 844 | msg: "{{ out_ios_sla | clay584.genie.parse_genie(command='test show ip sla summary', os='ios', generic_tabular=True, generic_tabular_metadata=parse_genie) }}" 845 | delegate_to: localhost 846 | 847 | ``` 848 | 849 | `parse_genie_generic_commands.yml` contents: 850 | 851 | ``` 852 | 853 | --- 854 | 855 | parse_genie: 856 | ios: 857 | "test show ip sla summary": 858 | headers: 859 | - - ID 860 | - Type 861 | - Destination 862 | - Stats 863 | - Return 864 | - Last 865 | - - '' 866 | - '' 867 | - '' 868 | - '' 869 | - Code 870 | - Run 871 | index: 872 | - 0 873 | iosxe: 874 | "test show ip sla summary": 875 | headers: 876 | - - ID 877 | - Type 878 | - Destination 879 | - Stats 880 | - Return 881 | - Last 882 | - - '' 883 | - '' 884 | - '' 885 | - '' 886 | - Code 887 | - Run 888 | index: 889 | - 1 890 | 891 | ``` 892 | 893 | Playbook Output: 894 | 895 | ``` 896 | 897 | PLAY [localhost] ****************************************************************************************************************************************************************************************************************************************************************** 898 | 899 | TASK [Gathering Facts] ************************************************************************************************************************************************************************************************************************************************************ 900 | ok: [localhost] 901 | 902 | TASK [Include Parse Genie Role] *************************************************************************************************************************************************************************************************************************************************** 903 | 904 | TASK [Include vars] *************************************************************************************************************************************************************************************************************************************************************** 905 | ok: [localhost] 906 | 907 | TASK [Test Genie Filter for generic tabular data] ********************************************************************************************************************************************************************************************************************************* 908 | ok: [localhost -> localhost] => { 909 | "msg": { 910 | "*1": { 911 | "Destination ": "10.0.0.2", 912 | "ID ": "*1", 913 | "Last Run": "20 seconds ago", 914 | "Return Code": "OK", 915 | "Stats ": "RTT=900u", 916 | "Type ": "udp-jitter" 917 | }, 918 | "*2": { 919 | "Destination ": "10.0.0.2", 920 | "ID ": "*2", 921 | "Last Run": "3 seconds ago", 922 | "Return Code": "OK", 923 | "Stats ": "RTT=1", 924 | "Type ": "icmp-echo" 925 | } 926 | } 927 | } 928 | 929 | PLAY RECAP ************************************************************************************************************************************************************************************************************************************************************************ 930 | localhost : ok=3 changed=0 unreachable=0 failed=0 931 | 932 | ``` 933 | 934 | 935 | 936 | ### Development 937 | 938 | Set up your development environment: 939 | 940 | 1. Clone the repo and go into it. `git clone https://github.com/clay584/parse_genie.git && cd parse_genie` 941 | 2. Create a virtual environment. `python3 -m venv .venv` 942 | 3. Activate the virtual environment. `source .venv/bin/activate` 943 | 4. Install Ansible. `pip install ansible` 944 | 5. Install Genie and pyATS. `pip install genie` 945 | 6. Install yamllint. `pip install yamllint` 946 | 947 | ### Testing 948 | 949 | Run these commands to test locally: 950 | 951 | 1. Lint all of the YAML files. `yamllint -c yamllint_config.yml *` 952 | 2. Run the test playbook. `ansible-playbook tests/test.yml --connection=local -i tests/inventory` 953 | 954 | ### Pushing 955 | 956 | Ansible Galaxy works on tags. 957 | 958 | 1. `git commit -m"whatever'` 959 | 2. `git tag -a X.X.X` - where X.X.X is a symantec versioning number. 960 | 3. `git push origin master` 961 | 4. `git push X.X.X` 962 | -------------------------------------------------------------------------------- /clay584/genie/README.md: -------------------------------------------------------------------------------- 1 | # Cisco Genie Ansible Collection (Unofficial) 2 | 3 | [![Published](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-published.svg)](https://developer.cisco.com/codeexchange/github/repo/clay584/genie_collection) 4 | ![Ansible Version](https://img.shields.io/badge/ansible-%3E%3D2.8-blue.svg) 5 | ![Python Version](https://img.shields.io/badge/python-%3E%3D3.4-blue.svg) 6 | 7 | This is an Ansible collection that brings the functionality of Cisco's pyATS and Genie 8 | libraries to Ansible users. 9 | 10 | For more information on the Cisco pyATS project, see [https://developer.cisco.com/docs/pyats/ 11 | ](https://developer.cisco.com/docs/pyats/) 12 | 13 | - [Cisco Genie Ansible Collection (Unofficial)](#cisco-genie-ansible-collection--unofficial-) 14 | * [What's in this Collection?](#what-s-in-this-collection-) 15 | + [Learn Genie Ansible Module](#learn-genie-ansible-module) 16 | + [Parse Genie Ansible Filter Plugin](#parse-genie-ansible-filter-plugin) 17 | * [Prerequisites](#prerequisites) 18 | * [Installation](#installation) 19 | - [Usage](#usage) 20 | * [Learn Genie Module](#learn-genie-module) 21 | - [Module Parameters](#module-parameters) 22 | - [Examples](#examples) 23 | * [Parse Genie Filter Plugin](#parse-genie-filter-plugin) 24 | + [Ansible to Genie OS Mappings](#ansible-to-genie-os-mappings) 25 | - [Usage](#usage-1) 26 | - [Short Example](#short-example) 27 | - [Full Example #1](#full-example--1) 28 | - [Full Example #2](#full-example--2) 29 | + [Generic Tabular Parsing](#generic-tabular-parsing) 30 | - [How Tabular Parsing Works](#how-tabular-parsing-works) 31 | - [Preparing to Use the Tabular Parser](#preparing-to-use-the-tabular-parser) 32 | - [Calling the Tabular Parser in a Playbook](#calling-the-tabular-parser-in-a-playbook) 33 | - [Full Example #1](#full-example--1-1) 34 | + [Development](#development) 35 | + [Testing](#testing) 36 | + [Pushing](#pushing) 37 | 38 | ## What's in this Collection? 39 | 40 | ### Learn Genie Ansible Module 41 | 42 | This is an Ansible module that allows you to `learn` a feature on a device in your Ansible playbook. 43 | This is the equivalent of running `genie learn ` from the Genie CLI tool. 44 | Using Cisco's Genie libraries, this will connect to the device, run a series of commands, and return 45 | a data structure that conforms to an OS-agnostic data model, meaning that you could run the `learn_genie` 46 | module against an IOS, NXOS, IOS-XR, or IOS-XE device for a given feature, and the data returned will be 47 | in the same format. This allows for much more simple automation logic as the data structures are 48 | identical regardless of device OS. 49 | 50 | The second part of this module allows you to, again, `learn` a feature, but then also compare it 51 | against a previous run. For example, if you have a playbook, you could learn a feature, make some 52 | device configuration changes, and then the final task of the playbook, you could learn the feature again 53 | and compare the two using `genie diff`. 54 | 55 | ### Parse Genie Ansible Filter Plugin 56 | 57 | This is an Ansible filter plugin that can take raw CLI output and return structured data. This is the 58 | same filter plugin located [here](https://galaxy.ansible.com/clay584/parse_genie). Since Ansible has 59 | created Ansible Collections, this has been added to this collection. The other project will remain unchanged for 60 | backward-compatibility, but no further updates will be done to that project. All future work on `parse_genie` 61 | will be in this collection. 62 | 63 | ## Prerequisites 64 | 65 | This collection will require the following on the Ansible control machine: 66 | 67 | - Python 3.4+ 68 | - Ansible 2.9+ 69 | - pyATS 70 | - Genie 71 | - colorama 72 | 73 | ## Installation 74 | 75 | Please follow these instructions to ensure that the filter plugin will function with your playbooks: 76 | 77 | 1. Create a directory for your playbook and go into it. 78 | - `mkdir my_playbook && cd my_playbook` 79 | 2. Create a virtual environment. 80 | - `python3 -m venv .venv` 81 | 3. Activate the virtual environment. 82 | - `source .venv/bin/activate` 83 | 4. Install the required Python packages. 84 | - `pip install ansible pyats genie colorama` 85 | 5. Deactivate and reactivate the Python virtual environment. 86 | - `deactivate && source .venv/bin/activate` 87 | 6. Install Ansible Collection 88 | - `ansible-galaxy collection install clay584.genie` 89 | 90 | 91 | # Usage 92 | 93 | ## Learn Genie Module 94 | 95 | This module is very similar to invoking the `genie learn` and `genie diff` CLI tool. In your Ansible 96 | playbook, you would call the module `learn_genie` and pass in parameters as described in the following 97 | section. The returned data from Genie is returned, and when using this module in your playbooks, it is 98 | appropriate the `register` the output from the task in your playbook. That registered output can then be 99 | used in subsequent tasks or plays. Also, you can use the registered output in later runs of the `learn_genie` 100 | module in order to take advantage of the `genie diff` functionality. 101 | 102 | #### A Note About Ansible Interpreter 103 | 104 | There are some silly Ansible settings regarding localhost settings and `ansible_python_interpreter` 105 | and what you have to do in order to use your virtual environment on localhost when `connection: local`. 106 | 107 | You will have to set the `ansible_python_interpreter` to the current playbook interpreter, either via 108 | the ansible.cfg file or via the inventory file. 109 | 110 | Here is an example: 111 | 112 | ``` 113 | all: 114 | hosts: 115 | sbx-nxos-mgmt.cisco.com: 116 | ansible_connection: local 117 | ansible_python_interpreter: "{{ ansible_playbook_python }}" 118 | ``` 119 | 120 | 121 | [Here](https://docs.ansible.com/ansible/latest/inventory/implicit_localhost.html) is the documentation 122 | around this setting. 123 | 124 | #### Module Parameters 125 | 126 | ![Learn Genie Parameters](https://github.com/clay584/genie_collection/raw/master/clay584/genie/learn_genie_params.png) 127 | 128 | #### Examples 129 | 130 | ``` 131 | --- 132 | 133 | - hosts: all 134 | gather_facts: false 135 | connection: local 136 | collections: 137 | - clay584.genie 138 | tasks: 139 | - name: Learn Genie - ARP 140 | learn_genie: 141 | host: "{{ ansible_host }}" 142 | port: 8181 143 | protocol: ssh 144 | username: admin 145 | password: Admin_1234! 146 | os: nxos 147 | feature: arp 148 | register: genie_arp1 149 | 150 | - name: Debug Genie 151 | debug: 152 | msg: "{{ genie_arp1 }}" 153 | ``` 154 | 155 | The above playbook would yield the following: 156 | 157 | ``` 158 | $ ansible-playbook -i inventory test.yml 159 | 160 | PLAY [all] ****************************************************************** 161 | 162 | TASK [Learn Genie - ARP] **************************************************** 163 | ok: [sbx-nxos-mgmt.cisco.com] 164 | 165 | TASK [Debug Genie] ********************************************************** 166 | ok: [sbx-nxos-mgmt.cisco.com] => 167 | msg: 168 | changed: false 169 | failed: false 170 | genie: 171 | arp: 172 | interfaces: 173 | Ethernet1/5: 174 | arp_dynamic_learning: 175 | local_proxy_enable: false 176 | proxy_enable: false 177 | Vlan100: 178 | arp_dynamic_learning: 179 | local_proxy_enable: false 180 | proxy_enable: false 181 | Vlan101: 182 | arp_dynamic_learning: 183 | local_proxy_enable: false 184 | proxy_enable: false 185 | Vlan102: 186 | arp_dynamic_learning: 187 | local_proxy_enable: false 188 | proxy_enable: false 189 | Vlan103: 190 | arp_dynamic_learning: 191 | local_proxy_enable: false 192 | proxy_enable: false 193 | Vlan104: 194 | arp_dynamic_learning: 195 | local_proxy_enable: false 196 | proxy_enable: false 197 | Vlan105: 198 | arp_dynamic_learning: 199 | local_proxy_enable: false 200 | proxy_enable: false 201 | loopback1: 202 | arp_dynamic_learning: 203 | local_proxy_enable: false 204 | proxy_enable: false 205 | mgmt0: 206 | arp_dynamic_learning: 207 | local_proxy_enable: false 208 | proxy_enable: false 209 | statistics: 210 | entries_total: 0 211 | in_drops: 3387630 212 | in_replies_pkts: 153 213 | in_requests_pkts: 3387463 214 | in_total: 0 215 | incomplete_total: 0 216 | out_drops: 0 217 | out_gratuitous_pkts: 14 218 | out_replies_pkts: 0 219 | out_requests_pkts: 14 220 | out_total: 28 221 | 222 | PLAY RECAP ****************************************************************** 223 | sbx-nxos-mgmt.cisco.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 224 | 225 | ``` 226 | 227 | Here is an example of using the diff functionality... 228 | 229 | ``` 230 | --- 231 | 232 | - hosts: all 233 | gather_facts: false 234 | connection: local 235 | tasks: 236 | - name: Learn Genie 1st Run 237 | learn_genie: 238 | host: "{{ ansible_host }}" 239 | port: 8181 240 | protocol: ssh 241 | username: admin 242 | password: Admin_1234! 243 | os: nxos 244 | feature: ospf 245 | register: genie1 246 | 247 | - name: Learn Genie with Diff 248 | learn_genie: 249 | host: "{{ ansible_host }}" 250 | port: 8181 251 | protocol: ssh 252 | username: admin 253 | password: Admin_1234! 254 | os: nxos 255 | feature: ospf 256 | compare_to: "{{ genie1 }}" 257 | diff: true 258 | ``` 259 | 260 | The above play outputs the following: 261 | 262 | ``` 263 | $ ansible-playbook -i inventory test.yml 264 | 265 | PLAY [all] ****************************************************************** 266 | 267 | TASK [Learn Genie - 1st Run] ************************************************ 268 | ok: [sbx-nxos-mgmt.cisco.com] 269 | 270 | ... truncated - made some OSPF changes ... 271 | 272 | TASK [Learn Genie with Diff] ************************************************ 273 | vrf: 274 | default: 275 | address_family: 276 | ipv4: 277 | instance: 278 | 1: 279 | + router_id: 172.16.1.0 280 | - router_id: 172.16.0.1 281 | changed: [sbx-nxos-mgmt.cisco.com] 282 | 283 | PLAY RECAP ****************************************************************** 284 | sbx-nxos-mgmt.cisco.com : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 285 | 286 | ``` 287 | 288 | If the module parameter `colors` is not set to `false` and `colorama` is installed, the diff output 289 | will be colored. 290 | 291 | ![Colored Diff Output](https://github.com/clay584/genie_collection/raw/master/clay584/genie/colored_diff.png) 292 | 293 | ## Parse Genie Filter Plugin 294 | 295 | **ATTENTION!!! - If you run into an issue with a command failing to parse, it is possible that there is a bug in the parsing library which is maintained by Cisco. For those issues, you can open an issue [here](https://github.com/CiscoTestAutomation/genieparser/issues).** 296 | 297 | The network genie filter takes unstructured network CLI command output from all 298 | Cisco network operating systems, and outputs structured data. While similar to other 299 | network CLI parsers already available (parse_cli, parse_cli_textfsm), this parser is 300 | powered by a very mature and robust library written by Cisco called Genie (and underlying framework pyATS). 301 | This provides over 1200 parsers that transform configuration and CLI 302 | output to structured data that is normalized and conforms to standard, OS-agnostic data models. 303 | 304 | The Genie library can also serve as an engine to parse tabular and non-tabular free-form text 305 | using much less code than traditional parsing requires. Therefore, it can be used to 306 | parse any vendor output; not just that of Cisco devices. However, that would involve writing custom parsers. 307 | This release does not include the functionality to utilize custom parsers. The supported parsers are whatever 308 | is included in the release of Genie that the user has installed on the Ansible control machine. 309 | 310 | The list of supported operating systems and commands, as well 311 | as the data's schema definitions (data models) which describe exactly what fields and 312 | data types will be returned for any given command, is available from Cisco at the link below. 313 | 314 | https://pubhub.devnetcloud.com/media/pyats-packages/docs/genie/genie_libs/#/parsers 315 | 316 | ### Ansible to Genie OS Mappings 317 | 318 | Below are the mappings from Ansible's `ansible_network_os` to Genie's `os`: 319 | 320 | | Ansible Network OS | Genie OS | 321 | | ------------------- | ------------- | 322 | | ios | ios, iosxe | 323 | | nxos | nxos | 324 | | iosxr | iosxr | 325 | | junos | junos | 326 | 327 | If you are working with IOS or IOS-XE there is ambiguity in that Ansible considers IOS and IOS-XE 328 | the same and therefore the `ansible_network_os = ios`, but Genie needs to know specifically if it is 329 | IOS or IOS-XE in order to parse the CLI output correctly. If you pass `ansible_network_os` to this filter plugin, 330 | and it is equal to `ios`, parse_genie will try to parse it with Genie using `os=ios` first, and if that fails, it will 331 | then try to parse it with `os=iosxe`. 332 | 333 | So keep that in mind when creating your playbooks. It may be best to pass the real OS to the parse_genie. 334 | You can do that by keeping another inventory variable or host_var to specify the Genie OS for each network device 335 | and using that variable as the OS for the parse_genie. 336 | 337 | #### Usage 338 | 339 | Make sure to read in the parse_genie role before you attempt to use it later in your playbook. 340 | 341 | ...trunctated... 342 | 343 | tasks: 344 | - name: Read in parse_genie role 345 | collections: 346 | - clay584.genie 347 | 348 | ...trunctated... 349 | 350 | **There is a stupid bug in Ansible, which the Ansible maintainers have listed as "by design", 351 | whereby modules and such can be referenced by their short-name, but filter plugins must be reference 352 | by their fully qualified name. So, when using the parse_genie filter plugin, you must use it in the following way.** 353 | 354 | ``` 355 | "{{ show_cli_output | clay584.genie.parse_genie(command='show version', os='iosxe') }}" 356 | ``` 357 | 358 | For deeper abstraction, you might want to add `platform` to `parse_genie`. 359 | 360 | ``` 361 | "{{ show_cli_output | clay584.genie.parse_genie(command='show environment all', os='iosxe', platform='asr1k') }}" 362 | ``` 363 | 364 | #### Short Example 365 | To convert the output of a network device CLI command, use the `parse_genie` filter as shown in this example 366 | (do not use abbreviated CLI commands). 367 | 368 | Converting CLI output of the `show version` command from a Cisco IOS-XE device to structured data:: 369 | 370 | {{ cli_output | clay584.genie.parse_genie(command='show version', os='iosxe') }} 371 | 372 | The above example would yield the following: 373 | 374 | { 375 | "version": { 376 | "chassis": "CSR1000V", 377 | "chassis_sn": "9TKUWGKX5MO", 378 | "curr_config_register": "0x2102", 379 | "disks": { 380 | "bootflash:.": { 381 | "disk_size": "7774207", 382 | "type_of_disk": "virtual hard disk" 383 | }, 384 | "webui:.": { 385 | "disk_size": "0", 386 | "type_of_disk": "WebUI ODM Files" 387 | } 388 | }, 389 | "hostname": "host-172-16-1-96", 390 | "image_id": "X86_64_LINUX_IOSD-UNIVERSALK9-M", 391 | "image_type": "production image", 392 | "last_reload_reason": "Reload Command", 393 | "license_level": "ax", 394 | "license_type": "Default. No valid license found.", 395 | "main_mem": "1126522", 396 | "mem_size": { 397 | "non-volatile configuration": "32768", 398 | "physical": "3018840" 399 | }, 400 | "next_reload_license_level": "ax", 401 | "number_of_intfs": { 402 | "Gigabit Ethernet": "2" 403 | }, 404 | "os": "IOS-XE", 405 | "platform": "Virtual XE", 406 | "processor_type": "VXE", 407 | "rom": "IOS-XE ROMMON", 408 | "rtr_type": "CSR1000V", 409 | "system_image": "bootflash:packages.conf", 410 | "uptime": "2 minutes", 411 | "uptime_this_cp": "3 minutes", 412 | "version": "16.5.1b,", 413 | "version_short": "16.5" 414 | } 415 | } 416 | 417 | #### Full Example #1 418 | 419 | Playbook: 420 | 421 | --- 422 | 423 | - hosts: localhost 424 | connection: local 425 | collections: 426 | - clay584.genie 427 | vars: 428 | show_version_output: | 429 | Cisco IOS XE Software, Version 16.05.01b 430 | Cisco IOS Software [Everest], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.5.1b, RELEASE SOFTWARE (fc1) 431 | Technical Support: http://www.cisco.com/techsupport 432 | Copyright (c) 1986-2017 by Cisco Systems, Inc. 433 | Compiled Tue 11-Apr-17 16:41 by mcpre 434 | 435 | 436 | Cisco IOS-XE software, Copyright (c) 2005-2017 by cisco Systems, Inc. 437 | All rights reserved. Certain components of Cisco IOS-XE software are 438 | licensed under the GNU General Public License ("GPL") Version 2.0. The 439 | software code licensed under GPL Version 2.0 is free software that comes 440 | with ABSOLUTELY NO WARRANTY. You can redistribute and/or modify such 441 | GPL code under the terms of GPL Version 2.0. For more details, see the 442 | documentation or "License Notice" file accompanying the IOS-XE software, 443 | or the applicable URL provided on the flyer accompanying the IOS-XE 444 | software. 445 | 446 | 447 | ROM: IOS-XE ROMMON 448 | 449 | host-172-16-1-96 uptime is 2 minutes 450 | Uptime for this control processor is 3 minutes 451 | System returned to ROM by reload 452 | System image file is "bootflash:packages.conf" 453 | Last reload reason: Reload Command 454 | 455 | 456 | 457 | This product contains cryptographic features and is subject to United 458 | States and local country laws governing import, export, transfer and 459 | use. Delivery of Cisco cryptographic products does not imply 460 | third-party authority to import, export, distribute or use encryption. 461 | Importers, exporters, distributors and users are responsible for 462 | compliance with U.S. and local country laws. By using this product you 463 | agree to comply with applicable laws and regulations. If you are unable 464 | to comply with U.S. and local laws, return this product immediately. 465 | 466 | A summary of U.S. laws governing Cisco cryptographic products may be found at: 467 | http://www.cisco.com/wwl/export/crypto/tool/stqrg.html 468 | 469 | If you require further assistance please contact us by sending email to 470 | export@cisco.com. 471 | 472 | License Level: ax 473 | License Type: Default. No valid license found. 474 | Next reload license Level: ax 475 | 476 | cisco CSR1000V (VXE) processor (revision VXE) with 1126522K/3075K bytes of memory. 477 | Processor board ID 9TKUWGKX5MO 478 | 2 Gigabit Ethernet interfaces 479 | 32768K bytes of non-volatile configuration memory. 480 | 3018840K bytes of physical memory. 481 | 7774207K bytes of virtual hard disk at bootflash:. 482 | 0K bytes of WebUI ODM Files at webui:. 483 | 484 | Configuration register is 0x2102 485 | 486 | - name: Debug Genie Filter 487 | debug: 488 | msg: "{{ show_version_output | clay584.genie.parse_genie(command='show version', os='iosxe') }}" 489 | delegate_to: localhost 490 | 491 | 492 | Output: 493 | 494 | $ ansible-playbook -i inventory debug.yml 495 | 496 | PLAY [localhost] ************************************************************************* 497 | 498 | TASK [Gathering Facts] ******************************************************************* 499 | ok: [localhost] 500 | 501 | TASK [Debug Genie Filter] **************************************************************** 502 | ok: [localhost -> localhost] => { 503 | "msg": { 504 | "version": { 505 | "chassis": "CSR1000V", 506 | "chassis_sn": "9TKUWGKX5MO", 507 | "curr_config_register": "0x2102", 508 | "disks": { 509 | "bootflash:.": { 510 | "disk_size": "7774207", 511 | "type_of_disk": "virtual hard disk" 512 | }, 513 | "webui:.": { 514 | "disk_size": "0", 515 | "type_of_disk": "WebUI ODM Files" 516 | } 517 | }, 518 | "hostname": "host-172-16-1-96", 519 | "image_id": "X86_64_LINUX_IOSD-UNIVERSALK9-M", 520 | "image_type": "production image", 521 | "last_reload_reason": "Reload Command", 522 | "license_level": "ax", 523 | "license_type": "Default. No valid license found.", 524 | "main_mem": "1126522", 525 | "mem_size": { 526 | "non-volatile configuration": "32768", 527 | "physical": "3018840" 528 | }, 529 | "next_reload_license_level": "ax", 530 | "number_of_intfs": { 531 | "Gigabit Ethernet": "2" 532 | }, 533 | "os": "IOS-XE", 534 | "platform": "Virtual XE", 535 | "processor_type": "VXE", 536 | "rom": "IOS-XE ROMMON", 537 | "rtr_type": "CSR1000V", 538 | "system_image": "bootflash:packages.conf", 539 | "uptime": "2 minutes", 540 | "uptime_this_cp": "3 minutes", 541 | "version": "16.5.1b,", 542 | "version_short": "16.5" 543 | } 544 | } 545 | } 546 | 547 | 548 | #### Full Example #2 549 | 550 | Playbook: 551 | 552 | --- 553 | 554 | - hosts: csr1000v 555 | gather_facts: False 556 | collections: 557 | - clay584.genie 558 | tasks: 559 | - name: Get Data From Device 560 | ios_command: 561 | commands: show arp vrf Mgmt-intf 562 | register: arp_output 563 | 564 | - name: Print Structured Data 565 | debug: 566 | msg: "{{ arp_output['stdout'][0] | clay584.genie.parse_genie(command='show arp vrf Mgmt-intf', os='iosxe') }}" 567 | delegate_to: localhost 568 | 569 | Output: 570 | 571 | $ ansible-playbook -i inventory playbook.yml 572 | 573 | PLAY [csr1000v] ************************************************************************** 574 | 575 | TASK [Get Data From Device] ************************************************************** 576 | ok: [csr1000v] 577 | 578 | TASK [Print Structured Data] ************************************************************* 579 | ok: [csr1000v -> localhost] => { 580 | "msg": { 581 | "interfaces": { 582 | "GigabitEthernet1": { 583 | "ipv4": { 584 | "neighbors": { 585 | "172.16.1.111": { 586 | "age": "0", 587 | "ip": "172.16.1.111", 588 | "link_layer_address": "5e00.4004.0000", 589 | "origin": "dynamic", 590 | "protocol": "Internet", 591 | "type": "ARPA" 592 | }, 593 | "172.16.1.114": { 594 | "age": "-", 595 | "ip": "172.16.1.114", 596 | "link_layer_address": "5e00.4001.0000", 597 | "origin": "static", 598 | "protocol": "Internet", 599 | "type": "ARPA" 600 | } 601 | } 602 | } 603 | } 604 | } 605 | } 606 | } 607 | 608 | ### Generic Tabular Parsing 609 | 610 | Cisco Genie has support for 1200 commands and counting, but for those show commands where there is not 611 | a parser that has been built by Cisco, there is the generic tabular parsing functionality. For more 612 | information on the Genie tabular parsing functionality, see their [oper_fill_tabular](https://pubhub.devnetcloud.com/media/pyats-packages/docs/parsergen/tabular/tabular.html) documentation. 613 | 614 | #### How Tabular Parsing Works 615 | 616 | In order to parse a command output when there is a parser that has been built, all that is required is the `command`, `command ouput`, and `os`. 617 | But if there is not a parser built, you must specify some additional information to help the parser determine how 618 | to parse the command output. This additional data is two-fold: 619 | 620 | 1. Headers - The column headers as shown in the command's output. 621 | 2. Index - The key of the dictionary items that the parser will return. 622 | 623 | Consider the following example: 624 | 625 | 1. Command: `show ip sla summary` 626 | 2. Command Output: 627 | 628 | ``` 629 | IPSLAs Latest Operation Summary 630 | Codes: * active, ^ inactive, ~ pending 631 | All Stats are in milliseconds. Stats with u are in microseconds 632 | 633 | ID Type Destination Stats Return Last 634 | Code Run 635 | ------------------------------------------------------------------------------------------------ 636 | *1 udp-jitter 10.0.0.2 RTT=900u OK 20 seconds ago 637 | *2 icmp-echo 10.0.0.2 RTT=1 OK 3 seconds ago 638 | ``` 639 | 3. Headers - `ID`, `Type`, `Destination`, `Stats`, `Return Code`, and `Last Run`. 640 | 4. Index - We want to use the `ID` column as the index for this data when we get it back from the parser. 641 | 5. Parser Output: 642 | 643 | ``` 644 | {'*1': {'Destination ': '10.0.0.2', 645 | 'ID ': '*1', 646 | 'Last Run': '20 seconds ago', 647 | 'Return Code': 'OK', 648 | 'Stats ': 'RTT=900u', 649 | 'Type ': 'udp-jitter'}, 650 | '*2': {'Destination ': '10.0.0.2', 651 | 'ID ': '*2', 652 | 'Last Run': '3 seconds ago', 653 | 'Return Code': 'OK', 654 | 'Stats ': 'RTT=1', 655 | 'Type ': 'icmp-echo'}} 656 | 657 | ``` 658 | 659 | #### Preparing to Use the Tabular Parser 660 | 661 | In order to use this tabular parser we must first construct the `headers` and `index` for a given command on 662 | a given OS in a format that can be read into an Ansible playbook, and subsequently fed into the parse_genie filter plugin. 663 | 664 | In order to do this, you must create a vars file in your playbook that is in the following format. It is 665 | organized by OS, then by command. Then under each command, the headers and index are defined. You can 666 | define as many commands as you like for each network OS as long as it is within this data structure. 667 | 668 | ``` 669 | parse_genie: 670 | ios: 671 | "show ip sla summary": 672 | headers: 673 | - - ID 674 | - Type 675 | - Destination 676 | - Stats 677 | - Return 678 | - Last 679 | - - '' 680 | - '' 681 | - '' 682 | - '' 683 | - Code 684 | - Run 685 | index: 686 | - 0 687 | iosxe: 688 | "show ip sla summary": 689 | headers: 690 | - - ID 691 | - Type 692 | - Destination 693 | - Stats 694 | - Return 695 | - Last 696 | - - '' 697 | - '' 698 | - '' 699 | - '' 700 | - Code 701 | - Run 702 | index: 703 | - 1 704 | 705 | ``` 706 | 707 | The python equivalent of the above yaml format is: 708 | 709 | ``` 710 | python_dict = { 711 | "parse_genie": { 712 | "ios": { 713 | "show ip sla summary": { 714 | "headers": [ 715 | [ 716 | "ID", 717 | "Type", 718 | "Destination", 719 | "Stats", 720 | "Return", 721 | "Last" 722 | ], 723 | [ 724 | "", 725 | "", 726 | "", 727 | "", 728 | "Code", 729 | "Run" 730 | ] 731 | ], 732 | "index": [ 733 | 0 734 | ] 735 | } 736 | }, 737 | "iosxe": { 738 | "show ip sla summary": { 739 | "headers": [ 740 | [ 741 | "ID", 742 | "Type", 743 | "Destination", 744 | "Stats", 745 | "Return", 746 | "Last" 747 | ], 748 | [ 749 | "", 750 | "", 751 | "", 752 | "", 753 | "Code", 754 | "Run" 755 | ] 756 | ], 757 | "index": [ 758 | 1 759 | ] 760 | } 761 | } 762 | } 763 | } 764 | ``` 765 | 766 | #### Calling the Tabular Parser in a Playbook 767 | 768 | Now that we have defined a generic tabular command and its headers and index, we can actually call 769 | it from a playbook. 770 | 771 | First, we read in the vars file that contains the tabular command parsing metadata. 772 | 773 | ``` 774 | - name: Include vars file with generic command metadata 775 | include_vars: 776 | file: parse_genie_generic_commands.yml 777 | name: parse_genie 778 | ``` 779 | 780 | Next, we pass the command output to `parse_genie` but with a couple of extra parameters. 781 | 782 | ``` 783 | - name: Parse generic tabular command output 784 | debug: 785 | msg: "{{ command_output | parse_genie(command='show ip sla summary', os='ios', generic_tabular=True, generic_tabular_metadata=parse_genie) }}" 786 | delegate_to: localhost 787 | ``` 788 | 789 | The resulting parsed output will show as follows: 790 | 791 | ``` 792 | ok: [localhost -> localhost] => { 793 | "msg": { 794 | "*1": { 795 | "Destination ": "10.0.0.2", 796 | "ID ": "*1", 797 | "Last Run": "20 seconds ago", 798 | "Return Code": "OK", 799 | "Stats ": "RTT=900u", 800 | "Type ": "udp-jitter" 801 | }, 802 | "*2": { 803 | "Destination ": "10.0.0.2", 804 | "ID ": "*2", 805 | "Last Run": "3 seconds ago", 806 | "Return Code": "OK", 807 | "Stats ": "RTT=1", 808 | "Type ": "icmp-echo" 809 | } 810 | } 811 | } 812 | ``` 813 | 814 | #### Full Example #1 815 | 816 | Playbook: 817 | 818 | ``` 819 | 820 | --- 821 | 822 | - hosts: localhost 823 | connection: local 824 | collections: 825 | - clay584.genie 826 | vars: 827 | out_ios_sla: | 828 | IPSLAs Latest Operation Summary 829 | Codes: * active, ^ inactive, ~ pending 830 | All Stats are in milliseconds. Stats with u are in microseconds 831 | 832 | ID Type Destination Stats Return Last 833 | Code Run 834 | ------------------------------------------------------------------------------------------------ 835 | *1 udp-jitter 10.0.0.2 RTT=900u OK 20 seconds ago 836 | *2 icmp-echo 10.0.0.2 RTT=1 OK 3 seconds ago 837 | 838 | tasks: 839 | - name: Include vars file that has generic tabular command metadata 840 | include_vars: 841 | file: parse_genie_generic_commands.yml 842 | name: parse_genie 843 | 844 | - name: Test Genie Filter for generic tabular data 845 | debug: 846 | msg: "{{ out_ios_sla | clay584.genie.parse_genie(command='test show ip sla summary', os='ios', generic_tabular=True, generic_tabular_metadata=parse_genie) }}" 847 | delegate_to: localhost 848 | 849 | ``` 850 | 851 | `parse_genie_generic_commands.yml` contents: 852 | 853 | ``` 854 | 855 | --- 856 | 857 | parse_genie: 858 | ios: 859 | "test show ip sla summary": 860 | headers: 861 | - - ID 862 | - Type 863 | - Destination 864 | - Stats 865 | - Return 866 | - Last 867 | - - '' 868 | - '' 869 | - '' 870 | - '' 871 | - Code 872 | - Run 873 | index: 874 | - 0 875 | iosxe: 876 | "test show ip sla summary": 877 | headers: 878 | - - ID 879 | - Type 880 | - Destination 881 | - Stats 882 | - Return 883 | - Last 884 | - - '' 885 | - '' 886 | - '' 887 | - '' 888 | - Code 889 | - Run 890 | index: 891 | - 1 892 | 893 | ``` 894 | 895 | Playbook Output: 896 | 897 | ``` 898 | 899 | PLAY [localhost] ****************************************************************************************************************************************************************************************************************************************************************** 900 | 901 | TASK [Gathering Facts] ************************************************************************************************************************************************************************************************************************************************************ 902 | ok: [localhost] 903 | 904 | TASK [Include Parse Genie Role] *************************************************************************************************************************************************************************************************************************************************** 905 | 906 | TASK [Include vars] *************************************************************************************************************************************************************************************************************************************************************** 907 | ok: [localhost] 908 | 909 | TASK [Test Genie Filter for generic tabular data] ********************************************************************************************************************************************************************************************************************************* 910 | ok: [localhost -> localhost] => { 911 | "msg": { 912 | "*1": { 913 | "Destination ": "10.0.0.2", 914 | "ID ": "*1", 915 | "Last Run": "20 seconds ago", 916 | "Return Code": "OK", 917 | "Stats ": "RTT=900u", 918 | "Type ": "udp-jitter" 919 | }, 920 | "*2": { 921 | "Destination ": "10.0.0.2", 922 | "ID ": "*2", 923 | "Last Run": "3 seconds ago", 924 | "Return Code": "OK", 925 | "Stats ": "RTT=1", 926 | "Type ": "icmp-echo" 927 | } 928 | } 929 | } 930 | 931 | PLAY RECAP ************************************************************************************************************************************************************************************************************************************************************************ 932 | localhost : ok=3 changed=0 unreachable=0 failed=0 933 | 934 | ``` 935 | 936 | 937 | 938 | ### Development 939 | 940 | Set up your development environment: 941 | 942 | 1. Clone the repo and go into it. `git clone https://github.com/clay584/parse_genie.git && cd parse_genie` 943 | 2. Create a virtual environment. `python3 -m venv .venv` 944 | 3. Activate the virtual environment. `source .venv/bin/activate` 945 | 4. Install Ansible. `pip install ansible` 946 | 5. Install Genie and pyATS. `pip install genie` 947 | 6. Install yamllint. `pip install yamllint` 948 | 949 | ### Testing 950 | 951 | Run these commands to test locally: 952 | 953 | 1. Lint all of the YAML files. `yamllint -c yamllint_config.yml *` 954 | 2. Run the test playbook. `ansible-playbook tests/test.yml --connection=local -i tests/inventory` 955 | 956 | ### Pushing 957 | 958 | Ansible Galaxy works on tags. 959 | 960 | 1. `git commit -m"whatever'` 961 | 2. `git tag -a X.X.X` - where X.X.X is a symantec versioning number. 962 | 3. `git push origin master` 963 | 4. `git push X.X.X` 964 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "fd04544ec67fddaecf030b6f1b9e387499688d5447dc76a9184e664ba7777444" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiohttp": { 20 | "hashes": [ 21 | "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", 22 | "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", 23 | "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", 24 | "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", 25 | "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", 26 | "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", 27 | "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", 28 | "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", 29 | "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", 30 | "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", 31 | "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", 32 | "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" 33 | ], 34 | "version": "==3.6.2" 35 | }, 36 | "ansible": { 37 | "hashes": [ 38 | "sha256:2517bf4743d52f00d509396a41e9ce44e5bc1285bd7aa53dfe28ea02fc1a75a6" 39 | ], 40 | "index": "pypi", 41 | "version": "==2.9.4" 42 | }, 43 | "async-timeout": { 44 | "hashes": [ 45 | "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", 46 | "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" 47 | ], 48 | "version": "==3.0.1" 49 | }, 50 | "attrs": { 51 | "hashes": [ 52 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 53 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 54 | ], 55 | "version": "==19.3.0" 56 | }, 57 | "backports.ssl": { 58 | "hashes": [ 59 | "sha256:079549f44a5d52e6b6c97455cc322725e0590772237d6dc362cda57635128e1d" 60 | ], 61 | "version": "==0.0.9" 62 | }, 63 | "certifi": { 64 | "hashes": [ 65 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 66 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 67 | ], 68 | "version": "==2019.11.28" 69 | }, 70 | "cffi": { 71 | "hashes": [ 72 | "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", 73 | "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", 74 | "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", 75 | "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", 76 | "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", 77 | "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", 78 | "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", 79 | "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", 80 | "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", 81 | "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", 82 | "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", 83 | "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", 84 | "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", 85 | "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", 86 | "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", 87 | "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", 88 | "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", 89 | "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", 90 | "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", 91 | "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", 92 | "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", 93 | "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", 94 | "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", 95 | "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", 96 | "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", 97 | "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", 98 | "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", 99 | "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", 100 | "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", 101 | "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", 102 | "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", 103 | "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", 104 | "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" 105 | ], 106 | "version": "==1.13.2" 107 | }, 108 | "chardet": { 109 | "hashes": [ 110 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 111 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 112 | ], 113 | "version": "==3.0.4" 114 | }, 115 | "cryptography": { 116 | "hashes": [ 117 | "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", 118 | "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", 119 | "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", 120 | "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", 121 | "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", 122 | "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", 123 | "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", 124 | "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", 125 | "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", 126 | "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", 127 | "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", 128 | "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", 129 | "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", 130 | "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", 131 | "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", 132 | "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", 133 | "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", 134 | "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", 135 | "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", 136 | "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", 137 | "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8" 138 | ], 139 | "version": "==2.8" 140 | }, 141 | "dill": { 142 | "hashes": [ 143 | "sha256:42d8ef819367516592a825746a18073ced42ca169ab1f5f4044134703e7a049c" 144 | ], 145 | "version": "==0.3.1.1" 146 | }, 147 | "distro": { 148 | "hashes": [ 149 | "sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57", 150 | "sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4" 151 | ], 152 | "version": "==1.4.0" 153 | }, 154 | "genie": { 155 | "hashes": [ 156 | "sha256:022a0355a9f4f64d8e011a12cf6c18c3ad0ad26e5987e99da93f27fe3b58f11c", 157 | "sha256:045c787cb194d76f6f8ac92f921fe01179253bfed235df9bf2c8b47a95db206c", 158 | "sha256:3cf29ad3cc9223a9b78e274f8ac645085fd1a831dc43184c85f52ac36394acfd", 159 | "sha256:4f6fc7673d0ab829d573e6ea5e901cc52075408f7ba3473d1135f8bf1e4ff30a", 160 | "sha256:8f1e489de23e76dc4f17e2f82c811ad3f4fde9d9adc3b4bbfa0092adbdd9692f", 161 | "sha256:b0bcf8b9739f678e0327431f126940cd0ae0f4dc05cbc9b2dea4e09dc3aa8f56", 162 | "sha256:b5b854997f9d2cffea8a1e7b36ca26d7bf24395a553b20b7ad7bf426238aad2d", 163 | "sha256:b8d2454f470fcddfa3f48576bbc91ad3cf8bed1d8c82a9197344acf97cf10760", 164 | "sha256:bb0f4960218ab8a3c68ec71112470ee24d5af3b7d096d9dc57fb46f397c636e6", 165 | "sha256:bd7e9427b05c3025efef8cc3eb6dc1c3bca5b13ef5c87ae8cdc7eaf5935cd685", 166 | "sha256:e9f0f3d1523898adfa19679ceb84064e5ad8c07ed06f9bcb2735c1965a1575cf", 167 | "sha256:ef5bb54e7fb8a4aa65f105ef14dc79bd7987a935d4a6271e227b2d3811e4610f" 168 | ], 169 | "index": "pypi", 170 | "version": "==19.12" 171 | }, 172 | "genie.abstract": { 173 | "hashes": [ 174 | "sha256:05478b451af90ae2083ce805fd9848d20bbcdfa30aa46b6ad59fd6b64e5b1d8d", 175 | "sha256:0b2a33fdb34a84e607c12676c729c405b0694a348ef2954edf94c734a32b0bea", 176 | "sha256:28109e4eb761601156e32ba919cef60425069bd5840386900e514b69b76d9af9", 177 | "sha256:4055910e07ad1d3cf0a569d0501501d9c30a70797fa21bdb7b8ca0af34c7beba", 178 | "sha256:547f6018cd864f033a6aeb93444e5b7c6402f2999a337e668a96c3f103fb8681", 179 | "sha256:6df3dd2a4cce73418042ce6d141f3cb2a3cde51a3de4d5657a4e16c2874034a5", 180 | "sha256:6f02870b99dd2b7e8075685d8aab19415570e6ede8391a08cb915140ee5bc190", 181 | "sha256:ac68358759d7401bddb2e6afa3cb1f1496f834953250fedcf0999f3ac8c34a24", 182 | "sha256:ba01706162ef5361dbc1c7c33d0cd95fb9c030179d1b2c3ed847e013ff4e6bb0", 183 | "sha256:cffc85437779015200267fd74326e71bcfe426d0c379098b4a2caf3249032364", 184 | "sha256:d88d245e05ff9ae11f4f85c2de233a0da44dfec19f676496120b884c4a856de2", 185 | "sha256:f814082b43142d9e2b7bc48e9c60771e278c640370760c223aad80db299aa672" 186 | ], 187 | "version": "==19.12" 188 | }, 189 | "genie.conf": { 190 | "hashes": [ 191 | "sha256:0cbb4828e989abe11c5ec6e474fa2a13ea9e4e918416a7dfe0343258dd8441fe", 192 | "sha256:0df960f5bad8b18288dcb044c0f1ebeb8d6f65bee979c716f52d1050e3e4ff85", 193 | "sha256:1c0028d3f7765df757b74cc24d5ca67cf9a6998cf721a6e8040b11c4b035c0fd", 194 | "sha256:44f901b7d04b744867d2eb67150fc78dafdf8718de0eed4bf309d6e8accd3d8f", 195 | "sha256:5941badc28b3b9d6ec5f84674d238e530ec08be4a5fd7634b12da4f8745fa8b8", 196 | "sha256:6e07307389ddef7e61371534f0baf64b1a3eb701b9657532cf4b6e476b7f97a0", 197 | "sha256:a07119fda1402f409c86a834badf2339f616dc95fc7362fb83c52589b7d9012e", 198 | "sha256:c38ed4d5bdca68d3b9e16202c65e310afbd95a442a33ae7541c622d06c814ffd", 199 | "sha256:cc8ad7f8059ce4108c5a2947b1918d93cb12b8e5d57235621fb231d71d6c1e2e", 200 | "sha256:dd56339f384d52c6e71959e0efd763a94e9ddf8527acb1745f6e6846f7787d6a", 201 | "sha256:f8163c6a19cd85e318ef9c2ec474d4f77740fd0da5987789ce4839b73a70b75b", 202 | "sha256:fd8bd222170cbe5e8f76a9a83c2d996195c4607c2b460b5701090e0b6526ef63" 203 | ], 204 | "version": "==19.12" 205 | }, 206 | "genie.harness": { 207 | "hashes": [ 208 | "sha256:22758a7333bba591077b6de86f9e9ee7c81ed321139abd2d5abb7c7f7ca9ef4f", 209 | "sha256:2805916326e0508958f0c9c9a7afd51f926f411dd527e2cc80a49049bffb8980", 210 | "sha256:2fc96bdf9a170c112fa930dcb4bebbcba3aa5196bb01ed9763c8b7adeee9dd0f", 211 | "sha256:31d200e2ef3cd80de831c03b6256029f6beac29d98e78eadd9df6d72319484c0", 212 | "sha256:60b1b13a2fa919f7b6b915c43a7e47baa8e48bac2cfd5bb55128fc1b988feb1d", 213 | "sha256:6dcc1b0fe5291edb4ca39d404cf8fdc7e4fe013b4fce3a402fb406be56795cad", 214 | "sha256:737ed7255b0c0da252bbafae5a5751462239b1ee92de7195f560c6a93efb6c77", 215 | "sha256:77a13d1cc6726c080c72059c040df58d3a981a9048c6138ebca8cae09450719e", 216 | "sha256:a171030bca12354a6049a13c6e091d34e78ac342f640f2ee2e26c4a7949244f2", 217 | "sha256:c67cf90dfe9b9812875fd9b2ba5ac90d268147c84cd39ad0fc4bac1a490ad8e4", 218 | "sha256:de9cda7954d84965f26fa86e103d3230e40832df32aa0ae60b12f470742056fb", 219 | "sha256:ea3439a2aab1d9a2d12223627c8a2b88cb1d31121e54a9ab9c4dd3964c00bac9" 220 | ], 221 | "version": "==19.12" 222 | }, 223 | "genie.libs.conf": { 224 | "hashes": [ 225 | "sha256:53e904431dd9ada0965eea7ebfec2e6b54009eb144c7197cf1a37f380d29a7ee" 226 | ], 227 | "version": "==19.12" 228 | }, 229 | "genie.libs.filetransferutils": { 230 | "hashes": [ 231 | "sha256:bb41da1881fd7d5bc906138b818fa92512e6403157eea5779adb57d7c63c4feb" 232 | ], 233 | "version": "==19.12" 234 | }, 235 | "genie.libs.ops": { 236 | "hashes": [ 237 | "sha256:d68227547515fc1db7c1382771f04773f05c8b5966f144a6791f3f9f6c9f1ead" 238 | ], 239 | "version": "==19.12" 240 | }, 241 | "genie.libs.parser": { 242 | "hashes": [ 243 | "sha256:787b40fe9d3a620c39ef9250b45c2f5f12fb9e6e4926c991bd59685725afdc07" 244 | ], 245 | "version": "==19.12" 246 | }, 247 | "genie.libs.sdk": { 248 | "hashes": [ 249 | "sha256:19144911322a395368b53354326473616b7a418ce991bf15be6756e9ea951fc0" 250 | ], 251 | "version": "==19.12" 252 | }, 253 | "genie.libs.telemetry": { 254 | "hashes": [ 255 | "sha256:4327020f6542e615614190eec8c106ad03cbb6bb0f74b941f9bb3e0cc0adc790" 256 | ], 257 | "version": "==19.12" 258 | }, 259 | "genie.metaparser": { 260 | "hashes": [ 261 | "sha256:07550995b0f88a3959cb57220bd89b659b5b85c26d7ccedcb10ed40632dad5c5", 262 | "sha256:0e98bac738d097350540281ee21b0f0ae9dd76129ac35e8fe49a464f5a741955", 263 | "sha256:43ac6c609b2b5a3a2876f7a5b3408d95c3add895f56357b51bc42ab8f96ce431", 264 | "sha256:452d3a05156921c5d9aa2c891a8beff9c613cdfc066cc851194ac1864577e0c1", 265 | "sha256:996cbf1ed22899c897eb863092e47ca10ddc8eec51130540c57d15b7b42279a5", 266 | "sha256:b2baec73cd8c99b9fb1ae0e4fe7a7204298c4164a94125600ac60d6b9677d735", 267 | "sha256:b41fc769f46ed4ddf465fdc1ae93dc77cb86654085224c08d3f372a8732338cd", 268 | "sha256:bccf0bd44a156fcc4c07921552c648a7822bcf68016aaa565e8d8902878d6d0e", 269 | "sha256:bed02f46b6ed3bf5f9fbce784d698a8215f86beba381734609469033fa8788eb", 270 | "sha256:d26ed8a737009c4fd7cfabff4e0d78f02b9ae27ccdc801fc9e8c91ca4d2cc51c", 271 | "sha256:e392672c06a5fc8c9b6f64a12607843b62aae47e049a4215f0545e3aabbbbbbf", 272 | "sha256:f2fdd8b398df2bef772f9d90f39805a76ff9f23c5d37962896451c0282f920ed" 273 | ], 274 | "version": "==19.12" 275 | }, 276 | "genie.ops": { 277 | "hashes": [ 278 | "sha256:03834cae3b94468f69945f045106cb45cbbd940e84c3d6a172cb23751430ab51", 279 | "sha256:17985a06027193ae8ec4a74c0254867204c626a825163d2bd7d63d34a7c24882", 280 | "sha256:225d2c3ad7ea737a0277bb6e7aeccaae99a9367809e0845976cc9a0cdd92d926", 281 | "sha256:2640626e0196d3de0fc6145cc32176e64b8780911082a10222b8fe186982d93d", 282 | "sha256:5c017f7758016e3c85387adddf49fdc071ae3fa9e7c986f4ff2f74ba0c6ad404", 283 | "sha256:5dca326db0ce67ca240227aebd9226b658c8473378054073c58b7075c15fecaa", 284 | "sha256:6191e25ca0fcc4d7a08bea354fe1afc8f9cc17400a40a106941ff40931d0cf5b", 285 | "sha256:69afb9eb076bce247aefdd5b12cb9a8fe6131f0559890397f33d1057d5862153", 286 | "sha256:81489ffb44230968c4d78daf6379fd57092d58b952ec5f42b683afa06ed3c53d", 287 | "sha256:930b42152d8fb2c7302a26315279d10669d0bc49ed5c112d3c4d3a2410d67d1b", 288 | "sha256:bac2ae8341f7eaaaa6196a46c4d02f2b31a2f4e85b370f217b7e525d8a8d670e", 289 | "sha256:f62775324d0fa1a4863cb2a41ecee901ff8dddfefab71c7189dcddfb497be6fa" 290 | ], 291 | "version": "==19.12" 292 | }, 293 | "genie.parsergen": { 294 | "hashes": [ 295 | "sha256:0cd44da7e82a26c82bef4f58b0ae90ae044ecbe28e03f916c85203bf7da3b3e2", 296 | "sha256:1e80d6a1e33daf7c40b6ee403c6fba0f1177215ef4f155e94f8f3100fcbe45b0", 297 | "sha256:36147a2bc4a8fc6603205f8e8c7dc39c53f01f5a2977bc4fd330a87b07ab13b9", 298 | "sha256:3aaf7bacee2f890b177a1d07df4b90a7cf3b6ec45e77ee1b9404fc7a88f9c9fb", 299 | "sha256:53fafa5801f3739a2d910090ebabc409d05a2a7d885f4a5b7317f30313ddf31f", 300 | "sha256:a0612ed8d15a0aa9713f109d66f57dd0e073ea423f3166c158bceb8dcf9f4c9d", 301 | "sha256:a6e8461b0972dfe285fac7f694dbbe6f341a9ce8bae58c09eabc39f06dd161d7", 302 | "sha256:b029041ca8b0d388681c3a92f98005426a040f01c9a8a7469b6db1c494632fbc", 303 | "sha256:b3948443f9d50df0ece0167735ebbfaf7824d6ebb572d419884efac40412ed69", 304 | "sha256:e94fda262e4c56489d7685663467f27ec3a0cf24c5ed2c9f6411220bf592d41b", 305 | "sha256:f176ec58904bdd2f62ca59c9aaf13841ccccbfa51b8a7da02056d8e6e2a668e3", 306 | "sha256:fd9115612adad6629b3c5546034ffe8e0c7886be9e5dfdf48d9b61b231b54e98" 307 | ], 308 | "version": "==19.12" 309 | }, 310 | "genie.predcore": { 311 | "hashes": [ 312 | "sha256:019d983a390bd71ac9162ce243458d78d646eb4d202f774dbd9d9b747f3b5644", 313 | "sha256:07f4db2c358ddf501288680a7e5c68c66be3a49b78259bb6fda0d4dd30b43290", 314 | "sha256:2b32c1ade097294f49c3804b6a8779efdf8f1058eade1de8f8088e70811fae93", 315 | "sha256:31b44bfb643afdc5772b00ad614c1389d0c952af550e655e29fa77076311a447", 316 | "sha256:3712cf090cd46156c70a0e8017502ef787c5602a76cacd7d3dcfae532e3b46c6", 317 | "sha256:41c253c5210179ce78e706ce7cbfc6e5711b37d9c7b996d12fa6ca89cda8f521", 318 | "sha256:6cc1a8ffa12bcfac8b4463328e8bae61bd1e86ad1c23c55b48f63a74728d27a2", 319 | "sha256:706c676043532cd4ab5bb8f7a2b27b67884df9cb78fe5ec0d42d594737417e79", 320 | "sha256:95859a54dfc5efeabd3d8ed0ec2a64e0c5ccdccf202e501bffa2c549a5a592e2", 321 | "sha256:a0a5a41b40e6927f3fbe928b40d6d673629044beca9366a297b591a0496b93a8", 322 | "sha256:dc62c00d1b20f73f37742aaa7a08c46a591d5471156c21a5d43527d29b81d3ad", 323 | "sha256:e8b9eaa45f9fb6d07c1776cf77136654aa3eceb88e324baba92a18964c722a83" 324 | ], 325 | "version": "==19.12" 326 | }, 327 | "genie.telemetry": { 328 | "hashes": [ 329 | "sha256:00fe6f432758da97cb034453866a2cd360711e51fcc461b9472d704f1063f5fa", 330 | "sha256:12686f872d6eb293f46db45cbfedb04039c30fc37a8777844353116749dc1ca9", 331 | "sha256:1ac00faf2cdb5bd46ce6a29d051359df0a1a325914faff546f3624ca9a7c0382", 332 | "sha256:3f5b9502c5b01fdc5c5e6b50dea45360e98830b80b8e571058c7b4e0a51c6b0a", 333 | "sha256:517f1ef20fc9e4cb3db0e0aac3331a61479b79c56ff74ffbd378be4d43b4b901", 334 | "sha256:66bc3b6b241582e894da23cbf06e78b24d4c640f83321da8b2ea91a6a0567d40", 335 | "sha256:6e3171f6b60dc92d621b0dc952991571eb1779d1addf4056360cb1c4bc037adf", 336 | "sha256:7ff06f6903074755c0eedd4974c6638abb1c4277d6b589d3a84ab5074c661685", 337 | "sha256:901d1482c6919babda7ff3c1fe20699ec317929a8b7559ba7b611737b6b66391", 338 | "sha256:ba0f4597c5d90be20976be8e144a2e38a558821d70aa3718ea6b6412ac4b9fab", 339 | "sha256:ba35e2b0d312ca3723169f8eaeefe759b378e626aca0695744949829a6f90b2f", 340 | "sha256:c953ee89572440065eeaea2ba605c805449e5f5141b9931903a8b4d7d27e98e2" 341 | ], 342 | "version": "==19.12" 343 | }, 344 | "genie.trafficgen": { 345 | "hashes": [ 346 | "sha256:eb86746c1921e799818bd72af237b295f967c2f2db3f4309e712df37840f3be7" 347 | ], 348 | "version": "==19.12" 349 | }, 350 | "genie.utils": { 351 | "hashes": [ 352 | "sha256:1be48c92d44b827eb80c3e17a5deb459be06d89f186cc3c44c2ba975d205f125", 353 | "sha256:1c822b2d4873c0f20f3e128d8a91d1554af4b13883f0966854fd71cd314708f8", 354 | "sha256:38a7de904cdf3b3522da3b67bfeff830a53af319280172e1b651ad7776fae0cd", 355 | "sha256:93239912ed52b89b4957aea88bd77bb8cfb0e0f1e93d5375917f34bee0bb47c4", 356 | "sha256:a7835c5a5079b1e5e68c42cfa0d8c52ad14f11a81aa6be8be3266d1a69f12177", 357 | "sha256:bf668814a0dae1284d43a381dd22455ebf3d66bac82c4ed39287fe3c97829981", 358 | "sha256:c8afb0c4ef29cf4a11abe1a73a21a1199c913e89af4f0038ffb47220e7c983b2", 359 | "sha256:cacef37ce8725c6901210a11504b6675e2c2d13307984579f3a7187dca44a697", 360 | "sha256:d0e3ba61926abdeea694e0c121b447f9d5a46cfa3a83f258c030a6db2a0f609b", 361 | "sha256:d308ede78dbab850bfdacf99bb2b189fa165e6ea7803ebab4657279a85d09767", 362 | "sha256:d4ea3c624e7141782503a31e368f56d5cca886ac271618647fb9be6dea4286cd", 363 | "sha256:f1a959eec22f80cf07975ae1e7890175ef2899ac808f5a6c22caf7b90d5dc5d7" 364 | ], 365 | "version": "==19.12" 366 | }, 367 | "idna": { 368 | "hashes": [ 369 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 370 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 371 | ], 372 | "version": "==2.8" 373 | }, 374 | "idna-ssl": { 375 | "hashes": [ 376 | "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c" 377 | ], 378 | "markers": "python_version < '3.7'", 379 | "version": "==1.1.0" 380 | }, 381 | "ixnetwork": { 382 | "hashes": [ 383 | "sha256:d5319382eebe371d6e9988d29647a7333f30e2da8180430922fe0e371e71759c" 384 | ], 385 | "version": "==9.0.1915.16" 386 | }, 387 | "jinja2": { 388 | "hashes": [ 389 | "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", 390 | "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" 391 | ], 392 | "version": "==2.10.3" 393 | }, 394 | "jsonpickle": { 395 | "hashes": [ 396 | "sha256:d0c5a4e6cb4e58f6d5406bdded44365c2bcf9c836c4f52910cc9ba7245a59dc2", 397 | "sha256:d3e922d781b1d0096df2dad89a2e1f47177d7969b596aea806a9d91b4626b29b" 398 | ], 399 | "version": "==1.2" 400 | }, 401 | "junit-xml": { 402 | "hashes": [ 403 | "sha256:602f1c480a19d64edb452bf7632f76b5f2cb92c1938c6e071dcda8ff9541dc21" 404 | ], 405 | "version": "==1.8" 406 | }, 407 | "markupsafe": { 408 | "hashes": [ 409 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 410 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 411 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 412 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 413 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 414 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 415 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 416 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 417 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 418 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 419 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 420 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 421 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 422 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 423 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 424 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 425 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 426 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 427 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 428 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 429 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 430 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 431 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 432 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 433 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 434 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 435 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 436 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" 437 | ], 438 | "version": "==1.1.1" 439 | }, 440 | "multidict": { 441 | "hashes": [ 442 | "sha256:13f3ebdb5693944f52faa7b2065b751cb7e578b8dd0a5bb8e4ab05ad0188b85e", 443 | "sha256:26502cefa86d79b86752e96639352c7247846515c864d7c2eb85d036752b643c", 444 | "sha256:4fba5204d32d5c52439f88437d33ad14b5f228e25072a192453f658bddfe45a7", 445 | "sha256:527124ef435f39a37b279653ad0238ff606b58328ca7989a6df372fd75d7fe26", 446 | "sha256:5414f388ffd78c57e77bd253cf829373721f450613de53dc85a08e34d806e8eb", 447 | "sha256:5eee66f882ab35674944dfa0d28b57fa51e160b4dce0ce19e47f495fdae70703", 448 | "sha256:63810343ea07f5cd86ba66ab66706243a6f5af075eea50c01e39b4ad6bc3c57a", 449 | "sha256:6bd10adf9f0d6a98ccc792ab6f83d18674775986ba9bacd376b643fe35633357", 450 | "sha256:83c6ddf0add57c6b8a7de0bc7e2d656be3eefeff7c922af9a9aae7e49f225625", 451 | "sha256:93166e0f5379cf6cd29746989f8a594fa7204dcae2e9335ddba39c870a287e1c", 452 | "sha256:9a7b115ee0b9b92d10ebc246811d8f55d0c57e82dbb6a26b23c9a9a6ad40ce0c", 453 | "sha256:a38baa3046cce174a07a59952c9f876ae8875ef3559709639c17fdf21f7b30dd", 454 | "sha256:a6d219f49821f4b2c85c6d426346a5d84dab6daa6f85ca3da6c00ed05b54022d", 455 | "sha256:a8ed33e8f9b67e3b592c56567135bb42e7e0e97417a4b6a771e60898dfd5182b", 456 | "sha256:d7d428488c67b09b26928950a395e41cc72bb9c3d5abfe9f0521940ee4f796d4", 457 | "sha256:dcfed56aa085b89d644af17442cdc2debaa73388feba4b8026446d168ca8dad7", 458 | "sha256:f29b885e4903bd57a7789f09fe9d60b6475a6c1a4c0eca874d8558f00f9d4b51" 459 | ], 460 | "version": "==4.7.4" 461 | }, 462 | "netaddr": { 463 | "hashes": [ 464 | "sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd", 465 | "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca" 466 | ], 467 | "version": "==0.7.19" 468 | }, 469 | "pathspec": { 470 | "hashes": [ 471 | "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", 472 | "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" 473 | ], 474 | "version": "==0.7.0" 475 | }, 476 | "prettytable": { 477 | "hashes": [ 478 | "sha256:2d5460dc9db74a32bcc8f9f67de68b2c4f4d2f01fa3bd518764c69156d9cacd9", 479 | "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36", 480 | "sha256:a53da3b43d7a5c229b5e3ca2892ef982c46b7923b51e98f0db49956531211c4f" 481 | ], 482 | "version": "==0.7.2" 483 | }, 484 | "psutil": { 485 | "hashes": [ 486 | "sha256:094f899ac3ef72422b7e00411b4ed174e3c5a2e04c267db6643937ddba67a05b", 487 | "sha256:10b7f75cc8bd676cfc6fa40cd7d5c25b3f45a0e06d43becd7c2d2871cbb5e806", 488 | "sha256:1b1575240ca9a90b437e5a40db662acd87bbf181f6aa02f0204978737b913c6b", 489 | "sha256:21231ef1c1a89728e29b98a885b8e0a8e00d09018f6da5cdc1f43f988471a995", 490 | "sha256:28f771129bfee9fc6b63d83a15d857663bbdcae3828e1cb926e91320a9b5b5cd", 491 | "sha256:70387772f84fa5c3bb6a106915a2445e20ac8f9821c5914d7cbde148f4d7ff73", 492 | "sha256:b560f5cd86cf8df7bcd258a851ca1ad98f0d5b8b98748e877a0aec4e9032b465", 493 | "sha256:b74b43fecce384a57094a83d2778cdfc2e2d9a6afaadd1ebecb2e75e0d34e10d", 494 | "sha256:e85f727ffb21539849e6012f47b12f6dd4c44965e56591d8dec6e8bc9ab96f4a", 495 | "sha256:fd2e09bb593ad9bdd7429e779699d2d47c1268cbde4dda95fcd1bd17544a0217", 496 | "sha256:ffad8eb2ac614518bbe3c0b8eb9dffdb3a8d2e3a7d5da51c5b974fb723a5c5aa" 497 | ], 498 | "version": "==5.6.7" 499 | }, 500 | "pyats": { 501 | "hashes": [ 502 | "sha256:0fbc3d90b84713e09b0f92e268c50dcbe2697c62c1943304c037ab5cb228e028", 503 | "sha256:18b53dda1c35d644a189a6298e9c6375d8ed2acae99e9516d80b7cdd6182fcbf", 504 | "sha256:1eca7e9109bdd382809ed6f6d3586b5b50561f63eee0e7587a9e1f92b553cd34", 505 | "sha256:48e7ad3b44ee097d1eaeb45a3c35ebc9022890f52edbaf3a214c4c15461fdf70", 506 | "sha256:4b93b9974a13af11a469f1d36faf1a05fe7f823593cb5765c0b0703306489c49", 507 | "sha256:518e9a0f89f2ee6dc987b1db538202cf4e702f1f9fda47fcebf003def65504ae", 508 | "sha256:61627bbace48d8e779539942afb8e7abfe2c8e8ecc3a69d6c26876b25d528f0c", 509 | "sha256:9b8a2255d3ef3547688e2b37965a56f141bbd3085050706da2a6f5ac3942aeaa", 510 | "sha256:9cd3d7a83564f3c776f3451036b9335d547d643e5b5c1872d756660a723246a2", 511 | "sha256:a79f5f44893c241bcb7c9ca4b982ee323a899355716f0076e6a472c0f1c55bca", 512 | "sha256:e4dbd8ca97214a67b70f08d7c9b4cc61d00249de7ac5ddf516278eeb7d2c4369", 513 | "sha256:fbe3cccace6cb008438cc422289837e265b0a31fe649beeaeaad468c325515f3" 514 | ], 515 | "index": "pypi", 516 | "version": "==19.12" 517 | }, 518 | "pyats.aereport": { 519 | "hashes": [ 520 | "sha256:37e719154c5f70280847bda161d3d0149ec0515b2e2217fcc8866c1c6b168d9d", 521 | "sha256:40455d93823f2bc7c1e91d6935074a30b4114224ad75a2911c77626104b949e9", 522 | "sha256:42f067634496baf5aebc8c3cef5f82450fc37309accdf0b376adfd0211aa2837", 523 | "sha256:4947e28d35fe90f84167745213d2653fc5c4c35853688d47983cd3234df4bddd", 524 | "sha256:58fdca84e833198c00ae2065b85491fc1e9db091ceeefdad40134a3049bf5253", 525 | "sha256:5fd6c2bf4f1c96998b4fb3135f9448c185875270bb0cbeec300be54511572365", 526 | "sha256:a63681dd643f4d94e9aeb4a97fe6858ed1626fe976ee853921c0402af409d31e", 527 | "sha256:b0b6a9f8f8ee616d2d721849d1327d097bb6c26b4ff19f81051fc166aa62f299", 528 | "sha256:baf721a3fd03789699bc5072e2499d7b20a4b49c25d976816be887dfdde4301c", 529 | "sha256:da14fea4bb6e40c98e5cec68a7fe3bf98fb73d18bfac5f99c1d90deecdce1176", 530 | "sha256:dc22666594ffca2b236ca323e66a2ec765aadf1d46dd533e8e316f6f60835f64", 531 | "sha256:dd7b350eb59c06c7f00d1355e342a1cbabd38a938adc354b97571730f27d0e1c" 532 | ], 533 | "version": "==19.12" 534 | }, 535 | "pyats.aetest": { 536 | "hashes": [ 537 | "sha256:16179503995516f25675207ad0de250716c43fb1d19b0409ae7f49cce230eb58", 538 | "sha256:21edd5ab214c9c0c3e46308b95af6e6839a14f3f295dac81afb86ed6b507a402", 539 | "sha256:474f6533e6cba00bf464d4ffb2baee82ec7396d8bd1a4e29b1699d540f00c5f5", 540 | "sha256:4d5aa60f371b13535245ed696b0fee5986230aa38a8a66e1a4e69ad8f6970199", 541 | "sha256:564c93eed075c5ed3cf19ca52ccba59cb25cd392642d066fc98e5dc58770e50e", 542 | "sha256:7e9781a75cc2f59c71fe888c205939aa1f97e7d561f71d903e25960b6e0ef4fe", 543 | "sha256:80a9f80dd94d92262ba183e6cdee8d4546da64bf44befcbdfb7e696e144d4198", 544 | "sha256:c817b596c74208aed8afbc5f6b9ee7748553a5daca3a56c53779737f2c76f61f", 545 | "sha256:e30317fb8426dc9f1cc297e1eb6a334634336b4077c9dcfd0cda98c86e782038", 546 | "sha256:e394e50df5c6d0b58debedb4b94f5376cc174e5c3412353be14744079dfa9244", 547 | "sha256:ead9065af7be3cc1992a0f9dc9bc39ed9400bcc2c1944ff6bc88824845b27c9a", 548 | "sha256:eee73ab421eb63cafca7942efa8c1e7594a53c7531d862651a7f9ff7210e24c7" 549 | ], 550 | "version": "==19.12" 551 | }, 552 | "pyats.async": { 553 | "hashes": [ 554 | "sha256:01f0bee2a6731b065ad230281ec6b6da96f3c8c57fae3695a29bafedc593e1b5", 555 | "sha256:11478c2207973c4e7683b076b43144bb213e85b534c415136b1a0a5760b9ca19", 556 | "sha256:13587e6be5c7fde5a464d86ebca5544a5aa5e47f29d2c8f3dd886371a705a1ec", 557 | "sha256:1f3e83d09ccb053ef6c31330a987622db7a2098993053e35edf295e729b870dc", 558 | "sha256:4e241cf2fe8286801249715ee4883e5e2bfee5ce8d94512f6ba5c5eecdb38d63", 559 | "sha256:56efee41b75ded39bde81a25a7f27b9ef6125cd95a76006ed4689f0217d39e19", 560 | "sha256:6f6931d9acb99c09d04e643486502796efc35f9fbec939f92ff02e8cd7b1847e", 561 | "sha256:89eee086a9bb8646832055c0c091e643aacc384454c04673e9ce25d7f820c2d4", 562 | "sha256:adefa0430ac88987d7ea5bba8e91f1ba9f68253dd94df037bef26dce62a712e5", 563 | "sha256:af8c892d8edb2a7ec2f64822aff7d769987f1d39edb3227ca46dce6956215eae", 564 | "sha256:c13002f0d60f622b99161b9caeba56b67d117e3f9a43ba60f2785e0654d3d509", 565 | "sha256:e9241d3b631362d871c35658244758341abe72fafcec41e49192dd799c380f01" 566 | ], 567 | "version": "==19.12" 568 | }, 569 | "pyats.connections": { 570 | "hashes": [ 571 | "sha256:032b190b0bc26fb9e6a8e90b72cc612661fc4b67d38e445af5a9906c8c5cf0b5", 572 | "sha256:17422d15a50bd459ceee2159acb052ebf09dd8bebe6ecfc86421aa666e83b037", 573 | "sha256:3899374ec5b37f58074d59b087318dfdbee66930ec69cb0bc2fba52db8d1dbbe", 574 | "sha256:4d0482ec3a1cb449d5baf8999ceb3021a8a8a5230d258d008b3e495e0877e91b", 575 | "sha256:6a95b4c241ff7e17228331016f869521097eebbff899d64a509515e013346f8f", 576 | "sha256:9291fe35e5d5a51fc648281eb951fb53eaee1eefecfce4e22e951304a96e6370", 577 | "sha256:bb1b3d06ff15b2b088befbdabf1971f006e911e5dc9b284978e68005011b7a8e", 578 | "sha256:d481b0324f9bfe615113a86b96155f74597897ee9dbba55237f7a69325f85222", 579 | "sha256:d810d15afbeaba4936942912e9f1b16fcd49467e948eeac60278b0c2af88bac4", 580 | "sha256:dbdfafddf45d754064c44cdf4a00579b0a6293a4b534be0077ba6c33ba0383ff", 581 | "sha256:e1f5c6d47fefc11354f596c9eb5d3a78475f695e1e00e49ea47c07526e124091", 582 | "sha256:ec9341142d130a70d67b08f6858072008df8b0d71221ca456fe9e514e13dc74b" 583 | ], 584 | "version": "==19.12" 585 | }, 586 | "pyats.datastructures": { 587 | "hashes": [ 588 | "sha256:1a90acf17999934ec40f83174c8781a4bcf2cbebdc8d1537d47977838e7d6950", 589 | "sha256:21091eb1e4fd2385d3de6cd18fec703b7f09627f17dc376d073f56ef8aa0cce4", 590 | "sha256:3b3610802f3c7dde6dbeb60c57b81c7673c340f3d43fc2c02c9f516e4ad97e59", 591 | "sha256:4003199945cb407c07cfddbebf83b7c5d451e8fee75f0000bbcaa671d6bbc206", 592 | "sha256:5cacb83c40c1c5a337c11eefa619e66c628e24ddb025b804bcd6d6207c77510d", 593 | "sha256:705e10e5814ba6f8f26c0acbe47bfa5e28cca1d9c7e9a5e72a6ed962c4f35131", 594 | "sha256:734a69b6eba852c97ee3005022209c92c1bf6ac4f366c590e1561cd2453f637b", 595 | "sha256:8c1768a205f9b11a5379ec4a9172501c570bfa79fb7c2dbc33641f8e36130a10", 596 | "sha256:9abe9b4e6730c86c22fbdd4622f75995c2b2bc60152838070723d6c3175feff0", 597 | "sha256:ad0dafe9d92b52ca129ad851071e9b234e14774adbc8a6f4039dac1f5d634c95", 598 | "sha256:bc92aeefae82e6d6fab6eec469b39319820b0c16fd9a839645ab1b0ee025dbbd", 599 | "sha256:f2fb9abefa3c6a1f5927fc957d80a96bcc91bc97be261b2f9911af4e42b6b976" 600 | ], 601 | "version": "==19.12" 602 | }, 603 | "pyats.easypy": { 604 | "hashes": [ 605 | "sha256:2f0108f28166210fd2d972dd91e09de4ab0365a70f11c8221e958003b530764d", 606 | "sha256:3343b107ffd7be349418a9766804d584f19f760553dbb10c727593b1a2ab9fcc", 607 | "sha256:38a068cff18c3226cd0c4cab58dc74fadc988570e0b89432ac46a4c30649ad89", 608 | "sha256:3956fa4decf79e6a72ecf264eb082a5df5999f9eaf24244385575ae78c744b7a", 609 | "sha256:3baad62f87d09144c1b1b01ff2895553fef4fef225422bf43f7afd8f5e2c3c44", 610 | "sha256:4d1a82505f6dc9cdb68fc2496f431390817884e12e0d22cb9ae91f1e4551ec5b", 611 | "sha256:73767dbf101d445434967a8da52b46e1cf3ad15207525186353585bb1c27d1d8", 612 | "sha256:8e86997abfd2af1f1515f025f2c7240897342d9bdbffe1c5a589ceb6660da8cd", 613 | "sha256:990692eefc266c09a5e919a04b863cd6defe408d3b17c4d2c2f20b38c625b008", 614 | "sha256:ad27ab4b5029d7ca7366230755bcdb8add42b2b7b2a2b3262d2ebf01f8f0a368", 615 | "sha256:c36b7398569f363d86863b4bab595fb97db8d313f83743c94ca45423f25280a2", 616 | "sha256:ea07325f7b292cdba4382e2fdff6cbb854dba4c43cffa7ba8cfdaefa9046e3b5" 617 | ], 618 | "version": "==19.12" 619 | }, 620 | "pyats.kleenex": { 621 | "hashes": [ 622 | "sha256:00c40c54ec048dcd8324bef115fa2e395efe68a53deeb1b58ac9ded471c1d9f3", 623 | "sha256:35bf6e1696b4507977edee6dbb9285112d8a74abef26da5219bc91d01ed6c0dd", 624 | "sha256:447a016b0353bf631a510addbc6bfbe2fe66c23df25b1d7300dc4f9b410d6b75", 625 | "sha256:96d17fa9912ca12795ff7f01641b7cd8b070eb9f5d8a227a7e5d1f3d1177b562", 626 | "sha256:aa5e8d82061541ac4f23e70d5952a39b0baa258cffb05595f0f80064c857d3be", 627 | "sha256:b6849342522d5aa3c77238e11cd3dd69e84c75ca8b795599cbfa64cf362d1eec", 628 | "sha256:d1ae41d4553db4dffd1adb96626eaa02efa454acc2a02f30d2d4d56a262ae64a", 629 | "sha256:de51b5b7e7dde4c4d9c133a22adbed5429939da2819e194b5bb2676413460cde", 630 | "sha256:df6acac40f8492d8b54e926a13a48e569308a7285be3de4b7ef3ee9f878bccc9", 631 | "sha256:e3bca463a0d8d398aab82a87c21d8d058e4debc0155f30d7c77ed9ec099b587f", 632 | "sha256:f0c79b8da59e58ec36f3da192afad79de6b9ed42cc0916a77578c7562847598a", 633 | "sha256:fcae53bb18d16fc7f98f896880bffcfd5480306816ce130f232bab910a2cbce1" 634 | ], 635 | "version": "==19.12" 636 | }, 637 | "pyats.log": { 638 | "hashes": [ 639 | "sha256:336adc8f711845966821e1ec4a7c004e6a32d3308fd69681bd84b4f81eb281c4", 640 | "sha256:35537378bd77440b566554a582537a563e55050f634dd434599e881f051be7b2", 641 | "sha256:47ce14395c22647b45ad929555c85fe3746f17057b919831545bb9e4a0e2e377", 642 | "sha256:4f7d720a6b9b448b7109152528f293597aeaecbc5d54ae0d0f065ad19b49c650", 643 | "sha256:559afb0ac06ee3bc641ad9da1d461cdafd3de0dfb9abc9f57e19eabebfae9a92", 644 | "sha256:5f95df80490242b37846ada68a892bbc6a67b9b260abfc4bfb2e97b83a63c597", 645 | "sha256:672b0495ec3448cd57b3cc6d7e5abed2d6e9b6cb587d9e47ece60e14f081fae2", 646 | "sha256:6ff015cb843e7e4047fea0e7bc87116c585118513762463853bba867bcaa3347", 647 | "sha256:8818948e4f16005cc409896084fc4e4d5fdcd1d6465413512be9c18e7acbd175", 648 | "sha256:8a745c114097a22cee0cb07cc929ad9b98c23d9439605937e59afc51bf7dfced", 649 | "sha256:90a188df359dbac795362a0c3a4437167390e19205158be4b69ccfb02b5cb34c", 650 | "sha256:983f26e0f8b9e05afe0992c2427f91ac2c1e62d780cbabbfd7d1c9fb8aec01f4" 651 | ], 652 | "version": "==19.12" 653 | }, 654 | "pyats.reporter": { 655 | "hashes": [ 656 | "sha256:0ddfec3b92401ca390477f2fe5a111b9754a281ed4d619fc854960118fbd2808", 657 | "sha256:47062901261e85752aafda7b175d9930b9daddff0086c3727f732f6dd55eb6cc", 658 | "sha256:4fc68dcb7c7bf5477e09532d7ab0196826937315e7aab035c1e0ed226b487cd7", 659 | "sha256:6e9fb59b008b415b6d0b0b752bfbb4e04a7f6678d1fe0cd6c9cea899b197014f", 660 | "sha256:739ced392bbcb31619c85efca33cfc91aeb31137e51631b7317d328aa2d0147c", 661 | "sha256:81323f18d599b29173c24af0bf2360ca1c94ebf7b39492dbeff9a773bf2f0a9c", 662 | "sha256:ae165193e4daf3dcaac3b99dbf3bc2191ec3f5c448f4f836fcf9ec6952a4dfb5", 663 | "sha256:b9371e10cd9cb578df2484dafe83e3ffe0a4da7446db0e9563a7d9e8ddb954ef", 664 | "sha256:bd5549bfbbf0a8798fb483b617f35ac189d6ce9c1e0e423c33b3320bac0269a8", 665 | "sha256:c3defd9a660254a261dac06d226fc8117f77c71b17e9b1122941bf21665d8868", 666 | "sha256:f7e395ddc2b117dbd539a91ef74d581e1ee8e471693e7ed64896357c35792b37", 667 | "sha256:fbc5c0bc74dc576988caf1eb39c3adf4076df3786d06524bc98ad07c4164ff37" 668 | ], 669 | "version": "==19.12" 670 | }, 671 | "pyats.results": { 672 | "hashes": [ 673 | "sha256:0cd30a7e9305b494668fe9f8f1ebf44f3b2a3f0bca3742561730b22cbeb4ea4c", 674 | "sha256:0e81d5423b1ffcaad515fae6ee8d081c5c7fa67686c6376ec284a2dc2f09eb19", 675 | "sha256:0f639b0df16ac8f786d52765b95d88b4cb46283ac5afd7f7c3dde8abc572e53f", 676 | "sha256:27281197db2b66b9f305d34aac3ceda2ef35dc69e2bc192a8481cc54c95bb324", 677 | "sha256:2fbe8ffad33cfcdb8c3f62e469622ab7e188e7e3756e154e984b69d94e5018a4", 678 | "sha256:4b5e12e53016d02e6201bd4523a6c376acd0c930104d6807d37fc2921298ad84", 679 | "sha256:64fd1ce82cf806fa135709b52be0a9719ffe748d3e69affb249d990b2486db0f", 680 | "sha256:7414e2d8323888a55c37c827fa5130a064158c820f3e4ff54b933b423b47d836", 681 | "sha256:90addb836327e9676a3ae3a1a144e31f4ed212497ba5707d33b9da03dc4207d9", 682 | "sha256:ad93c996779617d428bc22341b93f91ca7bd96720563bf468addb2e58dc48a46", 683 | "sha256:bd1daa6f3d28b7db3510629d0e08e6235c724f6bc246bfb94cf5158f9f5980a9", 684 | "sha256:f445ea20db736892d4b22d3ee509f63eb01282268d7a64685867271301a12544" 685 | ], 686 | "version": "==19.12" 687 | }, 688 | "pyats.tcl": { 689 | "hashes": [ 690 | "sha256:15fcf99c72263107093799aa5fe89f76c67763cc71f267c32f36e480c17c8a5d", 691 | "sha256:256484a83904edd0cec3d8210f16f5c8d0e7ec198bdbf425d6016097fc478cfa", 692 | "sha256:29cffac2df357b55e9d539cb5a7ef319e5e3c1324b6b7a1aa935d15dadc9762f", 693 | "sha256:2f154799dd9e4464ef5d66fe1c60b49b42588441070bd1159e91f179268fc088", 694 | "sha256:31f7d46dd6031a6302f89f901607eb9d9906c07703b55c99e4d019ad74e3c56a", 695 | "sha256:391af4269f8cf08129281f59b2c5d2b98c4e2fdc8e479fe7423ca671712dbc85", 696 | "sha256:506e2e2c8c868e1227883fe62b17cae56d10e0c1ad6a209539644ae9b8297c89", 697 | "sha256:552b9ef8ba4c3221f992ca155e8c5acf04c677c339f01ce190e79884beb0e7de", 698 | "sha256:626c595dbde993645a6a99eb79e17d396cf346dd0577d95d89a81c68825534fd", 699 | "sha256:752e3b28c609391807f98adcfb2c0d86d1edb30884ed2b9945f717bfb23afb53", 700 | "sha256:ac94bd37fe9d0fef6641677871aeee4ba13f6c863bf382b8423f6f83b3babbb3", 701 | "sha256:bc463234801d112c8506bdabd11a28901101ce02fd79a3c73e1ec633e618142a" 702 | ], 703 | "version": "==19.12" 704 | }, 705 | "pyats.topology": { 706 | "hashes": [ 707 | "sha256:2fb02826cceefc63eec4eae0f84e678021eec48037dc706a927fd585510739eb", 708 | "sha256:36bc2198a78d0037603a3d7e8f572ddc90c16818205531779182a84eac9a8a39", 709 | "sha256:3b800a4ff40cf5e96edea1dcc73c44572c551bbeb4363f8aa42f3f3cb8266e5c", 710 | "sha256:4be906fe355d69ecc020c3e688ab7de1eba8e6572d06cb344b51816efd179c4e", 711 | "sha256:5e8e9626ad8127d356a9cd1a79b255f8be363668ff0b92048a4c0e8f83a1932b", 712 | "sha256:6c295cff01f4bb8667c41cec2815bdf27aa199ccdeab16404060c241669db8e3", 713 | "sha256:9544b93f79b400ea915c0e68bc0128e7e9dc044a8440fa80372677207a27c419", 714 | "sha256:b7dea0e3485d4587138a489b3cc60a8d8a0adf34a705763e8499eb58774fbd03", 715 | "sha256:b8b46cd4ecbbfb0bb16332fe94642561bf7eeee71bbdbef7f109fdf07604e29a", 716 | "sha256:da57050990a6d2fd2be210da13a525d90b376a03336a5bb78852c13f88771f86", 717 | "sha256:dbbf05c40c60ea59354ee8f70cba7758489e85d1f1bdc1d3d997588aee4128f3", 718 | "sha256:dc6175e17cc3664da5959018092d91bd46bf7f28e6c3cc2f32bff4130b88170a" 719 | ], 720 | "version": "==19.12" 721 | }, 722 | "pyats.utils": { 723 | "hashes": [ 724 | "sha256:2245d6c0bdfd9e2556be353da56d45f3f721442d69e3b01be56c9804b0ff016a", 725 | "sha256:60d2851ae9ea471d47a28d27468a0e522d135427a1ce5f230bc2606ab8422571", 726 | "sha256:6b070dd25e3c6a9bb3118d5c1d32db7f8bbb0d1981d246792d2d6459d11594a8", 727 | "sha256:6d4c55060c51261d3f0cf9d5df98ebd7480941b19f316599c188b97683ec1573", 728 | "sha256:7587b19d71de84942b91d9363da5b6ceecb1f8e73c1c29944d9ca57c2c28ffcf", 729 | "sha256:7bcd0381f93374e926c22ffa854653be992f4637e016dc8a54f6154fe557e49a", 730 | "sha256:a7e068d03c2e86f5a80739b766bd26330eac688bd9209692e5dab3adac5aaa24", 731 | "sha256:b1ca74aedb41c446d43dea241a54f86bce4857c3604c3dff4147f6e8e848c95b", 732 | "sha256:b73f373e91ca0706e7a577e6e8b3025bce70e289cd4497c41f1b5b057df87f4e", 733 | "sha256:c365d08e87d65db099427f21b960de306713590ea51208246df13652dfbad672", 734 | "sha256:d441fc78fb3530ac50251f61cba405ce59748fc01757061dd6b54a9f9416e644", 735 | "sha256:fd76b6012cca14e86315347d0e354627fe592f5b91e9935d751e212b40e93dbf" 736 | ], 737 | "version": "==19.12" 738 | }, 739 | "pycparser": { 740 | "hashes": [ 741 | "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" 742 | ], 743 | "version": "==2.19" 744 | }, 745 | "pyopenssl": { 746 | "hashes": [ 747 | "sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504", 748 | "sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507" 749 | ], 750 | "version": "==19.1.0" 751 | }, 752 | "pyyaml": { 753 | "hashes": [ 754 | "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", 755 | "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", 756 | "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", 757 | "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", 758 | "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", 759 | "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", 760 | "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", 761 | "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", 762 | "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", 763 | "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", 764 | "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" 765 | ], 766 | "version": "==5.3" 767 | }, 768 | "requests": { 769 | "hashes": [ 770 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 771 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 772 | ], 773 | "version": "==2.22.0" 774 | }, 775 | "six": { 776 | "hashes": [ 777 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 778 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 779 | ], 780 | "version": "==1.14.0" 781 | }, 782 | "tqdm": { 783 | "hashes": [ 784 | "sha256:4789ccbb6fc122b5a6a85d512e4e41fc5acad77216533a6f2b8ce51e0f265c23", 785 | "sha256:efab950cf7cc1e4d8ee50b2bb9c8e4a89f8307b49e0b2c9cfef3ec4ca26655eb" 786 | ], 787 | "version": "==4.41.1" 788 | }, 789 | "typing-extensions": { 790 | "hashes": [ 791 | "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", 792 | "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", 793 | "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" 794 | ], 795 | "markers": "python_version < '3.7'", 796 | "version": "==3.7.4.1" 797 | }, 798 | "unicon": { 799 | "hashes": [ 800 | "sha256:09908c5f57cc1a0718eb506fd23bcefd3db1391a6d885eaf255f3b605c64b7d5", 801 | "sha256:2c7ed6493e030cb92f9abedbd7e53b7b2e1f9ca766bf86bfdb15fbb6d5b654fc", 802 | "sha256:31de360651049f4a320f6a97ee191f57fc3811bdcaa67463f3d8c3ced322b36e", 803 | "sha256:5b99974fed43d58e27c44fac19d468264f87de9cca939ef475ffe37f8743f18a", 804 | "sha256:8400b219d68c3984f69882749709678cfe47cc939563a2ae0ce976506b291360", 805 | "sha256:9c4bac522756205352ac2fe7f242994fa0beef3519a95175361ec45f7ad46043", 806 | "sha256:ae01be13e12df7eca13d282ba226940c1e9f28f5b3040ac51e77d86b563245bf", 807 | "sha256:c2578033ac1d27361f00e0d28c58b5056744cefeca45f3a934c6b58bbac4c21a", 808 | "sha256:d9a35c3a1ef25fcf90773b5233e7a11de169764dbf74e93b49e46ff519f5ea0d", 809 | "sha256:f9f9a94f76b7ef72fe806988a26c926095b6672cc1a83b2a249e7f31109bb39b", 810 | "sha256:fd3013f19f5178f48dc701ed68797aeafc723fb36eefec7cb795100bce9d6f8c", 811 | "sha256:ff5f3e341e7906f298f8f82e86bbd609195f9ed85bdecd0eaad2bbdf1ef75776" 812 | ], 813 | "version": "==19.12.1" 814 | }, 815 | "unicon.plugins": { 816 | "hashes": [ 817 | "sha256:603987685d7684c2912fe71658d50760cbc12fa19c7abc308a55a5a9e4d1a0d6" 818 | ], 819 | "version": "==19.12.1" 820 | }, 821 | "urllib3": { 822 | "hashes": [ 823 | "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", 824 | "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" 825 | ], 826 | "version": "==1.25.8" 827 | }, 828 | "websocket-client": { 829 | "hashes": [ 830 | "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", 831 | "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" 832 | ], 833 | "version": "==0.57.0" 834 | }, 835 | "wheel": { 836 | "hashes": [ 837 | "sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646", 838 | "sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28" 839 | ], 840 | "version": "==0.33.6" 841 | }, 842 | "xlrd": { 843 | "hashes": [ 844 | "sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2", 845 | "sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde" 846 | ], 847 | "version": "==1.2.0" 848 | }, 849 | "xlsxwriter": { 850 | "hashes": [ 851 | "sha256:18fe8f891a4adf7556c05d56059e136f9fbce5b19f9335f6d7b42c389c4592bc", 852 | "sha256:5d3630ff9b2a277c939bd5053d0e7466499593abebbab9ce1dc9b1481a8ebbb6" 853 | ], 854 | "version": "==1.2.7" 855 | }, 856 | "xlwt": { 857 | "hashes": [ 858 | "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", 859 | "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" 860 | ], 861 | "version": "==1.3.0" 862 | }, 863 | "xmltodict": { 864 | "hashes": [ 865 | "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21", 866 | "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051" 867 | ], 868 | "version": "==0.12.0" 869 | }, 870 | "yamllint": { 871 | "hashes": [ 872 | "sha256:7318e189027951983c3cb4d6bcaa1e75deef7c752320ca3ce84e407f2551e8ce", 873 | "sha256:76912b6262fd7e0815d7b14c4c2bb2642c754d0aa38f2d3e4b4e21c77872a3bf" 874 | ], 875 | "version": "==1.20.0" 876 | }, 877 | "yarl": { 878 | "hashes": [ 879 | "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce", 880 | "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6", 881 | "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce", 882 | "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae", 883 | "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d", 884 | "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f", 885 | "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b", 886 | "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b", 887 | "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb", 888 | "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462", 889 | "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea", 890 | "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70", 891 | "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1", 892 | "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a", 893 | "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b", 894 | "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080", 895 | "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2" 896 | ], 897 | "version": "==1.4.2" 898 | } 899 | }, 900 | "develop": {} 901 | } 902 | --------------------------------------------------------------------------------