From 1ec64ccb178a4164ac204925c20c559d96508a9b Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Wed, 4 May 2022 15:04:46 +0200 Subject: add logging, offload addons into config, allow external addons --- labbot/__main__.py | 61 +++++++++++++++++++++++++----------------- labbot/addons/approve-merge.py | 10 +++++-- labbot/addons/merge-label.py | 4 +++ labbot/bot.py | 42 +++++++++++++++++++++++++++-- labbot/config.py | 34 +++++++++++++++++------ labbot/logger.py | 19 +++++++++++++ labbot/py.typed | 0 7 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 labbot/logger.py create mode 100644 labbot/py.typed diff --git a/labbot/__main__.py b/labbot/__main__.py index 9f0e3ba..57ffbcd 100644 --- a/labbot/__main__.py +++ b/labbot/__main__.py @@ -1,9 +1,16 @@ import click +import logging from typing import List from importlib import import_module -from labbot.config import read_config, write_config -from labbot.bot import Bot +import labbot.bot +import labbot.config +import labbot.logger + +DEFAULT_ADDONS = [ + "merge-label", + "approve-merge" +] @click.group() def main(): @@ -11,13 +18,15 @@ def main(): @main.command(help="Create a new instance") @click.option("--name", prompt=True, help="Name the instance will be given") -@click.option("--access_token", prompt=True, hide_input=True, help="Access Token to interact with the API") -@click.option("--secret", prompt=True, default="", help="Secret to receive webhook requests [can be empty]") +@click.option("--access_token", prompt="Access Token for the Bot account", hide_input=True, help="Access Token to interact with the API") +@click.option("--secret", prompt="Webhook Secret (can be empty)", default="", help="Secret to receive webhook requests (can be empty)") def setup(**data): instance_name = data.pop("name", "").replace(" ", "_").lower() + data["addons"] = DEFAULT_ADDONS + data["addon_path"] = "" - if not read_config(instance_name): - write_config(instance_name, data) + if not labbot.config.read_instance_config(instance_name): + labbot.config.write_instance_config(instance_name, data) click.echo(f"You can start your instance by running `lab-bot run {instance_name}`") else: click.echo(f"an instance with the name {instance_name} already exists") @@ -30,11 +39,11 @@ def config(name, **data): data = {k:v for k,v in data.items() if v} - conf = read_config(name) + conf = labbot.config.read_instance_config(name) if conf: if data: conf.update(data) - write_config(name, conf) + labbot.config.write_instance_config(name, conf) click.echo("configured") else: click.echo(f"nothing to change") @@ -43,38 +52,40 @@ def config(name, **data): pass -def load_addons(instance: Bot, addons: List[str]): - for addon in addons: - import_module(f"labbot.addons.{addon}").setup(instance) - click.echo(f"Imported {addon}") - @main.command(help="Run an instance") -@click.option("--port", default=8080) +@click.option("--port", default=8080, show_default=True, help="change the webhook port") +@click.option("--debug", is_flag=True, default=False, help="enable debug logging") @click.argument('name') -def run(name, port): - conf = read_config(name) +def run(name, port, debug: bool): + conf = labbot.config.read_instance_config(name) if not conf: click.echo(f"{name} is not an instance") return - instance = Bot( + if debug: + logger_level = logging.DEBUG + else: + logger_level = logging.INFO + + labbot.logger.init(logger_level) + + instance = labbot.bot.Bot( + name=name, + config=conf, secret=conf["secret"], access_token=conf["access_token"] ) - load_addons(instance, [ - "merge-label", - "approve-merge" - ]) - - click.echo(f"Started {name}") - instance.run( port=port ) - +@main.command(name="list", help="List all available instances") +def list_instances(): + print("Available Instances:") + for ins in labbot.config.list_instances(): + print(f"- {ins}") if __name__ == "__main__": main() \ No newline at end of file diff --git a/labbot/addons/approve-merge.py b/labbot/addons/approve-merge.py index 0fdeaa9..3c0ef30 100644 --- a/labbot/addons/approve-merge.py +++ b/labbot/addons/approve-merge.py @@ -2,6 +2,9 @@ automatically merges merge requests if a certain number of approvals is reached. """ +import logging + +log = logging.getLogger(__name__) required_approval_count = 2 @@ -9,12 +12,13 @@ async def merge_label_hook(event, gl, *args, **kwargs): title = event.object_attributes["title"] state = event.object_attributes["state"] merge_status = event.object_attributes["merge_status"] + iid = event.object_attributes['iid'] if state != "opened" or title.lower().startswith("draft"): return - approvals_url = f"/projects/{event.project_id}/merge_requests/{event.object_attributes['iid']}/approvals" - merge_url = f"/projects/{event.project_id}/merge_requests/{event.object_attributes['iid']}/merge" + approvals_url = f"/projects/{event.project_id}/merge_requests/{iid}/approvals" + merge_url = f"/projects/{event.project_id}/merge_requests/{iid}/merge" data = await gl.getitem(approvals_url) @@ -24,6 +28,8 @@ async def merge_label_hook(event, gl, *args, **kwargs): if merge_status == "can_be_merged": print("test") await gl.put(merge_url) + else: + log.debug(f"Cannot merge !{iid} because of its merge_statuc `{merge_status}`") def setup(bot): bot.register(merge_label_hook, "Merge Request Hook") diff --git a/labbot/addons/merge-label.py b/labbot/addons/merge-label.py index 9379b59..4798d4a 100644 --- a/labbot/addons/merge-label.py +++ b/labbot/addons/merge-label.py @@ -5,6 +5,10 @@ specified labels. import os import re +import logging + +log = logging.getLogger(__name__) + title_regex = r"^(?:#|)(\d+)\s*" word_regex = r"^#(\d+)$" diff --git a/labbot/bot.py b/labbot/bot.py index 3ea1929..1361656 100644 --- a/labbot/bot.py +++ b/labbot/bot.py @@ -1,13 +1,51 @@ +import os.path +import sys from gidgetlab.aiohttp import GitLabBot +from importlib import import_module +import logging + +import labbot +import labbot.config + +log = logging.getLogger(__name__) class Bot: def __init__(self, *args, **kwargs): - self.instance = GitLabBot("lab-bot", **kwargs) - pass + self.name = kwargs.get("name", "lab-bot") + self.secret = kwargs.get("secret", "") + self.config = kwargs.get("config", labbot.config.read_instance_config(self.name)) + + self.addons = self.config.get("addons", []) + self.addon_paths = [] + + self.core_addon_path = os.path.join(os.path.dirname(labbot.__file__), "addons") + self.addon_paths.append(self.core_addon_path) + if "addon_path" in self.config: + self.addon_paths.append(self.config.get("addon_path")) + + self.instance = GitLabBot(self.name, **kwargs) + + for path in self.addon_paths: + sys.path.insert(0, path) + + for addon in self.addons: + self.load_addon(addon) + + for path in self.addon_paths: + sys.path.remove(path) + + + def load_addon(self, addon: str): + try: + import_module(f"{addon}").setup(self) + log.info(f"Loaded {addon}") + except ModuleNotFoundError: + log.error(f"No addon named `{addon}`") def register(self, func, *args, **kwargs): return self.instance.router.register(*args, **kwargs)(func) def run(self, *args, **kwargs): + log.info(f"Started {self.name}") self.instance.run(*args, **kwargs) \ No newline at end of file diff --git a/labbot/config.py b/labbot/config.py index 3e8bcba..10858ea 100644 --- a/labbot/config.py +++ b/labbot/config.py @@ -1,27 +1,45 @@ import os import json -from appdirs import user_config_dir +from typing import List +from appdirs import user_config_dir # type: ignore + +CONFIG_FILE = "config.json" + +def list_instances() -> List[str]: + instances = [] + with os.scandir(config_dir()) as it: + for entry in it: + if entry.is_dir() and os.path.isfile(f"{entry.path}/{CONFIG_FILE}"): + instances.append(entry.name) + return instances def config_dir() -> str: - path = user_config_dir("labbot") + path: str = user_config_dir("labbot") os.makedirs(path, exist_ok=True) return path -def write_config(name: str, data: dict) -> None: - conf_path = os.path.join(config_dir(), f"{name}.json") +def instance_config_dir(name) -> str: + path = os.path.join(config_dir(), name) + + return path + +def write_instance_config(name: str, data: dict) -> None: + instance_path = instance_config_dir(name) + os.makedirs(instance_path, exist_ok=True) + conf_path = os.path.join(instance_path, CONFIG_FILE) with open(conf_path, "w") as file: json.dump(data, file) - if data != read_config(name): + if data != read_instance_config(name): raise ValueError("Config could not be saved properly") -def read_config(name: str) -> dict: - conf_path = os.path.join(config_dir(), f"{name}.json") +def read_instance_config(name: str) -> dict: + conf_path = os.path.join(instance_config_dir(name), CONFIG_FILE) try: with open(conf_path, "r") as file: return json.load(file) except FileNotFoundError: - return {} \ No newline at end of file + return {} diff --git a/labbot/logger.py b/labbot/logger.py new file mode 100644 index 0000000..067bcac --- /dev/null +++ b/labbot/logger.py @@ -0,0 +1,19 @@ +import sys +import logging + +def init(level: int): + logger = logging.getLogger() + logger.setLevel(level) + + formatter = logging.Formatter( + "[{asctime}] [{levelname}] {name}: {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{" + ) + + if not sys.stdout.closed: + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setFormatter(formatter) + + logger.addHandler(stdout_handler) + + + logging.captureWarnings(True) \ No newline at end of file diff --git a/labbot/py.typed b/labbot/py.typed new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3