From e3166c91026cf3c6a28859a1455dfcbc4e4023fe Mon Sep 17 00:00:00 2001
From: Andrew Hutchings <andrew@linuxjedi.co.uk>
Date: Mon, 30 Jan 2012 13:57:37 +0000
Subject: [PATCH] Add lodgeit to puppet

Will automatically install paste.drizzle.org and paste.openstack.org onto a server

Change-Id: Ia2c1e37892f3ae8e3d4034e38ddfaa01c6a92a54
---
 manifests/site.pp                         |  14 ++
 modules/lodgeit/files/database.py         | 202 ++++++++++++++++++++++
 modules/lodgeit/files/header-bg2.png      | Bin 0 -> 3670 bytes
 modules/lodgeit/manifests/init.pp         |  52 ++++++
 modules/lodgeit/manifests/site.pp         |  66 +++++++
 modules/lodgeit/templates/layout.html.erb |  91 ++++++++++
 modules/lodgeit/templates/manage.py.erb   |  38 ++++
 modules/lodgeit/templates/nginx.erb       |  10 ++
 modules/lodgeit/templates/upstart.erb     |   8 +
 9 files changed, 481 insertions(+)
 create mode 100644 modules/lodgeit/files/database.py
 create mode 100644 modules/lodgeit/files/header-bg2.png
 create mode 100644 modules/lodgeit/manifests/init.pp
 create mode 100644 modules/lodgeit/manifests/site.pp
 create mode 100644 modules/lodgeit/templates/layout.html.erb
 create mode 100644 modules/lodgeit/templates/manage.py.erb
 create mode 100644 modules/lodgeit/templates/nginx.erb
 create mode 100644 modules/lodgeit/templates/upstart.erb

diff --git a/manifests/site.pp b/manifests/site.pp
index b8da8b413e..3b1a77c072 100644
--- a/manifests/site.pp
+++ b/manifests/site.pp
@@ -194,6 +194,20 @@ node "docs.openstack.org" {
   include doc_server
 }
 
+node "paste.openstack.org" {
+  include openstack_server
+  include lodgeit
+  lodgeit::site { "openstack":
+    port => "5000",
+    image => "header-bg2.png"
+  }
+
+  lodgeit::site { "drizzle":
+    port => "5001"
+  }
+
+}
+
 node "devstack-oneiric.template.openstack.org" {
   include openstack_template
   include devstack_host
diff --git a/modules/lodgeit/files/database.py b/modules/lodgeit/files/database.py
new file mode 100644
index 0000000000..ce4718756c
--- /dev/null
+++ b/modules/lodgeit/files/database.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+"""
+    lodgeit.database
+    ~~~~~~~~~~~~~~~~
+
+    Database fun :)
+
+    :copyright: 2007-2008 by Armin Ronacher, Christopher Grebs.
+    :license: BSD
+"""
+import time
+import difflib
+from datetime import datetime
+from werkzeug import cached_property
+from sqlalchemy import MetaData, Integer, Text, DateTime, ForeignKey, \
+     String, Boolean, Table, Column, select, and_, func
+from sqlalchemy.orm import scoped_session, create_session, backref, relation
+from sqlalchemy.orm.scoping import ScopedSession
+from lodgeit import local
+from lodgeit.utils import generate_paste_hash
+from lodgeit.lib.highlighting import highlight, preview_highlight, LANGUAGES
+
+from sqlalchemy.orm import mapper as sqla_mapper
+
+def session_mapper(scoped_session):
+    def mapper(cls, *arg, **kw):
+        cls.query = scoped_session.query_property()
+        return sqla_mapper(cls, *arg, **kw)
+    return mapper
+
+session = scoped_session(lambda: create_session(local.application.engine),
+    scopefunc=local._local_manager.get_ident)
+
+metadata = MetaData()
+
+pastes = Table('pastes', metadata,
+    Column('paste_id', Integer, primary_key=True),
+    Column('code', Text),
+    Column('parent_id', Integer, ForeignKey('pastes.paste_id'),
+           nullable=True),
+    Column('pub_date', DateTime),
+    Column('language', String(30)),
+    Column('user_hash', String(40), nullable=True),
+    Column('handled', Boolean, nullable=False),
+    Column('private_id', String(40), unique=True, nullable=True)
+)
+
+
+class Paste(object):
+    """Represents a paste."""
+
+    def __init__(self, code, language, parent=None, user_hash=None,
+                 private=False):
+        if language not in LANGUAGES:
+            language = 'text'
+        self.code = u'\n'.join(code.splitlines())
+        self.language = language
+        if isinstance(parent, Paste):
+            self.parent = parent
+        elif parent is not None:
+            self.parent_id = parent
+        self.pub_date = datetime.now()
+        self.handled = False
+        self.user_hash = user_hash
+        self.private = private
+
+    @staticmethod
+    def get(identifier):
+        """Return the paste for an identifier.  Private pastes must be loaded
+        with their unique hash and public with the paste id.
+        """
+        if isinstance(identifier, basestring) and not identifier.isdigit():
+            return Paste.query.filter(Paste.private_id == identifier).first()
+        return Paste.query.filter(
+            (Paste.paste_id == int(identifier)) &
+            (Paste.private_id == None)
+        ).first()
+
+    @staticmethod
+    def find_all():
+        """Return a query for all public pastes ordered by the id in reverse
+        order.
+        """
+        return Paste.query.filter(Paste.private_id == None) \
+                          .order_by(Paste.paste_id.desc())
+
+    @staticmethod
+    def count():
+        """Count all pastes."""
+        s = select([func.count(pastes.c.paste_id)])
+        return session.execute(s).fetchone()[0]
+
+    @staticmethod
+    def resolve_root(identifier):
+        """Find the root paste for a paste tree."""
+        paste = Paste.get(identifier)
+        if paste is None:
+            return
+        while paste.parent_id is not None:
+            paste = paste.parent
+        return paste
+
+    @staticmethod
+    def fetch_replies():
+        """Get the new replies for the ower of a request and flag them
+        as handled.
+        """
+        s = select([pastes.c.paste_id],
+            Paste.user_hash == local.request.user_hash
+        )
+
+        paste_list = Paste.query.filter(and_(
+            Paste.parent_id.in_(s),
+            Paste.handled == False,
+            Paste.user_hash != local.request.user_hash,
+        )).order_by(pastes.c.paste_id.desc()).all()
+
+        to_mark = [p.paste_id for p in paste_list]
+        session.execute(pastes.update(pastes.c.paste_id.in_(to_mark),
+                                      values={'handled': True}))
+        return paste_list
+
+    def _get_private(self):
+        return self.private_id is not None
+
+    def _set_private(self, value):
+        if not value:
+            self.private_id = None
+            return
+        if self.private_id is None:
+            while 1:
+                self.private_id = generate_paste_hash()
+                paste = Paste.query.filter(Paste.private_id ==
+                                           self.private_id).first()
+                if paste is None:
+                    break
+    private = property(_get_private, _set_private, doc='''
+        The private status of the paste.  If the paste is private it gets
+        a unique hash as identifier, otherwise an integer.
+    ''')
+    del _get_private, _set_private
+
+    @property
+    def identifier(self):
+        """The paste identifier.  This is a string, the same the `get`
+        method accepts.
+        """
+        if self.private:
+            return self.private_id
+        return str(self.paste_id)
+
+    @property
+    def url(self):
+        """The URL to the paste."""
+        return '/show/%s/' % self.identifier
+
+    def compare_to(self, other, context_lines=4, template=False):
+        """Compare the paste with another paste."""
+        udiff = u'\n'.join(difflib.unified_diff(
+            self.code.splitlines(),
+            other.code.splitlines(),
+            fromfile='Paste #%s' % self.identifier,
+            tofile='Paste #%s' % other.identifier,
+            lineterm='',
+            n=context_lines
+        ))
+        if template:
+            from lodgeit.lib.diff import prepare_udiff
+            diff, info = prepare_udiff(udiff)
+            return diff and diff[0] or None
+        return udiff
+
+    @cached_property
+    def parsed_code(self):
+        """The paste as rendered code."""
+        return highlight(self.code, self.language)
+
+    def to_xmlrpc_dict(self):
+        """Convert the paste into a dict for XMLRCP."""
+        return {
+            'paste_id':         self.paste_id,
+            'code':             self.code,
+            'parsed_code':      self.parsed_code,
+            'pub_date':         int(time.mktime(self.pub_date.timetuple())),
+            'language':         self.language,
+            'parent_id':        self.parent_id,
+            'url':              self.url
+        }
+
+    def render_preview(self, num=5):
+        """Render a preview for this paste."""
+        return preview_highlight(self.code, self.language, num)
+
+mapper= session_mapper(session)
+
+mapper(Paste, pastes, properties={
+    'children': relation(Paste,
+        primaryjoin=pastes.c.parent_id==pastes.c.paste_id,
+        cascade='all',
+        backref=backref('parent', remote_side=[pastes.c.paste_id])
+    )
+})
diff --git a/modules/lodgeit/files/header-bg2.png b/modules/lodgeit/files/header-bg2.png
new file mode 100644
index 0000000000000000000000000000000000000000..146faec5cfe3773824f4caf39e4480e4974d10df
GIT binary patch
literal 3670
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

literal 0
HcmV?d00001

diff --git a/modules/lodgeit/manifests/init.pp b/modules/lodgeit/manifests/init.pp
new file mode 100644
index 0000000000..f25a500636
--- /dev/null
+++ b/modules/lodgeit/manifests/init.pp
@@ -0,0 +1,52 @@
+class lodgeit {
+  $packages = [ "nginx",
+                "python-imaging",
+                "python-pip",
+                "python-jinja2",
+                "python-pybabel",
+                "python-werkzeug",
+                "python-simplejson",
+                "python-pygments",
+                "mercurial",
+                "drizzle",
+                "python-mysqldb" ]
+
+  package { $packages: ensure => latest }
+
+  package { 'SQLAlchemy':
+    provider => pip,
+    ensure => present,
+    require => Package[python-pip]
+  }
+
+  file { '/srv/lodgeit':
+    ensure => directory
+  }
+
+  service { 'drizzle':
+    ensure => running,
+    hasrestart => true
+  }
+
+
+# if we already have the mercurial repo the pull updates
+
+  exec { "update_lodgeit":
+    command => "hg pull /tmp/lodgeit-main",
+    path => "/bin:/usr/bin",
+    onlyif => "test -d /tmp/lodgeit-main"
+  }
+
+# otherwise get a new clone of it
+
+  exec { "get_lodgeit":
+    command => "hg clone http://dev.pocoo.org/hg/lodgeit-main /tmp/lodgeit-main",
+    path => "/bin:/usr/bin",
+    onlyif => "test ! -d /tmp/lodgeit-main"
+  }
+
+  service { 'nginx':
+    ensure => running,
+    hasrestart => true
+  }
+}
diff --git a/modules/lodgeit/manifests/site.pp b/modules/lodgeit/manifests/site.pp
new file mode 100644
index 0000000000..7a4b56b41d
--- /dev/null
+++ b/modules/lodgeit/manifests/site.pp
@@ -0,0 +1,66 @@
+define lodgeit::site($port, $image="") {
+
+  file { "/etc/nginx/sites-available/${name}":
+    ensure => 'present',
+    content => template("lodgeit/nginx.erb"),
+    replace => 'true',
+    require => Package[nginx]
+  }
+
+  file { "/etc/nginx/sites-enabled/${name}":
+    ensure => link,
+    target => "/etc/nginx/sites-available/${name}",
+    require => Package[nginx]
+  }
+
+  file { "/etc/init/${name}-paste.conf":
+    ensure => 'present',
+    content => template("lodgeit/upstart.erb"),
+    replace => 'true',
+    require => Package[nginx]
+  }
+
+  file { "/srv/lodgeit/${name}":
+    ensure => directory,
+    recurse => true,
+    source => "/tmp/lodgeit-main"
+  }
+
+  if $image != '' {
+    file { "/srv/lodgeit/${name}/lodgeit/static/${image}":
+      ensure => present,
+      source => "puppet:///lodgeit/${image}"
+    }
+  }
+
+# Database file needs replacing to be compatible with SQLAlchemy 0.7
+
+  file { "/srv/lodgeit/${name}/lodgeit/database.py":
+    replace => true,
+    source => 'puppet:///modules/lodgeit/database.py'
+  }
+
+  file { "/srv/lodgeit/${name}/manage.py":
+    mode => 755,
+    replace => true,
+    content => template("lodgeit/manage.py.erb")
+  }
+
+  file { "/srv/lodgeit/${name}/lodgeit/views/layout.html":
+    replace => true,
+    content => template("lodgeit/layout.html.erb")
+  }
+
+  exec { "create_database_${name}":
+    command => "drizzle --user=root -e \"create database if not exists ${name};\"",
+    path => "/bin:/usr/bin",
+    require => Service["drizzle"]
+  }
+
+  service { "${name}-paste":
+    provider => upstart,
+    ensure => running,
+    require => [Service["drizzle", "nginx"], Exec["create_database_${name}"]]
+  }
+
+}
diff --git a/modules/lodgeit/templates/layout.html.erb b/modules/lodgeit/templates/layout.html.erb
new file mode 100644
index 0000000000..0838fb948c
--- /dev/null
+++ b/modules/lodgeit/templates/layout.html.erb
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+  "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+  <head>
+    <title>{{ page_title|e }} | LodgeIt!</title>
+    <link rel="stylesheet" href="/static/style.css" type="text/css">
+    <link rel="stylesheet" href="/static/print.css" type="text/css" media="print">
+    <script type="text/javascript" src="/static/jquery.js"></script>
+    <script type="text/javascript" src="/static/cookie.js"></script>
+    <script type="text/javascript" src="/static/lodgeit.js"></script>
+    {%- if css %}
+    <style type="text/css">
+      {{ css|e }}
+    </style>
+    {%- endif %}
+  </head>
+  <body>
+    <div class="page">
+      <div id="header">
+        <h1>
+          <% if has_variable?("image") then %>
+          <img src="/static/<%= image %>" style="padding-bottom: 10px; margin-left: 10px;" alt="<%= name.capitalize %> Pastebin" />
+          <% else %>
+          <%= name.capitalize %> Pastebin
+          <% end %>
+        </h1></div>
+      <ul id="navigation">
+      {%- for href, id, caption in [
+        ('/', 'new', _('New')),
+        ('/all/', 'all', _('All')),
+        ('/about/', 'about', _('About')),
+        ('/help/', 'help', '?')
+      ] %}
+        <li{% if active_page == id %} class="active"{% endif %}>
+          <a href="{{ href|e }}">{{ caption|e }}</a>
+        </li>
+      {%- endfor %}
+      </ul>
+      {# <ul id="languages">
+      {% for lang, name in i18n_languages %}
+        <li{% if request.locale.language == lang %} class="active"{% endif %}>
+          <a href="{{ '/language/%s'|format(lang) }}"><img alt="{{ lang }}" src="{{ '/static/flags/%s.png'|format(lang) }}"></a>
+        </li>
+      {% endfor %}
+      </ul> #}
+      <div class="content">
+        <h2>{{ page_title|e }}</h2>
+        {%- if new_replies %}
+          <div class="notification">
+            <h3>{% trans %}Someone Replied To Your Paste{% endtrans %}</h3>
+            {% for paste in new_replies %}
+            <p>{% trans date=paste.pub_date|datetimeformat, parent=paste.parent.paste_id,
+                        paste=paste.paste_id, paste_url=paste.url|e, parent_url=paste.parent.url|e %}
+              on {{ date }} someone replied to your paste 
+              <a href="{{ parent_url }}">#{{ parent }}</a>,
+              in paste <a href="{{ paste_url }}">#{{ paste }}</a>. Click here to
+              <a href="/compare/{{ paste }}/{{ parent }}/">compare
+              those two pastes</a>.{% endtrans %}
+            </p>
+            {% endfor %}
+            <p><a href="javascript:LodgeIt.hideNotification()">{% trans %}hide this notification{% endtrans %}</a></p>
+          </div>
+        {% elif request.first_visit %}
+          <div class="notification">
+            <h3>{% trans %}Welcome On LodgeIt{% endtrans %}</h3>
+            <p>{%- trans -%}
+              Welcome to the LodgeIt pastebin. In order to use the notification feature
+              a 31 day cookie with an unique ID was created for you. The lodgeit database
+              does not store any information about you, it's just used for an advanced
+              pastebin experience :-). Read more on the <a href="/about/#privacy">about
+              lodgeit</a> page. Have fun :-){%- endtrans -%}
+            </p>
+            <p><a href="javascript:LodgeIt.hideNotification()">{% trans %}hide this notification{% endtrans %}</a></p>
+          </div>
+        {% endif -%}
+        {% block body %}{% endblock -%}
+        <div class="footer"></div>
+      </div>
+    </div>
+    <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-27485426-1']);
+      _gaq.push(['_trackPageview']);
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+  </body>
+</html>
diff --git a/modules/lodgeit/templates/manage.py.erb b/modules/lodgeit/templates/manage.py.erb
new file mode 100644
index 0000000000..a17a318cd1
--- /dev/null
+++ b/modules/lodgeit/templates/manage.py.erb
@@ -0,0 +1,38 @@
+import os
+
+from werkzeug import script, create_environ, run_wsgi_app
+from werkzeug.serving import run_simple
+
+from lodgeit import local
+from lodgeit.application import make_app
+from lodgeit.database import session
+
+#dburi = 'sqlite:////tmp/lodgeit.db'
+dburi = 'drizzle://127.0.0.1:4427/<%= name %>'
+
+SECRET_KEY = 'no secret key'
+
+
+def run_app(app, path='/'):
+    env = create_environ(path, SECRET_KEY)
+    return run_wsgi_app(app, env)
+
+action_runserver = script.make_runserver(
+    lambda: make_app(dburi, SECRET_KEY),
+    use_reloader=True)
+
+action_shell = script.make_shell(
+    lambda: {
+        'app': make_app(dburi, SECRET_KEY, False, True),
+        'local': local,
+        'session': session,
+        'run_app': run_app
+    },
+    ('\nWelcome to the interactive shell environment of LodgeIt!\n'
+     '\n'
+     'You can use the following predefined objects: app, local, session.\n'
+     'To run the application (creates a request) use *run_app*.')
+)
+
+if __name__ == '__main__':
+    script.run()
diff --git a/modules/lodgeit/templates/nginx.erb b/modules/lodgeit/templates/nginx.erb
new file mode 100644
index 0000000000..13223fd032
--- /dev/null
+++ b/modules/lodgeit/templates/nginx.erb
@@ -0,0 +1,10 @@
+server {
+  listen 80;
+  server_name paste.<%= name %>.org;
+  root /srv/lodgeit/<%= name %>;
+
+  location / {
+    proxy_pass http://localhost:<%= port %>/;
+  }
+}
+
diff --git a/modules/lodgeit/templates/upstart.erb b/modules/lodgeit/templates/upstart.erb
new file mode 100644
index 0000000000..4b2cb47956
--- /dev/null
+++ b/modules/lodgeit/templates/upstart.erb
@@ -0,0 +1,8 @@
+description "<%= name %> Lodgeit server"
+author      "Andrew Hutchings <andrew@linuxjedi.co.uk>"
+
+start on (local-filesystems and net-device-up)
+stop on runlevel [!2345]
+
+exec python /srv/lodgeit/<%= name %>/manage.py runserver -h 127.0.0.1 -p <%= port %>
+