Ori.livneh has uploaded a new change for review.
https://gerrit.wikimedia.org/r/61078
Change subject: Add 'tcpircbot' Puppet class
......................................................................
Add 'tcpircbot' Puppet class
This Puppet class configures a Python script that listens on a TCP socket and
forwards incoming data to an IRC channel. It allows setting up automatic
announcements to IRC from machines in the cluster that do not have a public
interface. The script runs as an upstart service. By default, it connects to
freenode via SSL.
Sample configuration:
include tcpircbot
tcpircbot::instance { 'announcebot':
password => 'nickserv_secret123',
channel => '#wikimedia-operations',
}
Change-Id: I6d4f661b70e6c4d4111672f5a7f8018389986a18
---
A modules/tcpircbot/files/tcpircbot.py
A modules/tcpircbot/manifests/init.pp
A modules/tcpircbot/manifests/instance.pp
A modules/tcpircbot/templates/tcpircbot.conf.erb
A modules/tcpircbot/templates/tcpircbot.json.erb
5 files changed, 234 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/operations/puppet
refs/changes/78/61078/1
diff --git a/modules/tcpircbot/files/tcpircbot.py
b/modules/tcpircbot/files/tcpircbot.py
new file mode 100755
index 0000000..7fb5efb
--- /dev/null
+++ b/modules/tcpircbot/files/tcpircbot.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+"""
+TCP -> IRC forwarder bot
+Forward data from a TCP socket to an IRC channel
+
+Usage: tcpircbot.py CONFIGFILE
+
+CONFIGFILE should be a JSON file with the following structure:
+
+ {
+ "irc": {
+ "channel": "#wikimedia-operations",
+ "network": ["irc.freenode.net", 7000, "serverpassword"],
+ "nickname": "tcpircbot",
+ "ssl": true
+ },
+ "tcp": {
+ "max_clients": 5,
+ "port": 9125
+ }
+ }
+
+Requires irclib >=0.4.8 <http://bitbucket.org/jaraco/irc>
+Available in Ubuntu as 'python-irclib'
+
+"""
+import sys
+reload(sys)
+sys.setdefaultencoding('utf8')
+
+import atexit
+import codecs
+import json
+import logging
+import os
+import select
+import socket
+
+try:
+ # irclib 0.7+
+ import irc.bot as ircbot
+except ImportError:
+ import ircbot
+
+
+BUFSIZE = 460 # Read from socket in IRC-message-sized chunks.
+AF = socket.AF_INET6 # Change to 'AF_INET' to disable IPv6
+
+logging.basicConfig(level=logging.INFO, stream=sys.stderr,
+ format='%(asctime)-15s %(message)s')
+
+
+class ForwarderBot(ircbot.SingleServerIRCBot):
+ """Minimal IRC bot; joins a channel."""
+
+ def __init__(self, network, nickname, channel, **options):
+ ircbot.SingleServerIRCBot.__init__(self, [network], nickname, nickname)
+ self.channel = channel
+ self.options = options
+ for event in ['disconnect', 'join', 'part', 'welcome']:
+ self.connection.add_global_handler(event, self.log_event)
+
+ def connect(self, *args, **kwargs):
+ """Intercepts call to ircbot.SingleServerIRCBot.connect to add support
+ for ssl and ipv6 params."""
+ kwargs.update(self.options)
+ ircbot.SingleServerIRCBot.connect(self, *args, **kwargs)
+
+ def on_privnotice(self, connection, event):
+ logging.info('%s %s', event.source(), event.arguments())
+
+ def log_event(self, connection, event):
+ if connection.real_nickname in [event._source, event._target]:
+ logging.info('%(_eventtype)s [%(_source)s -> %(_target)s]'
+ % vars(event))
+
+ def on_welcome(self, connection, event):
+ connection.join(self.channel)
+
+
+if len(sys.argv) < 2 or sys.argv[1] in ('-h', '--help'):
+ sys.exit(__doc__.lstrip())
+
+with open(sys.argv[1]) as f:
+ config = json.load(f)
+
+# Create a TCP server socket
+server = socket.socket(AF, socket.SOCK_STREAM)
+server.setblocking(0)
+server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+server.bind((config['tcp'].get('iface', ''), config['tcp']['port']))
+server.listen(config['tcp']['max_clients'])
+
+# Create a bot and connect to IRC
+bot = ForwarderBot(**config['irc'])
+bot._connect()
+
+sockets = [server, bot.connection.socket]
+
+
+def close_sockets():
+ for sock in sockets:
+ sock.close()
+atexit.register(close_sockets)
+
+while 1:
+ readable, _, _ = select.select(sockets, [], [])
+ for sock in readable:
+ if sock is server:
+ conn, addr = server.accept()
+ conn.setblocking(0)
+ logging.info('Connection from %s', addr)
+ sockets.append(conn)
+ elif sock is bot.connection.socket:
+ bot.connection.process_data()
+ else:
+ data = sock.recv(BUFSIZE)
+ data = codecs.decode(data, 'utf8', 'replace').strip()
+ if data:
+ logging.info('TCP %s: "%s"', sock.getpeername(), data)
+ bot.connection.privmsg(bot.channel, data)
+ else:
+ sock.close()
+ sockets.remove(sock)
diff --git a/modules/tcpircbot/manifests/init.pp
b/modules/tcpircbot/manifests/init.pp
new file mode 100644
index 0000000..7171ca0
--- /dev/null
+++ b/modules/tcpircbot/manifests/init.pp
@@ -0,0 +1,42 @@
+# Listens on a TCP port and forwards messages to an IRC channel.
+# Connects to freenode via SSL by default.
+#
+# Example:
+#
+# include tcpircbot
+#
+# tcpircbot::instance { 'announcebot':
+# password => 'nickserv_secret123',
+# channel => '#wikimedia-operations',
+# }
+#
+class tcpircbot (
+ $user = 'tcpircbot',
+ $group = 'tcpircbot',
+ $dir = '/srv/tcpircbot',
+) {
+ package { 'python-irclib':
+ ensure => present,
+ }
+
+ group { $group:
+ ensure => present,
+ }
+
+ user { $user:
+ ensure => present,
+ gid => $group,
+ shell => '/bin/false',
+ home => $dir,
+ managehome => true,
+ system => true,
+ }
+
+ file { "${dir}/tcpircbot.py":
+ ensure => present,
+ source => 'puppet:///modules/tcpircbot/tcpircbot.py',
+ owner => $user,
+ group => $group,
+ mode => '0555',
+ }
+}
diff --git a/modules/tcpircbot/manifests/instance.pp
b/modules/tcpircbot/manifests/instance.pp
new file mode 100644
index 0000000..b913491
--- /dev/null
+++ b/modules/tcpircbot/manifests/instance.pp
@@ -0,0 +1,37 @@
+define tcpircbot::instance(
+ $channel,
+ $password,
+ $nickname = $title,
+ $server_host = 'chat.freenode.net',
+ $server_port = 7000,
+ $ssl = true,
+ $max_clients = 5,
+ $listen_port = 9200,
+) {
+ include tcpircbot
+
+ file { "${tcpircbot::dir}/${title}.json":
+ ensure => present,
+ content => template('tcpircbot/tcpircbot.json.erb'),
+ }
+
+ file { "/etc/init/tcpircbot-${title}.conf":
+ ensure => present,
+ content => template('tcpircbot/tcpircbot.conf.erb'),
+ }
+
+ file { "/etc/init.d/tcpircbot-${title}":
+ ensure => link,
+ target => '/lib/init/upstart-job',
+ }
+
+ service { "tcpircbot-${title}":
+ ensure => running,
+ provider => 'upstart',
+ require => [
+ Package['python-irclib'],
+ File["${tcpircbot::dir}/${title}.json"],
+ File["/etc/init/tcpircbot-${title}.conf"]
+ ],
+ }
+}
diff --git a/modules/tcpircbot/templates/tcpircbot.conf.erb
b/modules/tcpircbot/templates/tcpircbot.conf.erb
new file mode 100755
index 0000000..50e25dd
--- /dev/null
+++ b/modules/tcpircbot/templates/tcpircbot.conf.erb
@@ -0,0 +1,17 @@
+# vim: set ft=upstart:
+
+# Upstart job configuration for tcpircbot
+# This file is managed by Puppet
+
+description "TCP socket to IRC bot: <%= @title %>"
+
+start on (local-filesystems and net-device-up IFACE!=lo)
+stop on runlevel [!2345]
+
+setuid <%= scope.lookupvar('tcpircbot::user') %>
+setgid <%= scope.lookupvar('tcpircbot::group') %>
+
+chdir "<%= scope.lookupvar('tcpircbot::dir') %>"
+exec python tcpircbot.py "<%= @title %>.json"
+
+respawn
diff --git a/modules/tcpircbot/templates/tcpircbot.json.erb
b/modules/tcpircbot/templates/tcpircbot.json.erb
new file mode 100755
index 0000000..8c4a82a
--- /dev/null
+++ b/modules/tcpircbot/templates/tcpircbot.json.erb
@@ -0,0 +1,12 @@
+{
+ "irc": {
+ "channel": "<%= @channel %>",
+ "network": ["<%= @server_host %>", <%= @server_port %>, "<%= @nickname
%>:<%= @password %>"],
+ <% if @ssl %>"ssl": true,<% end %>
+ "nickname": "<%= @nickname %>"
+ },
+ "tcp": {
+ "max_clients": <%= @max_clients %>,
+ "port": <%= @listen_port %>
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/61078
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6d4f661b70e6c4d4111672f5a7f8018389986a18
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Ori.livneh <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits