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

Reply via email to