├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── commit.yaml
│ └── publish.yaml
├── .gitignore
├── .pylintrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── _config.yml
├── comparison_tests
├── hosts.py
├── test_netmiko.py
└── test_ssh2net.py
├── docker-compose.yaml
├── docs
└── ssh2net
│ ├── base.html
│ ├── channel.html
│ ├── community
│ └── index.html
│ ├── core
│ ├── arista_eos
│ │ ├── driver.html
│ │ └── index.html
│ ├── cisco_iosxe
│ │ ├── driver.html
│ │ └── index.html
│ ├── cisco_iosxr
│ │ ├── driver.html
│ │ └── index.html
│ ├── cisco_nxos
│ │ ├── driver.html
│ │ └── index.html
│ ├── driver.html
│ ├── index.html
│ └── juniper_junos
│ │ ├── driver.html
│ │ ├── helper.html
│ │ └── index.html
│ ├── decorators.html
│ ├── exceptions.html
│ ├── helper.html
│ ├── index.html
│ ├── netmiko_compatibility.html
│ ├── session.html
│ ├── session_miko.html
│ ├── session_ssh2.html
│ └── ssh_config.html
├── examples
├── basic_usage
│ ├── basic_usage_ssh2net_connecthandler_style.py
│ ├── basic_usage_ssh2net_driver_style.py
│ └── basic_usage_ssh2net_native_style.py
├── logging
│ └── session_and_channel_log_diff_files.py
├── ssh_keys
│ └── ssh_key_basic.py
└── textfsm
│ └── textfsm_parse_output.py
├── pyproject.toml
├── requirements-dev.txt
├── requirements-paramiko.txt
├── requirements-textfsm.txt
├── requirements.txt
├── setup.cfg
├── setup.py
├── ssh2net
├── __init__.py
├── base.py
├── channel.py
├── community
│ └── __init__.py
├── core
│ ├── __init__.py
│ ├── arista_eos
│ │ ├── __init__.py
│ │ └── driver.py
│ ├── cisco_iosxe
│ │ ├── __init__.py
│ │ └── driver.py
│ ├── cisco_iosxr
│ │ ├── __init__.py
│ │ └── driver.py
│ ├── cisco_nxos
│ │ ├── __init__.py
│ │ └── driver.py
│ ├── driver.py
│ └── juniper_junos
│ │ ├── __init__.py
│ │ ├── driver.py
│ │ └── helper.py
├── decorators.py
├── exceptions.py
├── helper.py
├── netmiko_compatibility.py
├── session.py
├── session_miko.py
├── session_ssh2.py
└── ssh_config.py
├── tests
├── __init__.py
├── functional
│ ├── __init__.py
│ ├── arista_eos
│ │ ├── expected_output
│ │ │ ├── show_run
│ │ │ └── show_run_no_strip
│ │ ├── ext_test_funcs.py
│ │ └── test_eos.py
│ ├── base_functional_tests.py
│ ├── cisco_iosxe
│ │ ├── expected_output
│ │ │ ├── interactive
│ │ │ ├── show_run
│ │ │ ├── show_run_execute
│ │ │ └── show_run_no_strip
│ │ ├── ext_test_funcs.py
│ │ └── test_iosxe.py
│ ├── cisco_iosxr
│ │ ├── expected_output
│ │ │ ├── interactive
│ │ │ ├── show_run
│ │ │ ├── show_run_execute
│ │ │ └── show_run_no_strip
│ │ ├── ext_test_funcs.py
│ │ └── test_iosxr.py
│ ├── cisco_nxos
│ │ ├── expected_output
│ │ │ ├── interactive
│ │ │ ├── show_run
│ │ │ ├── show_run_execute
│ │ │ └── show_run_no_strip
│ │ ├── ext_test_funcs.py
│ │ └── test_nxos.py
│ ├── comparison_tests
│ │ └── test_comparison_tests.py
│ ├── examples
│ │ └── README.md
│ └── juniper_junos
│ │ ├── expected_output
│ │ ├── interactive
│ │ ├── show_run
│ │ └── show_run_no_strip
│ │ ├── ext_test_funcs.py
│ │ └── test_junos.py
└── unit
│ ├── __init__.py
│ ├── _ssh_config
│ ├── drivers
│ ├── __init__.py
│ ├── base_driver_unit_tests.py
│ └── core
│ │ ├── __init__.py
│ │ ├── test_core_arista_eos_driver.py
│ │ ├── test_core_cisco_iosxe_driver.py
│ │ ├── test_core_cisco_iosxr_driver.py
│ │ ├── test_core_cisco_nxos_driver.py
│ │ └── test_core_juniper_junos_driver.py
│ ├── ext_test_funcs.py
│ ├── test_base.py
│ ├── test_channel.py
│ ├── test_core_driver.py
│ ├── test_decorators.py
│ ├── test_helper.py
│ ├── test_netmiko_compatibility.py
│ ├── test_session.py
│ └── test_ssh_config.py
└── tox.ini
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Your script
16 | 2. What you're connecting to (vendor, platform, version)
17 | 3. Anything else relevant
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Stack Trace**
23 | Copy of your stack trace here, please format it properly using triple back ticks (top left key on US keyboards!)
24 |
25 | **Screenshots**
26 | If applicable, add screenshots to help explain your problem.
27 |
28 | **OS (please complete the following information):**
29 | - OS: [e.g. Ubuntu, MacOS, etc. - Note this is *not* tested on Windows and likely will not be supported]
30 | - ssh2net version
31 | - ssh2python version
32 | - python version
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/workflows/commit.yaml:
--------------------------------------------------------------------------------
1 | name: linting and unit tests
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | max-parallel: 6
10 | matrix:
11 | os: [ubuntu-latest, macos-latest]
12 | python-version: [3.6, 3.7, 3.8]
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: set up python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v1
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: setup test env
20 | run: |
21 | python -m pip install --upgrade pip
22 | python -m pip install setuptools
23 | python -m pip install tox
24 | - name: run tox
25 | run: python -m tox --skip-missing-interpreters=true
26 |
27 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: re-test and publish to pypi
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | build:
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | max-parallel: 6
12 | matrix:
13 | os: [ubuntu-latest, macos-latest]
14 | python-version: [3.6, 3.7, 3.8]
15 | steps:
16 | - uses: actions/checkout@v1
17 | - name: set up python ${{ matrix.python-version }}
18 | uses: actions/setup-python@v1
19 | with:
20 | python-version: ${{ matrix.python-version }}
21 | - name: setup test env
22 | run: |
23 | python -m pip install --upgrade pip
24 | python -m pip install setuptools
25 | python -m pip install tox
26 | - name: run tox
27 | run: python -m tox --skip-missing-interpreters=true
28 |
29 | deploy:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v1
33 | - name: set up python 3.7
34 | uses: actions/setup-python@v1
35 | with:
36 | python-version: 3.7
37 | - name: setup publish env
38 | run: |
39 | python -m pip install --upgrade pip
40 | python -m pip install setuptools
41 | python -m pip install wheel
42 | python -m pip install twine
43 | - name: build and publish
44 | env:
45 | TWINE_USERNAME: ${{ secrets.PYPI_USER }}
46 | TWINE_PASSWORD: ${{ secrets.PYPI_PASS }}
47 | run: |
48 | python setup.py sdist bdist_wheel
49 | python -m twine upload dist/*
50 |
--------------------------------------------------------------------------------
/.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 | .hypothesis/
51 | .pytest_cache/
52 |
53 | # Environments
54 | .env
55 | .venv
56 | env/
57 | venv/
58 | ENV/
59 | env.bak/
60 | venv.bak/
61 |
62 | # pycharm
63 | .idea
64 |
65 | # mypy
66 | .mypy_cache/
67 | .dmypy.json
68 | dmypy.json
69 |
70 | # swap files
71 | *.swp
72 |
73 | # macos stuff
74 | .DS_Store
75 | */.DS_Store
76 |
77 | # private dir for notes and such
78 | private/
79 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | Be excellent to each other!
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | This was created as a learning project, so contributions are not expected, but are very welcome! Feel free to open PRs or Issues as needed. Any contributions would need to at a minimum successfully complete a tox run since there is no CI at this point.
4 |
5 | Between tox and the Makefile, I hope that testing should be pretty easy, with a big caveat that you will need to get vrnetlab setup and working for functional testing to occur in a reasonable and repeatable fashion.
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Carl Montanari
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | DOCKER_COMPOSE_FILE=docker-compose.yaml
2 | DOCKER_COMPOSE=docker-compose -f ${DOCKER_COMPOSE_FILE}
3 |
4 | start_dev_env:
5 | ${DOCKER_COMPOSE} \
6 | up -d \
7 | iosxe \
8 | nxos \
9 | junos \
10 | iosxr
11 |
12 | start_dev_env_iosxe:
13 | ${DOCKER_COMPOSE} \
14 | up -d \
15 | iosxe
16 |
17 | start_dev_env_nxos:
18 | ${DOCKER_COMPOSE} \
19 | up -d \
20 | nxos
21 |
22 | start_dev_env_iosxr:
23 | ${DOCKER_COMPOSE} \
24 | up -d \
25 | iosxr
26 |
27 | start_dev_env_junos:
28 | ${DOCKER_COMPOSE} \
29 | up -d \
30 | junos
31 |
32 | start_dev_env_eos:
33 | ${DOCKER_COMPOSE} \
34 | up -d \
35 | eos
36 |
37 | stop_dev_env:
38 | ${DOCKER_COMPOSE} \
39 | down
40 |
41 | # run with sudo!
42 | add_delay_dev_env:
43 | tc qdisc del dev br_ssh2net root; \
44 | tc qdisc add dev br_ssh2net root handle 1: prio; \
45 | tc qdisc add dev br_ssh2net parent 1:3 handle 30: tbf rate 20kbit buffer 1600 limit 3000; \
46 | tc qdisc add dev br_ssh2net parent 30:1 handle 31: netem delay 1000ms 10ms distribution normal loss 10%; \
47 | tc filter add dev br_ssh2net protocol ip parent 1:0 prio 3 u32 match ip dst 172.18.0.0/26 flowid 1:3
48 |
49 | # run with sudo!
50 | remove_delay_dev_env:
51 | tc qdisc del dev br_ssh2net root
52 |
53 | test_unit:
54 | python -m pytest \
55 | --cov=ssh2net \
56 | --cov-report html \
57 | --cov-report term \
58 | tests/unit/.
59 |
60 | test_functional:
61 | python -m pytest \
62 | --cov=ssh2net \
63 | --cov-report html \
64 | --cov-report term \
65 | tests/functional/. \
66 | --ignore tests/functional/cisco_iosxr \
67 | --ignore tests/functional/arista_eos \
68 | --ignore tests/functional/comparison_tests
69 |
70 | test_all:
71 | python -m pytest \
72 | --cov=ssh2net \
73 | --cov-report html \
74 | --cov-report term \
75 | tests/. \
76 | --ignore tests/functional/cisco_iosxr \
77 | --ignore tests/functional/arista_eos \
78 | --ignore tests/functional/comparison_tests
79 |
80 | test_iosxe:
81 | python -m pytest \
82 | --cov=ssh2net \
83 | --cov-report html \
84 | --cov-report term \
85 | tests/unit \
86 | tests/functional/cisco_iosxe
87 |
88 | test_nxos:
89 | python -m pytest \
90 | --cov=ssh2net \
91 | --cov-report html \
92 | --cov-report term \
93 | tests/unit \
94 | tests/functional/cisco_nxos
95 |
96 | test_iosxr:
97 | python -m pytest \
98 | --cov=ssh2net \
99 | --cov-report html \
100 | --cov-report term \
101 | tests/unit \
102 | tests/functional/cisco_iosxr
103 |
104 | test_junos:
105 | python -m pytest \
106 | --cov=ssh2net \
107 | --cov-report html \
108 | --cov-report term \
109 | tests/unit \
110 | tests/functional/juniper_junos
111 |
112 | test_eos:
113 | python -m pytest \
114 | --cov=ssh2net \
115 | --cov-report html \
116 | --cov-report term \
117 | tests/unit \
118 | tests/functional/arista_eos
119 |
120 | .PHONY: docs
121 | docs:
122 | python -m pdoc \
123 | --html \
124 | --output-dir docs \
125 | ssh2net \
126 | --force
127 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-midnight
--------------------------------------------------------------------------------
/comparison_tests/hosts.py:
--------------------------------------------------------------------------------
1 | IOSXE_TEST = {
2 | "host": "172.18.0.11",
3 | "username": "vrnetlab",
4 | "password": "VR-netlab9",
5 | "device_type": "cisco_xe",
6 | "test_commands": ["show run", "show version"],
7 | }
8 |
9 | NXOS_TEST = {
10 | "host": "172.18.0.12",
11 | "username": "vrnetlab",
12 | "password": "VR-netlab9",
13 | "device_type": "cisco_nxos",
14 | "test_commands": ["show run", "show version"],
15 | }
16 |
17 | IOSXR_TEST = {
18 | "host": "172.18.0.13",
19 | "username": "vrnetlab",
20 | "password": "VR-netlab9",
21 | "device_type": "cisco_iosxr",
22 | "test_commands": ["show run", "show version"],
23 | }
24 |
25 | # need to get arista_eos image in vrnetlab for testing + fix libssh2 keyboard interactive auth issue
26 | # EOS_TEST = {
27 | # "host": "172.18.0.14",
28 | # "username": "vrnetlab",
29 | # "password": "VR-netlab9",
30 | # "device_type": "arista_eos",
31 | # "test_commands": ["show run", "show version"],
32 | # }
33 |
34 | JUNOS_TEST = {
35 | "host": "172.18.0.15",
36 | "username": "vrnetlab",
37 | "password": "VR-netlab9",
38 | "device_type": "juniper_junos",
39 | "test_commands": ["show configuration", "show version"],
40 | }
41 |
--------------------------------------------------------------------------------
/comparison_tests/test_netmiko.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from datetime import datetime
3 |
4 | from netmiko import ConnectHandler
5 |
6 | import hosts
7 |
8 | IOSXE_TEST = hosts.IOSXE_TEST
9 | NXOS_TEST = hosts.NXOS_TEST
10 | test_hosts = [IOSXE_TEST, NXOS_TEST]
11 |
12 |
13 | def main():
14 | for host in test_hosts:
15 | commands = host.pop("test_commands")
16 | conn = ConnectHandler(**host)
17 | for command in commands:
18 | print(f"Sending command: '{command}'")
19 | print("*" * 80)
20 | command_start_time = datetime.now()
21 | r = conn.send_command(command)
22 | command_end_time = datetime.now()
23 | print(r)
24 | print("*" * 80)
25 | print(
26 | f"Sending command: '{command}' took {command_end_time - command_start_time} seconds!"
27 | )
28 | print("*" * 80)
29 | print()
30 |
31 |
32 | if __name__ == "__main__":
33 | start_time = datetime.now()
34 | main()
35 | end_time = datetime.now()
36 | print(f"Full test took {end_time - start_time} seconds!")
37 |
--------------------------------------------------------------------------------
/comparison_tests/test_ssh2net.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from datetime import datetime
3 |
4 | from ssh2net import SSH2Net
5 |
6 | import hosts
7 |
8 | IOSXE_TEST = hosts.IOSXE_TEST
9 | NXOS_TEST = hosts.NXOS_TEST
10 | test_hosts = [IOSXE_TEST, NXOS_TEST]
11 |
12 |
13 | def main():
14 | for host in test_hosts:
15 | del host["device_type"]
16 | commands = host.pop("test_commands")
17 | host["setup_host"] = host.pop("host")
18 | host["auth_user"] = host.pop("username")
19 | host["auth_password"] = host.pop("password")
20 | conn = SSH2Net(**host)
21 | conn.open_shell()
22 | for command in commands:
23 | print(f"Sending command: '{command}'")
24 | print("*" * 80)
25 | command_start_time = datetime.now()
26 | r = conn.send_inputs(command)
27 | command_end_time = datetime.now()
28 | print(r[0])
29 | print("*" * 80)
30 | print(
31 | f"Sending command: '{command}' took {command_end_time - command_start_time} seconds!"
32 | )
33 | print("*" * 80)
34 | print()
35 |
36 |
37 | if __name__ == "__main__":
38 | start_time = datetime.now()
39 | main()
40 | end_time = datetime.now()
41 | print(f"Full test took {end_time - start_time} seconds!")
42 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: "3.3"
3 |
4 | services:
5 | iosxe:
6 | hostname: cisco_iosxe
7 | privileged: true
8 | image: ssh2netiosxe
9 | networks:
10 | net1:
11 | ipv4_address: 172.18.0.11
12 |
13 | nxos:
14 | hostname: cisco_nxos
15 | privileged: true
16 | image: ssh2netnxos
17 | networks:
18 | net1:
19 | ipv4_address: 172.18.0.12
20 |
21 | iosxr:
22 | hostname: cisco_iosxr
23 | privileged: true
24 | image: ssh2netiosxr
25 | networks:
26 | net1:
27 | ipv4_address: 172.18.0.13
28 |
29 | eos:
30 | hostname: arista_eos
31 | privileged: true
32 | image: ssh2neteos
33 | networks:
34 | net1:
35 | ipv4_address: 172.18.0.14
36 |
37 | junos:
38 | hostname: juniper_junos
39 | privileged: true
40 | image: ssh2netjunos
41 | networks:
42 | net1:
43 | ipv4_address: 172.18.0.15
44 |
45 | networks:
46 | net1:
47 | driver: bridge
48 | ipam:
49 | config:
50 | - subnet: 172.18.0.0/26
51 | driver_opts:
52 | com.docker.network.bridge.name: br_ssh2net
53 |
54 |
--------------------------------------------------------------------------------
/docs/ssh2net/community/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.community API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.community
21 |
22 |
23 | ssh2net community platform drivers
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net community platform drivers"""
29 |
30 |
31 |
33 |
35 |
37 |
39 |
40 |
53 |
54 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/arista_eos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core.arista_eos API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core.arista_eos
21 |
22 |
23 | ssh2net arista eos driver
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net arista eos driver"""
29 |
30 |
31 |
40 |
42 |
44 |
46 |
47 |
65 |
66 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/cisco_iosxe/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core.cisco_iosxe API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core.cisco_iosxe
21 |
22 |
23 | ssh2net cisco iosxe driver
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net cisco iosxe driver"""
29 |
30 |
31 |
40 |
42 |
44 |
46 |
47 |
65 |
66 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/cisco_iosxr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core.cisco_iosxr API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core.cisco_iosxr
21 |
22 |
23 | ssh2net cisco iosxr driver
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net cisco iosxr driver"""
29 |
30 |
31 |
40 |
42 |
44 |
46 |
47 |
65 |
66 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/cisco_nxos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core.cisco_nxos API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core.cisco_nxos
21 |
22 |
23 | ssh2net cisco nxos driver
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net cisco nxos driver"""
29 |
30 |
31 |
40 |
42 |
44 |
46 |
47 |
65 |
66 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core
21 |
22 |
23 | ssh2net core platform drivers
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net core platform drivers"""
29 |
30 |
31 |
60 |
62 |
64 |
66 |
67 |
90 |
91 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/juniper_junos/helper.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core.juniper_junos.helper API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core.juniper_junos.helper
21 |
22 |
23 | ssh2net.core.juniper_junos.helper
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net.core.juniper_junos.helper"""
29 |
30 |
31 | def disable_paging(cls):
32 | """
33 | Disable paging and set screen width for Junos
34 |
35 | Args:
36 | cls: SSH2Net connection object
37 |
38 | Returns:
39 | N/A # noqa
40 |
41 | Raises:
42 | N/A # noqa
43 | """
44 | cls.send_inputs("set cli screen-length 0")
45 | cls.send_inputs("set cli screen-width 511")
46 |
47 |
48 |
50 |
52 |
53 |
54 |
55 |
56 | def disable_paging(cls)
57 |
58 | -
59 |
Disable paging and set screen width for Junos
60 | Args
61 |
62 | cls
63 | - SSH2Net connection object
64 |
65 | Returns
66 |
67 | N
/A
68 | # noqa
69 | -
70 |
71 | Raises
72 |
73 | N
/A
74 | # noqa
75 | -
76 |
77 |
78 |
79 | Expand source code
80 |
81 | def disable_paging(cls):
82 | """
83 | Disable paging and set screen width for Junos
84 |
85 | Args:
86 | cls: SSH2Net connection object
87 |
88 | Returns:
89 | N/A # noqa
90 |
91 | Raises:
92 | N/A # noqa
93 | """
94 | cls.send_inputs("set cli screen-length 0")
95 | cls.send_inputs("set cli screen-width 511")
96 |
97 |
98 |
99 |
100 |
102 |
103 |
121 |
122 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/docs/ssh2net/core/juniper_junos/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ssh2net.core.juniper_junos API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Module ssh2net.core.juniper_junos
21 |
22 |
23 | ssh2net juniper junos driver
24 |
25 |
26 | Expand source code
27 |
28 | """ssh2net juniper junos driver"""
29 |
30 |
31 |
44 |
46 |
48 |
50 |
51 |
70 |
71 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/examples/basic_usage/basic_usage_ssh2net_connecthandler_style.py:
--------------------------------------------------------------------------------
1 | from ssh2net import ConnectHandler
2 |
3 | my_device = {
4 | "device_type": "cisco_xe",
5 | "host": "172.18.0.11",
6 | "username": "vrnetlab",
7 | "password": "VR-netlab9",
8 | }
9 |
10 | driver = ConnectHandler(**my_device)
11 | output = driver.send_command("show version")
12 | # send_inputs returns a list of results; print the zeroith result
13 | print(output[0])
14 | driver.send_config_set(["interface loopback123", "description ssh2net was here"])
15 | output = driver.send_command("show run int loopback123")
16 | print(output[0])
17 | driver.send_config_set("no interface loopback123")
18 |
--------------------------------------------------------------------------------
/examples/basic_usage/basic_usage_ssh2net_driver_style.py:
--------------------------------------------------------------------------------
1 | from ssh2net.core.cisco_iosxe.driver import IOSXEDriver
2 |
3 | my_device = {"setup_host": "172.18.0.11", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
4 |
5 | iosxe_driver = IOSXEDriver
6 |
7 | with IOSXEDriver(**my_device) as conn:
8 | output = conn.send_command("show version")
9 | # send_inputs returns a list of results; print the zeroith result
10 | print(output[0])
11 | conn.send_config_set(["interface loopback123", "description ssh2net was here"])
12 | output = conn.send_command("show run int loopback123")
13 | print(output[0])
14 | conn.send_config_set("no interface loopback123")
15 |
--------------------------------------------------------------------------------
/examples/basic_usage/basic_usage_ssh2net_native_style.py:
--------------------------------------------------------------------------------
1 | from ssh2net import SSH2Net
2 |
3 | # Example assumes IOSXE, but should work on most platform where the command syntax below is valid!
4 |
5 | my_device = {"setup_host": "172.18.0.11", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
6 |
7 | # Example with context manager:
8 | with SSH2Net(**my_device) as conn:
9 | output = conn.send_inputs("show version")
10 | # send_inputs returns a list of results; print the zeroith result
11 | print(output[0])
12 | # native style doesn't handle privilege escalation/deescalation for you
13 | conn.send_inputs(["conf t", "interface loopback123", "description ssh2net was here", "end"])
14 | output = conn.send_inputs("show run int loopback123")
15 | print(output[0])
16 | conn.send_inputs(["conf t", "no interface loopback123", "end"])
17 |
18 | # Example without context manager; note you need to open the shell without the context manager!
19 | conn = SSH2Net(**my_device)
20 | conn.open_shell()
21 | output = conn.send_inputs("show version")
22 | print(output[0])
23 | conn.send_inputs(["conf t", "interface loopback123", "description ssh2net was here", "end"])
24 | output = conn.send_inputs("show run int loopback123")
25 | print(output[0])
26 | conn.send_inputs(["conf t", "no interface loopback123", "end"])
27 |
--------------------------------------------------------------------------------
/examples/logging/session_and_channel_log_diff_files.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import ssh2net
3 |
4 |
5 | # Set desired log format
6 | log_format = "%(asctime)s [%(levelname)s] in %(pathname)s:%(lineno)d \n\t %(message)s"
7 |
8 | # Setup session logging; session logging is connection stuff only -- i.e. socket/session/channel
9 | session_log_file = "session.log"
10 | # Set the "ssh2net_session" logger
11 | session_logger = logging.getLogger("ssh2net_session")
12 | # Set log level to level of your choice
13 | session_logger.setLevel(logging.DEBUG)
14 | session_logger_file_handler = logging.FileHandler(session_log_file)
15 | # Assign formatter to log handler
16 | session_logger_file_handler.setFormatter(logging.Formatter(log_format))
17 | # Add log handler to the session_logger
18 | session_logger.addHandler(session_logger_file_handler)
19 | # Do not propagate logs to stdout
20 | session_logger.propagate = False
21 |
22 | # Repeat similar steps as above for channel logger; channel logger captures reads/writes
23 | # ssh2net_channel logger removes duplicate entries from the log to avoid logs being massive
24 | channel_log_file = "channel.log"
25 | channel_logger = logging.getLogger("ssh2net_channel")
26 | channel_logger.setLevel(logging.DEBUG)
27 | channel_logger_file_handler = logging.FileHandler(channel_log_file)
28 | channel_logger_file_handler.setFormatter(logging.Formatter(log_format))
29 | channel_logger.addHandler(channel_logger_file_handler)
30 | channel_logger.propagate = False
31 |
32 | my_device = {"setup_host": "172.18.0.11", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
33 | with ssh2net.SSH2Net(**my_device) as conn:
34 | show_run = conn.send_inputs("show run")
35 |
36 | print(show_run[0])
37 |
--------------------------------------------------------------------------------
/examples/ssh_keys/ssh_key_basic.py:
--------------------------------------------------------------------------------
1 | from ssh2net.core.cisco_iosxe.driver import IOSXEDriver
2 |
3 | my_device = {
4 | "setup_host": "IP/NAME",
5 | "auth_user": "USERNAME",
6 | "auth_public_key": "/PATH/TO/KEY/TO/USE",
7 | }
8 |
9 |
10 | with IOSXEDriver(**my_device) as conn:
11 | output = conn.send_command("show version")
12 | print(output[0])
13 | conn.send_config_set(["interface loopback123", "description ssh2net was here"])
14 | output = conn.send_command("show run int loopback123")
15 | print(output[0])
16 | conn.send_config_set("no interface loopback123")
17 |
--------------------------------------------------------------------------------
/examples/textfsm/textfsm_parse_output.py:
--------------------------------------------------------------------------------
1 | from ssh2net.core.cisco_iosxe.driver import IOSXEDriver
2 |
3 | my_device = {"setup_host": "172.18.0.11", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
4 |
5 |
6 | with IOSXEDriver(**my_device) as conn:
7 | output = conn.send_command("show version")
8 | # as ssh2net always returns a list of outputs, pass the zeroith element of the output
9 | output = conn.textfsm_parse_output("show version", output[0])
10 | print(output)
11 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 100
3 | target-version = ['py37']
4 | include = '\.pyi?$'
5 | exclude = '''
6 | (
7 | /(
8 | \.eggs
9 | | \.git
10 | | \.hg
11 | | \.mypy_cache
12 | | \.tox
13 | | venv
14 | )/
15 | )
16 | '''
17 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | tox>=3.14.0
2 | black>=19.3b0
3 | pytest>=5.0.1
4 | pytest-cov>=2.7.1
5 | pyfakefs>=3.6
6 | pylama>=7.6.6
7 | pycodestyle>=2.5.0
8 | pydocstyle>=4.0.1
9 | pylint>=2.3.1
10 | darglint>=1.1.0
11 | pdoc3>=0.6.2
12 | netmiko>=2.4.1
13 | -r requirements.txt
14 | -r requirements-textfsm.txt
15 | -r requirements-paramiko.txt
16 |
--------------------------------------------------------------------------------
/requirements-paramiko.txt:
--------------------------------------------------------------------------------
1 | paramiko>=2.6.0
--------------------------------------------------------------------------------
/requirements-textfsm.txt:
--------------------------------------------------------------------------------
1 | textfsm>=1.1.0
2 | ntc_templates>=1.1.0
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | ssh2-python>=0.18.0-1
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [pylama]
2 | linters = mccabe,pycodestyle,pylint
3 | skip = tests/*,.tox/*,venv/*,build/*,private/*,comparison_tests/*,examples/*
4 |
5 | [pylama:pycodestyle]
6 | max_line_length = 100
7 |
8 | [pylama:pylint]
9 | rcfile = .pylintrc
10 |
11 | [pydocstyle]
12 | ignore = D101,D202,D212,D400,D406,D407,D408,D409,D415
13 | match-dir = ^ssh2net/*
14 |
15 | [darglint]
16 | docstring_style = google
17 |
18 | [mypy]
19 | python_version = 3.7
20 | strict_optional = False
21 | disallow_any_generics = False
22 | ignore_missing_imports = True
23 | warn_redundant_casts = True
24 | warn_unused_configs = True
25 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """ssh2net network ssh client library"""
3 | import setuptools
4 |
5 |
6 | __author__ = "Carl Montanari"
7 |
8 | with open("README.md", "r") as f:
9 | README = f.read()
10 |
11 | setuptools.setup(
12 | name="ssh2net",
13 | version="2020.01.10",
14 | author=__author__,
15 | author_email="carl.r.montanari@gmail.com",
16 | description="SSH client for network devices built on ssh2-python",
17 | long_description=README,
18 | long_description_content_type="text/markdown",
19 | url="https://github.com/carlmontanari/ssh2net",
20 | packages=setuptools.find_packages(),
21 | install_requires=["ssh2-python>=0.18.0-1"],
22 | extras_require={
23 | "textfsm": ["textfsm>=1.1.0", "ntc-templates>=1.1.0"],
24 | "paramiko": ["paramiko>=2.6.0"],
25 | },
26 | classifiers=[
27 | "License :: OSI Approved :: MIT License",
28 | "Programming Language :: Python :: 3",
29 | "Programming Language :: Python :: 3.6",
30 | "Programming Language :: Python :: 3.7",
31 | "Programming Language :: Python :: 3.8",
32 | "Operating System :: POSIX :: Linux",
33 | "Operating System :: MacOS",
34 | ],
35 | python_requires=">=3.6",
36 | )
37 |
--------------------------------------------------------------------------------
/ssh2net/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net network ssh client library"""
2 | import logging
3 | from logging import NullHandler
4 |
5 | from ssh2net.base import SSH2Net
6 | from ssh2net.channel import SSH2NetChannel
7 | from ssh2net.session import SSH2NetSession
8 | from ssh2net.netmiko_compatibility import connect_handler as ConnectHandler
9 | from ssh2net.ssh_config import SSH2NetSSHConfig
10 | from ssh2net.core.driver import BaseNetworkDriver
11 | from ssh2net.core.cisco_iosxe.driver import IOSXEDriver
12 | from ssh2net.core.cisco_nxos.driver import NXOSDriver
13 | from ssh2net.core.cisco_iosxr.driver import IOSXRDriver
14 | from ssh2net.core.arista_eos.driver import EOSDriver
15 | from ssh2net.core.juniper_junos.driver import JunosDriver
16 |
17 | __version__ = "2020.01.10"
18 | __all__ = (
19 | "SSH2Net",
20 | "SSH2NetSession",
21 | "SSH2NetChannel",
22 | "SSH2NetSSHConfig",
23 | "ConnectHandler",
24 | "BaseNetworkDriver",
25 | "IOSXEDriver",
26 | "NXOSDriver",
27 | "IOSXRDriver",
28 | "EOSDriver",
29 | "JunosDriver",
30 | )
31 |
32 |
33 | # Class to filter duplicate log entries for the channel logger
34 | # Stolen from: https://stackoverflow.com/questions/44691558/ \
35 | # suppress-multiple-messages-with-same-content-in-python-logging-module-aka-log-co
36 | class DuplicateFilter(logging.Filter):
37 | def filter(self, record):
38 | # Fields to compare to previous log entry if these fields match; skip log entry
39 | current_log = (record.module, record.levelno, record.msg)
40 | if current_log != getattr(self, "last_log", None):
41 | self.last_log = current_log
42 | return True
43 | return False
44 |
45 |
46 | # Setup session logger
47 | session_log = logging.getLogger(f"{__name__}_session")
48 | logging.getLogger(f"{__name__}_session").addHandler(NullHandler())
49 |
50 | # Setup channel logger
51 | channel_log = logging.getLogger(f"{__name__}_channel")
52 | # Add duplicate filter to channel log
53 | channel_log.addFilter(DuplicateFilter())
54 | logging.getLogger(f"{__name__}_channel").addHandler(NullHandler())
55 |
--------------------------------------------------------------------------------
/ssh2net/community/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net community platform drivers"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net core platform drivers"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/arista_eos/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net arista eos driver"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/arista_eos/driver.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.arista_eos.driver"""
2 | import re
3 | from typing import Any, Dict
4 |
5 | from ssh2net.core.driver import BaseNetworkDriver, PrivilegeLevel
6 |
7 |
8 | EOS_ARG_MAPPER = {
9 | "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
10 | "comms_return_char": "\n",
11 | "comms_pre_login_handler": "",
12 | "comms_disable_paging": "terminal length 0",
13 | }
14 |
15 | PRIVS = {
16 | "exec": (
17 | PrivilegeLevel(
18 | re.compile(r"^[a-z0-9.\-@()/:]{1,32}>$", flags=re.M | re.I),
19 | "exec",
20 | None,
21 | None,
22 | "privilege_exec",
23 | "enable",
24 | True,
25 | "Password:",
26 | True,
27 | 0,
28 | )
29 | ),
30 | "privilege_exec": (
31 | PrivilegeLevel(
32 | re.compile(r"^[a-z0-9.\-@/:]{1,32}#$", flags=re.M | re.I),
33 | "privilege_exec",
34 | "exec",
35 | "disable",
36 | "configuration",
37 | "configure terminal",
38 | False,
39 | False,
40 | True,
41 | 1,
42 | )
43 | ),
44 | "configuration": (
45 | PrivilegeLevel(
46 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config\)#$", flags=re.M | re.I),
47 | "configuration",
48 | "priv",
49 | "end",
50 | None,
51 | None,
52 | False,
53 | False,
54 | True,
55 | 2,
56 | )
57 | ),
58 | "special_configuration": (
59 | PrivilegeLevel(
60 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config[a-z0-9.\-@/:]{1,16}\)#$", flags=re.M | re.I),
61 | "special_configuration",
62 | "priv",
63 | "end",
64 | None,
65 | None,
66 | False,
67 | False,
68 | False,
69 | 3,
70 | )
71 | ),
72 | }
73 |
74 |
75 | class EOSDriver(BaseNetworkDriver):
76 | def __init__(self, **kwargs: Dict[str, Any]):
77 | """
78 | Initialize SSH2Net EOSDriver Object
79 |
80 | Args:
81 | **kwargs: keyword args to pass to inherited class(es)
82 |
83 | Returns:
84 | N/A # noqa
85 |
86 | Raises:
87 | N/A # noqa
88 | """
89 | super().__init__(**kwargs)
90 | self.privs = PRIVS
91 | self.default_desired_priv = "privilege_exec"
92 | self.textfsm_platform = "arista_eos"
93 |
--------------------------------------------------------------------------------
/ssh2net/core/cisco_iosxe/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net cisco iosxe driver"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/cisco_iosxe/driver.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.cisco_iosxe.driver"""
2 | import re
3 | from typing import Any, Dict
4 |
5 | from ssh2net.core.driver import BaseNetworkDriver, PrivilegeLevel
6 |
7 |
8 | IOSXE_ARG_MAPPER = {
9 | "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
10 | "comms_return_char": "\n",
11 | "comms_pre_login_handler": "",
12 | "comms_disable_paging": "terminal length 0",
13 | }
14 |
15 | PRIVS = {
16 | "exec": (
17 | PrivilegeLevel(
18 | re.compile(r"^[a-z0-9.\-@()/:]{1,32}>$", flags=re.M | re.I),
19 | "exec",
20 | None,
21 | None,
22 | "privilege_exec",
23 | "enable",
24 | True,
25 | "Password:",
26 | True,
27 | 0,
28 | )
29 | ),
30 | "privilege_exec": (
31 | PrivilegeLevel(
32 | re.compile(r"^[a-z0-9.\-@/:]{1,32}#$", flags=re.M | re.I),
33 | "privilege_exec",
34 | "exec",
35 | "disable",
36 | "configuration",
37 | "configure terminal",
38 | False,
39 | False,
40 | True,
41 | 1,
42 | )
43 | ),
44 | "configuration": (
45 | PrivilegeLevel(
46 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config\)#$", flags=re.M | re.I),
47 | "configuration",
48 | "priv",
49 | "end",
50 | None,
51 | None,
52 | False,
53 | False,
54 | True,
55 | 2,
56 | )
57 | ),
58 | "special_configuration": (
59 | PrivilegeLevel(
60 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config[a-z0-9.\-@/:]{1,16}\)#$", flags=re.M | re.I),
61 | "special_configuration",
62 | "priv",
63 | "end",
64 | None,
65 | None,
66 | False,
67 | False,
68 | False,
69 | 3,
70 | )
71 | ),
72 | }
73 |
74 |
75 | class IOSXEDriver(BaseNetworkDriver):
76 | def __init__(self, **kwargs: Dict[str, Any]):
77 | """
78 | Initialize SSH2Net IOSXEDriver Object
79 |
80 | Args:
81 | **kwargs: keyword args to pass to inherited class(es)
82 |
83 | Returns:
84 | N/A # noqa
85 |
86 | Raises:
87 | N/A # noqa
88 | """
89 | super().__init__(**kwargs)
90 | self.privs = PRIVS
91 | self.default_desired_priv = "privilege_exec"
92 | self.textfsm_platform = "cisco_ios"
93 |
--------------------------------------------------------------------------------
/ssh2net/core/cisco_iosxr/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net cisco iosxr driver"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/cisco_iosxr/driver.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.cisco_iosxr.driver"""
2 | import re
3 | import time
4 | from typing import Any, Dict
5 |
6 | from ssh2net.core.driver import BaseNetworkDriver, PrivilegeLevel
7 |
8 |
9 | IOSXR_ARG_MAPPER = {
10 | "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
11 | "comms_return_char": "\n",
12 | "comms_pre_login_handler": "",
13 | "comms_disable_paging": "terminal length 0",
14 | }
15 |
16 | PRIVS = {
17 | "privilege_exec": (
18 | PrivilegeLevel(
19 | re.compile(r"^[a-z0-9.\-@/:]{1,32}#$", flags=re.M | re.I),
20 | "privilege_exec",
21 | "exec",
22 | "disable",
23 | "configuration",
24 | "configure terminal",
25 | False,
26 | False,
27 | True,
28 | 1,
29 | )
30 | ),
31 | "configuration": (
32 | PrivilegeLevel(
33 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config\)#$", flags=re.M | re.I),
34 | "configuration",
35 | "priv",
36 | "end",
37 | None,
38 | None,
39 | False,
40 | False,
41 | True,
42 | 2,
43 | )
44 | ),
45 | "special_configuration": (
46 | PrivilegeLevel(
47 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config[a-z0-9.\-@/:]{1,16}\)#$", flags=re.M | re.I),
48 | "special_configuration",
49 | "priv",
50 | "end",
51 | None,
52 | None,
53 | False,
54 | False,
55 | False,
56 | 3,
57 | )
58 | ),
59 | }
60 |
61 |
62 | def comms_pre_login_handler(cls): # pylint: disable=W0613
63 | # sleep for session to establish; without this we never find base prompt
64 | time.sleep(1)
65 |
66 |
67 | class IOSXRDriver(BaseNetworkDriver):
68 | def __init__(self, **kwargs: Dict[str, Any]):
69 | """
70 | Initialize SSH2Net IOSXRDriver Object
71 |
72 | Args:
73 | **kwargs: keyword args to pass to inherited class(es)
74 |
75 | Returns:
76 | N/A # noqa
77 |
78 | Raises:
79 | N/A # noqa
80 | """
81 | super().__init__(**kwargs)
82 | self.privs = PRIVS
83 | self.default_desired_priv = "privilege_exec"
84 | self.textfsm_platform = "cisco_xr"
85 |
--------------------------------------------------------------------------------
/ssh2net/core/cisco_nxos/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net cisco nxos driver"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/cisco_nxos/driver.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.cisco_nxos.driver"""
2 | import re
3 | from typing import Any, Dict
4 |
5 | from ssh2net.core.driver import BaseNetworkDriver, PrivilegeLevel
6 |
7 |
8 | NXOS_ARG_MAPPER = {
9 | "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
10 | "comms_return_char": "\n",
11 | "comms_pre_login_handler": "",
12 | "comms_disable_paging": "terminal length 0",
13 | }
14 |
15 | PRIVS = {
16 | "exec": (
17 | PrivilegeLevel(
18 | re.compile(r"^[a-z0-9.\-@()/:]{1,32}>$", flags=re.M | re.I),
19 | "exec",
20 | None,
21 | None,
22 | "privilege_exec",
23 | "enable",
24 | True,
25 | "Password:",
26 | True,
27 | 0,
28 | )
29 | ),
30 | "privilege_exec": (
31 | PrivilegeLevel(
32 | re.compile(r"^[a-z0-9.\-@/:]{1,32}#$", flags=re.M | re.I),
33 | "privilege_exec",
34 | "exec",
35 | "disable",
36 | "configuration",
37 | "configure terminal",
38 | False,
39 | False,
40 | True,
41 | 1,
42 | )
43 | ),
44 | "configuration": (
45 | PrivilegeLevel(
46 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config\)#$", flags=re.M | re.I),
47 | "configuration",
48 | "priv",
49 | "end",
50 | None,
51 | None,
52 | False,
53 | False,
54 | True,
55 | 2,
56 | )
57 | ),
58 | "special_configuration": (
59 | PrivilegeLevel(
60 | re.compile(r"^[a-z0-9.\-@/:]{1,32}\(config[a-z0-9.\-@/:]{1,16}\)#$", flags=re.M | re.I),
61 | "special_configuration",
62 | "priv",
63 | "end",
64 | None,
65 | None,
66 | False,
67 | False,
68 | False,
69 | 3,
70 | )
71 | ),
72 | }
73 |
74 |
75 | class NXOSDriver(BaseNetworkDriver):
76 | def __init__(self, **kwargs: Dict[str, Any]):
77 | """
78 | Initialize SSH2Net NXOSDriver Object
79 |
80 | Args:
81 | **kwargs: keyword args to pass to inherited class(es)
82 |
83 | Returns:
84 | N/A # noqa
85 |
86 | Raises:
87 | N/A # noqa
88 | """
89 | super().__init__(**kwargs)
90 | self.privs = PRIVS
91 | self.default_desired_priv = "privilege_exec"
92 |
--------------------------------------------------------------------------------
/ssh2net/core/driver.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.driver"""
2 | import collections
3 | import re
4 | from typing import Any, Dict, Optional, Union
5 |
6 | from ssh2net.base import SSH2Net
7 | from ssh2net.exceptions import UnknownPrivLevel
8 | from ssh2net.helper import _textfsm_get_template, textfsm_parse
9 |
10 |
11 | PrivilegeLevel = collections.namedtuple(
12 | "PrivLevel",
13 | "pattern "
14 | "name "
15 | "deescalate_priv "
16 | "deescalate "
17 | "escalate_priv "
18 | "escalate "
19 | "escalate_auth "
20 | "escalate_prompt "
21 | "requestable "
22 | "level",
23 | )
24 |
25 | PRIVS = {}
26 |
27 |
28 | class BaseNetworkDriver(SSH2Net):
29 | def __init__(self, auth_secondary: Optional[Union[str]] = None, **kwargs: Dict[str, Any]):
30 | """
31 | Initialize SSH2Net BaseNetworkDriver Object
32 |
33 | Args:
34 | auth_secondary: password to use for secondary authentication (enable)
35 |
36 | Returns:
37 | N/A # noqa
38 |
39 | Raises:
40 | N/A # noqa
41 | """
42 | self.auth_secondary = auth_secondary
43 | super().__init__(**kwargs)
44 | self.privs = PRIVS
45 | self.default_desired_priv = None
46 | self.textfsm_platform = None
47 |
48 | def _determine_current_priv(self, current_prompt: str):
49 | """
50 | Determine current privilege level from prompt string
51 |
52 | Args:
53 | current_prompt: string of current prompt
54 |
55 | Returns:
56 | priv_level: NamedTuple of current privilege level
57 |
58 | Raises:
59 | UnknownPrivLevel: if privilege level cannot be determined # noqa
60 | # darglint raises DAR401 for some reason hence the noqa...
61 |
62 | """
63 | for priv_level in self.privs.values():
64 | if re.search(priv_level.pattern, current_prompt):
65 | return priv_level
66 | raise UnknownPrivLevel
67 |
68 | def _escalate(self) -> None:
69 | """
70 | Escalate to the next privilege level up
71 |
72 | Args:
73 | N/A # noqa
74 |
75 | Returns:
76 | N/A # noqa
77 |
78 | Raises:
79 | N/A # noqa
80 |
81 | """
82 | current_priv = self._determine_current_priv(self.get_prompt())
83 | if current_priv.escalate:
84 | if current_priv.escalate_auth:
85 | self.send_inputs_interact(
86 | (
87 | current_priv.escalate,
88 | current_priv.escalate_prompt,
89 | self.auth_enable,
90 | self.privs.get("escalate_priv"),
91 | ),
92 | hidden_response=True,
93 | )
94 | else:
95 | self.send_inputs(current_priv.escalate)
96 |
97 | def _deescalate(self) -> None:
98 | """
99 | Deescalate to the next privilege level down
100 |
101 | Args:
102 | N/A # noqa
103 |
104 | Returns:
105 | N/A # noqa
106 |
107 | Raises:
108 | N/A # noqa
109 |
110 | """
111 | current_priv = self._determine_current_priv(self.get_prompt())
112 | if current_priv.deescalate:
113 | self.send_inputs(current_priv.deescalate)
114 |
115 | def attain_priv(self, desired_priv) -> None:
116 | """
117 | Attain desired priv level
118 |
119 | Args:
120 | desired_priv: string name of desired privilege level
121 | (see ssh2net.core..driver for levels)
122 |
123 | Returns:
124 | N/A # noqa
125 |
126 | Raises:
127 | N/A # noqa
128 |
129 | """
130 | while True:
131 | current_priv = self._determine_current_priv(self.get_prompt())
132 | if current_priv == self.privs[desired_priv]:
133 | return
134 | if current_priv.level > self.privs[desired_priv].level:
135 | self._deescalate()
136 | else:
137 | self._escalate()
138 |
139 | def send_command(self, commands):
140 | """
141 | Send command(s)
142 |
143 | Args:
144 | commands: string or list of strings to send to device in privilege exec mode
145 |
146 | Returns:
147 | N/A # noqa
148 |
149 | Raises:
150 | N/A # noqa
151 | """
152 | self.attain_priv(self.default_desired_priv)
153 | result = self.send_inputs(commands)
154 | return result
155 |
156 | def send_config_set(self, configs):
157 | """
158 | Send configuration(s)
159 |
160 | Args:
161 | configs: string or list of strings to send to device in config mode
162 |
163 | Returns:
164 | N/A # noqa
165 |
166 | Raises:
167 | N/A # noqa
168 | """
169 | self.attain_priv("configuration")
170 | result = self.send_inputs(configs)
171 | self.attain_priv(self.default_desired_priv)
172 | return result
173 |
174 | def textfsm_parse_output(self, command: str, output: str) -> str:
175 | """
176 | Parse output with TextFSM and ntc-templates
177 |
178 | Args:
179 | command: command used to get output
180 | output: output from command
181 |
182 | Returns:
183 | output: parsed output
184 |
185 | Raises:
186 | N/A # noqa
187 | """
188 | template = _textfsm_get_template(self.textfsm_platform, command)
189 | if template:
190 | output = textfsm_parse(template, output)
191 | return output
192 |
--------------------------------------------------------------------------------
/ssh2net/core/juniper_junos/__init__.py:
--------------------------------------------------------------------------------
1 | """ssh2net juniper junos driver"""
2 |
--------------------------------------------------------------------------------
/ssh2net/core/juniper_junos/driver.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.juniper_junos.driver"""
2 | import re
3 | from typing import Any, Dict
4 |
5 | from ssh2net.core.driver import BaseNetworkDriver, PrivilegeLevel
6 |
7 |
8 | JUNOS_ARG_MAPPER = {
9 | "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
10 | "comms_return_char": "\n",
11 | "comms_pre_login_handler": "",
12 | "comms_disable_paging": "ssh2net.core.juniper_junos.helper.disable_paging",
13 | }
14 |
15 | PRIVS = {
16 | "exec": (
17 | PrivilegeLevel(
18 | re.compile(r"^[a-z0-9.\-@()/:]{1,32}>$", flags=re.M | re.I),
19 | "exec",
20 | None,
21 | None,
22 | "configuration",
23 | "configure",
24 | False,
25 | False,
26 | True,
27 | 0,
28 | )
29 | ),
30 | "configuration": (
31 | PrivilegeLevel(
32 | re.compile(r"^[a-z0-9.\-@()/:]{1,32}#$", flags=re.M | re.I),
33 | "configuration",
34 | "exec",
35 | "exit configuration-mode",
36 | None,
37 | None,
38 | False,
39 | False,
40 | True,
41 | 1,
42 | )
43 | ),
44 | }
45 |
46 |
47 | class JunosDriver(BaseNetworkDriver):
48 | def __init__(self, **kwargs: Dict[str, Any]):
49 | """
50 | Initialize SSH2Net IOSXEDriver Object
51 |
52 | Args:
53 | **kwargs: keyword args to pass to inherited class(es)
54 |
55 | Returns:
56 | N/A # noqa
57 |
58 | Raises:
59 | N/A # noqa
60 | """
61 | super().__init__(**kwargs)
62 | self.privs = PRIVS
63 | self.default_desired_priv = "exec"
64 |
--------------------------------------------------------------------------------
/ssh2net/core/juniper_junos/helper.py:
--------------------------------------------------------------------------------
1 | """ssh2net.core.juniper_junos.helper"""
2 |
3 |
4 | def disable_paging(cls):
5 | """
6 | Disable paging and set screen width for Junos
7 |
8 | Args:
9 | cls: SSH2Net connection object
10 |
11 | Returns:
12 | N/A # noqa
13 |
14 | Raises:
15 | N/A # noqa
16 | """
17 | cls.send_inputs("set cli screen-length 0")
18 | cls.send_inputs("set cli screen-width 511")
19 |
--------------------------------------------------------------------------------
/ssh2net/decorators.py:
--------------------------------------------------------------------------------
1 | """ssh2net.decorators"""
2 | import logging
3 | import multiprocessing.pool
4 | import time
5 |
6 |
7 | channel_log = logging.getLogger("ssh2net_channel")
8 |
9 |
10 | def operation_timeout(attribute):
11 | """
12 | Decorate an "operation" -- raises exception if the operation timeout is exceeded
13 |
14 | This decorator wraps an entire "operation" -- in other words for each send_input or
15 | send_inputs_interact this decorator wraps that entire operation. The `comms_operation_timeout`
16 | value governs this timeout decorator -- and as such it should always be larger than the base
17 | `session_timeout` argument plus the amount of time that `channel_timeout` decorator will delay
18 | while retrying the read operation.
19 |
20 | Note: Different versions for Windows vs POSIX systems as I think the Windows version is ugly
21 | and would prefer to not use it if possible. I imagine there is a better way to implement
22 | a OS dependant decorator, but for now this works!
23 |
24 | Args:
25 | attribute: attribute to inspect in class (to set timeout duration)
26 |
27 | Returns:
28 | decorate: wrapped function
29 |
30 | Raises:
31 | TimeoutError: if timeout exceeded
32 |
33 | """
34 | import signal # noqa
35 |
36 | def _raise_exception(signum, frame):
37 | raise TimeoutError
38 |
39 | def decorate(wrapped_func):
40 | def timeout_wrapper(self, *args, **kwargs):
41 | timeout_duration = getattr(self, attribute)
42 | if not timeout_duration:
43 | return wrapped_func
44 | old = signal.signal(signal.SIGALRM, _raise_exception)
45 | signal.setitimer(signal.ITIMER_REAL, timeout_duration)
46 | try:
47 | return wrapped_func(self, *args, **kwargs)
48 | finally:
49 | if timeout_duration:
50 | signal.setitimer(signal.ITIMER_REAL, 0)
51 | signal.signal(signal.SIGALRM, old)
52 |
53 | return timeout_wrapper
54 |
55 | return decorate
56 |
57 |
58 | def operation_timeout_win(attribute):
59 | """
60 | Decorate an "operation" -- raises exception if the operation timeout is exceeded
61 |
62 | See documentation for "operation_timeout"
63 |
64 | This is a variation of the "operation_timeout" decorator that supports windows. I think the
65 | Windows version is ugly and would prefer to not use it if possible, so the `ssh2net.channel`
66 | module checks for os type and imports the appropriate version to not use this if it can at all
67 | be avoided.
68 |
69 | Args:
70 | attribute: attribute to inspect in class (to set timeout duration)
71 |
72 | Returns:
73 | decorate: wrapped function
74 |
75 | Raises:
76 | TimeoutError: if timeout exceeded
77 |
78 | """
79 |
80 | def decorate(wrapped_func):
81 | def timeout_wrapper(self, *args, **kwargs):
82 | timeout_duration = getattr(self, attribute)
83 | pool = multiprocessing.pool.ThreadPool(processes=1)
84 | args = [self, *args]
85 | async_result = pool.apply_async(wrapped_func, args, kwargs)
86 | try:
87 | return async_result.get(timeout_duration)
88 | except multiprocessing.context.TimeoutError:
89 | raise TimeoutError
90 |
91 | return timeout_wrapper
92 |
93 | return decorate
94 |
95 |
96 | def channel_timeout(exception_to_check, attempts=5, starting_delay=0.1, backoff=2):
97 | """
98 | Decorate read operations; basic backoff timer to try to read channel for reasonable time
99 |
100 | This decorator wraps individual read operations. If/when the read operation times out
101 | (timeout configured by `session_timeout`), run a basic backoff process retrying five times
102 |
103 | Args:
104 | exception_to_check: Exception to handle; basically if this exception is seen, try again
105 | attempts: number of attempts to make to retry
106 | starting_delay: initial backoff delay
107 | backoff: backoff multiplier
108 |
109 | Returns:
110 | decorate: wrapped function
111 |
112 | Raises:
113 | N/A # noqa
114 |
115 | """
116 |
117 | def decorate(wrapped_func):
118 | def retry_wrapper(self, *args, **kwargs):
119 | attempt, delay = attempts, starting_delay
120 | while attempt > 1:
121 | try:
122 | return wrapped_func(self, *args, **kwargs)
123 | except exception_to_check:
124 | channel_log.info(f"Retrying read operation in {delay} seconds...")
125 | time.sleep(delay)
126 | attempt -= 1
127 | delay *= backoff
128 | return wrapped_func(self, *args, **kwargs)
129 |
130 | return retry_wrapper
131 |
132 | return decorate
133 |
--------------------------------------------------------------------------------
/ssh2net/exceptions.py:
--------------------------------------------------------------------------------
1 | """ssh2net.exceptions"""
2 |
3 |
4 | class ValidationError(Exception):
5 | pass
6 |
7 |
8 | class RequirementsNotSatisfied(Exception):
9 | pass
10 |
11 |
12 | class AuthenticationFailed(Exception):
13 | pass
14 |
15 |
16 | class SetupTimeout(Exception):
17 | pass
18 |
19 |
20 | class UnknownPrivLevel(Exception):
21 | pass
22 |
--------------------------------------------------------------------------------
/ssh2net/helper.py:
--------------------------------------------------------------------------------
1 | """ssh2net.helper"""
2 | import importlib
3 | from io import TextIOWrapper
4 | import pkg_resources # pylint: disable=C0411
5 | import warnings
6 |
7 |
8 | def validate_external_function(possible_function):
9 | """
10 | Validate string representing external function is a callable
11 |
12 | Args:
13 | possible_function: string "pointing" to external function
14 |
15 | Returns:
16 | None/Callable: None or callable function
17 |
18 | Raises:
19 | N/A # noqa
20 |
21 | """
22 | try:
23 | if not isinstance(possible_function, str):
24 | return None
25 | if "." not in possible_function:
26 | return None
27 | ext_func_path = possible_function.split(".")
28 | ext_module = ".".join(ext_func_path[:-1])
29 | ext_function = ext_func_path[-1]
30 | ext_module = importlib.import_module(ext_module)
31 | return getattr(ext_module, ext_function)
32 | except ModuleNotFoundError:
33 | return None
34 |
35 |
36 | def _textfsm_get_template(platform: str, command: str):
37 | """
38 | Find correct TextFSM template based on platform and command executed
39 |
40 | Args:
41 | platform: ntc-templates device type; i.e. cisco_ios, arista_eos, etc.
42 | command: string of command that was executed (to find appropriate template)
43 |
44 | Returns:
45 | None or TextIOWrapper of opened template
46 |
47 | """
48 | try:
49 | from textfsm.clitable import CliTable # noqa
50 | import ntc_templates # noqa
51 | except ModuleNotFoundError as exc:
52 | err = f"Module '{exc.name}' not installed!"
53 | msg = f"***** {err} {'*' * (80 - len(err))}"
54 | fix = (
55 | f"To resolve this issue, install '{exc.name}'. You can do this in one of the following"
56 | " ways:\n"
57 | "1: 'pip install -r requirements-textfsm.txt'\n"
58 | "2: 'pip install ssh2net[textfsm]'"
59 | )
60 | warning = "\n" + msg + "\n" + fix + "\n" + msg
61 | warnings.warn(warning)
62 | return None
63 | template_dir = pkg_resources.resource_filename("ntc_templates", "templates")
64 | cli_table = CliTable("index", template_dir)
65 | template_index = cli_table.index.GetRowMatch({"Platform": platform, "Command": command})
66 | if not template_index:
67 | return None
68 | template_name = cli_table.index.index[template_index]["Template"]
69 | template = open(f"{template_dir}/{template_name}")
70 | return template
71 |
72 |
73 | def textfsm_parse(template, output):
74 | """
75 | Parse output with TextFSM and ntc-templates, try to return structured output
76 |
77 | Args:
78 | template: TextIOWrapper or string path to template to use to parse data
79 | output: unstructured output from device to parse
80 |
81 | Returns:
82 | output: structured data
83 |
84 | """
85 | import textfsm # noqa
86 |
87 | if not isinstance(template, TextIOWrapper):
88 | template = open(template)
89 | re_table = textfsm.TextFSM(template)
90 | try:
91 | output = re_table.ParseText(output)
92 | return output
93 | except textfsm.parser.TextFSMError:
94 | pass
95 | return output
96 |
--------------------------------------------------------------------------------
/ssh2net/netmiko_compatibility.py:
--------------------------------------------------------------------------------
1 | """ssh2net.netmiko_compatibility"""
2 | from ssh2net.core.cisco_iosxe.driver import IOSXEDriver, IOSXE_ARG_MAPPER
3 | from ssh2net.core.cisco_nxos.driver import NXOSDriver, NXOS_ARG_MAPPER
4 | from ssh2net.core.cisco_iosxr.driver import IOSXRDriver, IOSXR_ARG_MAPPER
5 | from ssh2net.core.arista_eos.driver import EOSDriver, EOS_ARG_MAPPER
6 | from ssh2net.core.juniper_junos.driver import JunosDriver, JUNOS_ARG_MAPPER
7 |
8 | NETMIKO_DEVICE_TYPE_MAPPER = {
9 | "cisco_ios": {"driver": IOSXEDriver, "arg_mapper": IOSXE_ARG_MAPPER},
10 | "cisco_xe": {"driver": IOSXEDriver, "arg_mapper": IOSXE_ARG_MAPPER},
11 | "cisco_nxos": {"driver": NXOSDriver, "arg_mapper": NXOS_ARG_MAPPER},
12 | "cisco_xr": {"driver": IOSXRDriver, "arg_mapper": IOSXR_ARG_MAPPER},
13 | "arista_eos": {"driver": EOSDriver, "arg_mapper": EOS_ARG_MAPPER},
14 | "juniper_junos": {"driver": JunosDriver, "arg_mapper": JUNOS_ARG_MAPPER},
15 | }
16 |
17 | VALID_SSH2NET_KWARGS = {
18 | "setup_host",
19 | "setup_validate_host",
20 | "setup_port",
21 | "setup_timeout",
22 | "setup_ssh_config_file",
23 | "session_keepalive",
24 | "session_keepalive_interval",
25 | "session_timeout",
26 | "auth_user",
27 | "auth_password",
28 | "auth_public_key",
29 | "comms_prompt_regex",
30 | "comms_operation_timeout",
31 | "comms_return_char",
32 | "comms_pre_login_handler",
33 | "comms_disable_paging",
34 | }
35 |
36 |
37 | def connect_handler(auto_open=True, **kwargs):
38 | """
39 | Convert netmiko style "ConnectHandler" device creation to SSH2Net style
40 |
41 | Args:
42 | auto_open: auto open connection or not (primarily for testing purposes)
43 | **kwargs: keyword arguments
44 |
45 | Returns:
46 | driver: SSH2Net connection object for specified device-type
47 |
48 | Raises:
49 | TypeError: if unsupported netmiko device type is provided
50 |
51 | """
52 | if kwargs["device_type"] not in NETMIKO_DEVICE_TYPE_MAPPER.keys():
53 | raise TypeError(f"Unsupported netmiko device type for ssh2net: {kwargs['device_type']}")
54 |
55 | driver_info = NETMIKO_DEVICE_TYPE_MAPPER.get(kwargs["device_type"])
56 | driver_class = driver_info["driver"]
57 | driver_args = driver_info["arg_mapper"]
58 | kwargs.pop("device_type")
59 |
60 | transformed_kwargs = transform_netmiko_kwargs(kwargs)
61 | final_kwargs = {**transformed_kwargs, **driver_args}
62 |
63 | driver = driver_class(**final_kwargs)
64 |
65 | if auto_open:
66 | driver.open_shell()
67 |
68 | return driver
69 |
70 |
71 | def transform_netmiko_kwargs(kwargs):
72 | """
73 | Transform netmiko style ConnectHandler arguments to ssh2net style
74 |
75 | Args:
76 | kwargs: netmiko-style ConnectHandler kwargs to transform to ssh2net style
77 |
78 | Returns:
79 | transformed_kwargs: converted keyword arguments
80 |
81 | """
82 | host = kwargs.pop("host", None)
83 | ip = kwargs.pop("ip", None)
84 | kwargs["setup_host"] = host if host is not None else ip
85 | kwargs["setup_validate_host"] = False
86 | kwargs["setup_port"] = kwargs.pop("port", 22)
87 | kwargs["setup_timeout"] = 5
88 | kwargs["setup_ssh_config_file"] = kwargs.pop("ssh_config_file", False)
89 | kwargs["session_keepalive"] = False
90 | kwargs["session_keepalive_interval"] = 10
91 | if "global_delay_factor" in kwargs.keys():
92 | kwargs["session_timeout"] = kwargs["global_delay_factor"] * 5000
93 | kwargs.pop("global_delay_factor")
94 | else:
95 | kwargs["session_timeout"] = 5000
96 | kwargs["auth_user"] = kwargs.pop("username")
97 | kwargs["auth_password"] = kwargs.pop("password", None)
98 | kwargs["auth_public_key"] = kwargs.pop("key_file", None)
99 | kwargs["comms_prompt_regex"] = ""
100 | kwargs["comms_operation_timeout"] = 10
101 | kwargs["comms_return_char"] = ""
102 | kwargs["comms_pre_login_handler"] = ""
103 | kwargs["comms_disable_paging"] = ""
104 |
105 | transformed_kwargs = {k: v for (k, v) in kwargs.items() if k in VALID_SSH2NET_KWARGS}
106 |
107 | return transformed_kwargs
108 |
--------------------------------------------------------------------------------
/ssh2net/session_miko.py:
--------------------------------------------------------------------------------
1 | """ssh2net.session_miko"""
2 | import logging
3 | import time
4 | import warnings
5 |
6 | from paramiko.ssh_exception import AuthenticationException
7 |
8 | from ssh2net.exceptions import AuthenticationFailed, RequirementsNotSatisfied
9 |
10 |
11 | class SSH2NetSessionParamiko:
12 | def __init__(self, p_self):
13 | """
14 | Initialize SSH2NetSessionParamiko Object
15 |
16 | This object, through composition, allows for using Paramiko as the underlying "driver"
17 | for SSH2Net instead of the default "ssh2-python". Paramiko will be ever so slightly slower
18 | but as you will most likely be I/O constrained it shouldn't matter! "ssh2-python" as of
19 | 20 October 2019 has a bug preventing keyboard interactive authentication from working as
20 | desired; this is the reason Paramiko is in here now!
21 |
22 | Args:
23 | p_self: SSH2Net object
24 |
25 | Returns:
26 | N/A # noqa
27 |
28 | Raises:
29 | N/A # noqa
30 |
31 | """
32 | self.__dict__ = p_self.__dict__
33 | self._session_alive = p_self._session_alive
34 | self._session_open = p_self._session_open
35 | self._channel_alive = p_self._channel_alive
36 |
37 | def _session_open_connect(self) -> None:
38 | """
39 | Perform session handshake for paramiko (instead of default ssh2-python)
40 |
41 | Args:
42 | N/A # noqa
43 |
44 | Returns:
45 | N/A # noqa
46 |
47 | Raises:
48 | RequirementsNotSatisfied: if paramiko is not installed
49 | Exception: catch all for unknown exceptions during session handshake
50 |
51 | """
52 | try:
53 | from paramiko import Transport # noqa
54 | except ModuleNotFoundError as exc:
55 | err = f"Module '{exc.name}' not installed!"
56 | msg = f"***** {err} {'*' * (80 - len(err))}"
57 | fix = (
58 | f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
59 | "following ways:\n"
60 | "1: 'pip install -r requirements-paramiko.txt'\n"
61 | "2: 'pip install ssh2net[paramiko]'"
62 | )
63 | warning = "\n" + msg + "\n" + fix + "\n" + msg
64 | warnings.warn(warning)
65 | raise RequirementsNotSatisfied
66 | try:
67 | self.session = Transport(self.sock)
68 | self.session.start_client()
69 | self.session.set_timeout = self._set_timeout
70 | except Exception as exc:
71 | logging.critical(
72 | f"Failed to complete handshake with host {self.host}; " f"Exception: {exc}"
73 | )
74 | raise exc
75 |
76 | def _session_public_key_auth(self) -> None:
77 | """
78 | Perform public key based auth on SSH2NetSession
79 |
80 | Args:
81 | N/A # noqa
82 |
83 | Returns:
84 | N/A # noqa
85 |
86 | Raises:
87 | Exception: catch all for unhandled exceptions
88 |
89 | """
90 | try:
91 | self.session.auth_publickey(self.auth_user, self.auth_public_key)
92 | except AuthenticationException:
93 | logging.critical(f"Public key authentication with host {self.host} failed.")
94 | except Exception as exc:
95 | logging.critical(
96 | "Unknown error occurred during public key authentication with host "
97 | f"{self.host}; Exception: {exc}"
98 | )
99 | raise exc
100 |
101 | def _session_password_auth(self) -> None:
102 | """
103 | Perform password or keyboard interactive based auth on SSH2NetSession
104 |
105 | Args:
106 | N/A # noqa
107 |
108 | Returns:
109 | N/A # noqa
110 |
111 | Raises:
112 | AuthenticationFailed: if authentication fails
113 | Exception: catch all for unknown other exceptions
114 |
115 | """
116 | try:
117 | self.session.auth_password(self.auth_user, self.auth_password)
118 | except AuthenticationException as exc:
119 | logging.critical(
120 | f"Password authentication with host {self.host} failed. Exception: {exc}."
121 | "\n\tNote: Paramiko automatically attempts both standard auth as well as keyboard "
122 | "interactive auth. Paramiko exception about bad auth type may be misleading!"
123 | )
124 | raise AuthenticationFailed
125 | except Exception as exc:
126 | logging.critical(
127 | "Unknown error occurred during password authentication with host "
128 | f"{self.host}; Exception: {exc}"
129 | )
130 | raise exc
131 |
132 | def _channel_open_driver(self) -> None:
133 | """
134 | Open channel
135 |
136 | Args:
137 | N/A # noqa
138 |
139 | Returns:
140 | N/A # noqa
141 |
142 | Raises:
143 | N/A # noqa
144 |
145 | """
146 | self.channel = self.session.open_session()
147 | self.channel.get_pty()
148 | logging.debug(f"Channel to host {self.host} opened")
149 |
150 | def _channel_invoke_shell(self) -> None:
151 | """
152 | Invoke shell on channel
153 |
154 | Additionally, this "re-points" some ssh2net method calls to the appropriate paramiko
155 | methods. This happens as ssh2net is primarily built on "ssh2-python" and there is not
156 | full parity between paramiko/ssh2-python.
157 |
158 | Args:
159 | N/A # noqa
160 |
161 | Returns:
162 | N/A # noqa
163 |
164 | Raises:
165 | N/A # noqa
166 |
167 | """
168 | self._shell = True
169 | self.channel.invoke_shell()
170 | self.channel.read = self._paramiko_read_channel
171 | self.channel.write = self.channel.sendall
172 | self.session.set_blocking = self._set_blocking
173 | self.channel.flush = self._flush
174 |
175 | def _paramiko_read_channel(self):
176 | """
177 | Patch channel.read method for paramiko driver
178 |
179 | "ssh2-python" returns a tuple of bytes and data, "paramiko" simply returns the data
180 | from the channel, patch this for parity with "ssh2-python".
181 |
182 | Args:
183 | N/A # noqa
184 |
185 | Returns:
186 | N/A # noqa
187 |
188 | Raises:
189 | N/A # noqa
190 |
191 | """
192 | channel_read = self.channel.recv(1024)
193 | return None, channel_read
194 |
195 | def _flush(self):
196 | """
197 | Patch a "flush" method for paramiko driver
198 |
199 | Need to investigate this further for two things:
200 | 1) is "flush" even necessary when using ssh2-python driver?
201 | 2) if it is necessary, is there a combination of reads/writes that would implement
202 | this in a sane fashion for paramiko
203 |
204 | Args:
205 | N/A # noqa
206 |
207 | Returns:
208 | N/A # noqa
209 |
210 | Raises:
211 | N/A # noqa
212 |
213 | """
214 | while True:
215 | time.sleep(0.01)
216 | if self.channel.recv_ready():
217 | self._paramiko_read_channel()
218 | else:
219 | self.channel.write("\n")
220 | return
221 |
222 | def _set_blocking(self, blocking):
223 | # Add docstring
224 | # need to reset timeout because it seems paramiko sets it to 0 if you set to non blocking
225 | # paramiko uses seconds instead of ms
226 | self.channel.setblocking(blocking)
227 | self.channel.settimeout(self.session_timeout / 1000)
228 |
229 | def _set_timeout(self, timeout):
230 | # paramiko uses seconds instead of ms
231 | self.channel.settimeout(timeout / 1000)
232 |
--------------------------------------------------------------------------------
/ssh2net/session_ssh2.py:
--------------------------------------------------------------------------------
1 | """ssh2net.session_ssh2"""
2 | import logging
3 |
4 | from ssh2.session import Session
5 | from ssh2.exceptions import AuthenticationError
6 |
7 | from ssh2net.exceptions import AuthenticationFailed
8 |
9 |
10 | class SSH2NetSessionSSH2:
11 | def __init__(self, p_self):
12 | """
13 | Initialize SSH2NetSessionSSH2 Object
14 |
15 | This is the default underlying "driver" for ssh2net. This has been pulled out of the
16 | "base" SSH2NetSession class to provide a mechanism for supporting both "ssh2-python" and
17 | "paramiko".
18 |
19 | Args:
20 | p_self: SSH2Net object
21 |
22 | Returns:
23 | N/A # noqa
24 |
25 | Raises:
26 | N/A # noqa
27 |
28 | """
29 | self.__dict__ = p_self.__dict__
30 | self._session_alive = p_self._session_alive
31 | self._session_open = p_self._session_open
32 | self._channel_alive = p_self._channel_alive
33 |
34 | def _session_open_connect(self) -> None:
35 | """
36 | Perform session handshake
37 |
38 | Args:
39 | N/A # noqa
40 |
41 | Returns:
42 | N/A # noqa
43 |
44 | Raises:
45 | Exception: catch all for unknown exceptions during session handshake
46 |
47 | """
48 | self.session = Session()
49 | if self.session_timeout:
50 | self.session.set_timeout(self.session_timeout)
51 | try:
52 | self.session.handshake(self.sock)
53 | except Exception as exc:
54 | logging.critical(
55 | f"Failed to complete handshake with host {self.host}; " f"Exception: {exc}"
56 | )
57 | raise exc
58 |
59 | def _session_public_key_auth(self) -> None:
60 | """
61 | Perform public key based auth on SSH2NetSession
62 |
63 | Args:
64 | N/A # noqa
65 |
66 | Returns:
67 | N/A # noqa
68 |
69 | Raises:
70 | Exception: catch all for unhandled exceptions
71 |
72 | """
73 | try:
74 | self.session.userauth_publickey_fromfile(self.auth_user, self.auth_public_key)
75 | except AuthenticationError:
76 | logging.critical(f"Public key authentication with host {self.host} failed. ")
77 | except Exception as exc:
78 | logging.critical(
79 | "Unknown error occurred during public key authentication with host "
80 | f"{self.host}; Exception: {exc}"
81 | )
82 | raise exc
83 |
84 | def _session_password_auth(self) -> None:
85 | """
86 | Perform password based auth on SSH2NetSession
87 |
88 | Args:
89 | N/A # noqa
90 |
91 | Returns:
92 | N/A # noqa
93 |
94 | Raises:
95 | AuthenticationFailed: if authentication fails
96 | Exception: catch all for unknown other exceptions
97 |
98 | """
99 | try:
100 | self.session.userauth_password(self.auth_user, self.auth_password)
101 | except AuthenticationError as exc:
102 | logging.critical(
103 | f"Password authentication with host {self.host} failed. Exception: {exc}."
104 | f"\n\tTrying keyboard interactive auth..."
105 | )
106 | try:
107 | self.session.userauth_keyboardinteractive(self.auth_user, self.auth_password)
108 | except AuthenticationError as exc:
109 | logging.critical(
110 | f"Keyboard interactive authentication with host {self.host} failed. "
111 | f"Exception: {exc}."
112 | )
113 | raise AuthenticationFailed
114 | except Exception as exc:
115 | logging.critical(
116 | "Unknown error occurred during keyboard interactive authentication with host "
117 | f"{self.host}; Exception: {exc}"
118 | )
119 | raise exc
120 | except Exception as exc:
121 | logging.critical(
122 | "Unknown error occurred during password authentication with host "
123 | f"{self.host}; Exception: {exc}"
124 | )
125 | raise exc
126 |
127 | def _channel_open_driver(self) -> None:
128 | """
129 | Open channel
130 |
131 | Args:
132 | N/A # noqa
133 |
134 | Returns:
135 | N/A # noqa
136 |
137 | Raises:
138 | N/A # noqa
139 |
140 | """
141 | self.channel = self.session.open_session()
142 | self.channel.pty()
143 | logging.debug(f"Channel to host {self.host} opened")
144 |
145 | def _channel_invoke_shell(self) -> None:
146 | """
147 | Invoke shell on channel
148 |
149 | Args:
150 | N/A # noqa
151 |
152 | Returns:
153 | N/A # noqa
154 |
155 | Raises:
156 | N/A # noqa
157 |
158 | """
159 | self._shell = True
160 | self.channel.shell()
161 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlmontanari/ssh2net/55e969b6d44ec3f2bd2ebbd8dedd68b99bee4c5b/tests/__init__.py
--------------------------------------------------------------------------------
/tests/functional/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlmontanari/ssh2net/55e969b6d44ec3f2bd2ebbd8dedd68b99bee4c5b/tests/functional/__init__.py
--------------------------------------------------------------------------------
/tests/functional/arista_eos/expected_output/show_run:
--------------------------------------------------------------------------------
1 | ! Command: show running-config
2 | ! device: localhost (vEOS, EOS-4.22.1F)
3 | !
4 | ! boot system flash:/vEOS-lab.swi
5 | !
6 | transceiver qsfp default-mode 4x10G
7 | !
8 | spanning-tree mode mstp
9 | !
10 | no aaa root
11 | !
12 | username vrnetlab role network-admin secret sha512 $6$LjhU1lKTyobLZJoG$PA5ZPYf8E3Xpto3BmEMG.9ItXUcsa.Bu8YPwoSX5GKlH/e.0qeP7GJh4ghTIwKySfd9GBZK1FvbtnCSy0Jlh2.
13 | !
14 | interface Ethernet1
15 | !
16 | interface Ethernet2
17 | !
18 | interface Ethernet3
19 | !
20 | interface Ethernet4
21 | !
22 | interface Ethernet5
23 | !
24 | interface Ethernet6
25 | !
26 | interface Ethernet7
27 | !
28 | interface Ethernet8
29 | !
30 | interface Ethernet9
31 | !
32 | interface Ethernet10
33 | !
34 | interface Ethernet11
35 | !
36 | interface Ethernet12
37 | !
38 | interface Ethernet13
39 | !
40 | interface Ethernet14
41 | !
42 | interface Ethernet15
43 | !
44 | interface Ethernet16
45 | !
46 | interface Ethernet17
47 | !
48 | interface Ethernet18
49 | !
50 | interface Ethernet19
51 | !
52 | interface Management1
53 | ip address 10.0.0.15/24
54 | !
55 | no ip routing
56 | !
57 | management api http-commands
58 | protocol unix-socket
59 | no shutdown
60 | !
61 | end
--------------------------------------------------------------------------------
/tests/functional/arista_eos/expected_output/show_run_no_strip:
--------------------------------------------------------------------------------
1 | ! Command: show running-config
2 | ! device: localhost (vEOS, EOS-4.22.1F)
3 | !
4 | ! boot system flash:/vEOS-lab.swi
5 | !
6 | transceiver qsfp default-mode 4x10G
7 | !
8 | spanning-tree mode mstp
9 | !
10 | no aaa root
11 | !
12 | username vrnetlab role network-admin secret sha512 $6$LjhU1lKTyobLZJoG$PA5ZPYf8E3Xpto3BmEMG.9ItXUcsa.Bu8YPwoSX5GKlH/e.0qeP7GJh4ghTIwKySfd9GBZK1FvbtnCSy0Jlh2.
13 | !
14 | interface Ethernet1
15 | !
16 | interface Ethernet2
17 | !
18 | interface Ethernet3
19 | !
20 | interface Ethernet4
21 | !
22 | interface Ethernet5
23 | !
24 | interface Ethernet6
25 | !
26 | interface Ethernet7
27 | !
28 | interface Ethernet8
29 | !
30 | interface Ethernet9
31 | !
32 | interface Ethernet10
33 | !
34 | interface Ethernet11
35 | !
36 | interface Ethernet12
37 | !
38 | interface Ethernet13
39 | !
40 | interface Ethernet14
41 | !
42 | interface Ethernet15
43 | !
44 | interface Ethernet16
45 | !
46 | interface Ethernet17
47 | !
48 | interface Ethernet18
49 | !
50 | interface Ethernet19
51 | !
52 | interface Management1
53 | ip address 10.0.0.15/24
54 | !
55 | no ip routing
56 | !
57 | management api http-commands
58 | protocol unix-socket
59 | no shutdown
60 | !
61 | end
--------------------------------------------------------------------------------
/tests/functional/arista_eos/ext_test_funcs.py:
--------------------------------------------------------------------------------
1 | def eos_disable_paging(cls):
2 | cls.send_inputs("term length 0")
3 |
--------------------------------------------------------------------------------
/tests/functional/arista_eos/test_eos.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import re
3 |
4 | import pytest
5 |
6 | from tests.functional.base_functional_tests import BaseFunctionalTest
7 | import ssh2net
8 |
9 | TEST_DEVICE = {"setup_host": "172.18.0.14", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
10 |
11 | dummy_conn = ssh2net.IOSXEDriver(**TEST_DEVICE)
12 | PRIV_LEVELS = dummy_conn.privs
13 |
14 |
15 | class TestEOS(BaseFunctionalTest):
16 | def setup_method(self):
17 | self.platform_driver = ssh2net.EOSDriver
18 |
19 | self.device_type = Path(__file__).resolve().parts[-2]
20 | self.func_test_dir = (
21 | f"{Path(ssh2net.__file__).parents[1]}/tests/functional/{self.device_type}/"
22 | )
23 | self.test_device = TEST_DEVICE
24 | # ensure eos device gets into "priv_exec" mode since base image drops you into just "exec"
25 | self.test_device["comms_pre_login_handler"] = self.pre_login_handler
26 | self.disable_paging_ext_function = f"tests.functional.{self.device_type}.ext_test_funcs.{self.device_type.split('_')[1]}_disable_paging"
27 |
28 | @staticmethod
29 | def _replace_trailing_chars_running_config(input_data):
30 | execute_trailing_chars_pattern = re.compile(r"^end.*$", flags=re.M | re.I | re.S)
31 | input_data = re.sub(execute_trailing_chars_pattern, "end", input_data)
32 | return input_data
33 |
34 | @staticmethod
35 | def replace_timestamps(input_data):
36 | datetime_pattern = re.compile(
37 | r"(mon|tue|wed|thu|fri|sat|sun)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+\d+\s+\d+:\d+:\d+\s+\d+$",
38 | flags=re.M | re.I,
39 | )
40 | input_data = re.sub(datetime_pattern, "TIME_STAMP_REPLACED", input_data)
41 | return input_data
42 |
43 | @staticmethod
44 | def _replace_crypto_strings(input_data):
45 | crypto_pattern = re.compile(r"secret\ssha512\s[\w$\.\/]+$", flags=re.M | re.I)
46 | input_data = re.sub(crypto_pattern, "CRYPTO_REPLACED", input_data)
47 | return input_data
48 |
49 | def disable_paging(self, cls):
50 | cls.send_inputs("term length 0")
51 |
52 | def pre_login_handler(self, cls):
53 | cls.send_inputs("enable")
54 |
55 | def test_show_run_execute(self):
56 | pytest.skip("no ssh2 support for keyboard interactive auth")
57 |
58 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
59 | def test_show_run_inputs(self, setup_use_paramiko):
60 | super().test_show_run_inputs(setup_use_paramiko)
61 |
62 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
63 | def test_show_run_inputs_no_strip_prompt(self, setup_use_paramiko):
64 | super().test_show_run_inputs_no_strip_prompt(setup_use_paramiko)
65 |
66 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
67 | def test_send_inputs_interact(self, setup_use_paramiko):
68 | pytest.skip("dont know what to do that is interactive on eos...?")
69 |
70 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
71 | def test_disable_paging_function(self, setup_use_paramiko):
72 | super().test_disable_paging_function(setup_use_paramiko)
73 |
74 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
75 | def test_disable_paging_external_function(self, setup_use_paramiko):
76 | super().test_disable_paging_external_function(setup_use_paramiko)
77 |
78 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
79 | @pytest.mark.parametrize("priv_level", [priv for priv in PRIV_LEVELS.values()])
80 | def test_acquire_all_priv_levels(self, setup_use_paramiko, priv_level):
81 | super().test_acquire_all_priv_levels(setup_use_paramiko, priv_level)
82 |
83 | @pytest.mark.parametrize("setup_use_paramiko", [True], ids=["paramiko"])
84 | def test__determine_current_priv_special_configuration(self, setup_use_paramiko):
85 | with self.platform_driver(
86 | **self.test_device, setup_use_paramiko=setup_use_paramiko
87 | ) as conn:
88 | conn.send_inputs(["configure terminal", "interface Ethernet1"])
89 | current_prompt = conn.get_prompt()
90 | current_priv = conn._determine_current_priv(current_prompt)
91 | assert current_priv.name == "special_configuration"
92 |
--------------------------------------------------------------------------------
/tests/functional/base_functional_tests.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | import ssh2net
4 |
5 | NET2_DIR = ssh2net.__file__
6 | privs = None
7 |
8 |
9 | class BaseFunctionalTest:
10 | def setup_method(self):
11 | pass
12 |
13 | @staticmethod
14 | def _replace_trailing_chars_running_config(input_data):
15 | return input_data
16 |
17 | @staticmethod
18 | def _replace_config_bytes(input_data):
19 | return input_data
20 |
21 | @staticmethod
22 | def _replace_timestamps(input_data):
23 | return input_data
24 |
25 | @staticmethod
26 | def _replace_configured_by(input_data):
27 | return input_data
28 |
29 | @staticmethod
30 | def _replace_crypto_strings(input_data):
31 | return input_data
32 |
33 | def clean_input_data(self, input_data):
34 | input_data = self._replace_trailing_chars_running_config(input_data)
35 | input_data = self._replace_config_bytes(input_data)
36 | input_data = self._replace_timestamps(input_data)
37 | input_data = self._replace_configured_by(input_data)
38 | input_data = self._replace_crypto_strings(input_data)
39 | return input_data
40 |
41 | def disable_paging(self, cls):
42 | # implement platform specific disable paging string function here
43 | pass
44 |
45 | def show_run_execute(self):
46 | conn = ssh2net.SSH2Net(**self.test_device)
47 | show_run = conn.open_and_execute("show run")
48 | show_run = self.clean_input_data(show_run)
49 | return show_run
50 |
51 | def show_run_inputs(self, setup_use_paramiko, command):
52 | with ssh2net.SSH2Net(**self.test_device, setup_use_paramiko=setup_use_paramiko) as conn:
53 | show_run = conn.send_inputs(command)[0]
54 | show_run = self.clean_input_data(show_run)
55 | return show_run
56 |
57 | def show_run_inputs_no_strip_prompt(self, setup_use_paramiko, command):
58 | with ssh2net.SSH2Net(**self.test_device, setup_use_paramiko=setup_use_paramiko) as conn:
59 | show_run = conn.send_inputs(command, strip_prompt=False)[0]
60 | show_run = self.clean_input_data(show_run)
61 | return show_run
62 |
63 | def _send_inputs_interact(self, setup_use_paramiko, interact, **kwargs):
64 | with ssh2net.SSH2Net(**self.test_device, setup_use_paramiko=setup_use_paramiko) as conn:
65 | try:
66 | current_prompt = interact[3]
67 | except IndexError:
68 | current_prompt = conn.get_prompt()
69 | interactive = conn.send_inputs_interact(
70 | (interact[0], interact[1], interact[2], current_prompt), **kwargs
71 | )[0]
72 | return interactive
73 |
74 | def _disable_paging_function(self, setup_use_paramiko):
75 | with ssh2net.SSH2Net(
76 | **self.test_device,
77 | setup_use_paramiko=setup_use_paramiko,
78 | comms_disable_paging=self.disable_paging,
79 | ) as conn:
80 | show_run = conn.send_inputs("show run")[0]
81 | show_run = self.clean_input_data(show_run)
82 | return show_run
83 |
84 | def _disable_paging_external_function(self, setup_use_paramiko):
85 | with ssh2net.SSH2Net(
86 | **self.test_device,
87 | setup_use_paramiko=setup_use_paramiko,
88 | comms_disable_paging=self.disable_paging_ext_function,
89 | ) as conn:
90 | show_run = conn.send_inputs("show run")[0]
91 | show_run = self.clean_input_data(show_run)
92 | return show_run
93 |
94 | def test_show_run_execute(self):
95 | with open(f"{self.func_test_dir}expected_output/show_run_execute", "r") as f:
96 | expected_show_run = f.read().strip()
97 | expected_show_run = self.clean_input_data(expected_show_run)
98 | show_run = self.show_run_execute()
99 | assert len(show_run.splitlines()) == len(expected_show_run.splitlines())
100 | assert show_run == expected_show_run
101 |
102 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
103 | def test_show_run_inputs(self, setup_use_paramiko, command="show run"):
104 | with open(f"{self.func_test_dir}expected_output/show_run", "r") as f:
105 | expected_show_run = f.read()
106 | expected_show_run = self.clean_input_data(expected_show_run)
107 | show_run = self.show_run_inputs(setup_use_paramiko, command)
108 | assert len(show_run.splitlines()) == len(expected_show_run.splitlines())
109 | assert show_run == expected_show_run
110 |
111 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
112 | def test_show_run_inputs_no_strip_prompt(self, setup_use_paramiko, command="show run"):
113 | with open(f"{self.func_test_dir}expected_output/show_run", "r") as f:
114 | expected_show_run = f.read()
115 | expected_show_run = self.clean_input_data(expected_show_run)
116 | show_run = self.show_run_inputs(setup_use_paramiko, command)
117 | assert len(show_run.splitlines()) == len(expected_show_run.splitlines())
118 | assert show_run == expected_show_run
119 |
120 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
121 | def test_send_inputs_interact(self, setup_use_paramiko, interact, clean=False, **kwargs):
122 | with open(f"{self.func_test_dir}expected_output/interactive", "r") as f:
123 | expected_interactive = f.read().strip()
124 | interactive = self._send_inputs_interact(setup_use_paramiko, interact, **kwargs)
125 | if clean:
126 | expected_interactive = self.clean_input_data(expected_interactive)
127 | interactive = self.clean_input_data(interactive)
128 | assert len(interactive.splitlines()) == len(expected_interactive.splitlines())
129 | assert interactive == expected_interactive
130 |
131 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
132 | def test_disable_paging_function(self, setup_use_paramiko):
133 | with open(f"{self.func_test_dir}expected_output/show_run", "r") as f:
134 | expected_show_run = f.read()
135 | expected_show_run = self.clean_input_data(expected_show_run)
136 | show_run = self._disable_paging_function(setup_use_paramiko)
137 | assert len(show_run.splitlines()) == len(expected_show_run.splitlines())
138 | assert show_run == expected_show_run
139 |
140 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
141 | def test_disable_paging_external_function(self, setup_use_paramiko):
142 | with open(f"{self.func_test_dir}expected_output/show_run", "r") as f:
143 | expected_show_run = f.read()
144 | expected_show_run = self.clean_input_data(expected_show_run)
145 | show_run = self._disable_paging_external_function(setup_use_paramiko)
146 | assert len(show_run.splitlines()) == len(expected_show_run.splitlines())
147 | assert show_run == expected_show_run
148 |
149 | def test_acquire_all_priv_levels(self, setup_use_paramiko, priv_level):
150 | if not priv_level.requestable:
151 | pytest.skip(f"priv level {priv_level.name} is not requestable by ssh2net")
152 | with self.platform_driver(
153 | **self.test_device, setup_use_paramiko=setup_use_paramiko
154 | ) as conn:
155 | conn.attain_priv(priv_level.name)
156 | current_prompt = conn.get_prompt()
157 | current_priv = conn._determine_current_priv(current_prompt)
158 | assert current_priv.name == priv_level.name
159 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxe/expected_output/interactive:
--------------------------------------------------------------------------------
1 | Clear logging buffer [confirm]
2 | csr1000v#
3 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxe/expected_output/show_run:
--------------------------------------------------------------------------------
1 | Building configuration...
2 |
3 | Current configuration : 5241 bytes
4 | !
5 | ! Last configuration change at 22:58:58 UTC Sat Aug 31 2019
6 | !
7 | version 16.4
8 | service timestamps debug datetime msec
9 | service timestamps log datetime msec
10 | no platform punt-keepalive disable-kernel-core
11 | platform console serial
12 | !
13 | hostname csr1000v
14 | !
15 | boot-start-marker
16 | boot-end-marker
17 | !
18 | !
19 | !
20 | no aaa new-model
21 | !
22 | !
23 | !
24 | !
25 | !
26 | !
27 | !
28 | !
29 | !
30 |
31 |
32 |
33 | ip domain name example.com
34 | !
35 | !
36 | !
37 | !
38 | !
39 | !
40 | !
41 | !
42 | !
43 | !
44 | subscriber templating
45 | !
46 | !
47 | !
48 | multilink bundle-name authenticated
49 | !
50 | !
51 | !
52 | !
53 | !
54 | crypto pki trustpoint TP-self-signed-2656125433
55 | enrollment selfsigned
56 | subject-name cn=IOS-Self-Signed-Certificate-2656125433
57 | revocation-check none
58 | rsakeypair TP-self-signed-2656125433
59 | !
60 | !
61 | crypto pki certificate chain TP-self-signed-2656125433
62 | certificate self-signed 01
63 | 30820330 30820218 A0030201 02020101 300D0609 2A864886 F70D0101 05050030
64 | 31312F30 2D060355 04031326 494F532D 53656C66 2D536967 6E65642D 43657274
65 | 69666963 6174652D 32363536 31323534 3333301E 170D3139 30383331 32323538
66 | 34305A17 0D323030 31303130 30303030 305A3031 312F302D 06035504 03132649
67 | 4F532D53 656C662D 5369676E 65642D43 65727469 66696361 74652D32 36353631
68 | 32353433 33308201 22300D06 092A8648 86F70D01 01010500 0382010F 00308201
69 | 0A028201 0100CBF9 9AECD91C F35EE6F1 7E3AEF64 0A2F80B1 8DB4EE0F 4B43AF56
70 | 618C79D9 6BFA3C8E D2608CE9 0C1E4B14 731268BD BFA0F6AF 72B63B7F 44447292
71 | 0FFDCF06 6FF90BF8 C3D7AD93 3AA27FF7 B7698829 197F9B82 68FD469C BF947A0B
72 | 73B298CE AECFC3E1 C4552A9E BC3C475E 01B5C3E0 81B39F0A B0F421B8 C4C6A618
73 | 5F7BA56C 3665B91D 2BA5A9C7 9FBBADE8 E5B69F3A CDCE2E04 CB629792 4A169D82
74 | F37B4C61 F73AB9EC B2DBF89B 15C91292 7ADA094F 675E17F0 ED08DB7C B074EE44
75 | F70FCB60 2472F3F6 7C3F481A C8142FFD B90CE60D 7E04B776 0EDBC015 045165EC
76 | 0A0FA55F 3C1FA8F7 40BAD546 08F51A63 C0FBFF20 F9A3299B 20056285 EAD15955
77 | 13B778A3 967F0203 010001A3 53305130 0F060355 1D130101 FF040530 030101FF
78 | 301F0603 551D2304 18301680 14D2C007 8F54C7C7 0B9316B1 8BD2011B C827E4B0
79 | 0D301D06 03551D0E 04160414 D2C0078F 54C7C70B 9316B18B D2011BC8 27E4B00D
80 | 300D0609 2A864886 F70D0101 05050003 82010100 2B73DCE9 6BCD9A31 E347277C
81 | 4F68F10C B647EDEF E7CD7DD5 2F2CC495 E98AA80F 6B0F8459 AFFE2BB6 8C4150C4
82 | 84371CF6 5B7EDD11 84E9A119 BA38D21C 6CF9F228 E1C7C173 56E02FCE B355284D
83 | 9CDA6C18 E6CE34E4 296AA44F EF540ADF CDDF54AA 76CC9548 8AD69A79 433E31CD
84 | 0362D246 579BE703 8D20094B 3652774B 589FB3DD 8B70EAC5 732F87A2 D0A505F9
85 | C3E05C8A B96B7651 E04CB391 1A2736EF 2AE48BB9 3EB4C991 A61C1757 08B631DF
86 | F6928817 275B55F9 940062B9 FDCDAD72 F337D8D6 9F3904E5 7E73D7A5 4EAA7208
87 | 05119518 74B72517 5F386BB0 94942518 4E95C404 C0E79DD5 4FBEB199 6216BDDF
88 | EB27FB95 02958DEB D6045978 661C515E 09D96719
89 | quit
90 |
91 |
92 | !
93 | !
94 | !
95 | !
96 | !
97 | !
98 | !
99 | license udi pid CSR1000V sn 9FKLJWM5EB0
100 | diagnostic bootup level minimal
101 | !
102 | spanning-tree extend system-id
103 | netconf-yang cisco-odm actions ACL
104 | netconf-yang cisco-odm actions BGP
105 | netconf-yang cisco-odm actions OSPF
106 | netconf-yang cisco-odm actions Archive
107 | netconf-yang cisco-odm actions IPRoute
108 | netconf-yang cisco-odm actions EFPStats
109 | netconf-yang cisco-odm actions IPSLAStats
110 | netconf-yang cisco-odm actions Interfaces
111 | netconf-yang cisco-odm actions Environment
112 | netconf-yang cisco-odm actions FlowMonitor
113 | netconf-yang cisco-odm actions MemoryStats
114 | netconf-yang cisco-odm actions BFDNeighbors
115 | netconf-yang cisco-odm actions BridgeDomain
116 | netconf-yang cisco-odm actions CPUProcesses
117 | netconf-yang cisco-odm actions LLDPNeighbors
118 | netconf-yang cisco-odm actions VirtualService
119 | netconf-yang cisco-odm actions MemoryProcesses
120 | netconf-yang cisco-odm actions EthernetCFMStats
121 | netconf-yang cisco-odm actions MPLSLDPNeighbors
122 | netconf-yang cisco-odm actions PlatformSoftware
123 | netconf-yang cisco-odm actions MPLSStaticBinding
124 | netconf-yang cisco-odm actions MPLSForwardingTable
125 | netconf-yang
126 | !
127 | restconf
128 | !
129 | username vrnetlab privilege 15 password 0 VR-netlab9
130 | !
131 | redundancy
132 | !
133 | !
134 | !
135 | !
136 | !
137 | !
138 | !
139 | !
140 | !
141 | !
142 | !
143 | !
144 | !
145 | !
146 | !
147 | !
148 | !
149 | !
150 | !
151 | !
152 | !
153 | !
154 | !
155 | !
156 | !
157 | !
158 | !
159 | interface GigabitEthernet1
160 | ip address 10.0.0.15 255.255.255.0
161 | negotiation auto
162 | no mop enabled
163 | no mop sysid
164 | !
165 | interface GigabitEthernet2
166 | no ip address
167 | shutdown
168 | negotiation auto
169 | no mop enabled
170 | no mop sysid
171 | !
172 | interface GigabitEthernet3
173 | no ip address
174 | shutdown
175 | negotiation auto
176 | no mop enabled
177 | no mop sysid
178 | !
179 | interface GigabitEthernet4
180 | no ip address
181 | shutdown
182 | negotiation auto
183 | no mop enabled
184 | no mop sysid
185 | !
186 | interface GigabitEthernet5
187 | no ip address
188 | shutdown
189 | negotiation auto
190 | no mop enabled
191 | no mop sysid
192 | !
193 | interface GigabitEthernet6
194 | no ip address
195 | shutdown
196 | negotiation auto
197 | no mop enabled
198 | no mop sysid
199 | !
200 | interface GigabitEthernet7
201 | no ip address
202 | shutdown
203 | negotiation auto
204 | no mop enabled
205 | no mop sysid
206 | !
207 | interface GigabitEthernet8
208 | no ip address
209 | shutdown
210 | negotiation auto
211 | no mop enabled
212 | no mop sysid
213 | !
214 | interface GigabitEthernet9
215 | no ip address
216 | shutdown
217 | negotiation auto
218 | no mop enabled
219 | no mop sysid
220 | !
221 | interface GigabitEthernet10
222 | no ip address
223 | shutdown
224 | negotiation auto
225 | no mop enabled
226 | no mop sysid
227 | !
228 | !
229 | virtual-service csr_mgmt
230 | !
231 | ip forward-protocol nd
232 | no ip http server
233 | no ip http secure-server
234 | !
235 | !
236 | !
237 | !
238 | !
239 | !
240 | !
241 | control-plane
242 | !
243 | !
244 | !
245 | !
246 | !
247 | !
248 | !
249 | !
250 | !
251 | !
252 | line con 0
253 | stopbits 1
254 | line vty 0
255 | login local
256 | transport input all
257 | line vty 1
258 | login local
259 | length 0
260 | transport input all
261 | line vty 2 4
262 | login local
263 | transport input all
264 | !
265 | !
266 | !
267 | !
268 | !
269 | !
270 | end
271 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxe/expected_output/show_run_execute:
--------------------------------------------------------------------------------
1 | Building configuration...
2 |
3 | Current configuration : CONFIG_BYTES
4 | !
5 | ! Last configuration change at TIME_STAMP_REPLACED
6 | !
7 | version 16.4
8 | service timestamps debug datetime msec
9 | service timestamps log datetime msec
10 | no platform punt-keepalive disable-kernel-core
11 | platform console serial
12 | !
13 | hostname csr1000v
14 | !
15 | boot-start-marker
16 | boot-end-marker
17 | !
18 | !
19 | !
20 | no aaa new-model
21 | !
22 | !
23 | !
24 | !
25 | !
26 | !
27 | !
28 | !
29 | !
30 |
31 |
32 |
33 | ip domain name example.com
34 | !
35 | !
36 | !
37 | !
38 | !
39 | !
40 | !
41 | !
42 | !
43 | !
44 | subscriber templating
45 | !
46 | !
47 | !
48 | multilink bundle-name authenticated
49 | !
50 | !
51 | !
52 | !
53 | !
54 | crypto pki trustpoint TP-self-signed-2656125433
55 | enrollment selfsigned
56 | subject-name cn=IOS-Self-Signed-Certificate-2656125433
57 | revocation-check none
58 | rsakeypair TP-self-signed-2656125433
59 | !
60 | !
61 | crypto pki certificate chain TP-self-signed-2656125433
62 | CRYPTO_REPLACED
63 |
64 |
65 | !
66 | !
67 | !
68 | !
69 | !
70 | !
71 | !
72 | license udi pid CSR1000V sn 9FKLJWM5EB0
73 | diagnostic bootup level minimal
74 | !
75 | spanning-tree extend system-id
76 | netconf-yang cisco-odm actions ACL
77 | netconf-yang cisco-odm actions BGP
78 | netconf-yang cisco-odm actions OSPF
79 | netconf-yang cisco-odm actions Archive
80 | netconf-yang cisco-odm actions IPRoute
81 | netconf-yang cisco-odm actions EFPStats
82 | netconf-yang cisco-odm actions IPSLAStats
83 | netconf-yang cisco-odm actions Interfaces
84 | netconf-yang cisco-odm actions Environment
85 | netconf-yang cisco-odm actions FlowMonitor
86 | netconf-yang cisco-odm actions MemoryStats
87 | netconf-yang cisco-odm actions BFDNeighbors
88 | netconf-yang cisco-odm actions BridgeDomain
89 | netconf-yang cisco-odm actions CPUProcesses
90 | netconf-yang cisco-odm actions LLDPNeighbors
91 | netconf-yang cisco-odm actions VirtualService
92 | netconf-yang cisco-odm actions MemoryProcesses
93 | netconf-yang cisco-odm actions EthernetCFMStats
94 | netconf-yang cisco-odm actions MPLSLDPNeighbors
95 | netconf-yang cisco-odm actions PlatformSoftware
96 | netconf-yang cisco-odm actions MPLSStaticBinding
97 | netconf-yang cisco-odm actions MPLSForwardingTable
98 | netconf-yang
99 | !
100 | restconf
101 | !
102 | username vrnetlab privilege 15 password 0 VR-netlab9
103 | !
104 | redundancy
105 | !
106 | !
107 | !
108 | !
109 | !
110 | !
111 | !
112 | !
113 | !
114 | !
115 | !
116 | !
117 | !
118 | !
119 | !
120 | !
121 | !
122 | !
123 | !
124 | !
125 | !
126 | !
127 | !
128 | !
129 | !
130 | !
131 | !
132 | interface GigabitEthernet1
133 | ip address 10.0.0.15 255.255.255.0
134 | negotiation auto
135 | no mop enabled
136 | no mop sysid
137 | !
138 | interface GigabitEthernet2
139 | no ip address
140 | shutdown
141 | negotiation auto
142 | no mop enabled
143 | no mop sysid
144 | !
145 | interface GigabitEthernet3
146 | no ip address
147 | shutdown
148 | negotiation auto
149 | no mop enabled
150 | no mop sysid
151 | !
152 | interface GigabitEthernet4
153 | no ip address
154 | shutdown
155 | negotiation auto
156 | no mop enabled
157 | no mop sysid
158 | !
159 | interface GigabitEthernet5
160 | no ip address
161 | shutdown
162 | negotiation auto
163 | no mop enabled
164 | no mop sysid
165 | !
166 | interface GigabitEthernet6
167 | no ip address
168 | shutdown
169 | negotiation auto
170 | no mop enabled
171 | no mop sysid
172 | !
173 | interface GigabitEthernet7
174 | no ip address
175 | shutdown
176 | negotiation auto
177 | no mop enabled
178 | no mop sysid
179 | !
180 | interface GigabitEthernet8
181 | no ip address
182 | shutdown
183 | negotiation auto
184 | no mop enabled
185 | no mop sysid
186 | !
187 | interface GigabitEthernet9
188 | no ip address
189 | shutdown
190 | negotiation auto
191 | no mop enabled
192 | no mop sysid
193 | !
194 | interface GigabitEthernet10
195 | no ip address
196 | shutdown
197 | negotiation auto
198 | no mop enabled
199 | no mop sysid
200 | !
201 | !
202 | virtual-service csr_mgmt
203 | !
204 | ip forward-protocol nd
205 | no ip http server
206 | no ip http secure-server
207 | !
208 | !
209 | !
210 | !
211 | !
212 | !
213 | !
214 | control-plane
215 | !
216 | !
217 | !
218 | !
219 | !
220 | !
221 | !
222 | !
223 | !
224 | !
225 | line con 0
226 | stopbits 1
227 | line vty 0
228 | login local
229 | transport input all
230 | line vty 1
231 | login local
232 | length 0
233 | transport input all
234 | line vty 2 4
235 | login local
236 | transport input all
237 | !
238 | !
239 | !
240 | !
241 | !
242 | !
243 | end
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 | csr1000v tty3 is now available
307 |
308 |
309 |
310 |
311 |
312 | Press RETURN to get started.
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxe/expected_output/show_run_no_strip:
--------------------------------------------------------------------------------
1 | Building configuration...
2 |
3 | Current configuration : 5241 bytes
4 | !
5 | ! Last configuration change at 22:58:58 UTC Sat Aug 31 2019
6 | !
7 | version 16.4
8 | service timestamps debug datetime msec
9 | service timestamps log datetime msec
10 | no platform punt-keepalive disable-kernel-core
11 | platform console serial
12 | !
13 | hostname csr1000v
14 | !
15 | boot-start-marker
16 | boot-end-marker
17 | !
18 | !
19 | !
20 | no aaa new-model
21 | !
22 | !
23 | !
24 | !
25 | !
26 | !
27 | !
28 | !
29 | !
30 |
31 |
32 |
33 | ip domain name example.com
34 | !
35 | !
36 | !
37 | !
38 | !
39 | !
40 | !
41 | !
42 | !
43 | !
44 | subscriber templating
45 | !
46 | !
47 | !
48 | multilink bundle-name authenticated
49 | !
50 | !
51 | !
52 | !
53 | !
54 | crypto pki trustpoint TP-self-signed-2656125433
55 | enrollment selfsigned
56 | subject-name cn=IOS-Self-Signed-Certificate-2656125433
57 | revocation-check none
58 | rsakeypair TP-self-signed-2656125433
59 | !
60 | !
61 | crypto pki certificate chain TP-self-signed-2656125433
62 | certificate self-signed 01
63 | 30820330 30820218 A0030201 02020101 300D0609 2A864886 F70D0101 05050030
64 | 31312F30 2D060355 04031326 494F532D 53656C66 2D536967 6E65642D 43657274
65 | 69666963 6174652D 32363536 31323534 3333301E 170D3139 30383331 32323538
66 | 34305A17 0D323030 31303130 30303030 305A3031 312F302D 06035504 03132649
67 | 4F532D53 656C662D 5369676E 65642D43 65727469 66696361 74652D32 36353631
68 | 32353433 33308201 22300D06 092A8648 86F70D01 01010500 0382010F 00308201
69 | 0A028201 0100CBF9 9AECD91C F35EE6F1 7E3AEF64 0A2F80B1 8DB4EE0F 4B43AF56
70 | 618C79D9 6BFA3C8E D2608CE9 0C1E4B14 731268BD BFA0F6AF 72B63B7F 44447292
71 | 0FFDCF06 6FF90BF8 C3D7AD93 3AA27FF7 B7698829 197F9B82 68FD469C BF947A0B
72 | 73B298CE AECFC3E1 C4552A9E BC3C475E 01B5C3E0 81B39F0A B0F421B8 C4C6A618
73 | 5F7BA56C 3665B91D 2BA5A9C7 9FBBADE8 E5B69F3A CDCE2E04 CB629792 4A169D82
74 | F37B4C61 F73AB9EC B2DBF89B 15C91292 7ADA094F 675E17F0 ED08DB7C B074EE44
75 | F70FCB60 2472F3F6 7C3F481A C8142FFD B90CE60D 7E04B776 0EDBC015 045165EC
76 | 0A0FA55F 3C1FA8F7 40BAD546 08F51A63 C0FBFF20 F9A3299B 20056285 EAD15955
77 | 13B778A3 967F0203 010001A3 53305130 0F060355 1D130101 FF040530 030101FF
78 | 301F0603 551D2304 18301680 14D2C007 8F54C7C7 0B9316B1 8BD2011B C827E4B0
79 | 0D301D06 03551D0E 04160414 D2C0078F 54C7C70B 9316B18B D2011BC8 27E4B00D
80 | 300D0609 2A864886 F70D0101 05050003 82010100 2B73DCE9 6BCD9A31 E347277C
81 | 4F68F10C B647EDEF E7CD7DD5 2F2CC495 E98AA80F 6B0F8459 AFFE2BB6 8C4150C4
82 | 84371CF6 5B7EDD11 84E9A119 BA38D21C 6CF9F228 E1C7C173 56E02FCE B355284D
83 | 9CDA6C18 E6CE34E4 296AA44F EF540ADF CDDF54AA 76CC9548 8AD69A79 433E31CD
84 | 0362D246 579BE703 8D20094B 3652774B 589FB3DD 8B70EAC5 732F87A2 D0A505F9
85 | C3E05C8A B96B7651 E04CB391 1A2736EF 2AE48BB9 3EB4C991 A61C1757 08B631DF
86 | F6928817 275B55F9 940062B9 FDCDAD72 F337D8D6 9F3904E5 7E73D7A5 4EAA7208
87 | 05119518 74B72517 5F386BB0 94942518 4E95C404 C0E79DD5 4FBEB199 6216BDDF
88 | EB27FB95 02958DEB D6045978 661C515E 09D96719
89 | quit
90 |
91 |
92 | !
93 | !
94 | !
95 | !
96 | !
97 | !
98 | !
99 | license udi pid CSR1000V sn 9FKLJWM5EB0
100 | diagnostic bootup level minimal
101 | !
102 | spanning-tree extend system-id
103 | netconf-yang cisco-odm actions ACL
104 | netconf-yang cisco-odm actions BGP
105 | netconf-yang cisco-odm actions OSPF
106 | netconf-yang cisco-odm actions Archive
107 | netconf-yang cisco-odm actions IPRoute
108 | netconf-yang cisco-odm actions EFPStats
109 | netconf-yang cisco-odm actions IPSLAStats
110 | netconf-yang cisco-odm actions Interfaces
111 | netconf-yang cisco-odm actions Environment
112 | netconf-yang cisco-odm actions FlowMonitor
113 | netconf-yang cisco-odm actions MemoryStats
114 | netconf-yang cisco-odm actions BFDNeighbors
115 | netconf-yang cisco-odm actions BridgeDomain
116 | netconf-yang cisco-odm actions CPUProcesses
117 | netconf-yang cisco-odm actions LLDPNeighbors
118 | netconf-yang cisco-odm actions VirtualService
119 | netconf-yang cisco-odm actions MemoryProcesses
120 | netconf-yang cisco-odm actions EthernetCFMStats
121 | netconf-yang cisco-odm actions MPLSLDPNeighbors
122 | netconf-yang cisco-odm actions PlatformSoftware
123 | netconf-yang cisco-odm actions MPLSStaticBinding
124 | netconf-yang cisco-odm actions MPLSForwardingTable
125 | netconf-yang
126 | !
127 | restconf
128 | !
129 | username vrnetlab privilege 15 password 0 VR-netlab9
130 | !
131 | redundancy
132 | !
133 | !
134 | !
135 | !
136 | !
137 | !
138 | !
139 | !
140 | !
141 | !
142 | !
143 | !
144 | !
145 | !
146 | !
147 | !
148 | !
149 | !
150 | !
151 | !
152 | !
153 | !
154 | !
155 | !
156 | !
157 | !
158 | !
159 | interface GigabitEthernet1
160 | ip address 10.0.0.15 255.255.255.0
161 | negotiation auto
162 | no mop enabled
163 | no mop sysid
164 | !
165 | interface GigabitEthernet2
166 | no ip address
167 | shutdown
168 | negotiation auto
169 | no mop enabled
170 | no mop sysid
171 | !
172 | interface GigabitEthernet3
173 | no ip address
174 | shutdown
175 | negotiation auto
176 | no mop enabled
177 | no mop sysid
178 | !
179 | interface GigabitEthernet4
180 | no ip address
181 | shutdown
182 | negotiation auto
183 | no mop enabled
184 | no mop sysid
185 | !
186 | interface GigabitEthernet5
187 | no ip address
188 | shutdown
189 | negotiation auto
190 | no mop enabled
191 | no mop sysid
192 | !
193 | interface GigabitEthernet6
194 | no ip address
195 | shutdown
196 | negotiation auto
197 | no mop enabled
198 | no mop sysid
199 | !
200 | interface GigabitEthernet7
201 | no ip address
202 | shutdown
203 | negotiation auto
204 | no mop enabled
205 | no mop sysid
206 | !
207 | interface GigabitEthernet8
208 | no ip address
209 | shutdown
210 | negotiation auto
211 | no mop enabled
212 | no mop sysid
213 | !
214 | interface GigabitEthernet9
215 | no ip address
216 | shutdown
217 | negotiation auto
218 | no mop enabled
219 | no mop sysid
220 | !
221 | interface GigabitEthernet10
222 | no ip address
223 | shutdown
224 | negotiation auto
225 | no mop enabled
226 | no mop sysid
227 | !
228 | !
229 | virtual-service csr_mgmt
230 | !
231 | ip forward-protocol nd
232 | no ip http server
233 | no ip http secure-server
234 | !
235 | !
236 | !
237 | !
238 | !
239 | !
240 | !
241 | control-plane
242 | !
243 | !
244 | !
245 | !
246 | !
247 | !
248 | !
249 | !
250 | !
251 | !
252 | line con 0
253 | stopbits 1
254 | line vty 0
255 | login local
256 | transport input all
257 | line vty 1
258 | login local
259 | length 0
260 | transport input all
261 | line vty 2 4
262 | login local
263 | transport input all
264 | !
265 | !
266 | !
267 | !
268 | !
269 | !
270 | end
271 |
272 | csr1000v#
273 |
274 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxe/ext_test_funcs.py:
--------------------------------------------------------------------------------
1 | def iosxe_disable_paging(cls):
2 | cls.send_inputs("term length 0")
3 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxe/test_iosxe.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import re
3 |
4 | import pytest
5 |
6 | from tests.functional.base_functional_tests import BaseFunctionalTest
7 | import ssh2net
8 |
9 | TEST_DEVICE = {"setup_host": "172.18.0.11", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
10 |
11 | dummy_conn = ssh2net.IOSXEDriver(**TEST_DEVICE)
12 | PRIV_LEVELS = dummy_conn.privs
13 |
14 |
15 | class TestIOSXE(BaseFunctionalTest):
16 | def setup_method(self):
17 | self.platform_driver = ssh2net.IOSXEDriver
18 |
19 | self.device_type = Path(__file__).resolve().parts[-2]
20 | self.func_test_dir = (
21 | f"{Path(ssh2net.__file__).parents[1]}/tests/functional/{self.device_type}/"
22 | )
23 | self.test_device = TEST_DEVICE
24 | self.disable_paging_ext_function = f"tests.functional.{self.device_type}.ext_test_funcs.{self.device_type.split('_')[1]}_disable_paging"
25 |
26 | @staticmethod
27 | def _replace_trailing_chars_running_config(input_data):
28 | execute_trailing_chars_pattern = re.compile(r"^end.*$", flags=re.M | re.I | re.S)
29 | input_data = re.sub(execute_trailing_chars_pattern, "end", input_data)
30 | return input_data
31 |
32 | @staticmethod
33 | def _replace_config_bytes(input_data):
34 | config_bytes_pattern = re.compile(r"^Current configuration : \d+ bytes$", flags=re.M | re.I)
35 | input_data = re.sub(
36 | config_bytes_pattern, "Current configuration : CONFIG_BYTES", input_data
37 | )
38 | return input_data
39 |
40 | @staticmethod
41 | def _replace_timestamps(input_data):
42 | datetime_pattern = re.compile(
43 | r"\d+:\d+:\d+\d+\s+[a-z]{3}\s+(mon|tue|wed|thu|fri|sat|sun)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+\d+\s+\d+",
44 | flags=re.M | re.I,
45 | )
46 | input_data = re.sub(datetime_pattern, "TIME_STAMP_REPLACED", input_data)
47 | return input_data
48 |
49 | @staticmethod
50 | def _replace_configured_by(input_data):
51 | configured_by_pattern = re.compile(
52 | r"^! Last configuration change at TIME_STAMP_REPLACED by (\w+)$", flags=re.M | re.I
53 | )
54 | input_data = re.sub(
55 | configured_by_pattern, "! Last configuration change at TIME_STAMP_REPLACED", input_data
56 | )
57 | return input_data
58 |
59 | @staticmethod
60 | def _replace_crypto_strings(input_data):
61 | crypto_pattern = re.compile(
62 | r"^\s+certificate self-signed.*$\s(^\s{2}(\w+\s){1,8})+\s+quit$", flags=re.M | re.I
63 | )
64 | input_data = re.sub(crypto_pattern, "CRYPTO_REPLACED", input_data)
65 | return input_data
66 |
67 | def disable_paging(self, cls):
68 | cls.send_inputs("term length 0")
69 |
70 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
71 | def test_send_inputs_interact(self, setup_use_paramiko):
72 | interact = ["clear logg", "Clear logging buffer [confirm]", ""]
73 | super().test_send_inputs_interact(setup_use_paramiko, interact)
74 |
75 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
76 | @pytest.mark.parametrize("priv_level", [priv for priv in PRIV_LEVELS.values()])
77 | def test_acquire_all_priv_levels(self, setup_use_paramiko, priv_level):
78 | super().test_acquire_all_priv_levels(setup_use_paramiko, priv_level)
79 |
80 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
81 | def test__determine_current_priv_special_configuration(self, setup_use_paramiko):
82 | with self.platform_driver(
83 | **self.test_device, setup_use_paramiko=setup_use_paramiko
84 | ) as conn:
85 | conn.send_inputs(["configure terminal", "interface GigabitEthernet1"])
86 | current_prompt = conn.get_prompt()
87 | current_priv = conn._determine_current_priv(current_prompt)
88 | assert current_priv.name == "special_configuration"
89 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxr/expected_output/interactive:
--------------------------------------------------------------------------------
1 | Fri Sep 6 01:06:15.228 UTC
2 | Clear logging buffer [confirm] [y/n] :y
3 | RP/0/RP0/CPU0:ios#
4 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxr/ext_test_funcs.py:
--------------------------------------------------------------------------------
1 | def iosxr_disable_paging(cls):
2 | cls.send_inputs("term length 0")
3 |
--------------------------------------------------------------------------------
/tests/functional/cisco_iosxr/test_iosxr.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import re
3 |
4 | import pytest
5 |
6 | from tests.functional.base_functional_tests import BaseFunctionalTest
7 | from ssh2net.core.cisco_iosxr.driver import comms_pre_login_handler
8 | import ssh2net
9 |
10 | TEST_DEVICE = {"setup_host": "172.18.0.13", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
11 |
12 | dummy_conn = ssh2net.IOSXRDriver(**TEST_DEVICE)
13 | PRIV_LEVELS = dummy_conn.privs
14 | PRE_LOGIN_HANDLER = comms_pre_login_handler
15 |
16 |
17 | class TestIOSXR(BaseFunctionalTest):
18 | def setup_method(self):
19 | self.platform_driver = ssh2net.IOSXRDriver
20 |
21 | self.device_type = Path(__file__).resolve().parts[-2]
22 | self.func_test_dir = (
23 | f"{Path(ssh2net.__file__).parents[1]}/tests/functional/{self.device_type}/"
24 | )
25 | test_device = TEST_DEVICE.copy()
26 | test_device["comms_pre_login_handler"] = PRE_LOGIN_HANDLER
27 | self.test_device = test_device
28 | self.disable_paging_ext_function = f"tests.functional.{self.device_type}.ext_test_funcs.{self.device_type.split('_')[1]}_disable_paging"
29 |
30 | @staticmethod
31 | def _replace_trailing_chars_running_config(input_data):
32 | execute_trailing_chars_pattern = re.compile(r"^end.*$", flags=re.M | re.I | re.S)
33 | input_data = re.sub(execute_trailing_chars_pattern, "end", input_data)
34 | return input_data
35 |
36 | @staticmethod
37 | def _replace_timestamps(input_data):
38 | datetime_pattern = re.compile(
39 | r"(mon|tue|wed|thu|fri|sat|sun)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+\d+\s+\d+:\d+:\d+((\.\d+\s\w+)|\s\d+)",
40 | flags=re.M | re.I,
41 | )
42 | input_data = re.sub(datetime_pattern, "TIME_STAMP_REPLACED", input_data)
43 | return input_data
44 |
45 | @staticmethod
46 | def _replace_configured_by(input_data):
47 | configured_by_pattern = re.compile(
48 | r"^!! Last configuration change at TIME_STAMP_REPLACED by (\w+)$", flags=re.M | re.I
49 | )
50 | input_data = re.sub(
51 | configured_by_pattern, "!! Last configuration change at TIME_STAMP_REPLACED", input_data
52 | )
53 | return input_data
54 |
55 | @staticmethod
56 | def _replace_crypto_strings(input_data):
57 | crypto_pattern = re.compile(r"^\ssecret\s5\s[\w$\.\/]+$", flags=re.M | re.I)
58 | input_data = re.sub(crypto_pattern, "CRYPTO_REPLACED", input_data)
59 | return input_data
60 |
61 | def disable_paging(self, cls):
62 | cls.send_inputs("term length 0")
63 |
64 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
65 | def test_send_inputs_interact(self, setup_use_paramiko):
66 | interact = ["clear logg", "Clear logging buffer [confirm] [y/n] :", "y"]
67 | super().test_send_inputs_interact(setup_use_paramiko, interact, clean=True)
68 |
69 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
70 | @pytest.mark.parametrize("priv_level", [priv for priv in PRIV_LEVELS.values()])
71 | def test_acquire_all_priv_levels(self, setup_use_paramiko, priv_level):
72 | super().test_acquire_all_priv_levels(setup_use_paramiko, priv_level)
73 |
74 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
75 | def test__determine_current_priv_special_configuration(self, setup_use_paramiko):
76 | with self.platform_driver(
77 | **self.test_device, setup_use_paramiko=setup_use_paramiko
78 | ) as conn:
79 | conn.send_inputs(["configure terminal", "interface GigabitEthernet0/0/0/127"])
80 | current_prompt = conn.get_prompt()
81 | current_priv = conn._determine_current_priv(current_prompt)
82 | assert current_priv.name == "special_configuration"
83 |
--------------------------------------------------------------------------------
/tests/functional/cisco_nxos/expected_output/interactive:
--------------------------------------------------------------------------------
1 | Do you want to delete "/virtual-instance.conf" ? (yes/no/abort) [y]n
2 |
3 |
4 | switch#
5 |
--------------------------------------------------------------------------------
/tests/functional/cisco_nxos/expected_output/show_run:
--------------------------------------------------------------------------------
1 | !Command: show running-config
2 | !Running configuration last done at: Sun Sep 1 02:24:31 2019
3 | !Time: Sun Sep 1 02:27:46 2019
4 |
5 | version 9.2(4) Bios:version
6 | vdc switch id 1
7 | limit-resource vlan minimum 16 maximum 4094
8 | limit-resource vrf minimum 2 maximum 4096
9 | limit-resource port-channel minimum 0 maximum 511
10 | limit-resource u4route-mem minimum 128 maximum 128
11 | limit-resource u6route-mem minimum 96 maximum 96
12 | limit-resource m4route-mem minimum 58 maximum 58
13 | limit-resource m6route-mem minimum 8 maximum 8
14 | no password strength-check
15 | username admin password 5 $5$DICBLF$Xhpg8rM1hb46a98tsxe249e9m6oa3TH00Y8C40.8JDC role network-admin
16 | username vrnetlab password 5 $5$tGG3FFLY$YXOnmUXhkQE5Vn6KCOzpZBABLFvEzochGeH87YOh2q7 role network-admin
17 | username vrnetlab passphrase lifetime 99999 warntime 14 gracetime 3
18 | ip domain-lookup
19 | copp profile strict
20 | snmp-server user admin network-admin auth md5 0x2d0b9ec17b5d1c3bc718bb33c08528a1 priv 0x2d0b9ec17b5d1c3bc718bb33c08528a1 localizedkey
21 | snmp-server user vrnetlab network-admin auth md5 0x3b9301da0b9f328bc2573fed20773dcb priv 0x3b9301da0b9f328bc2573fed20773dcb localizedkey
22 | rmon event 1 description FATAL(1) owner PMON@FATAL
23 | rmon event 2 description CRITICAL(2) owner PMON@CRITICAL
24 | rmon event 3 description ERROR(3) owner PMON@ERROR
25 | rmon event 4 description WARNING(4) owner PMON@WARNING
26 | rmon event 5 description INFORMATION(5) owner PMON@INFO
27 |
28 | vlan 1
29 |
30 | vrf context management
31 |
32 | interface Ethernet1/1
33 |
34 | interface Ethernet1/2
35 |
36 | interface Ethernet1/3
37 |
38 | interface Ethernet1/4
39 |
40 | interface Ethernet1/5
41 |
42 | interface Ethernet1/6
43 |
44 | interface Ethernet1/7
45 |
46 | interface Ethernet1/8
47 |
48 | interface Ethernet1/9
49 |
50 | interface Ethernet1/10
51 |
52 | interface Ethernet1/11
53 |
54 | interface Ethernet1/12
55 |
56 | interface Ethernet1/13
57 |
58 | interface Ethernet1/14
59 |
60 | interface Ethernet1/15
61 |
62 | interface Ethernet1/16
63 |
64 | interface Ethernet1/17
65 |
66 | interface Ethernet1/18
67 |
68 | interface Ethernet1/19
69 |
70 | interface Ethernet1/20
71 |
72 | interface Ethernet1/21
73 |
74 | interface Ethernet1/22
75 |
76 | interface Ethernet1/23
77 |
78 | interface Ethernet1/24
79 |
80 | interface Ethernet1/25
81 |
82 | interface Ethernet1/26
83 |
84 | interface Ethernet1/27
85 |
86 | interface Ethernet1/28
87 |
88 | interface Ethernet1/29
89 |
90 | interface Ethernet1/30
91 |
92 | interface Ethernet1/31
93 |
94 | interface Ethernet1/32
95 |
96 | interface Ethernet1/33
97 |
98 | interface Ethernet1/34
99 |
100 | interface Ethernet1/35
101 |
102 | interface Ethernet1/36
103 |
104 | interface Ethernet1/37
105 |
106 | interface Ethernet1/38
107 |
108 | interface Ethernet1/39
109 |
110 | interface Ethernet1/40
111 |
112 | interface Ethernet1/41
113 |
114 | interface Ethernet1/42
115 |
116 | interface Ethernet1/43
117 |
118 | interface Ethernet1/44
119 |
120 | interface Ethernet1/45
121 |
122 | interface Ethernet1/46
123 |
124 | interface Ethernet1/47
125 |
126 | interface Ethernet1/48
127 |
128 | interface Ethernet1/49
129 |
130 | interface Ethernet1/50
131 |
132 | interface Ethernet1/51
133 |
134 | interface Ethernet1/52
135 |
136 | interface Ethernet1/53
137 |
138 | interface Ethernet1/54
139 |
140 | interface Ethernet1/55
141 |
142 | interface Ethernet1/56
143 |
144 | interface Ethernet1/57
145 |
146 | interface Ethernet1/58
147 |
148 | interface Ethernet1/59
149 |
150 | interface Ethernet1/60
151 |
152 | interface Ethernet1/61
153 |
154 | interface Ethernet1/62
155 |
156 | interface Ethernet1/63
157 |
158 | interface Ethernet1/64
159 |
160 | interface Ethernet1/65
161 |
162 | interface Ethernet1/66
163 |
164 | interface Ethernet1/67
165 |
166 | interface Ethernet1/68
167 |
168 | interface Ethernet1/69
169 |
170 | interface Ethernet1/70
171 |
172 | interface Ethernet1/71
173 |
174 | interface Ethernet1/72
175 |
176 | interface Ethernet1/73
177 |
178 | interface Ethernet1/74
179 |
180 | interface Ethernet1/75
181 |
182 | interface Ethernet1/76
183 |
184 | interface Ethernet1/77
185 |
186 | interface Ethernet1/78
187 |
188 | interface Ethernet1/79
189 |
190 | interface Ethernet1/80
191 |
192 | interface Ethernet1/81
193 |
194 | interface Ethernet1/82
195 |
196 | interface Ethernet1/83
197 |
198 | interface Ethernet1/84
199 |
200 | interface Ethernet1/85
201 |
202 | interface Ethernet1/86
203 |
204 | interface Ethernet1/87
205 |
206 | interface Ethernet1/88
207 |
208 | interface Ethernet1/89
209 |
210 | interface Ethernet1/90
211 |
212 | interface Ethernet1/91
213 |
214 | interface Ethernet1/92
215 |
216 | interface Ethernet1/93
217 |
218 | interface Ethernet1/94
219 |
220 | interface Ethernet1/95
221 |
222 | interface Ethernet1/96
223 |
224 | interface Ethernet1/97
225 |
226 | interface Ethernet1/98
227 |
228 | interface Ethernet1/99
229 |
230 | interface Ethernet1/100
231 |
232 | interface Ethernet1/101
233 |
234 | interface Ethernet1/102
235 |
236 | interface Ethernet1/103
237 |
238 | interface Ethernet1/104
239 |
240 | interface Ethernet1/105
241 |
242 | interface Ethernet1/106
243 |
244 | interface Ethernet1/107
245 |
246 | interface Ethernet1/108
247 |
248 | interface Ethernet1/109
249 |
250 | interface Ethernet1/110
251 |
252 | interface Ethernet1/111
253 |
254 | interface Ethernet1/112
255 |
256 | interface Ethernet1/113
257 |
258 | interface Ethernet1/114
259 |
260 | interface Ethernet1/115
261 |
262 | interface Ethernet1/116
263 |
264 | interface Ethernet1/117
265 |
266 | interface Ethernet1/118
267 |
268 | interface Ethernet1/119
269 |
270 | interface Ethernet1/120
271 |
272 | interface Ethernet1/121
273 |
274 | interface Ethernet1/122
275 |
276 | interface Ethernet1/123
277 |
278 | interface Ethernet1/124
279 |
280 | interface Ethernet1/125
281 |
282 | interface Ethernet1/126
283 |
284 | interface Ethernet1/127
285 |
286 | interface Ethernet1/128
287 |
288 | interface mgmt0
289 | vrf member management
290 | ip address 10.0.0.15/24
291 | line console
292 | line vty
293 |
--------------------------------------------------------------------------------
/tests/functional/cisco_nxos/expected_output/show_run_execute:
--------------------------------------------------------------------------------
1 | !Command: show running-config
2 | !Running configuration last done at: Sun Sep 1 02:24:31 2019
3 | !Time: Sun Sep 1 02:27:46 2019
4 |
5 | version 9.2(4) Bios:version
6 | vdc switch id 1
7 | limit-resource vlan minimum 16 maximum 4094
8 | limit-resource vrf minimum 2 maximum 4096
9 | limit-resource port-channel minimum 0 maximum 511
10 | limit-resource u4route-mem minimum 128 maximum 128
11 | limit-resource u6route-mem minimum 96 maximum 96
12 | limit-resource m4route-mem minimum 58 maximum 58
13 | limit-resource m6route-mem minimum 8 maximum 8
14 | no password strength-check
15 | username admin password 5 $5$DICBLF$Xhpg8rM1hb46a98tsxe249e9m6oa3TH00Y8C40.8JDC role network-admin
16 | username vrnetlab password 5 $5$tGG3FFLY$YXOnmUXhkQE5Vn6KCOzpZBABLFvEzochGeH87YOh2q7 role network-admin
17 | username vrnetlab passphrase lifetime 99999 warntime 14 gracetime 3
18 | ip domain-lookup
19 | copp profile strict
20 | snmp-server user admin network-admin auth md5 0x2d0b9ec17b5d1c3bc718bb33c08528a1 priv 0x2d0b9ec17b5d1c3bc718bb33c08528a1 localizedkey
21 | snmp-server user vrnetlab network-admin auth md5 0x3b9301da0b9f328bc2573fed20773dcb priv 0x3b9301da0b9f328bc2573fed20773dcb localizedkey
22 | rmon event 1 description FATAL(1) owner PMON@FATAL
23 | rmon event 2 description CRITICAL(2) owner PMON@CRITICAL
24 | rmon event 3 description ERROR(3) owner PMON@ERROR
25 | rmon event 4 description WARNING(4) owner PMON@WARNING
26 | rmon event 5 description INFORMATION(5) owner PMON@INFO
27 |
28 | vlan 1
29 |
30 | vrf context management
31 |
32 | interface Ethernet1/1
33 |
34 | interface Ethernet1/2
35 |
36 | interface Ethernet1/3
37 |
38 | interface Ethernet1/4
39 |
40 | interface Ethernet1/5
41 |
42 | interface Ethernet1/6
43 |
44 | interface Ethernet1/7
45 |
46 | interface Ethernet1/8
47 |
48 | interface Ethernet1/9
49 |
50 | interface Ethernet1/10
51 |
52 | interface Ethernet1/11
53 |
54 | interface Ethernet1/12
55 |
56 | interface Ethernet1/13
57 |
58 | interface Ethernet1/14
59 |
60 | interface Ethernet1/15
61 |
62 | interface Ethernet1/16
63 |
64 | interface Ethernet1/17
65 |
66 | interface Ethernet1/18
67 |
68 | interface Ethernet1/19
69 |
70 | interface Ethernet1/20
71 |
72 | interface Ethernet1/21
73 |
74 | interface Ethernet1/22
75 |
76 | interface Ethernet1/23
77 |
78 | interface Ethernet1/24
79 |
80 | interface Ethernet1/25
81 |
82 | interface Ethernet1/26
83 |
84 | interface Ethernet1/27
85 |
86 | interface Ethernet1/28
87 |
88 | interface Ethernet1/29
89 |
90 | interface Ethernet1/30
91 |
92 | interface Ethernet1/31
93 |
94 | interface Ethernet1/32
95 |
96 | interface Ethernet1/33
97 |
98 | interface Ethernet1/34
99 |
100 | interface Ethernet1/35
101 |
102 | interface Ethernet1/36
103 |
104 | interface Ethernet1/37
105 |
106 | interface Ethernet1/38
107 |
108 | interface Ethernet1/39
109 |
110 | interface Ethernet1/40
111 |
112 | interface Ethernet1/41
113 |
114 | interface Ethernet1/42
115 |
116 | interface Ethernet1/43
117 |
118 | interface Ethernet1/44
119 |
120 | interface Ethernet1/45
121 |
122 | interface Ethernet1/46
123 |
124 | interface Ethernet1/47
125 |
126 | interface Ethernet1/48
127 |
128 | interface Ethernet1/49
129 |
130 | interface Ethernet1/50
131 |
132 | interface Ethernet1/51
133 |
134 | interface Ethernet1/52
135 |
136 | interface Ethernet1/53
137 |
138 | interface Ethernet1/54
139 |
140 | interface Ethernet1/55
141 |
142 | interface Ethernet1/56
143 |
144 | interface Ethernet1/57
145 |
146 | interface Ethernet1/58
147 |
148 | interface Ethernet1/59
149 |
150 | interface Ethernet1/60
151 |
152 | interface Ethernet1/61
153 |
154 | interface Ethernet1/62
155 |
156 | interface Ethernet1/63
157 |
158 | interface Ethernet1/64
159 |
160 | interface Ethernet1/65
161 |
162 | interface Ethernet1/66
163 |
164 | interface Ethernet1/67
165 |
166 | interface Ethernet1/68
167 |
168 | interface Ethernet1/69
169 |
170 | interface Ethernet1/70
171 |
172 | interface Ethernet1/71
173 |
174 | interface Ethernet1/72
175 |
176 | interface Ethernet1/73
177 |
178 | interface Ethernet1/74
179 |
180 | interface Ethernet1/75
181 |
182 | interface Ethernet1/76
183 |
184 | interface Ethernet1/77
185 |
186 | interface Ethernet1/78
187 |
188 | interface Ethernet1/79
189 |
190 | interface Ethernet1/80
191 |
192 | interface Ethernet1/81
193 |
194 | interface Ethernet1/82
195 |
196 | interface Ethernet1/83
197 |
198 | interface Ethernet1/84
199 |
200 | interface Ethernet1/85
201 |
202 | interface Ethernet1/86
203 |
204 | interface Ethernet1/87
205 |
206 | interface Ethernet1/88
207 |
208 | interface Ethernet1/89
209 |
210 | interface Ethernet1/90
211 |
212 | interface Ethernet1/91
213 |
214 | interface Ethernet1/92
215 |
216 | interface Ethernet1/93
217 |
218 | interface Ethernet1/94
219 |
220 | interface Ethernet1/95
221 |
222 | interface Ethernet1/96
223 |
224 | interface Ethernet1/97
225 |
226 | interface Ethernet1/98
227 |
228 | interface Ethernet1/99
229 |
230 | interface Ethernet1/100
231 |
232 | interface Ethernet1/101
233 |
234 | interface Ethernet1/102
235 |
236 | interface Ethernet1/103
237 |
238 | interface Ethernet1/104
239 |
240 | interface Ethernet1/105
241 |
242 | interface Ethernet1/106
243 |
244 | interface Ethernet1/107
245 |
246 | interface Ethernet1/108
247 |
248 | interface Ethernet1/109
249 |
250 | interface Ethernet1/110
251 |
252 | interface Ethernet1/111
253 |
254 | interface Ethernet1/112
255 |
256 | interface Ethernet1/113
257 |
258 | interface Ethernet1/114
259 |
260 | interface Ethernet1/115
261 |
262 | interface Ethernet1/116
263 |
264 | interface Ethernet1/117
265 |
266 | interface Ethernet1/118
267 |
268 | interface Ethernet1/119
269 |
270 | interface Ethernet1/120
271 |
272 | interface Ethernet1/121
273 |
274 | interface Ethernet1/122
275 |
276 | interface Ethernet1/123
277 |
278 | interface Ethernet1/124
279 |
280 | interface Ethernet1/125
281 |
282 | interface Ethernet1/126
283 |
284 | interface Ethernet1/127
285 |
286 | interface Ethernet1/128
287 |
288 | interface mgmt0
289 | vrf member management
290 | ip address 10.0.0.15/24
291 | line console
292 | line vty
293 |
--------------------------------------------------------------------------------
/tests/functional/cisco_nxos/expected_output/show_run_no_strip:
--------------------------------------------------------------------------------
1 | !Command: show running-config
2 | !Running configuration last done at: Sun Sep 1 02:24:31 2019
3 | !Time: Sun Sep 1 02:27:46 2019
4 |
5 | version 9.2(4) Bios:version
6 | vdc switch id 1
7 | limit-resource vlan minimum 16 maximum 4094
8 | limit-resource vrf minimum 2 maximum 4096
9 | limit-resource port-channel minimum 0 maximum 511
10 | limit-resource u4route-mem minimum 128 maximum 128
11 | limit-resource u6route-mem minimum 96 maximum 96
12 | limit-resource m4route-mem minimum 58 maximum 58
13 | limit-resource m6route-mem minimum 8 maximum 8
14 | no password strength-check
15 | username admin password 5 $5$DICBLF$Xhpg8rM1hb46a98tsxe249e9m6oa3TH00Y8C40.8JDC role network-admin
16 | username vrnetlab password 5 $5$tGG3FFLY$YXOnmUXhkQE5Vn6KCOzpZBABLFvEzochGeH87YOh2q7 role network-admin
17 | username vrnetlab passphrase lifetime 99999 warntime 14 gracetime 3
18 | ip domain-lookup
19 | copp profile strict
20 | snmp-server user admin network-admin auth md5 0x2d0b9ec17b5d1c3bc718bb33c08528a1 priv 0x2d0b9ec17b5d1c3bc718bb33c08528a1 localizedkey
21 | snmp-server user vrnetlab network-admin auth md5 0x3b9301da0b9f328bc2573fed20773dcb priv 0x3b9301da0b9f328bc2573fed20773dcb localizedkey
22 | rmon event 1 description FATAL(1) owner PMON@FATAL
23 | rmon event 2 description CRITICAL(2) owner PMON@CRITICAL
24 | rmon event 3 description ERROR(3) owner PMON@ERROR
25 | rmon event 4 description WARNING(4) owner PMON@WARNING
26 | rmon event 5 description INFORMATION(5) owner PMON@INFO
27 |
28 | vlan 1
29 |
30 | vrf context management
31 |
32 | interface Ethernet1/1
33 |
34 | interface Ethernet1/2
35 |
36 | interface Ethernet1/3
37 |
38 | interface Ethernet1/4
39 |
40 | interface Ethernet1/5
41 |
42 | interface Ethernet1/6
43 |
44 | interface Ethernet1/7
45 |
46 | interface Ethernet1/8
47 |
48 | interface Ethernet1/9
49 |
50 | interface Ethernet1/10
51 |
52 | interface Ethernet1/11
53 |
54 | interface Ethernet1/12
55 |
56 | interface Ethernet1/13
57 |
58 | interface Ethernet1/14
59 |
60 | interface Ethernet1/15
61 |
62 | interface Ethernet1/16
63 |
64 | interface Ethernet1/17
65 |
66 | interface Ethernet1/18
67 |
68 | interface Ethernet1/19
69 |
70 | interface Ethernet1/20
71 |
72 | interface Ethernet1/21
73 |
74 | interface Ethernet1/22
75 |
76 | interface Ethernet1/23
77 |
78 | interface Ethernet1/24
79 |
80 | interface Ethernet1/25
81 |
82 | interface Ethernet1/26
83 |
84 | interface Ethernet1/27
85 |
86 | interface Ethernet1/28
87 |
88 | interface Ethernet1/29
89 |
90 | interface Ethernet1/30
91 |
92 | interface Ethernet1/31
93 |
94 | interface Ethernet1/32
95 |
96 | interface Ethernet1/33
97 |
98 | interface Ethernet1/34
99 |
100 | interface Ethernet1/35
101 |
102 | interface Ethernet1/36
103 |
104 | interface Ethernet1/37
105 |
106 | interface Ethernet1/38
107 |
108 | interface Ethernet1/39
109 |
110 | interface Ethernet1/40
111 |
112 | interface Ethernet1/41
113 |
114 | interface Ethernet1/42
115 |
116 | interface Ethernet1/43
117 |
118 | interface Ethernet1/44
119 |
120 | interface Ethernet1/45
121 |
122 | interface Ethernet1/46
123 |
124 | interface Ethernet1/47
125 |
126 | interface Ethernet1/48
127 |
128 | interface Ethernet1/49
129 |
130 | interface Ethernet1/50
131 |
132 | interface Ethernet1/51
133 |
134 | interface Ethernet1/52
135 |
136 | interface Ethernet1/53
137 |
138 | interface Ethernet1/54
139 |
140 | interface Ethernet1/55
141 |
142 | interface Ethernet1/56
143 |
144 | interface Ethernet1/57
145 |
146 | interface Ethernet1/58
147 |
148 | interface Ethernet1/59
149 |
150 | interface Ethernet1/60
151 |
152 | interface Ethernet1/61
153 |
154 | interface Ethernet1/62
155 |
156 | interface Ethernet1/63
157 |
158 | interface Ethernet1/64
159 |
160 | interface Ethernet1/65
161 |
162 | interface Ethernet1/66
163 |
164 | interface Ethernet1/67
165 |
166 | interface Ethernet1/68
167 |
168 | interface Ethernet1/69
169 |
170 | interface Ethernet1/70
171 |
172 | interface Ethernet1/71
173 |
174 | interface Ethernet1/72
175 |
176 | interface Ethernet1/73
177 |
178 | interface Ethernet1/74
179 |
180 | interface Ethernet1/75
181 |
182 | interface Ethernet1/76
183 |
184 | interface Ethernet1/77
185 |
186 | interface Ethernet1/78
187 |
188 | interface Ethernet1/79
189 |
190 | interface Ethernet1/80
191 |
192 | interface Ethernet1/81
193 |
194 | interface Ethernet1/82
195 |
196 | interface Ethernet1/83
197 |
198 | interface Ethernet1/84
199 |
200 | interface Ethernet1/85
201 |
202 | interface Ethernet1/86
203 |
204 | interface Ethernet1/87
205 |
206 | interface Ethernet1/88
207 |
208 | interface Ethernet1/89
209 |
210 | interface Ethernet1/90
211 |
212 | interface Ethernet1/91
213 |
214 | interface Ethernet1/92
215 |
216 | interface Ethernet1/93
217 |
218 | interface Ethernet1/94
219 |
220 | interface Ethernet1/95
221 |
222 | interface Ethernet1/96
223 |
224 | interface Ethernet1/97
225 |
226 | interface Ethernet1/98
227 |
228 | interface Ethernet1/99
229 |
230 | interface Ethernet1/100
231 |
232 | interface Ethernet1/101
233 |
234 | interface Ethernet1/102
235 |
236 | interface Ethernet1/103
237 |
238 | interface Ethernet1/104
239 |
240 | interface Ethernet1/105
241 |
242 | interface Ethernet1/106
243 |
244 | interface Ethernet1/107
245 |
246 | interface Ethernet1/108
247 |
248 | interface Ethernet1/109
249 |
250 | interface Ethernet1/110
251 |
252 | interface Ethernet1/111
253 |
254 | interface Ethernet1/112
255 |
256 | interface Ethernet1/113
257 |
258 | interface Ethernet1/114
259 |
260 | interface Ethernet1/115
261 |
262 | interface Ethernet1/116
263 |
264 | interface Ethernet1/117
265 |
266 | interface Ethernet1/118
267 |
268 | interface Ethernet1/119
269 |
270 | interface Ethernet1/120
271 |
272 | interface Ethernet1/121
273 |
274 | interface Ethernet1/122
275 |
276 | interface Ethernet1/123
277 |
278 | interface Ethernet1/124
279 |
280 | interface Ethernet1/125
281 |
282 | interface Ethernet1/126
283 |
284 | interface Ethernet1/127
285 |
286 | interface Ethernet1/128
287 |
288 | interface mgmt0
289 | vrf member management
290 | ip address 10.0.0.15/24
291 | line console
292 | line vty
293 |
294 |
295 |
296 |
297 | switch#
298 |
--------------------------------------------------------------------------------
/tests/functional/cisco_nxos/ext_test_funcs.py:
--------------------------------------------------------------------------------
1 | def nxos_disable_paging(cls):
2 | cls.send_inputs("term length 0")
3 |
--------------------------------------------------------------------------------
/tests/functional/cisco_nxos/test_nxos.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import re
3 |
4 | import pytest
5 |
6 | from tests.functional.base_functional_tests import BaseFunctionalTest
7 | import ssh2net
8 |
9 | TEST_DEVICE = {"setup_host": "172.18.0.12", "auth_user": "vrnetlab", "auth_password": "VR-netlab9"}
10 |
11 | dummy_conn = ssh2net.NXOSDriver(**TEST_DEVICE)
12 | PRIV_LEVELS = dummy_conn.privs
13 | # remove exec priv level as it is not configured on the vrnetlab host for testing
14 | PRIV_LEVELS.pop("exec")
15 |
16 |
17 | class TestNXOS(BaseFunctionalTest):
18 | def setup_method(self):
19 | self.platform_driver = ssh2net.NXOSDriver
20 |
21 | self.device_type = Path(__file__).resolve().parts[-2]
22 | self.func_test_dir = (
23 | f"{Path(ssh2net.__file__).parents[1]}/tests/functional/{self.device_type}/"
24 | )
25 | self.test_device = TEST_DEVICE
26 | self.disable_paging_ext_function = f"tests.functional.{self.device_type}.ext_test_funcs.{self.device_type.split('_')[1]}_disable_paging"
27 |
28 | @staticmethod
29 | def _replace_trailing_chars_running_config(input_data):
30 | execute_trailing_chars_pattern = re.compile(r"^line vty.*$", flags=re.M | re.I | re.S)
31 | input_data = re.sub(execute_trailing_chars_pattern, "line vty", input_data)
32 | return input_data
33 |
34 | @staticmethod
35 | def _replace_timestamps(input_data):
36 | datetime_pattern = re.compile(
37 | r"(mon|tue|wed|thu|fri|sat|sun)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+\d+\s+\d+:\d+:\d+\s\d+",
38 | flags=re.M | re.I,
39 | )
40 | input_data = re.sub(datetime_pattern, "TIME_STAMP_REPLACED", input_data)
41 | return input_data
42 |
43 | @staticmethod
44 | def _replace_crypto_strings(input_data):
45 | crypto_pattern = re.compile(r"^(.*?\s(?:5|md5)\s)[\w$\.\/]+.*$", flags=re.M | re.I)
46 | input_data = re.sub(crypto_pattern, "CRYPTO_REPLACED", input_data)
47 | return input_data
48 |
49 | def disable_paging(self, cls):
50 | cls.send_inputs("term length 0")
51 |
52 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
53 | def test_send_inputs_interact(self, setup_use_paramiko):
54 | """
55 | expected output for this test is somewhat massaged to make this test pass
56 | in "real" device ssh session there aren't new lines between response and the prompt
57 | there is also a space after the prompt and before the "n"
58 | given that this is just for interactive "solving" these problems is not worth the effort
59 | """
60 | interact = ["delete bootflash:virtual-instance.conf", "(yes/no/abort) [y]", "n"]
61 | super().test_send_inputs_interact(setup_use_paramiko, interact)
62 |
63 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
64 | @pytest.mark.parametrize("priv_level", [priv for priv in PRIV_LEVELS.values()])
65 | def test_acquire_all_priv_levels(self, setup_use_paramiko, priv_level):
66 | super().test_acquire_all_priv_levels(setup_use_paramiko, priv_level)
67 |
68 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
69 | def test__determine_current_priv_special_configuration(self, setup_use_paramiko):
70 | with self.platform_driver(
71 | **self.test_device, setup_use_paramiko=setup_use_paramiko
72 | ) as conn:
73 | conn.send_inputs(["configure terminal", "interface Ethernet1/128"])
74 | current_prompt = conn.get_prompt()
75 | current_priv = conn._determine_current_priv(current_prompt)
76 | assert current_priv.name == "special_configuration"
77 |
--------------------------------------------------------------------------------
/tests/functional/comparison_tests/test_comparison_tests.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import subprocess
3 |
4 | import ssh2net
5 |
6 |
7 | NET2_DIR = ssh2net.__file__
8 | COMPARISON_TEST_DIR = f"{Path(NET2_DIR).parents[1]}/comparison_tests/"
9 |
10 |
11 | def subprocess_runner(cmd, cwd):
12 | with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) as proc:
13 | std_out, std_err = proc.communicate()
14 | return (std_out.decode(), std_err.decode(), proc.returncode)
15 |
16 |
17 | def test_test_ssh2net():
18 | cmd = ["python", "test_ssh2net.py"]
19 | std_out, std_err, return_code = subprocess_runner(cmd, COMPARISON_TEST_DIR)
20 | assert return_code == 0
21 | assert std_err == ""
22 |
23 |
24 | def test_test_netmiko():
25 | cmd = ["python", "test_netmiko.py"]
26 | std_out, std_err, return_code = subprocess_runner(cmd, COMPARISON_TEST_DIR)
27 | assert return_code == 0
28 | assert std_err == ""
29 |
--------------------------------------------------------------------------------
/tests/functional/examples/README.md:
--------------------------------------------------------------------------------
1 | put tests for any examples here!
2 |
--------------------------------------------------------------------------------
/tests/functional/juniper_junos/expected_output/interactive:
--------------------------------------------------------------------------------
1 | Password:
2 | root@%
3 |
--------------------------------------------------------------------------------
/tests/functional/juniper_junos/expected_output/show_run:
--------------------------------------------------------------------------------
1 | ## Last commit: 2019-09-04 00:07:59 UTC by root
2 | version 17.3R2.10;
3 | system {
4 | root-authentication {
5 | encrypted-password "$6$H3raU2MQ$9y6Tm/QPYA9Um2U2UuajFp1RcSIs7q8i9q/9CBmQK1FZQMGqYeMtSlgrYlH2x/FS/703Z5hRyDTUQQDvvYaA9/"; ## SECRET-DATA
6 | }
7 | login {
8 | user vrnetlab {
9 | uid 2000;
10 | class super-user;
11 | authentication {
12 | encrypted-password "$6$ay21MJjL$mFcINECEm2FXQZsTp4gnMmvYiaUo1KplHYuGJLKN0B0qVCW8N0M4ZN5Aci/JCwJbgM57b9Zs/sELszBra7O5C."; ## SECRET-DATA
13 | }
14 | }
15 | }
16 | services {
17 | ssh {
18 | protocol-version v2;
19 | }
20 | netconf {
21 | ssh;
22 | }
23 | web-management {
24 | http {
25 | interface fxp0.0;
26 | }
27 | }
28 | }
29 | syslog {
30 | user * {
31 | any emergency;
32 | }
33 | file messages {
34 | any any;
35 | authorization info;
36 | }
37 | file interactive-commands {
38 | interactive-commands any;
39 | }
40 | }
41 | license {
42 | autoupdate {
43 | url https://ae1.juniper.net/junos/key_retrieval;
44 | }
45 | }
46 | }
47 | security {
48 | screen {
49 | ids-option untrust-screen {
50 | icmp {
51 | ping-death;
52 | }
53 | ip {
54 | source-route-option;
55 | tear-drop;
56 | }
57 | tcp {
58 | syn-flood {
59 | alarm-threshold 1024;
60 | attack-threshold 200;
61 | source-threshold 1024;
62 | destination-threshold 2048;
63 | queue-size 2000; ## Warning: 'queue-size' is deprecated
64 | timeout 20;
65 | }
66 | land;
67 | }
68 | }
69 | }
70 | policies {
71 | from-zone trust to-zone trust {
72 | policy default-permit {
73 | match {
74 | source-address any;
75 | destination-address any;
76 | application any;
77 | }
78 | then {
79 | permit;
80 | }
81 | }
82 | }
83 | from-zone trust to-zone untrust {
84 | policy default-permit {
85 | match {
86 | source-address any;
87 | destination-address any;
88 | application any;
89 | }
90 | then {
91 | permit;
92 | }
93 | }
94 | }
95 | }
96 | zones {
97 | security-zone trust {
98 | tcp-rst;
99 | }
100 | security-zone untrust {
101 | screen untrust-screen;
102 | }
103 | }
104 | }
105 | interfaces {
106 | fxp0 {
107 | unit 0 {
108 | family inet {
109 | address 10.0.0.15/24;
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/functional/juniper_junos/expected_output/show_run_no_strip:
--------------------------------------------------------------------------------
1 | ## Last commit: 2019-09-04 00:07:59 UTC by root
2 | version 17.3R2.10;
3 | system {
4 | root-authentication {
5 | encrypted-password "$6$H3raU2MQ$9y6Tm/QPYA9Um2U2UuajFp1RcSIs7q8i9q/9CBmQK1FZQMGqYeMtSlgrYlH2x/FS/703Z5hRyDTUQQDvvYaA9/"; ## SECRET-DATA
6 | }
7 | login {
8 | user vrnetlab {
9 | uid 2000;
10 | class super-user;
11 | authentication {
12 | encrypted-password "$6$ay21MJjL$mFcINECEm2FXQZsTp4gnMmvYiaUo1KplHYuGJLKN0B0qVCW8N0M4ZN5Aci/JCwJbgM57b9Zs/sELszBra7O5C."; ## SECRET-DATA
13 | }
14 | }
15 | }
16 | services {
17 | ssh {
18 | protocol-version v2;
19 | }
20 | netconf {
21 | ssh;
22 | }
23 | web-management {
24 | http {
25 | interface fxp0.0;
26 | }
27 | }
28 | }
29 | syslog {
30 | user * {
31 | any emergency;
32 | }
33 | file messages {
34 | any any;
35 | authorization info;
36 | }
37 | file interactive-commands {
38 | interactive-commands any;
39 | }
40 | }
41 | license {
42 | autoupdate {
43 | url https://ae1.juniper.net/junos/key_retrieval;
44 | }
45 | }
46 | }
47 | security {
48 | screen {
49 | ids-option untrust-screen {
50 | icmp {
51 | ping-death;
52 | }
53 | ip {
54 | source-route-option;
55 | tear-drop;
56 | }
57 | tcp {
58 | syn-flood {
59 | alarm-threshold 1024;
60 | attack-threshold 200;
61 | source-threshold 1024;
62 | destination-threshold 2048;
63 | queue-size 2000; ## Warning: 'queue-size' is deprecated
64 | timeout 20;
65 | }
66 | land;
67 | }
68 | }
69 | }
70 | policies {
71 | from-zone trust to-zone trust {
72 | policy default-permit {
73 | match {
74 | source-address any;
75 | destination-address any;
76 | application any;
77 | }
78 | then {
79 | permit;
80 | }
81 | }
82 | }
83 | from-zone trust to-zone untrust {
84 | policy default-permit {
85 | match {
86 | source-address any;
87 | destination-address any;
88 | application any;
89 | }
90 | then {
91 | permit;
92 | }
93 | }
94 | }
95 | }
96 | zones {
97 | security-zone trust {
98 | tcp-rst;
99 | }
100 | security-zone untrust {
101 | screen untrust-screen;
102 | }
103 | }
104 | }
105 | interfaces {
106 | fxp0 {
107 | unit 0 {
108 | family inet {
109 | address 10.0.0.15/24;
110 | }
111 | }
112 | }
113 | }
114 |
115 | vrnetlab>
116 |
--------------------------------------------------------------------------------
/tests/functional/juniper_junos/ext_test_funcs.py:
--------------------------------------------------------------------------------
1 | def junos_disable_paging(cls):
2 | cls.send_inputs("set cli screen-length 0")
3 |
--------------------------------------------------------------------------------
/tests/functional/juniper_junos/test_junos.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import re
3 |
4 | import pytest
5 |
6 | from tests.functional.base_functional_tests import BaseFunctionalTest
7 | import ssh2net
8 |
9 | TEST_DEVICE = {
10 | "setup_host": "172.18.0.15",
11 | "auth_user": "vrnetlab",
12 | "auth_password": "VR-netlab9",
13 | "comms_disable_paging": "set cli screen-length 0",
14 | }
15 |
16 | dummy_conn = ssh2net.JunosDriver(**TEST_DEVICE)
17 | PRIV_LEVELS = dummy_conn.privs
18 |
19 |
20 | class TestJunos(BaseFunctionalTest):
21 | def setup_method(self):
22 | self.platform_driver = ssh2net.JunosDriver
23 |
24 | self.device_type = Path(__file__).resolve().parts[-2]
25 | self.func_test_dir = (
26 | f"{Path(ssh2net.__file__).parents[1]}/tests/functional/{self.device_type}/"
27 | )
28 | self.test_device = TEST_DEVICE
29 | self.disable_paging_ext_function = f"tests.functional.{self.device_type}.ext_test_funcs.{self.device_type.split('_')[1]}_disable_paging"
30 |
31 | @staticmethod
32 | def _replace_trailing_chars_running_config(input_data):
33 | execute_trailing_chars_pattern = re.compile(r"^}\s$", flags=re.M | re.I | re.S)
34 | input_data = re.sub(execute_trailing_chars_pattern, "}", input_data)
35 | return input_data
36 |
37 | @staticmethod
38 | def _replace_timestamps(input_data):
39 | datetime_pattern = re.compile(
40 | r"^## Last commit: \d+-\d+-\d+\s\d+:\d+:\d+\s\w+.*$", flags=re.M | re.I
41 | )
42 | input_data = re.sub(datetime_pattern, "TIME_STAMP_REPLACED", input_data)
43 | return input_data
44 |
45 | @staticmethod
46 | def _replace_crypto_strings(input_data):
47 | crypto_pattern = re.compile(
48 | r'^\s+encrypted-password\s"[\w$\.\/]+";\s.*$', flags=re.M | re.I
49 | )
50 | input_data = re.sub(crypto_pattern, "CRYPTO_REPLACED", input_data)
51 | return input_data
52 |
53 | def _disable_paging_function(self, setup_use_paramiko):
54 | test_device_copy = self.test_device.copy()
55 | test_device_copy.pop("comms_disable_paging")
56 | with ssh2net.SSH2Net(
57 | **test_device_copy,
58 | setup_use_paramiko=setup_use_paramiko,
59 | comms_disable_paging=self.disable_paging,
60 | ) as conn:
61 | show_run = conn.send_inputs("show configuration")[0]
62 | show_run = self.clean_input_data(show_run)
63 | return show_run
64 |
65 | def _disable_paging_external_function(self, setup_use_paramiko):
66 | test_device_copy = self.test_device.copy()
67 | test_device_copy.pop("comms_disable_paging")
68 | with ssh2net.SSH2Net(
69 | **test_device_copy,
70 | setup_use_paramiko=setup_use_paramiko,
71 | comms_disable_paging=self.disable_paging_ext_function,
72 | ) as conn:
73 | show_run = conn.send_inputs("show configuration")[0]
74 | show_run = self.clean_input_data(show_run)
75 | return show_run
76 |
77 | def disable_paging(self, cls):
78 | cls.send_inputs("set cli screen-length 0")
79 |
80 | def test_show_run_execute(self):
81 | pytest.skip("no support for execute on junos")
82 |
83 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
84 | def test_show_run_inputs(self, setup_use_paramiko):
85 | command = "show configuration"
86 | super().test_show_run_inputs(setup_use_paramiko, command)
87 |
88 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
89 | def test_show_run_inputs_no_strip_prompt(self, setup_use_paramiko):
90 | command = "show configuration"
91 | super().test_show_run_inputs_no_strip_prompt(setup_use_paramiko, command)
92 |
93 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
94 | def test_send_inputs_interact(self, setup_use_paramiko):
95 | interact = ["start shell user root", "Password:", TEST_DEVICE["auth_password"], r"^root@%$"]
96 | super().test_send_inputs_interact(setup_use_paramiko, interact, hidden_response=True)
97 |
98 | @pytest.mark.parametrize("setup_use_paramiko", [False, True], ids=["ssh2", "paramiko"])
99 | @pytest.mark.parametrize("priv_level", [priv for priv in PRIV_LEVELS.values()])
100 | def test_acquire_all_priv_levels(self, setup_use_paramiko, priv_level):
101 | super().test_acquire_all_priv_levels(setup_use_paramiko, priv_level)
102 |
--------------------------------------------------------------------------------
/tests/unit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlmontanari/ssh2net/55e969b6d44ec3f2bd2ebbd8dedd68b99bee4c5b/tests/unit/__init__.py
--------------------------------------------------------------------------------
/tests/unit/_ssh_config:
--------------------------------------------------------------------------------
1 | Host 1.2.3.4 someswitch1
2 | User carl
3 | IdentityFile ~/.ssh/mysshkey
4 | IdentitiesOnly yes
5 | Hostname someswitch1.bogus.com
6 | Port 1234
7 |
8 | Host *
9 | User carl
10 |
11 | Host someswitch?
12 | User carl
13 | IdentityFile ~/.ssh/mysshkey
14 | IdentitiesOnly yes
15 | Hostname someswitch1.bogus.com
16 | Port 1234
17 |
18 |
--------------------------------------------------------------------------------
/tests/unit/drivers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlmontanari/ssh2net/55e969b6d44ec3f2bd2ebbd8dedd68b99bee4c5b/tests/unit/drivers/__init__.py
--------------------------------------------------------------------------------
/tests/unit/drivers/base_driver_unit_tests.py:
--------------------------------------------------------------------------------
1 | class BaseDriverUnitTest:
2 | def setup_method(self):
3 | pass
4 |
5 | def get_prompt(self):
6 | return self.standard_prompt
7 |
8 | @staticmethod
9 | def send_inputs():
10 | return True
11 |
12 | @staticmethod
13 | def send_inputs_interact():
14 | return True
15 |
16 | def test__determine_current_priv_exec(self):
17 | assert self.driver._determine_current_priv("myrouter>") == self.privs["exec"]
18 |
19 | def test__determine_current_priv_privilege_exec(self):
20 | assert self.driver._determine_current_priv("myrouter#") == self.privs["privilege_exec"]
21 |
22 | def test__determine_current_priv_config(self):
23 | assert (
24 | self.driver._determine_current_priv("myrouter(config)#") == self.privs["configuration"]
25 | )
26 |
27 | def test__determine_current_priv_special_config(self):
28 | assert (
29 | self.driver._determine_current_priv("myrouter(config-if)#")
30 | == self.privs["special_configuration"]
31 | )
32 |
--------------------------------------------------------------------------------
/tests/unit/drivers/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlmontanari/ssh2net/55e969b6d44ec3f2bd2ebbd8dedd68b99bee4c5b/tests/unit/drivers/core/__init__.py
--------------------------------------------------------------------------------
/tests/unit/drivers/core/test_core_arista_eos_driver.py:
--------------------------------------------------------------------------------
1 | from ssh2net.core.arista_eos.driver import EOSDriver, PRIVS
2 | from tests.unit.drivers.base_driver_unit_tests import BaseDriverUnitTest
3 |
4 |
5 | class TestEOS(BaseDriverUnitTest):
6 | def setup_method(self):
7 | self.privs = PRIVS
8 | self.driver = EOSDriver()
9 |
--------------------------------------------------------------------------------
/tests/unit/drivers/core/test_core_cisco_iosxe_driver.py:
--------------------------------------------------------------------------------
1 | from ssh2net.core.cisco_iosxe.driver import IOSXEDriver, PRIVS
2 | from tests.unit.drivers.base_driver_unit_tests import BaseDriverUnitTest
3 |
4 |
5 | class TestIOSXE(BaseDriverUnitTest):
6 | def setup_method(self):
7 | self.privs = PRIVS
8 | self.driver = IOSXEDriver()
9 |
--------------------------------------------------------------------------------
/tests/unit/drivers/core/test_core_cisco_iosxr_driver.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from ssh2net.core.cisco_iosxr.driver import IOSXRDriver, PRIVS
4 | from tests.unit.drivers.base_driver_unit_tests import BaseDriverUnitTest
5 |
6 |
7 | class TestIOSXR(BaseDriverUnitTest):
8 | def setup_method(self):
9 | self.privs = PRIVS
10 | self.driver = IOSXRDriver()
11 |
12 | def test__determine_current_priv_exec(self):
13 | pytest.skip("no privilege exec on iosxr")
14 |
--------------------------------------------------------------------------------
/tests/unit/drivers/core/test_core_cisco_nxos_driver.py:
--------------------------------------------------------------------------------
1 | from ssh2net.core.cisco_nxos.driver import NXOSDriver, PRIVS
2 | from tests.unit.drivers.base_driver_unit_tests import BaseDriverUnitTest
3 |
4 |
5 | class TestNXOS(BaseDriverUnitTest):
6 | def setup_method(self):
7 | self.privs = PRIVS
8 | self.driver = NXOSDriver()
9 |
--------------------------------------------------------------------------------
/tests/unit/drivers/core/test_core_juniper_junos_driver.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from ssh2net.core.juniper_junos.driver import JunosDriver, PRIVS
4 | from tests.unit.drivers.base_driver_unit_tests import BaseDriverUnitTest
5 |
6 |
7 | class TestIOSXE(BaseDriverUnitTest):
8 | def setup_method(self):
9 | self.privs = PRIVS
10 | self.driver = JunosDriver()
11 |
12 | def test__determine_current_priv_privilege_exec(self):
13 | pytest.skip("no privilege exec on junos")
14 |
15 | def test__determine_current_priv_config(self):
16 | assert self.driver._determine_current_priv("myrouter#") == self.privs["configuration"]
17 |
18 | def test__determine_current_priv_special_config(self):
19 | pytest.skip("not testing for special config pattern on juniper")
20 |
--------------------------------------------------------------------------------
/tests/unit/ext_test_funcs.py:
--------------------------------------------------------------------------------
1 | def some_pre_login_handler_func(cls):
2 | pass
3 |
4 |
5 | def some_disable_paging_func(cls):
6 | pass
7 |
--------------------------------------------------------------------------------
/tests/unit/test_channel.py:
--------------------------------------------------------------------------------
1 | from ssh2net import SSH2NetChannel
2 |
3 |
4 | def test__rstrip_all_lines():
5 | test_input = b"""
6 | some line
7 | another line
8 | one final line
9 | """
10 | output = SSH2NetChannel._rstrip_all_lines(test_input)
11 | for line in output.splitlines():
12 | assert line[-1] != " "
13 |
14 |
15 | def test__restructure_output():
16 | output = "\n\nsomedata\n3560CX#"
17 | output = SSH2NetChannel._restructure_output(output)
18 | assert output.splitlines()[0] != ""
19 | assert output.splitlines()[1] != ""
20 |
21 |
22 | def test__restructure_output_strip_prompt():
23 | output = "\n\nsomedata\n3560CX#"
24 | output = SSH2NetChannel._restructure_output(output, strip_prompt=True)
25 | assert output.splitlines()[0] != ""
26 | assert output.splitlines()[-1] != "3560CX#"
27 |
28 |
29 | def test__strip_ansi():
30 | output = b"[admin@CoolDevice.Sea1: \x1b[1m/\x1b[0;0m]$"
31 | output = SSH2NetChannel._strip_ansi(output)
32 | assert output == b"[admin@CoolDevice.Sea1: /]$"
33 |
--------------------------------------------------------------------------------
/tests/unit/test_core_driver.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import pytest
4 |
5 | from ssh2net.exceptions import UnknownPrivLevel
6 | from ssh2net.core.driver import BaseNetworkDriver
7 | from ssh2net.core.cisco_iosxe.driver import PRIVS
8 |
9 |
10 | IOS_ARP = """Protocol Address Age (min) Hardware Addr Type Interface
11 | Internet 172.31.254.1 - 0000.0c07.acfe ARPA Vlan254
12 | Internet 172.31.254.2 - c800.84b2.e9c2 ARPA Vlan254
13 | """
14 |
15 |
16 | def test__determine_current_priv():
17 | base_driver = BaseNetworkDriver()
18 | base_driver.privs = PRIVS
19 | current_priv = base_driver._determine_current_priv("execprompt>")
20 | assert current_priv.name == "exec"
21 |
22 |
23 | def test__determine_current_priv_unknown():
24 | base_driver = BaseNetworkDriver()
25 | base_driver.privs = PRIVS
26 | with pytest.raises(UnknownPrivLevel):
27 | base_driver._determine_current_priv("!!!!thisissoooowrongggg!!!!!!?!")
28 |
29 |
30 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting textfsm on windows")
31 | def test_textfsm_parse_output():
32 | base_driver = BaseNetworkDriver()
33 | base_driver.textfsm_platform = "cisco_ios"
34 | result = base_driver.textfsm_parse_output("show ip arp", IOS_ARP)
35 | assert isinstance(result, list)
36 | assert result[0] == ["Internet", "172.31.254.1", "-", "0000.0c07.acfe", "ARPA", "Vlan254"]
37 |
--------------------------------------------------------------------------------
/tests/unit/test_decorators.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 |
4 | import pytest
5 |
6 | from ssh2net import SSH2Net
7 |
8 | if not sys.platform.startswith("win"):
9 | from ssh2net.decorators import operation_timeout
10 | else:
11 | from ssh2net.decorators import operation_timeout_win as operation_timeout
12 |
13 |
14 | class MockSSH2Net(SSH2Net):
15 | def __init__(self):
16 | super().__init__()
17 | self.comms_operation_timeout = 0.1
18 |
19 | @operation_timeout("comms_operation_timeout")
20 | def operation_timeout_func(self):
21 | time.sleep(1000)
22 |
23 | @operation_timeout("comms_operation_timeout")
24 | def operation_success_func(self):
25 | time.sleep(0.00001)
26 |
27 |
28 | def test_operation_timeout_timeout():
29 | timeout_test = MockSSH2Net()
30 | with pytest.raises(TimeoutError):
31 | timeout_test.operation_timeout_func()
32 |
33 |
34 | def test_operation_timeout_success():
35 | timeout_test = MockSSH2Net()
36 | timeout_test.operation_success_func()
37 |
38 |
39 | def test_operation_timeout_no_timeout_value():
40 | timeout_test = MockSSH2Net()
41 | timeout_test.comms_prompt_timeout = None
42 | timeout_test.operation_success_func()
43 |
--------------------------------------------------------------------------------
/tests/unit/test_helper.py:
--------------------------------------------------------------------------------
1 | from io import TextIOWrapper
2 | import pkg_resources
3 | import sys
4 |
5 | import pytest
6 |
7 | from ssh2net.helper import _textfsm_get_template, textfsm_parse
8 |
9 |
10 | IOS_ARP = """Protocol Address Age (min) Hardware Addr Type Interface
11 | Internet 172.31.254.1 - 0000.0c07.acfe ARPA Vlan254
12 | Internet 172.31.254.2 - c800.84b2.e9c2 ARPA Vlan254
13 | """
14 |
15 |
16 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting textfsm on windows")
17 | def test__textfsm_get_template_valid_template():
18 | template = _textfsm_get_template("cisco_nxos", "show ip arp")
19 | template_dir = pkg_resources.resource_filename("ntc_templates", "templates")
20 | assert isinstance(template, TextIOWrapper)
21 | assert template.name == f"{template_dir}/cisco_nxos_show_ip_arp.template"
22 |
23 |
24 | def test__textfsm_get_template_invalid_template():
25 | template = _textfsm_get_template("cisco_nxos", "show racecar")
26 | assert not template
27 |
28 |
29 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting textfsm on windows")
30 | def test_text_textfsm_parse_success():
31 | template = _textfsm_get_template("cisco_ios", "show ip arp")
32 | result = textfsm_parse(template, IOS_ARP)
33 | assert isinstance(result, list)
34 | assert result[0] == ["Internet", "172.31.254.1", "-", "0000.0c07.acfe", "ARPA", "Vlan254"]
35 |
36 |
37 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting textfsm on windows")
38 | def test_text_textfsm_parse_success_string_path():
39 | template = _textfsm_get_template("cisco_ios", "show ip arp")
40 | result = textfsm_parse(template.name, IOS_ARP)
41 | assert isinstance(result, list)
42 | assert result[0] == ["Internet", "172.31.254.1", "-", "0000.0c07.acfe", "ARPA", "Vlan254"]
43 |
44 |
45 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting textfsm on windows")
46 | def test_text_textfsm_parse_failure():
47 | template = _textfsm_get_template("cisco_ios", "show ip arp")
48 | result = textfsm_parse(template, "not really arp data")
49 | assert isinstance(result, str)
50 | assert result == "not really arp data"
51 |
--------------------------------------------------------------------------------
/tests/unit/test_netmiko_compatibility.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from ssh2net.netmiko_compatibility import connect_handler, transform_netmiko_kwargs
4 |
5 |
6 | def test_connect_handler_invalid_device_type():
7 | device = {"device_type": "tacocat"}
8 | with pytest.raises(TypeError):
9 | connect_handler(**device)
10 |
11 |
12 | def test_connect_handler_ip_no_hostname():
13 | netmiko_args = {
14 | "ip": "1.2.3.4",
15 | "username": "person",
16 | "password": "password",
17 | "port": 123,
18 | "global_delay_factor": 5,
19 | "device_type": "cisco_xe",
20 | }
21 | conn = connect_handler(auto_open=False, **netmiko_args)
22 | assert conn.textfsm_platform == "cisco_ios"
23 |
24 |
25 | def test_connect_handler_valid_connection():
26 | netmiko_args = {
27 | "host": "1.2.3.4",
28 | "username": "person",
29 | "password": "password",
30 | "port": 123,
31 | "global_delay_factor": 5,
32 | "device_type": "cisco_xe",
33 | }
34 | conn = connect_handler(auto_open=False, **netmiko_args)
35 | assert conn.textfsm_platform == "cisco_ios"
36 |
37 |
38 | def test_transform_netmiko_args():
39 | netmiko_args = {
40 | "host": "1.2.3.4",
41 | "username": "person",
42 | "password": "password",
43 | "port": 123,
44 | "global_delay_factor": 5,
45 | }
46 | transformed_args = transform_netmiko_kwargs(netmiko_args)
47 | assert transformed_args["setup_host"] == "1.2.3.4"
48 | assert transformed_args["session_timeout"] == 25000
49 |
50 |
51 | def test_transform_netmiko_args_setup_timeout():
52 | netmiko_args = {"host": "1.2.3.4", "username": "person", "password": "password", "port": 123}
53 | transformed_args = transform_netmiko_kwargs(netmiko_args)
54 | assert transformed_args["setup_host"] == "1.2.3.4"
55 | assert transformed_args["session_timeout"] == 5000
56 |
--------------------------------------------------------------------------------
/tests/unit/test_session.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlmontanari/ssh2net/55e969b6d44ec3f2bd2ebbd8dedd68b99bee4c5b/tests/unit/test_session.py
--------------------------------------------------------------------------------
/tests/unit/test_ssh_config.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 | import sys
4 |
5 | import pytest
6 |
7 | import ssh2net
8 | from ssh2net import SSH2NetSSHConfig
9 | from ssh2net.ssh_config import Host
10 |
11 |
12 | NET2_DIR = ssh2net.__file__
13 | UNIT_TEST_DIR = f"{Path(NET2_DIR).parents[1]}/tests/unit/"
14 |
15 |
16 | def test_init_ssh_config_file_explicit():
17 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
18 | with open(f"{UNIT_TEST_DIR}_ssh_config", "r") as f:
19 | ssh_config_file = f.read()
20 | assert ssh_conf.ssh_config_file == ssh_config_file
21 |
22 |
23 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting ssh config on windows")
24 | def test_init_ssh_config_file_user(fs):
25 | fs.add_real_file("/etc/ssh/ssh_config", target_path=f"{os.path.expanduser('~')}/.ssh/config")
26 | ssh_conf = SSH2NetSSHConfig()
27 | with open(f"{os.path.expanduser('~')}/.ssh/config", "r") as f:
28 | ssh_config_file = f.read()
29 | assert ssh_conf.ssh_config_file == ssh_config_file
30 |
31 |
32 | @pytest.mark.skipif(sys.platform.startswith("win"), reason="not supporting ssh config on windows")
33 | def test_init_ssh_config_file_system(fs):
34 | fs.add_real_file("/etc/ssh/ssh_config")
35 | ssh_conf = SSH2NetSSHConfig()
36 | with open("/etc/ssh/ssh_config", "r") as f:
37 | ssh_config_file = f.read()
38 | assert ssh_conf.ssh_config_file == ssh_config_file
39 |
40 |
41 | def test_init_ssh_config_file_no_config_file(fs):
42 | ssh_conf = SSH2NetSSHConfig()
43 | assert ssh_conf.hosts is None
44 |
45 |
46 | def test_init_ssh_config_file_no_hosts():
47 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}__init__.py")
48 | assert ssh_conf.hosts is None
49 |
50 |
51 | def test_str():
52 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
53 | assert str(ssh_conf) == "SSH2NetSSHConfig Object"
54 |
55 |
56 | def test_repr():
57 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
58 | assert repr(ssh_conf) == (
59 | "SSH2NetSSHConfig {'hosts': {'1.2.3.4 someswitch1': HostEntry {'hosts': '1.2.3.4 "
60 | "someswitch1', 'hostname': 'someswitch1.bogus.com', 'port': '1234', 'user': 'carl', "
61 | "'address_family': None, 'bind_address': None, 'connect_timeout': None, "
62 | "'identities_only': 'yes', 'identity_file': '~/.ssh/mysshkey', 'keyboard_interactive': "
63 | "None, 'password_authentication': None, 'preferred_authentication': None}, '*': HostEntry "
64 | "{'hosts': '*', 'hostname': None, 'port': None, 'user': 'carl', 'address_family': None, "
65 | "'bind_address': None, 'connect_timeout': None, 'identities_only': None, 'identity_file': "
66 | "None, 'keyboard_interactive': None, 'password_authentication': None, "
67 | "'preferred_authentication': None}, 'someswitch?': HostEntry {'hosts': 'someswitch?', "
68 | "'hostname': 'someswitch1.bogus.com', 'port': '1234', 'user': 'carl', 'address_family': "
69 | "None, 'bind_address': None, 'connect_timeout': None, 'identities_only': 'yes', "
70 | "'identity_file': '~/.ssh/mysshkey', 'keyboard_interactive': None, "
71 | "'password_authentication': None, 'preferred_authentication': None}}}"
72 | )
73 |
74 |
75 | def test_bool_true():
76 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
77 | assert bool(ssh_conf) is True
78 |
79 |
80 | def test_bool_false():
81 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}__init__.py")
82 | assert bool(ssh_conf) is False
83 |
84 |
85 | def test_host__str():
86 | host = Host()
87 | assert str(host) == "Host: None"
88 |
89 |
90 | def test_host__repr():
91 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
92 | assert repr(ssh_conf.hosts["1.2.3.4 someswitch1"]) == (
93 | "HostEntry {'hosts': '1.2.3.4 someswitch1', 'hostname': 'someswitch1.bogus.com', 'port': "
94 | "'1234', 'user': 'carl', 'address_family': None, 'bind_address': None, 'connect_timeout': "
95 | "None, 'identities_only': 'yes', 'identity_file': '~/.ssh/mysshkey', "
96 | "'keyboard_interactive': None, 'password_authentication': None, 'preferred_authentication': "
97 | "None}"
98 | )
99 |
100 |
101 | def test_host_lookup_exact_host():
102 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
103 | host = ssh_conf.lookup("1.2.3.4 someswitch1")
104 | assert repr(host) == (
105 | "HostEntry {'hosts': '1.2.3.4 someswitch1', 'hostname': 'someswitch1.bogus.com', 'port': "
106 | "'1234', 'user': 'carl', 'address_family': None, 'bind_address': None, 'connect_timeout': "
107 | "None, 'identities_only': 'yes', 'identity_file': '~/.ssh/mysshkey', "
108 | "'keyboard_interactive': None, 'password_authentication': None, 'preferred_authentication':"
109 | " None}"
110 | )
111 |
112 |
113 | def test_host_lookup_exact_host_in_list():
114 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
115 | host = ssh_conf.lookup("someswitch1")
116 | assert repr(host) == (
117 | "HostEntry {'hosts': '1.2.3.4 someswitch1', 'hostname': 'someswitch1.bogus.com', 'port': "
118 | "'1234', 'user': 'carl', 'address_family': None, 'bind_address': None, 'connect_timeout': "
119 | "None, 'identities_only': 'yes', 'identity_file': '~/.ssh/mysshkey', "
120 | "'keyboard_interactive': None, 'password_authentication': None, 'preferred_authentication':"
121 | " None}"
122 | )
123 |
124 |
125 | def test_host_lookup_host_fuzzy():
126 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
127 | host = ssh_conf.lookup("someswitch2")
128 | assert repr(host) == (
129 | "HostEntry {'hosts': 'someswitch?', 'hostname': 'someswitch1.bogus.com', 'port': "
130 | "'1234', 'user': 'carl', 'address_family': None, 'bind_address': None, 'connect_timeout': "
131 | "None, 'identities_only': 'yes', 'identity_file': '~/.ssh/mysshkey', "
132 | "'keyboard_interactive': None, 'password_authentication': None, 'preferred_authentication': "
133 | "None}"
134 | )
135 |
136 |
137 | def test_host_lookup_host_fuzzy_multi_match():
138 | ssh_conf = SSH2NetSSHConfig(f"{UNIT_TEST_DIR}_ssh_config")
139 | host = ssh_conf.lookup("someswitch9999")
140 | assert repr(host) == (
141 | "HostEntry {'hosts': 'someswitch?', 'hostname': 'someswitch1.bogus.com', 'port': "
142 | "'1234', 'user': 'carl', 'address_family': None, 'bind_address': None, 'connect_timeout': "
143 | "None, 'identities_only': 'yes', 'identity_file': '~/.ssh/mysshkey', "
144 | "'keyboard_interactive': None, 'password_authentication': None, 'preferred_authentication': "
145 | "None}"
146 | )
147 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | py36,py37,py38
4 |
5 |
6 | [testenv]
7 | deps =
8 | -rrequirements-dev.txt
9 | commands =
10 | python -m pytest tests/unit/.
11 |
12 |
13 | [testenv:py38]
14 | deps =
15 | -rrequirements-dev.txt
16 | commands =
17 | python -m pytest \
18 | --cov=ssh2net \
19 | --cov-report html \
20 | --cov-report term \
21 | tests/unit/.
22 | python -m black .
23 | python -m pylama .
24 | python -m pydocstyle .
25 | darglint ssh2net/.
26 |
--------------------------------------------------------------------------------