diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f662582..4d6643d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -43,6 +43,8 @@ jobs: python-version: [ '3.6', '3.7', '3.8' ] steps: - uses: actions/checkout@v2 + - name: Start dev env + run: make start_dev_env - name: Setup python uses: actions/setup-python@v1 with: diff --git a/Makefile b/Makefile index 9635f2b..5d2cb63 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ NAME=$(shell basename $(PWD)) PYTHON:=3.7 +DOCKER_COMPOSE_FILE=docker-compose.yml +DOCKER_COMPOSE=docker-compose -f ${DOCKER_COMPOSE_FILE} + DOCKER=docker run \ --rm -it \ --name $(NAME)-tests \ @@ -17,6 +20,15 @@ docker: -f Dockerfile \ . +.PHONY: start_dev_env +start_dev_env: + ${DOCKER_COMPOSE} \ + up -d + +.PHONY: stop_dev_env +stop_dev_env: + ${DOCKER_COMPOSE} \ + down .PHONY: pytest pytest: rm -f docs/source/tutorials/out_files/*.txt @@ -37,12 +49,16 @@ mypy: .PHONY: tests tests: black pylama mypy pytest +.PHONY: +jupyter: + poetry run jupyter notebook --no-browser + .PHONY: docker-tests docker-tests: docker $(DOCKER) make tests -.PHONY: jupyter -jupyter: +.PHONY: docker-jupyter +docker-jupyter: docker run \ --name $(NAME)-jupyter --rm \ -v $(PWD):/$(NAME) \ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..23ce3ee --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +--- +version: '2.1' + +services: + dev1.group_1: + hostname: dev1.group_1 + image: dbarroso/stupid_ssh_container + ports: + - "65020:22" + networks: + net1: + ipv4_address: 10.22.33.101 + +networks: + net1: + driver: bridge + ipam: + config: + - subnet: 10.22.33.0/24 diff --git a/docs/source/tutorials/tcp_ping.ipynb b/docs/source/tutorials/tcp_ping.ipynb new file mode 100644 index 0000000..efd5da5 --- /dev/null +++ b/docs/source/tutorials/tcp_ping.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1c7affb8", + "metadata": {}, + "source": [ + "# tcp_ping \n", + "Tests connection to a tcp port and tries to establish a three way handshake. To be used for network discovery or testing." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ab598e0c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m\u001b[36mtcp_ping************************************************************************\u001b[0m\n", + "\u001b[0m\u001b[1m\u001b[34m* dev1.group_1 ** changed : False **********************************************\u001b[0m\n", + "\u001b[0m\u001b[1m\u001b[32mvvvv tcp_ping ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO\u001b[0m\n", + "\u001b[0m{23: False, 65020: True}\u001b[0m\n", + "\u001b[0m\u001b[1m\u001b[32m^^^^ END tcp_ping ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "from nornir import InitNornir\n", + "from nornir_utils.plugins.tasks.networking import tcp_ping\n", + "from nornir_utils.plugins.functions import print_result\n", + "\n", + "nr = InitNornir(\n", + " inventory={\n", + " \"plugin\": \"SimpleInventory\",\n", + " \"options\": {\"host_file\": \"data/hosts.yaml\", \"group_file\": \"data/groups.yaml\"},\n", + " }\n", + ")\n", + "nr = nr.filter(name=\"dev1.group_1\")\n", + "r = nr.run(task=tcp_ping, ports=[23, 65020], timeout=3)\n", + "print_result(r)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nornir_utils/plugins/tasks/networking/__init__.py b/nornir_utils/plugins/tasks/networking/__init__.py new file mode 100644 index 0000000..cc53f77 --- /dev/null +++ b/nornir_utils/plugins/tasks/networking/__init__.py @@ -0,0 +1,4 @@ +from .tcp_ping import tcp_ping + + +__all__ = ("tcp_ping",) diff --git a/nornir_utils/plugins/tasks/networking/tcp_ping.py b/nornir_utils/plugins/tasks/networking/tcp_ping.py new file mode 100644 index 0000000..ab19122 --- /dev/null +++ b/nornir_utils/plugins/tasks/networking/tcp_ping.py @@ -0,0 +1,53 @@ +import socket +from typing import Optional, List + +from nornir.core.task import Result, Task + + +def tcp_ping( + task: Task, ports: List[int], timeout: int = 2, host: Optional[str] = None +) -> Result: + """ + Tests connection to a tcp port and tries to establish a three way + handshake. To be used for network discovery or testing. + + Arguments: + ports (list of int): tcp ports to ping + timeout (int, optional): defaults to 2 + host (string, optional): defaults to ``hostname`` + + + Returns: + Result object with the following attributes set: + * result (``dict``): Contains port numbers as keys with True/False as values + """ + + if isinstance(ports, int): + ports = [ports] + + if isinstance(ports, list): + if not all(isinstance(port, int) for port in ports): + raise ValueError("Invalid value for 'ports'") + + else: + raise ValueError("Invalid value for 'ports'") + + host = host or task.host.hostname + + result = {} + for port in ports: + s = socket.socket() + s.settimeout(timeout) + try: + status = s.connect_ex((host, port)) + if status == 0: + connection = True + else: + connection = False + except (socket.gaierror, socket.timeout, socket.error): + connection = False + finally: + s.close() + result[port] = connection + + return Result(host=task.host, result=result)