#!/usr/bin/env python3 import argparse import dataclasses import os import shutil import subprocess import sys import textwrap from argparse import ArgumentParser, Namespace from pathlib import Path _SCRIPT_NAME = Path(sys.argv[0]).name _SCRIPT_DIR = Path(sys.argv[0]).parent.resolve() _TOP_LEVEL = Path( subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip() ) class JqBinResolver: def __init__(self, parser: ArgumentParser) -> None: parser.add_argument("--jq-bin", help="jq executable") def resolve(self, args: Namespace) -> Path: res_str = args.jq_bin or shutil.which("jq") if res_str is None: print( textwrap.dedent( """ jq not found. Try setting --jq-bin or ensuring it's on your PATH. On debian, it can be installed with: apt install jq """ ) ) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"jq resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ChutneyBinResolver: def __init__(self, parser: ArgumentParser) -> None: parser.add_argument("--chutney-bin", help="chutney executable") def resolve(self, args: Namespace) -> Path: res_str = args.chutney_bin or shutil.which("chutney") if res_str is None: print( textwrap.dedent( """ chutney (https://gitlab.torproject.org/tpo/core/chutney) not found. Try setting --chutney-bin or ensuring it's on your PATH. quick install: python3 -m pip install git+https://gitlab.torproject.org/tpo/core/chutney.git """ ) ) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"chutney resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ArtiBinResolver: def __init__(self, parser: ArgumentParser) -> None: parser.add_argument("--arti-bin", help="arti executable") def resolve(self, args: Namespace) -> Path: res_str = args.arti_bin if res_str is None: for s in [ "target/x86_64-unknown-linux-gnu/quicktest/arti", "target/quicktest/arti", "target/x86_64-unknown-linux-gnu/debug/arti", "target/debug/arti", ]: p = _TOP_LEVEL.joinpath(Path(s)) if p.exists(): res_str = str(p) break if res_str is None: print( textwrap.dedent( """ arti client not found. Try setting --arti-bin or building one in this repo: cargo build --locked --profile=quicktest -p arti """ ) ) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"arti resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ArtiBenchBinResolver: def __init__(self, parser: ArgumentParser) -> None: parser.add_argument("--arti-bench-bin", help="arti-bench executable") def resolve(self, args: Namespace) -> Path: res_str = args.arti_bin if res_str is None: for s in [ "target/x86_64-unknown-linux-gnu/release/arti-bench", "target/release/arti-bench", "target/x86_64-unknown-linux-gnu/quicktest/arti-bench", "target/quicktest/arti-bench", "target/x86_64-unknown-linux-gnu/debug/arti-bench", "target/debug/arti-bench", ]: p = _TOP_LEVEL.joinpath(Path(s)) if p.exists(): res_str = str(p) break if res_str is None: print( textwrap.dedent( """ arti-bench not found. Try setting --arti-bench-bin or building one in this repo: cargo build --locked --profile=quicktest -p arti-bench """ ) ) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"arti-bench resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ChutneyDataDirResolver: def __init__(self, parser: ArgumentParser) -> None: parser.add_argument( "--chutney-data-dir", help="directory for chutney to put its data" ) def resolve(self, args: Namespace) -> Path: return Path( args.chutney_data_dir or os.getenv("CHUTNEY_DATA_DIR") or os.getcwd() ) class ChutneyNetworkResolver: def __init__(self, parser: ArgumentParser) -> None: parser.add_argument( "--network", "-n", help="name or path of a chutney network to launch" ) def resolve(self, args: Namespace) -> str: return args.network or str(_SCRIPT_DIR.joinpath("networks", "arti-ci")) @dataclasses.dataclass class Config: chutney: Path arti: Path arti_bench: Path jq: Path chutney_data_dir: Path network: str class ConfigResolver: def __init__(self, parser: ArgumentParser) -> None: self._jq_resolver = JqBinResolver(parser) self._chutney_resolver = ChutneyBinResolver(parser) self._arti_resolver = ArtiBinResolver(parser) self._arti_bench_resolver = ArtiBenchBinResolver(parser) self._chutney_data_dir_resolver = ChutneyDataDirResolver(parser) self._network_resolver = ChutneyNetworkResolver(parser) def resolve(self, args: Namespace) -> Config: return Config( chutney=self._chutney_resolver.resolve(args), arti=self._arti_resolver.resolve(args), arti_bench=self._arti_bench_resolver.resolve(args), jq=self._jq_resolver.resolve(args), chutney_data_dir=self._chutney_data_dir_resolver.resolve(args), network=self._network_resolver.resolve(args), ) def _main() -> None: # Set up command-line parser, registering config options parser = argparse.ArgumentParser( prog=_SCRIPT_NAME, description="Configure a chutney testbed" ) config_resolver = ConfigResolver(parser) args = parser.parse_args() config = config_resolver.resolve(args) with Path(_SCRIPT_DIR).joinpath("arti.run").open("w") as c: print(f'target="{config.network}"', file=c) print(f'jq_bin="{config.jq}"', file=c) print(f'arti_bench_bin="{config.arti_bench}"', file=c) print(f'chutney_bin="{config.chutney}"', file=c) print(f'export CHUTNEY_ARTI="{config.arti}"', file=c) print(f'export CHUTNEY_DATA_DIR="{config.chutney_data_dir}"', file=c) if __name__ == "__main__": _main()