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 0000000000..146faec5cf
Binary files /dev/null and b/modules/lodgeit/files/header-bg2.png differ
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 @@
+
+
+
+ {{ page_title|e }} | LodgeIt!
+
+
+
+
+
+ {%- if css %}
+
+ {%- endif %}
+
+
+
+
+
+ {%- for href, id, caption in [
+ ('/', 'new', _('New')),
+ ('/all/', 'all', _('All')),
+ ('/about/', 'about', _('About')),
+ ('/help/', 'help', '?')
+ ] %}
+
+ {{ caption|e }}
+
+ {%- endfor %}
+
+ {#
+ {% for lang, name in i18n_languages %}
+
+
+
+ {% endfor %}
+ #}
+
+
{{ page_title|e }}
+ {%- if new_replies %}
+
+ {% elif request.first_visit %}
+
+
{% trans %}Welcome On LodgeIt{% endtrans %}
+
{%- 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 about
+ lodgeit page. Have fun :-){%- endtrans -%}
+
+
{% trans %}hide this notification{% endtrans %}
+
+ {% endif -%}
+ {% block body %}{% endblock -%}
+
+
+
+
+
+
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 "
+
+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 %>
+