diff --git a/docs/dev/code_reference/hash.md b/docs/dev/code_reference/hash.md new file mode 100644 index 00000000..4c3ae960 --- /dev/null +++ b/docs/dev/code_reference/hash.md @@ -0,0 +1,5 @@ +# Hash + +::: netutils.hash + options: + show_submodules: True \ No newline at end of file diff --git a/docs/user/include_jinja_list.md b/docs/user/include_jinja_list.md index 156ee645..362fae63 100644 --- a/docs/user/include_jinja_list.md +++ b/docs/user/include_jinja_list.md @@ -20,6 +20,7 @@ | paloalto_panos_brace_to_set | netutils.config.conversion.paloalto_panos_brace_to_set | | fqdn_to_ip | netutils.dns.fqdn_to_ip | | is_fqdn_resolvable | netutils.dns.is_fqdn_resolvable | +| hash_data | netutils.hash.hash_data | | abbreviated_interface_name | netutils.interface.abbreviated_interface_name | | abbreviated_interface_name_list | netutils.interface.abbreviated_interface_name_list | | canonical_interface_name | netutils.interface.canonical_interface_name | diff --git a/docs/user/lib_use_cases.md b/docs/user/lib_use_cases.md index b184a21c..5f7b7bb8 100644 --- a/docs/user/lib_use_cases.md +++ b/docs/user/lib_use_cases.md @@ -15,6 +15,7 @@ Functions are grouped with like functions, such as IP or MAC address based funct - Conversion - Provides the ability to convert between different syntax's within the same OS. - Parsing - Provides the ability to parse configuration for the minor differences that are there. - DNS - Provides the ability to work with DNS, such as validating that a FQDN is resolvable. +- Hash - Provide a convenience method for hashlib to be used in Jinja2 - Interface - Provides the ability to work with interface names, expanding, abbreviating, and splitting the names. - IP Address - Provides the ability to work with IP addresses, primarily exposing Python `ipaddress` functionality. - Library Helpers - Provides helpers to pull useful information, e.g. NAPALM getters. diff --git a/mkdocs.yml b/mkdocs.yml index 932d02ca..7c0a66ff 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -141,6 +141,7 @@ nav: - Banner: "dev/code_reference/banner.md" - Configs: "dev/code_reference/configs.md" - DNS: "dev/code_reference/dns.md" + - Hash: "dev/code_reference/hash.md" - Interface: "dev/code_reference/interface.md" - IP: "dev/code_reference/ip.md" - Library Helpers: "dev/code_reference/lib_helpers.md" diff --git a/netutils/hash.py b/netutils/hash.py new file mode 100644 index 00000000..3e74d682 --- /dev/null +++ b/netutils/hash.py @@ -0,0 +1,39 @@ +"""Functions for hashing data.""" +import hashlib +import typing as t + + +def hash_data(data: bytes, algorithm: str) -> t.Any: + """Convenience function primarily built to expose hashlib to Jinja. + + Args: + data (bytes): Data to hash. + algorithm (str): Hashing algorithm to use. + + Returns: + bytes: Hashed data. + + Raises: + AttributeError: Invalid algorithm specified. + + Examples: + >>> from netutils.hash import hash_data + >>> hash_data("test", "md5") + '098f6bcd4621d373cade4e832627b4f6' + + >>> from jinja2 import Environment + >>> from netutils.utils import jinja2_convenience_function + >>> + >>> env = Environment(trim_blocks=True, lstrip_blocks=True) + >>> env.filters.update(jinja2_convenience_function()) + >>> template_str = "{{ 'test' | hash_data('md5') }}" + >>> template = env.from_string(template_str) + >>> result = template.render() + >>> print(result) + 098f6bcd4621d373cade4e832627b4f6 + """ + if not isinstance(data, bytes): + data = str(data).encode() + algorithm = algorithm.lower() + hasher = getattr(hashlib, algorithm) + return hasher(data).hexdigest() diff --git a/netutils/utils.py b/netutils/utils.py index 3c011314..9c56dae2 100644 --- a/netutils/utils.py +++ b/netutils/utils.py @@ -88,6 +88,7 @@ "get_napalm_getters": "lib_helpers.get_napalm_getters", "paloalto_panos_brace_to_set": "config.conversion.paloalto_panos_brace_to_set", "get_upgrade_path": "os_version.get_upgrade_path", + "hash_data": "hash.hash_data", } diff --git a/tests/unit/test_hash.py b/tests/unit/test_hash.py new file mode 100644 index 00000000..64648993 --- /dev/null +++ b/tests/unit/test_hash.py @@ -0,0 +1,70 @@ +"""Tests for the hash functions.""" +import pytest + +from netutils.hash import hash_data + + +EXPECTED_HASHES = [ + ( + "md5", + "b31be8e621f7d7cb80289c3634a2463f", + ), + ( + "sha1", + "696de4dae5e77515f0460c78dc712f9b055ae7f2", + ), + ( + "sha224", + "bead2aad3706b211e825f5919db78dceca775cae4bd5b58078652ad2", + ), + ( + "sha256", + "a9675e13424e5009161f7b7da6c1bb7e091f1401459176e8efce23c0f1fc5ba9", + ), + ( + "sha384", + "4476744d8167497e9cbc85901a753be7bef5a33a1ce36926c5a21b68c7c2d420daa6cd347d515dd21af1e93927c7ba5c", + ), + ( + "sha512", + "75dc2cbd4b2e025f8c0a1f495bc321343eef8d5561dfa02e29f77b32b9685f7add41169e7f9fb085f5110ac4635de286437c758c115b8eadacc20f086e39cc28", + ), + ( + "blake2b", + "82a094789746f0a0405845ced806282e1bd6f317dd8a9464b6e660105e16108f6582c0f091d787a833c8d8fd5c53004dac2571113045fefe25d1f159f8c1f934", + ), + ( + "blake2s", + "b8fecb4ff8b866c7638985eb66d4ba9cb5f908d0b1a25def4c593ba140b791af", + ), + ( + "sha3_224", + "f0b2b40e360489e0e2da83094238e9591677e1d304d70a1feb1188f2", + ), + ( + "sha3_256", + "308a5dd839eb055ee84f0b2c99344526a716c58a14dffb704b6784437aee91ba", + ), + ( + "sha3_384", + "80e4d0e43bf447ef4d3a6dcd1a795a3573bc6f34d42b81ee78bb757bd86ed6bc9210d752797fd62bfbdb6fc17eb52ed1", + ), + ( + "sha3_512", + "13dddfadbe95282b6b0da1c7c3c7dc28c086cdcc3de39baafb1fb45913ac39c0d9744927c10fb1d858ab257069d3ef367c8913553e7f7eabb1f4ffe6480e5924", + ), +] + + +@pytest.mark.parametrize("algorithm,expected", EXPECTED_HASHES) +def test_hash_data(algorithm, expected): + """Test the hash_data function.""" + data = "Network To Code" + assert hash_data(data, algorithm) == expected + + +def test_hash_data_invalid_algorithm(): + """Test the hash_data function with an invalid algorithm.""" + data = "Network To Code" + with pytest.raises(AttributeError): + hash_data(data, "invalid")