From 4d60e89f102f18f26f345471a41506ed34fb2dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sat, 20 Oct 2018 22:51:47 +0100 Subject: [PATCH] add irc notifications --- bin/nur | 2 +- ci/deploy.sh | 3 +- nur/__init__.py | 5 +++ nur/combine.py | 48 ++++++++++++++++++++------ nur/irc_notify.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++ release.nix | 2 ++ setup.cfg | 3 ++ setup.py | 1 + 8 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 nur/irc_notify.py diff --git a/bin/nur b/bin/nur index e998a1984..3bb1db39d 100755 --- a/bin/nur +++ b/bin/nur @@ -1,5 +1,5 @@ #!/usr/bin/env nix-shell -#!nix-shell -p python3 -p nix-prefetch-git -p nix -i python3 +#!nix-shell -p python3 -p python3.pkgs.irc -p nix-prefetch-git -p nix -i python3 import sys import os diff --git a/ci/deploy.sh b/ci/deploy.sh index 43a38f05e..88dfc49ae 100755 --- a/ci/deploy.sh +++ b/ci/deploy.sh @@ -48,7 +48,8 @@ git config --global commit.gpgsign true git clone git@github.com:nix-community/nur-combined -result/bin/nur combine nur-combined +result/bin/nur combine \ + --irc-notify nur-bot@chat.freenode.net:6697/nixos-nur nur-combined if [[ -z "$(git diff --exit-code)" ]]; then echo "No changes to the output on this push; exiting." diff --git a/nur/__init__.py b/nur/__init__.py index 8fc098734..5830ad34f 100644 --- a/nur/__init__.py +++ b/nur/__init__.py @@ -18,6 +18,11 @@ def parse_arguments(argv: List[str]) -> argparse.Namespace: subparsers = parser.add_subparsers(description="subcommands") combine = subparsers.add_parser("combine") + combine.add_argument( + "--irc-notify", + type=str, + help="Example nur-bot@chat.freenode.net:6697/nixos-nur", + ) combine.add_argument("directory") combine.set_defaults(func=combine_command) diff --git a/nur/combine.py b/nur/combine.py index d48bd2d80..10c3f7306 100644 --- a/nur/combine.py +++ b/nur/combine.py @@ -2,11 +2,11 @@ import logging import os import shutil import subprocess -from tempfile import TemporaryDirectory from argparse import Namespace from distutils.dir_util import copy_tree from pathlib import Path -from typing import Dict, List, Optional +from tempfile import TemporaryDirectory +from typing import Dict, List, Optional, Tuple from .fileutils import chdir, write_json_file from .manifest import Repo, load_manifest, update_lock_file @@ -65,28 +65,39 @@ def commit_repo(repo: Repo, message: str, path: Path) -> Repo: return repo +def repo_link(path: Path) -> str: + commit = subprocess.check_output(["git", "-C", path, "rev-parse", "HEAD"]) + rev = commit.decode("utf-8").strip()[:10] + return f"https://github.com/nix-community/nur-combined/commit/{rev}" + + def update_combined_repo( combined_repo: Optional[Repo], repo: Repo, path: Path -) -> Optional[Repo]: +) -> Tuple[Optional[Repo], Optional[str]]: if repo.locked_version is None: - return None + return None, None new_rev = repo.locked_version.rev if combined_repo is None: - return commit_repo(repo, f"{repo.name}: init at {new_rev}", path) + message = f"{repo.name}: init at {new_rev[:10]} ({repo_link(path)})" + repo = commit_repo(repo, message, path) + message += f" ({repo_link(path)})" + return repo, message assert combined_repo.locked_version is not None old_rev = combined_repo.locked_version.rev if combined_repo.locked_version == repo.locked_version: - return repo + return repo, None if new_rev != old_rev: - message = f"{repo.name}: {old_rev} -> {new_rev}" + message = f"{repo.name}: {old_rev[:10]} -> {new_rev[:10]}" else: message = f"{repo.name}: update" - return commit_repo(repo, message, path) + repo = commit_repo(repo, message, path) + message += f" ({repo_link(path)})" + return repo, message def remove_repo(repo: Repo, path: Path) -> None: @@ -104,7 +115,7 @@ def update_manifest(repos: List[Repo], path: Path) -> None: write_json_file(dict(repos=d), path) -def update_combined(path: Path) -> None: +def update_combined(path: Path) -> List[str]: manifest = load_manifest(MANIFEST_PATH, LOCK_PATH) combined_repos = load_combined_repos(path) @@ -113,6 +124,7 @@ def update_combined(path: Path) -> None: os.makedirs(repos_path, exist_ok=True) updated_repos = [] + notifications = [] for repo in manifest.repos: combined_repo = None @@ -120,13 +132,17 @@ def update_combined(path: Path) -> None: combined_repo = combined_repos[repo.name] del combined_repos[repo.name] try: - new_repo = update_combined_repo(combined_repo, repo, repos_path) + new_repo, notification = update_combined_repo( + combined_repo, repo, repos_path + ) except Exception: logger.exception(f"Failed to updated repository {repo.name}") continue if new_repo is not None: updated_repos.append(new_repo) + if notification is not None: + notifications.append(notification) for combined_repo in combined_repos.values(): remove_repo(combined_repo, path) @@ -138,6 +154,8 @@ def update_combined(path: Path) -> None: with chdir(path): commit_files(["repos.json", "repos.json.lock"], "update repos.json + lock") + return notifications + def setup_combined() -> None: manifest_path = "repos.json" @@ -164,4 +182,12 @@ def combine_command(args: Namespace) -> None: with chdir(combined_path): setup_combined() - update_combined(combined_path) + notifications = update_combined(combined_path) + + if args.irc_notify: + from .irc_notify import send + + try: + send(args.irc_notify, notifications) + except Exception as e: + print(f"failed to send irc notifications: {e}") diff --git a/nur/irc_notify.py b/nur/irc_notify.py new file mode 100644 index 000000000..79e85c17e --- /dev/null +++ b/nur/irc_notify.py @@ -0,0 +1,86 @@ +import ssl +from typing import List, Optional +from urllib.parse import urlparse + +from irc.client import Connection, Event, Reactor, ServerConnectionError, is_channel +from irc.connection import Factory + + +class Exit(SystemExit): + pass + + +def send(url: str, notifications: List[str]) -> None: + parsed = urlparse(f"http://{url}") + username = parsed.username or "nur-bot" + server = parsed.hostname or "chat.freenode.de" + if parsed.path != "/" or parsed.path == "": + channel = f"#{parsed.path[1:]}" + else: + channel = "#nixos-nur" + port = parsed.port or 6697 + password = parsed.password + if len(notifications) == 0: + return + _send( + notifications=notifications, + nickname=username, + password=password, + server=server, + channel=channel, + port=port, + ) + + +class _send: + def __init__( + self, + notifications: List[str], + server: str, + nickname: str, + port: int, + channel: str, + password: Optional[str] = None, + use_ssl: bool = True, + ) -> None: + self.notifications = notifications + self.channel = channel + + ssl_factory = None + if use_ssl: + ssl_factory = Factory(wrapper=ssl.wrap_socket) + reactor = Reactor() + try: + s = reactor.server() + c = s.connect( + server, port, nickname, password=password, connect_factory=ssl_factory + ) + except ServerConnectionError as e: + print(f"error sending irc notification {e}") + return + + c.add_global_handler("welcome", self.on_connect) + c.add_global_handler("join", self.on_join) + c.add_global_handler("disconnect", self.on_disconnect) + + try: + reactor.process_forever() + except Exit: + pass + + def on_connect(self, connection: Connection, event: Event) -> None: + if is_channel(self.channel): + connection.join(self.channel) + return + self.main_loop(connection) + + def on_join(self, connection: Connection, event: Event) -> None: + self.main_loop(connection) + + def on_disconnect(self, connection: Connection, event: Event) -> None: + raise Exit() + + def main_loop(self, connection: Connection) -> None: + for notification in self.notifications: + connection.privmsg(self.channel, notification) + connection.quit("Bye") diff --git a/release.nix b/release.nix index e5840565b..0fcf5fee9 100644 --- a/release.nix +++ b/release.nix @@ -6,6 +6,8 @@ python3Packages.buildPythonApplication { name = "nur"; src = ./.; + propagatedBuildInputs = [ python3Packages.irc ]; + makeWrapperArgs = [ "--prefix" "PATH" ":" "${stdenv.lib.makeBinPath [ nix-prefetch-git git nix ]}" "--set" "LOCALE_ARCHIVE" "${glibcLocales}/lib/locale/locale-archive" diff --git a/setup.cfg b/setup.cfg index 4a149e661..ad9e4daea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,6 +10,9 @@ max-line-length = 88 ignore = E501,E741,W503 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist +[mypy-irc.*] +ignore_missing_imports = True + [mypy] warn_redundant_casts = true disallow_untyped_calls = true diff --git a/setup.py b/setup.py index 4332d5454..431d30867 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ setup( license="MIT", packages=find_packages(), entry_points={"console_scripts": ["nur = nur:main"]}, + install_requires=["irc"], extras_require={"dev": ["mypy", "flake8>=3.5,<3.6", "black"]}, classifiers=[ "Development Status :: 5 - Production/Stable",