From e3166c91026cf3c6a28859a1455dfcbc4e4023fe Mon Sep 17 00:00:00 2001 From: Andrew Hutchings 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 @@ + + + + {{ page_title|e }} | LodgeIt! + + + + + + {%- if css %} + + {%- endif %} + + +
+ + + {#
    + {% for lang, name in i18n_languages %} + + {{ lang }} + + {% endfor %} +
#} +
+

{{ page_title|e }}

+ {%- if new_replies %} +
+

{% trans %}Someone Replied To Your Paste{% endtrans %}

+ {% for paste in new_replies %} +

{% 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 + #{{ parent }}, + in paste #{{ paste }}. Click here to + compare + those two pastes.{% endtrans %} +

+ {% endfor %} +

{% trans %}hide this notification{% endtrans %}

+
+ {% 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 %> +