diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a89c8bb..f1709971 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,16 +117,18 @@ jobs: uses: "actions/checkout@v2" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v2" - - name: "Cache Docker images" - uses: "actions/cache@v2" - id: "cached-docker-images" - with: - path: "/tmp/docker" - key: "${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('./Dockerfile') }}" - - name: "Load docker image" - run: "docker load < /tmp/docker/netutils-py${{ matrix.python-version }}.tar" - - name: "Show docker images" - run: "docker image ls" + # - name: "Cache Docker images" + # uses: "actions/cache@v2" + # id: "cached-docker-images" + # with: + # path: "/tmp/docker" + # key: "${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('./Dockerfile') }}" + # - name: "Load docker image" + # run: "docker load < /tmp/docker/netutils-py${{ matrix.python-version }}.tar" + # - name: "Show docker images" + # run: "docker image ls" + - name: "Build Container" + run: "poetry run invoke build" - name: "Linting: Pylint" run: "poetry run invoke pylint" needs: @@ -144,14 +146,18 @@ jobs: uses: "actions/checkout@v2" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v2" - - name: "Cache Docker images" - uses: "actions/cache@v2" - id: "cached-docker-images" - with: - path: "/tmp/docker" - key: "${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('./Dockerfile') }}" - - name: "Load docker image" - run: "docker load < /tmp/docker/netutils-py${{ matrix.python-version }}.tar" + # - name: "Cache Docker images" + # uses: "actions/cache@v2" + # id: "cached-docker-images" + # with: + # path: "/tmp/docker" + # key: "${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('./Dockerfile') }}" + # - name: "Load docker image" + # run: "docker load < /tmp/docker/netutils-py${{ matrix.python-version }}.tar" + - name: "Build Container" + run: "poetry run invoke build" + - name: "Linting: Pylint" + run: "poetry run invoke pylint" - name: "Run Tests" run: "poetry run invoke pytest" needs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 551293b0..419327bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## v0.2.4 - 2021-11 + +### Added + +- #33 Add interface range compress function +- #53 Add get peer address function +- #59 Add bandwidth converting function +- #65 Added Docker caching +- #68 Add Fortinet Fortios Parser support + +### Changed + +- #64 CI implementation on GitHub actions + +### Fixed + +- #52 Update pyproject.toml build-server +- #55 update version in toml and init files +- #63 Fix lack of zero padding on ip to binary conversion +- #70 Fix lack of zero padding on ip to hex conversion +- #68 Update Black pinning + ## v0.2.3 - 2021-09 ### Added diff --git a/docs/source/sphinxext/exec.py b/docs/source/sphinxext/exec.py index f6929b15..6cdf5b26 100644 --- a/docs/source/sphinxext/exec.py +++ b/docs/source/sphinxext/exec.py @@ -33,7 +33,7 @@ def run(self): return [ nodes.error( None, - nodes.paragraph(text="Unable to execute python code at %s:%d:" % (basename(source), self.lineno)), + nodes.paragraph(text=f"Unable to execute python code at {basename(source)}:{self.lineno}:"), nodes.paragraph(text=str(sys.exc_info()[1])), ) ] diff --git a/netutils/__init__.py b/netutils/__init__.py index 2c1b8acd..41c1aec3 100644 --- a/netutils/__init__.py +++ b/netutils/__init__.py @@ -1,3 +1,3 @@ """Initialization file for library.""" -__version__ = "0.2.3" +__version__ = "0.2.4" diff --git a/netutils/config/compliance.py b/netutils/config/compliance.py index 84bac72d..5a0257fe 100644 --- a/netutils/config/compliance.py +++ b/netutils/config/compliance.py @@ -99,7 +99,7 @@ def _is_feature_ordered_compliant(feature_intended_cfg, feature_actual_cfg): def _open_file_config(cfg_path): """Open config file from local disk.""" try: - with open(cfg_path) as filehandler: + with open(cfg_path, encoding="utf-8") as filehandler: device_cfg = filehandler.read() except IOError: return False @@ -168,7 +168,7 @@ def compliance(features, backup, intended, network_os, cfg_type="file"): backup_cfg = backup intended_cfg = intended - compliance_results = dict() + compliance_results = {} for feature in features: backup_str = section_config(feature, backup_cfg, network_os) @@ -207,7 +207,7 @@ def config_section_not_parsed(features, device_cfg, network_os): {'remaining_cfg': '!\naccess-list 1 permit 10.10.10.10\naccess-list 1 permit 10.10.10.11', 'section_not_found': []} """ remaining_cfg = device_cfg - section_not_found = list() + section_not_found = [] for feature in features: feature_cfg = section_config(feature, device_cfg, network_os) if not feature_cfg: @@ -357,7 +357,7 @@ def find_unordered_cfg_lines(intended_cfg, actual_cfg): """ intended_lines = intended_cfg.splitlines() actual_lines = actual_cfg.splitlines() - unordered_lines = list() + unordered_lines = [] if len(intended_lines) == len(actual_lines): # Process to find actual lines that are misordered unordered_lines = [(e1, e2) for e1, e2 in zip(intended_lines, actual_lines) if e1 != e2] diff --git a/netutils/config/parser.py b/netutils/config/parser.py index ef466b61..cfa7990f 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -172,7 +172,7 @@ def _remove_parents(self, line, current_spaces): previous_parent = self._current_parents[-deindent_level] previous_indent = self.get_leading_space_count(previous_parent) except IndexError: - raise IndexError("\nValidate the first line does not begin with a space" "\n{}\n".format(line)) + raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n") parents = self._current_parents[:-deindent_level] or (self._current_parents[0],) return parents diff --git a/netutils/constants.py b/netutils/constants.py index f49cfec9..3089369f 100644 --- a/netutils/constants.py +++ b/netutils/constants.py @@ -4,8 +4,8 @@ from netutils import __file__ as netutils_file # Load the PROTOCOLS json file. -with open("/".join([dirname(netutils_file), "protocols.json"])) as f: - PROTOCOLS = json.loads(f.read()) +with open("/".join([dirname(netutils_file), "protocols.json"]), encoding="utf-8") as fh: + PROTOCOLS = json.loads(fh.read()) # This variable provides mapping for known interface variants, to the associated long form. BASE_INTERFACES = { diff --git a/netutils/interface.py b/netutils/interface.py index 9e2e33b2..d0579261 100644 --- a/netutils/interface.py +++ b/netutils/interface.py @@ -2,7 +2,7 @@ import itertools import re import typing as t -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractmethod from functools import total_ordering from operator import itemgetter from .constants import BASE_INTERFACES, REVERSE_MAPPING @@ -253,7 +253,8 @@ def __lt__(self, other) -> bool: # noqa: D105 def __eq__(self, other) -> bool: # noqa: D105 return self.weight == other.weight and self.val == other.val - @abstractproperty + @property + @abstractmethod def weight(self) -> int: """Weight property.""" ... diff --git a/netutils/ip.py b/netutils/ip.py index 95de4ff6..b7ecaa5b 100644 --- a/netutils/ip.py +++ b/netutils/ip.py @@ -15,10 +15,11 @@ def ip_to_hex(ip): Example: >>> from netutils.ip import ip_to_hex >>> ip_to_hex("10.100.100.100") - 'a646464' + '0a646464' >>> """ - return str(hex(int(ipaddress.ip_address(ip))))[2:] + ip_obj = ipaddress.ip_address(ip) + return str(hex(int(ip_obj)))[2:].zfill(int(ip_obj.max_prefixlen / 4)) def ip_addition(ip, val): @@ -243,7 +244,7 @@ def get_first_usable(ip_network): >>> """ net = ipaddress.ip_network(ip_network) - if net.prefixlen == 31 or net.prefixlen == 127: + if net.prefixlen in [31, 127]: return str(net[0]) return str(net[1]) @@ -302,7 +303,7 @@ def get_usable_range(ip_network): >>> """ net = ipaddress.ip_network(ip_network) - if net.prefixlen == 31 or net.prefixlen == 127: + if net.prefixlen in [31, 127]: lower_bound = str(net[0]) upper_bound = str(net[1]) else: diff --git a/netutils/password.py b/netutils/password.py index 5ee58285..814eaf6c 100644 --- a/netutils/password.py +++ b/netutils/password.py @@ -185,7 +185,7 @@ def encrypt_type5(unencrypted_password, salt=None, salt_len=4): if not salt: salt = "".join(secrets.choice(ALPHABET) for i in range(salt_len)) elif not set(salt) <= set(ALPHABET): - raise ValueError("type5_pw salt used inproper characters, must be one of %s" % (ALPHABET)) + raise ValueError(f"type5_pw salt used inproper characters, must be one of {ALPHABET}") return crypt.crypt(unencrypted_password, f"$1${salt}$") @@ -207,9 +207,10 @@ def encrypt_type7(unencrypted_password, salt=None): """ if not salt: salt = random.randrange(0, 15) # nosec - encrypted_password = "%02x" % salt + encrypted_password = "%02x" % salt # pylint: disable=consider-using-f-string for i, _ in enumerate(unencrypted_password): - encrypted_password += "%02x" % (ord(unencrypted_password[i]) ^ XLAT[salt]) + hex_password = "%02x" % (ord(unencrypted_password[i]) ^ XLAT[salt]) # pylint: disable=consider-using-f-string + encrypted_password += hex_password salt += 1 if salt == 51: salt = 0 @@ -233,5 +234,5 @@ def get_hash_salt(encrypted_password): """ split_password = encrypted_password.split("$") if len(split_password) != 4: - raise ValueError("Could not parse salt out password correctly from {0}".format(encrypted_password)) + raise ValueError(f"Could not parse salt out password correctly from {encrypted_password}") return split_password[2] diff --git a/netutils/vlan.py b/netutils/vlan.py index fec89a27..a937ca6a 100644 --- a/netutils/vlan.py +++ b/netutils/vlan.py @@ -31,12 +31,12 @@ def vlanlist_to_config(vlan_list, first_line_len=48, other_line_len=44): raise ValueError("Valid VLAN range is 1-4094") # Group consecutive VLANs - vlan_groups = list() + vlan_groups = [] for _, vlan in groupby(enumerate(clean_vlan_list), lambda vlan: vlan[0] - vlan[1]): vlan_groups.append(list(map(itemgetter(1), vlan))) # Create VLAN portion of config - vlan_strings = list() + vlan_strings = [] for group in vlan_groups: if len(group) == 1: vlan_strings.append(f"{group[0]}") diff --git a/pyproject.toml b/pyproject.toml index f50f26f4..2acfb75d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "netutils" -version = "0.2.3" +version = "0.2.4" description = "Common helper functions useful in network automation." authors = ["Network to Code, LLC "] license = "Apache-2.0" @@ -78,6 +78,7 @@ good-names="i,ip,j,k,ex,Run,_" disable = """, line-too-long, bad-continuation, + consider-iterating-dictionary, """ [tool.pylint.miscellaneous] diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index d9a11011..059e4710 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -19,7 +19,7 @@ def _method(_file): Returns: dict: The data structure from the JSON file. """ - with open(_file) as file: + with open(_file, encoding="utf-8") as file: data = json.load(file) return data @@ -39,7 +39,7 @@ def _method(_file): Returns: str: The data structure from the text file. """ - with open(_file) as file: + with open(_file, encoding="utf-8") as file: data = file.read() return data diff --git a/tests/unit/test_docs.py b/tests/unit/test_docs.py index e2f90808..3d5a7ffa 100644 --- a/tests/unit/test_docs.py +++ b/tests/unit/test_docs.py @@ -54,7 +54,7 @@ ] -with open("README.md", "r") as file: +with open("README.md", "r", encoding="utf-8") as file: README_LIST = file.readlines() README_LIST.insert(0, "") diff --git a/tests/unit/test_ip.py b/tests/unit/test_ip.py index a1f69080..c6b8b49b 100644 --- a/tests/unit/test_ip.py +++ b/tests/unit/test_ip.py @@ -6,7 +6,7 @@ IP_TO_HEX = [ { "sent": {"ip": "10.1.1.1"}, - "received": "a010101", + "received": "0a010101", }, { "sent": {"ip": "2001:db8:3333:4444:5555:6666:7777:8888"},