From e5df45650decc0023903aea7cfeef1049413b785 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Sat, 11 Apr 2020 00:17:36 +0200 Subject: [PATCH] Login is possible Closes https://git.autonomic.zone/autonomic-cooperative/autonomic/issues/1. --- autonomic/__init__.py | 6 +- autonomic/__main__.py | 22 ++- .../{__version__.py => command/__init__.py} | 0 autonomic/command/init.py | 71 +++++++++ autonomic/config.py | 34 +++++ autonomic/infrastructure.py | 20 +++ autonomic/logger.py | 142 ++++++++++++++++++ autonomic/settings.py | 13 ++ autonomic/system.py | 35 +++++ autonomic/yaml.py | 6 + autonomic/yaml.yml | 7 + pyproject.toml | 2 - setup.cfg | 5 +- 13 files changed, 352 insertions(+), 11 deletions(-) rename autonomic/{__version__.py => command/__init__.py} (100%) create mode 100644 autonomic/command/init.py create mode 100644 autonomic/config.py create mode 100644 autonomic/infrastructure.py create mode 100644 autonomic/logger.py create mode 100644 autonomic/settings.py create mode 100644 autonomic/system.py create mode 100644 autonomic/yaml.py create mode 100644 autonomic/yaml.yml diff --git a/autonomic/__init__.py b/autonomic/__init__.py index 9962bb6..8b7f4b8 100644 --- a/autonomic/__init__.py +++ b/autonomic/__init__.py @@ -1,4 +1,4 @@ -"""autonomic module.""" +"""Autonomic module.""" try: import pkg_resources @@ -7,6 +7,6 @@ except ImportError: try: - __version__ = pkg_resources.get_distribution('autonomic').version + __version__ = pkg_resources.get_distribution("autonomic").version except Exception: - __version__ = 'unknown' + __version__ = "unknown" diff --git a/autonomic/__main__.py b/autonomic/__main__.py index 93219ec..06bfbf3 100644 --- a/autonomic/__main__.py +++ b/autonomic/__main__.py @@ -1,10 +1,21 @@ """Command-line entrypoint.""" +import os + import click +from autonomic.command import init -@click.command() -def main(): + +@click.group() +@click.option( + "--debug/--no-debug", + default=False, + help="Enable or disable debug mode", + show_default=True, +) +@click.pass_context +def autonomic(ctx, debug): """ \b ___ _ _ @@ -14,8 +25,9 @@ def main(): | | | | |_| | || (_) | | | | (_) | | | | | | | (__ \_| |_/\__,_|\__\___/|_| |_|\___/|_| |_| |_|_|\___| """ # noqa - pass + ctx.obj = {} + ctx.obj["DEBUG"] = debug + ctx.obj["HOME"] = "/home/{user}".format(user=os.getlogin()) -if __name__ == '__main__': - main() +autonomic.add_command(init.init) diff --git a/autonomic/__version__.py b/autonomic/command/__init__.py similarity index 100% rename from autonomic/__version__.py rename to autonomic/command/__init__.py diff --git a/autonomic/command/init.py b/autonomic/command/init.py new file mode 100644 index 0000000..fc54813 --- /dev/null +++ b/autonomic/command/init.py @@ -0,0 +1,71 @@ +"""Initialise the toolbelt.""" + +import os + +import click +from PyInquirer import prompt + +from autonomic import logger +from autonomic.config import add_to_config +from autonomic.infrastructure import get_members +from autonomic.settings import ( + AUTONOMIC_YAML, + CONFIG_PATH, + INFRASTRUCTURE_PATH, + INFRASTRUCTURE_REPOSITORY, +) +from autonomic.system import ensure_installed, run_command + +log = logger.get_logger(__name__) + + +@click.command() +@click.pass_context +def init(ctx): + """Initialise the toolbelt.""" + create_configuration_directory() + clone_infrastructure_repo() + create_configuration_file() + ask_to_login() + + +def create_configuration_directory(): + """Create toolbelt config directory.""" + if not os.path.exists(CONFIG_PATH): + os.mkdir(CONFIG_PATH) + + +def clone_infrastructure_repo(): + """Clone the infrastructure repository.""" + if not os.path.exists(INFRASTRUCTURE_PATH): + ensure_installed("git") + run_command( + ["git", "clone", INFRASTRUCTURE_REPOSITORY, INFRASTRUCTURE_PATH] + ) + else: + os.chdir(INFRASTRUCTURE_PATH) + run_command(["git", "pull", "origin", "master"]) + + +def ask_to_login(): + """Log in as your autonomic member username.""" + members = get_members() + choices = [info["username"] for info in members["autonomic_members"]] + question = [ + { + "type": "list", + "name": "username", + "message": "What is your Autonomic username?", + "choices": choices, + "filter": lambda val: val.lower(), + } + ] + answer = prompt(question) + add_to_config(answer) + + +def create_configuration_file(): + """Create toolbelt config file.""" + if not os.path.exists(AUTONOMIC_YAML): + with open(AUTONOMIC_YAML, "w") as handle: + handle.write("---") diff --git a/autonomic/config.py b/autonomic/config.py new file mode 100644 index 0000000..49aad16 --- /dev/null +++ b/autonomic/config.py @@ -0,0 +1,34 @@ +"""Configuration handling module.""" + +import os + +from autonomic import logger +from autonomic.settings import AUTONOMIC_YAML +from autonomic.system import exit_with_msg +from autonomic.yaml import yaml + +log = logger.get_logger(__name__) + + +def ensure_config(): + """Ensure the configuration exists.""" + if not os.path.exists(AUTONOMIC_YAML): + msg = "{} is missing, run: autonomic init".format(AUTONOMIC_YAML) + exit_with_msg(msg) + + +def add_to_config(data): + """Add values to the autonomic.yml file.""" + ensure_config() + + with open(AUTONOMIC_YAML, "r") as handle: + config = yaml.load(handle.read()) + + if config is None: + config = {} + + for key in data: + config[key] = data[key] + + with open(AUTONOMIC_YAML, "w") as handle: + yaml.dump(config, handle) diff --git a/autonomic/infrastructure.py b/autonomic/infrastructure.py new file mode 100644 index 0000000..fa7320e --- /dev/null +++ b/autonomic/infrastructure.py @@ -0,0 +1,20 @@ +"""Infrastructure handling module.""" + +import os + +from autonomic.settings import MEMBERS_YAML +from autonomic.system import exit_with_msg +from autonomic.yaml import yaml + + +def get_members(): + """Get list of Autonomic members.""" + if not os.path.exists(MEMBERS_YAML): + msg = "{} is missing, run: autonomic init".format(MEMBERS_YAML) + exit_with_msg(msg) + + with open(MEMBERS_YAML, "r") as handle: + try: + return yaml.load(handle.read()) + except Exception as exception: + exit_with_msg(str(exception)) diff --git a/autonomic/logger.py b/autonomic/logger.py new file mode 100644 index 0000000..ffa36a2 --- /dev/null +++ b/autonomic/logger.py @@ -0,0 +1,142 @@ +"""Logging Module.""" + +import logging +import os +import sys + +import colorama + + +def should_do_markup(): + return sys.stdout.isatty() and os.environ.get("TERM") != "dumb" + + +SUCCESS = 100 +OUT = 101 + + +class LogFilter(object): + def __init__(self, level): + self.__level = level + + def filter(self, logRecord): + return logRecord.levelno <= self.__level + + +class CustomLogger(logging.getLoggerClass()): + def __init__(self, name, level=logging.NOTSET): + super(logging.getLoggerClass(), self).__init__(name, level) + logging.addLevelName(SUCCESS, "SUCCESS") + logging.addLevelName(OUT, "OUT") + + def success(self, msg, *args, **kwargs): + if self.isEnabledFor(SUCCESS): + self._log(SUCCESS, msg, args, **kwargs) + + def out(self, msg, *args, **kwargs): + if self.isEnabledFor(OUT): + self._log(OUT, msg, args, **kwargs) + + +class TrailingNewlineFormatter(logging.Formatter): + def format(self, record): + if record.msg: + record.msg = record.msg.rstrip() + return super(TrailingNewlineFormatter, self).format(record) + + +def get_logger(name=None): + logging.setLoggerClass(CustomLogger) + + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + + logger.addHandler(_get_info_handler()) + logger.addHandler(_get_out_handler()) + logger.addHandler(_get_warn_handler()) + logger.addHandler(_get_error_handler()) + logger.addHandler(_get_critical_handler()) + logger.addHandler(_get_success_handler()) + logger.propagate = False + + return logger + + +def _get_info_handler(): + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.INFO) + handler.addFilter(LogFilter(logging.INFO)) + handler.setFormatter( + TrailingNewlineFormatter("--> {}".format(cyan_text("%(message)s"))) + ) + + return handler + + +def _get_out_handler(): + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(OUT) + handler.addFilter(LogFilter(OUT)) + handler.setFormatter(TrailingNewlineFormatter(" %(message)s")) + + return handler + + +def _get_warn_handler(): + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.WARN) + handler.addFilter(LogFilter(logging.WARN)) + handler.setFormatter(TrailingNewlineFormatter(yellow_text("%(message)s"))) + + return handler + + +def _get_error_handler(): + handler = logging.StreamHandler(sys.stderr) + handler.setLevel(logging.ERROR) + handler.addFilter(LogFilter(logging.ERROR)) + handler.setFormatter(TrailingNewlineFormatter(red_text("%(message)s"))) + + return handler + + +def _get_critical_handler(): + handler = logging.StreamHandler(sys.stderr) + handler.setLevel(logging.CRITICAL) + handler.addFilter(LogFilter(logging.CRITICAL)) + handler.setFormatter( + TrailingNewlineFormatter(red_text("ERROR: %(message)s")) + ) + + return handler + + +def _get_success_handler(): + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(SUCCESS) + handler.addFilter(LogFilter(SUCCESS)) + handler.setFormatter(TrailingNewlineFormatter(green_text("%(message)s"))) + + return handler + + +def red_text(msg): + return color_text(colorama.Fore.RED, msg) + + +def yellow_text(msg): + return color_text(colorama.Fore.YELLOW, msg) + + +def green_text(msg): + return color_text(colorama.Fore.GREEN, msg) + + +def cyan_text(msg): + return color_text(colorama.Fore.CYAN, msg) + + +def color_text(color, msg): + if should_do_markup(): + return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL) + return msg diff --git a/autonomic/settings.py b/autonomic/settings.py new file mode 100644 index 0000000..409f8a9 --- /dev/null +++ b/autonomic/settings.py @@ -0,0 +1,13 @@ +"""Static settings.""" + +import os + +USER = os.getlogin() +HOME = "/home/{}".format(USER) +CONFIG_PATH = "{}/.autonomic".format(HOME) +INFRASTRUCTURE_REPOSITORY = ( + "ssh://git@git.autonomic.zone:222/autonomic-cooperative/infrastructure.git" +) +INFRASTRUCTURE_PATH = "{}/infrastructure".format(CONFIG_PATH) +MEMBERS_YAML = "{}/resources/members.yml".format(INFRASTRUCTURE_PATH) +AUTONOMIC_YAML = "{}/autonomic.yml".format(CONFIG_PATH) diff --git a/autonomic/system.py b/autonomic/system.py new file mode 100644 index 0000000..941aa18 --- /dev/null +++ b/autonomic/system.py @@ -0,0 +1,35 @@ +"""System related functions.""" + +import shutil +import subprocess +import sys + +from autonomic import logger + +log = logger.get_logger(__name__) + + +def ensure_installed(package): + """Ensure a system dependency is installed""" + if shutil.which(package) is None: + msg = "{} is not installed?".format(package) + exit_with_msg(msg) + + +def run_command(command): + """Run a command.""" + try: + log.info("Running '{}'".format(" ".join(command))) + subprocess.check_output(command) + except subprocess.CalledProcessError as exception: + msg = "{} failed! Saw {}".format(" ".join(command), str(exception)) + exit_with_msg(msg) + + +def exit(code=1): + sys.exit(code) + + +def exit_with_msg(msg, code=1): + log.critical(msg) + exit(code) diff --git a/autonomic/yaml.py b/autonomic/yaml.py new file mode 100644 index 0000000..0ed8ec2 --- /dev/null +++ b/autonomic/yaml.py @@ -0,0 +1,6 @@ +"""YAML initialisation module.""" + +from ruamel.yaml import YAML + +yaml = YAML() +yaml.explicit_start = True diff --git a/autonomic/yaml.yml b/autonomic/yaml.yml new file mode 100644 index 0000000..5ddb766 --- /dev/null +++ b/autonomic/yaml.yml @@ -0,0 +1,7 @@ +"""YAML initialisation module.""" + +from ruamel.yaml import YAML + + +yaml = YAML() +yaml.explicit_start = True diff --git a/pyproject.toml b/pyproject.toml index ed12962..ae0315e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,11 +20,9 @@ black = "^19.10b0" isort = "^4.3.21" flake8 = "^3.7.9" - [tool.black] line-length = 80 target-version = ["py38"] -skip-string-normalization = true include = '\.pyi?$' [tool.towncrier] diff --git a/setup.cfg b/setup.cfg index 503a201..60fb686 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,13 +43,16 @@ packages = find: zip_safe = False install_requires = click >= 7.1.1, <= 8.0 + colorama >= 0.4.3, <= 0.5 + pyinquirer >= 1.0.3, <= 1.1 + ruamel.yaml >= 0.16.10, <= 0.17 [options.packages.find] where = . [options.entry_points] console_scripts = - autonomic = autonomic.__main__:main + autonomic = autonomic.__main__:autonomic [build_sphinx] all_files = 1