def create_dir(path, **kwargs) -> bool: """Validate and attempt to create a directory, if it does not exist.""" # If input path is not a path object, try to make it one if not isinstance(path, Path): try: path = Path(path) except TypeError: error("{p} is not a valid path", p=path) # If path does not exist, try to create it if not path.exists(): try: path.mkdir(**kwargs) except PermissionError: error( "{u} does not have permission to create {p}. Try running with sudo?", u=os.getlogin(), p=path, ) # Verify the path was actually created if path.exists(): success("Created {p}", p=path) # If the path already exists, inform the user elif path.exists(): info("{p} already exists", p=path) return True
def migrate_config(config_dir): """Copy example config files and remove .example extensions.""" status("Migrating example config files...") import shutil examples = Path(PROJECT_ROOT / "examples").glob("*.yaml.example") if not isinstance(config_dir, Path): config_dir = Path(config_dir) if not config_dir.exists(): error("'{d}' does not exist", d=str(config_dir)) migrated = 0 for file in examples: target_file = config_dir / file.with_suffix("").name try: if target_file.exists(): info("{f} already exists", f=str(target_file)) else: shutil.copyfile(file, target_file) migrated += 1 info("Migrated {f}", f=str(target_file)) except Exception as e: error("Failed to migrate '{f}': {e}", f=str(target_file), e=e) if migrated == 0: info("Migrated {n} example config files", n=migrated) elif migrated > 0: success("Successfully migrated {n} example config files", n=migrated)
def build_ui(timeout: int) -> None: """Create a new UI build.""" try: # Project from hyperglass.configuration import CONFIG_PATH, params, frontend_params from hyperglass.util.frontend import build_frontend from hyperglass.compat._asyncio import aiorun except ImportError as e: error("Error importing UI builder: {e}", e=e) status("Starting new UI build with a {t} second timeout...", t=timeout) if params.developer_mode: dev_mode = "development" else: dev_mode = "production" try: build_success = aiorun( build_frontend( dev_mode=params.developer_mode, dev_url=f"http://localhost:{str(params.listen_port)}/", prod_url="/api/", params=frontend_params, force=True, app_path=CONFIG_PATH, )) if build_success: success("Completed UI build in {m} mode", m=dev_mode) except Exception as e: error("Error building UI: {e}", e=e) return True
def start(build, direct, workers): """Start web server and optionally build frontend assets.""" try: from hyperglass.main import start from hyperglass.api import start as uvicorn_start except ImportError as e: error("Error importing hyperglass: {}", str(e)) kwargs = {} if workers != 0: kwargs["workers"] = workers try: if build: build_complete = build_ui() if build_complete and not direct: start(**kwargs) elif build_complete and direct: uvicorn_start(**kwargs) if not build and not direct: start(**kwargs) elif not build and direct: uvicorn_start(**kwargs) except Exception as err: error(str(err))
def install_systemd(app_path): """Installs generated systemd file to system's systemd directory. Arguments: app_path {Path} -- hyperglass runtime path Raises: ClickException: Raised if the /etc/systemd/system does not exist ClickException: Raised if the symlinked file does not exit Returns: {bool} -- True if successful """ service = app_path / "hyperglass.service" systemd = Path("/etc/systemd/system") installed = systemd / "hyperglass.service" if not systemd.exists(): error("{e} does not exist. Unable to install systemd service.", e=systemd) installed.symlink_to(service) if not installed.exists(): error("Unable to symlink {s} to {d}", s=service, d=installed) success("Symlinked {s} to {d}", s=service, d=installed) return True
def write_to_file(file, data) -> bool: """Write string data to a file.""" try: with file.open("w+") as f: f.write(data.strip()) except PermissionError: error( "{u} does not have permission to write to {f}. Try running with sudo?", u=os.getlogin(), f=file, ) if not file.exists(): error("Error writing file {f}", f=file) elif file.exists(): success("Wrote systemd file {f}", f=file) return True
def fix_permissions(directory): """Make directory readable by public.""" import os try: for root, dirs, files in os.walk(directory): for d in dirs: full_path = os.path.join(root, d) os.chmod(full_path, 0o744) for f in files: full_path = os.path.join(root, f) os.chmod(full_path, 0o744) os.chmod(root, 0o744) except Exception as e: error("Failed to change '{d}' ownership: {e}", d="hyperglass/", e=e) success("Successfully changed '{d}' ownership", d="hyperglass/")
def start_web_server(start, params): """Start web server.""" msg_start = "Starting hyperglass web server on" msg_uri = "http://" msg_host = str(params["host"]) msg_port = str(params["port"]) msg_len = len("".join( [msg_start, WS[1], msg_uri, msg_host, CL[1], msg_port])) try: echo(NL[1] + WS[msg_len + 8] + E.ROCKET + NL[1] + E.CHECK + style(msg_start, fg="green", bold=True) + WS[1] + style(msg_uri, fg="white") + style(msg_host, fg="blue", bold=True) + style(CL[1], fg="white") + style(msg_port, fg="magenta", bold=True) + WS[1] + E.ROCKET + NL[1] + WS[1] + NL[1]) start() except Exception as e: error("Failed to start web server: {e}", e=e)
def migrate_systemd(source, destination): """Copy example systemd service file to /etc/systemd/system/.""" import os import shutil basefile, extension = os.path.splitext(source) newfile = os.path.join(destination, basefile) try: status("Migrating example systemd service...") if os.path.exists(newfile): info("'{f}' already exists", f=str(newfile)) else: shutil.copyfile(source, newfile) except Exception as e: error("Error migrating example systemd service: {e}", e=e) success("Successfully migrated systemd service to: {f}", f=str(newfile))
def fix_ownership(user, group, directory): """Make user & group the owner of the directory.""" import grp import pwd import os uid = pwd.getpwnam(user).pw_uid gid = grp.getgrnam(group).gr_gid try: for root, dirs, files in os.walk(directory): for d in dirs: full_path = os.path.join(root, d) os.chown(full_path, uid, gid) for f in files: full_path = os.path.join(root, f) os.chown(full_path, uid, gid) os.chown(root, uid, gid) except Exception as e: error("Failed to change '{d}' ownership: {e}", d="hyperglass/", e=e) success("Successfully changed '{d}' ownership", d="hyperglass/")
def build_ui(): """Create a new UI build. Raises: ClickException: Raised on any errors. """ try: from hyperglass.compat._asyncio import aiorun from hyperglass.util import build_frontend from hyperglass.configuration import params, frontend_params, CONFIG_PATH except ImportError as e: error("Error importing UI builder: {e}", e=e) status("Starting new UI build...") if params.developer_mode: dev_mode = "development" else: dev_mode = "production" try: build_success = aiorun( build_frontend( dev_mode=params.developer_mode, dev_url=f"http://localhost:{str(params.listen_port)}/", prod_url="/api/", params=frontend_params, force=True, app_path=CONFIG_PATH, )) if build_success: success("Completed UI build in {m} mode", m=dev_mode) except Exception as e: error("Error building UI: {e}", e=e) return True
def write_to_file(file, data): """Write string data to a file. Arguments: file {Path} -- File path data {str} -- String data to write Returns: {bool} -- True if successful """ try: with file.open("w+") as f: f.write(data.strip()) except PermissionError: error( "{u} does not have permission to write to {f}. Try running with sudo?", u=os.getlogin(), f=file, ) if not file.exists(): error("Error writing file {f}", f=file) elif file.exists(): success("Wrote systemd file {f}", f=file) return True
def setup(unattended): """Define application directory, move example files, generate systemd service.""" from hyperglass.cli.util import ( create_dir, move_files, make_systemd, write_to_file, migrate_static_assets, install_systemd, ) user_path = Path.home() / "hyperglass" root_path = Path("/etc/hyperglass/") install_paths = [ inquirer.List( "install_path", message="Choose a directory for hyperglass", choices=[user_path, root_path], ) ] if not unattended: answer = inquirer.prompt(install_paths) if answer is None: error("A directory for hyperglass is required") install_path = answer["install_path"] elif unattended: install_path = user_path ui_dir = install_path / "static" / "ui" images_dir = install_path / "static" / "images" favicon_dir = images_dir / "favicons" custom_dir = install_path / "static" / "custom" create_dir(install_path) for path in (ui_dir, images_dir, custom_dir, favicon_dir): create_dir(path, parents=True) migrate_static_assets(install_path) example_dir = WORKING_DIR.parent / "examples" files = example_dir.iterdir() do_move = True if not unattended and not confirm( "Do you want to install example configuration files? (This is non-destructive)" ): do_move = False if do_move: move_files(example_dir, install_path, files) if install_path == user_path: user = getuser() else: user = "******" do_systemd = True if not unattended and not confirm( "Do you want to generate a systemd service file?"): do_systemd = False if do_systemd: systemd_file = install_path / "hyperglass.service" systemd = make_systemd(user) write_to_file(systemd_file, systemd) install_systemd(install_path)
def move_files(src, dst, files): # noqa: C901 """Move iterable of files from source to destination. Arguments: src {Path} -- Current directory of files dst {Path} -- Target destination directory files {Iterable} -- Iterable of files """ if not isinstance(src, Path): try: src = Path(src) except TypeError: error("{p} is not a valid path", p=src) if not isinstance(dst, Path): try: dst = Path(dst) except TypeError: error("{p} is not a valid path", p=dst) if not isinstance(files, Iterable): error( "{fa} must be an iterable (list, tuple, or generator). Received {f}", fa="Files argument", f=files, ) for path in (src, dst): if not path.exists(): error("{p} does not exist", p=str(path)) migrated = 0 for file in files: dst_file = dst / file.name if not file.exists(): error("{f} does not exist", f=file) try: if dst_file.exists(): warning("{f} already exists", f=dst_file) else: shutil.copyfile(file, dst_file) migrated += 1 info("Migrated {f}", f=dst_file) except Exception as e: error("Failed to migrate {f}: {e}", f=dst_file, e=e) if migrated == 0: warning("Migrated {n} files", n=migrated) elif migrated > 0: success("Successfully migrated {n} files", n=migrated) return True