diff --git a/README.md b/README.md index 72c323d..d049f30 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,14 @@ $ pip3 install -r requirements.txt Kartograf has not been tested on Windows. +#### Environment check + +To check whether you have all the dependencies required to use Kartograf, you can run the following from the root of the project directory: + +``` +python check.py +``` + ## Usage ### Building IP prefix to ASN maps diff --git a/check.py b/check.py new file mode 100644 index 0000000..73171d2 --- /dev/null +++ b/check.py @@ -0,0 +1,58 @@ +import sys +import re +from importlib.metadata import distribution, PackageNotFoundError + +from kartograf.util import check_compatibility + +CHECK_MARK = "\U00002705" +CROSS_MARK = "\U0000274C" + +def __print_with_format(name, version, success, error_msg=None): + col1 = f"{name} version:" + col2 = f"{CHECK_MARK} OK" if success else CROSS_MARK + col3 = "" if success else f"{error_msg}" + return print(f"{col1:25} {col2:5} ({version}) {col3}") + +def __read_requirements_txt(): + with open("requirements.txt", "r") as f: + package_versions = {} + pattern = r"^(.*?)(>=|==)(.*)$" + for line in f.readlines(): + match = re.match(pattern, line) + if match: + package_versions[match.group(1)] = match.group(3) + return package_versions + +def rpki_version(): + try: + check_compatibility() + except RuntimeError as e: + print(e) + +def python_version(): + min_version = (3, 10) + sys_version = sys.version_info + result = sys_version >= min_version + __print_with_format( + "Python", + f"{sys_version.major}.{sys_version.minor}", + result) + +def installed_packages(): + required_packages = __read_requirements_txt() + for package, min_version in required_packages.items(): + try: + dist = distribution(package) + result = dist.version >= min_version + __print_with_format(package, dist.version, result) + except PackageNotFoundError: + __print_with_format(package, None, False, "Not Found!") + + +def check_all(): + rpki_version() + python_version() + installed_packages() + +if __name__ == "__main__": + check_all() diff --git a/flake.nix b/flake.nix index b8b3734..e628079 100644 --- a/flake.nix +++ b/flake.nix @@ -38,18 +38,28 @@ license = licenses.bsd3; }; }; + + rpki-client = rpki-cli.defaultPackage.${system}; + pythonBuildDeps = pkgs.python311.withPackages (ps: [ + ps.beautifulsoup4 + ps.numpy + ps.pandas + ps.requests + ps.tqdm + pandarallel + ]); + pythonDevDeps = pkgs.python311.withPackages (ps: [ + ps.beautifulsoup4 + ps.numpy + ps.pandas + ps.requests + ps.tqdm + pandarallel + ]); kartografDeps = [ - rpki-cli.defaultPackage.${system} - (pkgs.python311.withPackages (ps: [ - ps.pandas - ps.beautifulsoup4 - ps.numpy - ps.requests - ps.tqdm - pandarallel - ])) + pythonBuildDeps + rpki-client ]; - in { # This flake exposes the following attributes: # * A development shell containing the rpki-client and the necessary @@ -58,16 +68,17 @@ # * A default/kartograf package # * A NixOS module devShells.default = pkgs.mkShell { - packages = kartografDeps; + packages = [rpki-client pythonDevDeps]; }; packages = { - kartograf = pkgs.stdenv.mkDerivation { # not a python-installable package, so just manually copy files + kartograf = pkgs.stdenv.mkDerivation { + # not a python-installable package, so just manually copy files pname = "kartograf"; version = "1.0.0"; src = ./.; - nativeBuildInputs = [ pkgs.makeWrapper ]; + nativeBuildInputs = [pkgs.makeWrapper]; buildInputs = kartografDeps; - propagatedBuildInputs = [ rpki-cli.defaultPackage.${system} ]; + propagatedBuildInputs = [rpki-client]; buildPhase = '' mkdir -p $out/lib/kartograf cp -r ${./kartograf}/* $out/lib/kartograf/ diff --git a/kartograf/util.py b/kartograf/util.py index 1251fd5..8490390 100644 --- a/kartograf/util.py +++ b/kartograf/util.py @@ -6,6 +6,8 @@ import subprocess import time +RPKI_VERSION = 9.3 + def calculate_sha256(file_path): sha256_hash = hashlib.sha256() @@ -53,41 +55,47 @@ def rir_from_str(maybe_rir): raise Exception("No RIR found in String") -def check_compatibility(): - exception_msg = "Could not determine rpki-client version. Is it installed?" - +def get_rpki_local_version(): + """Return the rpki-client version in the user's path""" try: - result = subprocess.run(['rpki-client', '-V'], - capture_output=True, - text=True, - check=True) + result = subprocess.run( + ["rpki-client", "-V"], capture_output=True, text=True, check=True + ) # On OpenBSD the result should include 'rpki-client', everywhere else # it should be 'rpki-client-portable'. - version_match = re.search(r'rpki-client(?:-portable)? (\d+\.\d+)', - result.stderr) + version_match = re.search( + r"rpki-client(?:-portable)? (\d+\.\d+)", result.stderr + ) if version_match: version = version_match.group(1) - version_number = float(version) - latest_version = 9.3 + return float(version) + return None - if version_number < 8.4: - raise Exception("Error: rpki-client version 8.4 or higher is " - "required.") + except FileNotFoundError: + return None - if version_number == latest_version: - print(f"Using rpki-client version {version} (recommended).") - elif version_number > latest_version: - print("Warning: This kartograf version has not been tested with " - f"rpki-client versions higher than {latest_version}.") - else: - print(f"Using rpki-client version {version}. Please beware that running with the latest tested version ({latest_version}) is recommend.") - else: - raise Exception(exception_msg) - - except subprocess.CalledProcessError: - raise Exception(exception_msg) +def check_compatibility(): + local_version = get_rpki_local_version() + latest_version = RPKI_VERSION + + if local_version is None: + raise RuntimeError("Could not determine rpki-client version. Is it installed?") + if local_version < 8.4: + raise Exception("Error: rpki-client version 8.4 or higher is required.") + + if local_version == latest_version: + print(f"Using rpki-client version {local_version} (recommended).") + elif local_version > latest_version: + print( + "Warning: This kartograf version has not been tested with " + f"rpki-client versions higher than {latest_version}." + ) + else: + print( + f"Using rpki-client version {local_version}. Please beware that running with the latest tested version ({latest_version}) is recommend." + ) def wait_for_launch(wait): @@ -104,13 +112,21 @@ def wait_for_launch(wait): days, remainder = divmod(remaining, 86400) hours, remainder = divmod(remainder, 3600) minutes, seconds = divmod(remainder, 60) - days, hours, minutes, seconds = int(days), int(hours), int(minutes), int(seconds) + days, hours, minutes, seconds = ( + int(days), + int(hours), + int(minutes), + int(seconds), + ) # Print the countdown, using '\r' to remain on the same line - print(f"Countdown:{'' if days <= 0 else ' ' + str(days) + ' day(s),'}" - f"{'' if hours <= 0 else ' ' + str(hours) + ' hour(s),'}" - f"{'' if minutes <= 0 else ' ' + str(minutes) + ' minute(s),'}" - f" {seconds} second(s)".ljust(80), end='\r') + print( + f"Countdown:{'' if days <= 0 else ' ' + str(days) + ' day(s),'}" + f"{'' if hours <= 0 else ' ' + str(hours) + ' hour(s),'}" + f"{'' if minutes <= 0 else ' ' + str(minutes) + ' minute(s),'}" + f" {seconds} second(s)".ljust(80), + end="\r", + ) time.sleep(1) @@ -121,7 +137,7 @@ def format_pfx(pfx): which can cause problems. """ try: - if '/' in pfx: + if "/" in pfx: formatted_pfx = str(ipaddress.ip_network(pfx)) return f"{formatted_pfx}" return str(ipaddress.ip_address(pfx))