source: traclegosscript/anyrelease/traclegos/db.py

Last change on this file was 7767, checked in by Jeff Hammel, 14 years ago

slightly more verbose handling of the postgres database

  • Property svn:executable set to *
File size: 6.8 KB
Line 
1#!/usr/bin/env python
2"""
3database interfaces for making new trac projects with TracLegos
4"""
5
6# XXX there is heavy overlap between here and repository.py
7# maybe these can be abstracted
8
9import pkg_resources
10import subprocess
11
12from paste.script.templates import var
13
14class DatabaseCreationError(Exception):
15    """marker exception for errors on database creation"""
16
17class DatabaseSetup(object):
18    """interface defining database setup using TracLegos"""
19
20    options = []
21
22    def __init__(self):
23        self.name = self.__class__.__name__
24        if not hasattr(self, 'description'):
25            self.description = self.__doc__ 
26
27    def enabled(self):
28        """
29        is this type of database capable of being created
30        on this computer?
31        """
32        return False
33
34    def config(self):
35        """return template form of the db_string"""
36        # XXX maybe this should just replace db_string?
37        return { 'trac': { 'database': self.db_string() } }
38
39    def db_string(self):
40        """returns the string as needed for trac.ini:
41       
42        [trac]
43        database = sqlite:db/trac.db
44        """
45        # XXX could return more general options a la repository.py
46        raise NotImplementedError
47   
48
49    def setup(self, **vars):
50        """initial creation and setup of the database, if necessary"""
51        return
52
53
54class SQLite(DatabaseSetup):
55    """trac sqlite database"""
56
57    def enabled(self):
58        return True
59
60    def db_string(self):
61        return 'sqlite:db/trac.db'
62
63
64class MySQL(DatabaseSetup):
65    """MySQL database"""
66    prefix = 'trac_' # prefix to prepend database names with
67    options = [ var('database_user', 'name of the database user',
68                    default='trac'),
69                var('database_password', 'user password for the database'),
70                var('database_admin', 'name of database root user (admin)',
71                    default='root'),
72                var('database_admin_password', 'password for the admin user'),
73                var('mysql_port', 'port where MySQL is served',
74                    default='3306')
75                ]
76
77    def enabled(self):
78        try:
79            subprocess.call(['mysql', '--help'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
80        except OSError:
81            return False
82        try:
83            import MySQLdb
84        except ImportError:
85            return False
86        return True
87
88    def db_string(self):
89        return 'mysql://${database_user}:${database_password}@localhost:${mysql_port}/%s${project}' % self.prefix
90
91    def setup(self, **vars):
92        """create and grant priveleges on a MySQL db"""
93
94        vars = vars.copy()
95        vars['database'] = self.prefix + vars['project']
96
97        sql = """create database %(database)s;
98grant all privileges on %(database)s.* to %(database_user)s@localhost identified by '%(database_password)s';
99""" % vars
100
101        command = ['mysql', '-u', vars['database_admin']]
102        password = vars['database_admin_password']
103        if password:
104            command.append('--password=%s' % password)
105
106        process = subprocess.Popen(command, stdin=subprocess.PIPE)
107        process.communicate(input=sql)
108
109
110class PostgreSQL(DatabaseSetup):
111    """PostgreSQL database using psycopg2 bindings"""
112    # see:
113    # * http://trac.edgewall.org/wiki/DatabaseBackend#Postgresql
114    # * http://trac.edgewall.org/wiki/PostgresqlRecipe
115    # This currently only works with UNIX sockets databases
116    # using the trust authentication method for the database_user
117    # and the database_admin enabled in your pg_hba.conf file
118    # see http://www.postgresql.org/docs/8.3/static/auth-pg-hba-conf.html
119
120    prefix = 'trac_' # prefix to prepend database names with
121    options = [ var('database_user', 'name of the database user',
122                    default='trac'),
123                var('database_admin', 'name of database root user (admin)',
124                    default='postgres'),
125#                var('postgres_port', 'port where MySQL is served',
126#                    default='5432')
127                ]
128
129   
130    def enabled(self):
131
132        # ensure the psycopg2 python library is installed
133        try: 
134            import psycopg2
135        except ImportError:
136            return False
137
138        # ensure that the necessary commands are installed
139        for command in ["createuser", "createdb"]:
140            if subprocess.call([command, "--help"], stdout=subprocess.PIPE):
141                return False
142
143        return True
144               
145    def db_string(self):
146        """trac.ini string to connect to the PostgreSQL DB"""
147#        return 'postgres://${database_user}:${database_password}@localhost:${postgres_port}/%s${project}?schema=schemaname' %  self.prefix
148        return 'postgres://${database_user}:@/%s${project}' %  self.prefix
149
150    def setup(self, **vars):
151        """create a postgres db"""
152
153        # commands to create the user and the database
154        createuser = ["createuser", "--username", vars['database_admin'], "--createdb", "--superuser", "--createrole", vars['database_user'],]
155        createdb = ["createdb", "-E", "UTF8", "--username", vars['database_admin'], "--owner", vars['database_user'], self.prefix + vars['project']]
156
157        # create the database user if they don't exist
158        process = subprocess.Popen(['psql', '--username', vars['database_admin'], '--command',
159                                    'SELECT rolname FROM pg_roles;'], stdout=subprocess.PIPE)
160        stdout, sterr = process.communicate()
161        roles = [ i.strip() for i in stdout.split('\n') if i.strip() ]
162        roles = set(roles[2:][:-1])
163        if vars['database_user'] not in roles:
164            retval = subprocess.call(createuser)
165            if retval:
166                raise DatabaseCreationError('PostgreSQL: Error executing command: "%s"' % ' '.join(createuser))
167
168        # create the database
169        process = subprocess.Popen(createdb,
170                                   stdout=subprocess.PIPE,
171                                   stderr=subprocess.PIPE)
172        stdout, stderr = process.communicate()
173        retval = process.wait()
174        if retval:
175            if not stderr.strip().endswith(' already exists'):
176                raise DatabaseCreationError('PostgreSQL: Error executing command: "%s"' % ' '.join(createdb))
177
178
179def available_databases():
180    """return installed and enabled database setup methods"""
181    databases = []
182    for entry_point in pkg_resources.iter_entry_points('traclegos.database'):
183        try:
184            database = entry_point.load()
185        except:
186            continue
187        database = database()
188        if database.enabled():
189            databases.append(database)
190    return dict((database.name, database) for database in databases)
191
192
193if __name__ == '__main__':
194    print 'Available databases:'
195    for name, database in available_databases().items():
196        print '%s: %s' % (name, database.description)
Note: See TracBrowser for help on using the repository browser.