Merge pull request #89 from nix-community/irc

add irc notifications
This commit is contained in:
Jörg Thalheim 2018-10-20 23:28:30 +01:00 committed by GitHub
commit 979744edd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 13 deletions

View file

@ -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

View file

@ -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."

View file

@ -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)

View file

@ -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}")

86
nur/irc_notify.py Normal file
View file

@ -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")

View file

@ -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"

View file

@ -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

View file

@ -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",