1
0
Fork 0

add PasteDeploy dependency, remove pudge dependency

* upgrade migrate repository structure (fixes #32, #27)
 * switch to PasteDeploy (fixes #31)
 * update for SQLAlchemy 0.5 compatibility
 * add python-gnutls dependency (addresses #35)
This commit is contained in:
Jan Dittberner 2009-07-19 01:03:23 +02:00
parent 483c1f9038
commit 222b35b033
24 changed files with 247 additions and 177 deletions

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -25,7 +25,6 @@ import logging
import tempfile
from gnuviechadmin.exceptions import MissingFieldsError
from gnuviechadmin.backend.settings import config
from gnuviechadmin.util import gpgmail
from subprocess import Popen, PIPE
from sqlalchemy.orm import object_mapper
@ -34,9 +33,10 @@ from sqlalchemy.orm import object_mapper
class BackendEntity(object):
"""This is the abstract base class for all backend entity classes."""
def __init__(self, delegateto, verbose = False):
def __init__(self, config, delegateto, verbose = False):
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
self.config = config
self.delegateto = delegateto
self.verbose = verbose
@ -49,7 +49,7 @@ class BackendEntity(object):
subprocess."""
self.logger.debug("sucommand called: %s (pipedata=%s)", cmdline,
str(pipedata))
suwrapper = config.get('common', 'suwrapper')
suwrapper = self.config['suwrapper']
toexec = "%s %s" % (suwrapper, cmdline)
if pipedata:
pipeproc = Popen(toexec, shell = True, stdin=PIPE)
@ -72,7 +72,7 @@ class BackendEntity(object):
"""Executes multiple commands as root and pipes the output of
the commands to the input of the next commands."""
self.logger.debug("supipe called: %s", " | ".join(cmdlines))
suwrapper = config.get('common', 'suwrapper')
suwrapper = self.config['suwrapper']
predecessor = None
for cmdline in cmdlines:
toexec = "%s %s" % (suwrapper, cmdline)
@ -89,7 +89,7 @@ class BackendEntity(object):
"""This method sends a mail with the given text and subject
and signs it usign GnuPG. If a public key of the recipient is
available the mail is encrypted."""
gpgmail.send_mail(subject, text)
gpgmail.send_mail(self.config, subject, text)
def validate(self):
"""Validates whether all mandatory fields of the entity have

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -21,38 +21,41 @@
"""This module defines the BackendEntityHandler class."""
import logging
from sqlalchemy.orm import create_session
from sqlalchemy.orm import create_session, mapper, relation
from gnuviechadmin.backend.tables import dbsetup
class BackendEntityHandler(object):
"""This class is a handler for BackendEntity instances."""
def __init__(self, entityclass, toclass, verbose = False):
def __init__(self, entityclass, toclass, config, verbose = False):
"""Initialize the handler with a specific entity class,
transfer object class and verbosity flag."""
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
dbsetup(config)
self.entityclass = entityclass
self.toclass = toclass
self.config = config
self.verbose = verbose
def create(self, **kwargs):
"""Create a new entity of the managed type with the fields set
to the values in kwargs."""
self.logger.debug("create with params %s", str(kwargs))
delegate = self.toclass(self.config, **kwargs)
entity = self.entityclass(self.config, delegate, self.verbose)
sess = create_session()
transaction = sess.create_transaction()
delegate = self.toclass(**kwargs)
entity = self.entityclass(delegate, self.verbose)
try:
sess.save(delegate)
sess.begin()
sess.add(delegate)
sess.flush()
sess.refresh(delegate)
entity.create_hook(sess)
sess.flush()
transaction.commit()
sess.commit()
except:
transaction.rollback()
sess.rollback()
self.logger.exception("Exception in create.")
raise
@ -65,7 +68,7 @@ class BackendEntityHandler(object):
allentities = query.filter_by(**kwargs).all()
else:
allentities = query.all()
return [self.entityclass(entity, self.verbose) \
return [self.entityclass(self.config, entity, self.verbose) \
for entity in allentities]
def delete(self, pkvalue):
@ -73,19 +76,18 @@ class BackendEntityHandler(object):
specified primary key value."""
self.logger.debug("delete with primary key %s", str(pkvalue))
sess = create_session()
transaction = sess.create_transaction()
try:
sess.begin()
tobj = sess.query(self.toclass).get(pkvalue)
if tobj:
entity = self.entityclass(tobj, self.verbose)
entity = self.entityclass(self.config, tobj, self.verbose)
self.logger.info("delete %s", str(entity))
if self.verbose:
print "delete %s" % (str(entity))
entity.delete_hook(sess)
sess.delete(tobj)
sess.flush()
transaction.commit()
sess.commit()
except Exception:
transaction.rollback()
sess.rollback()
self.logger.exception("Exception in delete.")
raise

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,16 +19,15 @@
#
# Version: $Id$
from sqlalchemy.orm import object_mapper, mapper, relation
from tables import *
from sqlalchemy.orm import object_mapper
class BackendTo(object):
"""Backend transfer object class."""
def __init__(self, **kwargs):
def __init__(self, config, **kwargs):
for (key, value) in kwargs.items():
self.__setattr__(key, value)
self.__setattr__(key, unicode(value, 'utf8'))
def __repr__(self, **kwargs):
if 'verbose' in kwargs and kwargs['verbose']:
@ -64,12 +63,3 @@ class Domain(BackendTo):
class Record(BackendTo):
"""Transfer object class for DNS domain records."""
_shortkeys = ("recordid", "domainid", "name", "type", "content")
client_mapper = mapper(Client, client_table, {
'sysusers': relation(Sysuser, backref = 'client')})
sysuser_mapper = mapper(Sysuser, sysuser_table, {
'domains': relation(Domain, backref = 'sysuser')})
domain_mapper = mapper(Domain, domain_table, {
'records': relation(Record, cascade = 'all', backref = 'domain')})
record_mapper = mapper(Record, record_table)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,8 +20,7 @@
# Version: $Id$
"""This module defines the ClientEntity class."""
from gnuviechadmin.backend.settings import config, get_template, \
get_template_string
from gnuviechadmin.backend.settings import get_template, get_template_string
from gnuviechadmin.exceptions import CannotDeleteError
from gnuviechadmin.backend.BackendTo import Client
from gnuviechadmin.backend.BackendEntity import BackendEntity
@ -31,9 +30,9 @@ from gnuviechadmin.backend.BackendEntityHandler import BackendEntityHandler
class ClientEntity(BackendEntity):
"""Entity class for clients."""
def __init__(self, delegate, verbose = False, **kwargs):
def __init__(self, config, delegate, verbose = False, **kwargs):
"""Initializes the client entity instance."""
BackendEntity.__init__(self, delegate, verbose)
BackendEntity.__init__(self, config, delegate, verbose)
for (key, value) in kwargs.items():
self.__setattr__(key, value)
if not self.delegateto.country:
@ -42,8 +41,8 @@ class ClientEntity(BackendEntity):
def _client_mail(self):
"""Mails a summary about the creation of the client."""
text = get_template(config.get('common', 'mailtemplates'),
config.get('client', 'create.mail')).substitute({
text = get_template(self.config['mailtemplates'],
self.config['client.create.mail']).substitute({
'firstname': self.delegateto.firstname,
'lastname': self.delegateto.lastname,
'email': self.delegateto.email,
@ -52,11 +51,11 @@ class ClientEntity(BackendEntity):
'city': self.delegateto.city,
'phone': self.delegateto.phone})
subject = get_template_string(
config.get('client', 'create_subject')).substitute({
self.config['client.create_subject']).substitute({
'firstname': self.delegateto.firstname,
'lastname': self.delegateto.lastname})
self.send_mail(subject, text)
def create_hook(self, session):
"""Actions to perform when a client is created."""
self._client_mail()
@ -72,11 +71,12 @@ class ClientEntity(BackendEntity):
def _get_default_country(self):
"""Gets the default country."""
return config.get('common', 'defaultcountry')
return self.config['client.defaultcountry']
class ClientHandler(BackendEntityHandler):
"""BackendEntityHandler for Client entities."""
def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, ClientEntity, Client, verbose)
def __init__(self, config, verbose = False):
BackendEntityHandler.__init__(self, ClientEntity, Client, config,
verbose)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -25,32 +25,13 @@ This module handles all central configuration of Gnuviech Admin. It
parses configuration files and provides functions for reading
templates."""
import ConfigParser
import os
from string import Template
# global settings which must not be user configurable
required_version = 3
# load user configuration
config = ConfigParser.ConfigParser()
config.readfp(open('gnuviechadmin/defaults.cfg'))
config.read(['gnuviechadmin/gva.cfg', os.path.expanduser('~/.gva.cfg')])
dbschema = None
if config.get('database', 'uri').startswith('postgres://'):
dbschema = 'gva'
def get_template_dir(dirname):
"""Returns the template directory for the given directory."""
templatepath = config.get('common', 'templatedir')
return os.path.join(templatepath, dirname)
def get_template(dirname, filename):
def get_template(templatedir, filename):
"""Returns the template data from the given template file."""
templatefile = file(os.path.join(get_template_dir(dirname),
templatefile = file(os.path.join(templatedir,
filename))
templatedata = templatefile.read()
return Template(templatedata.decode('utf_8'))

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,57 +19,69 @@
#
# Version: $Id$
from sqlalchemy import *
from sqlalchemy import MetaData, Table
from sqlalchemy.orm import mapper, relation
from sqlalchemy.exceptions import NoSuchTableError
import sys
import migrate.versioning.api
from settings import *
import logging
from gnuviechadmin.backend.BackendTo import Client, Sysuser, Domain, Record
try:
dbversion = migrate.versioning.api.db_version(
config.get('database', 'uri'),
config.get('database', 'repository'))
if dbversion < required_version:
print("""Database version is %d but required version is %d. Trying
automatic upgrade.""" %
(dbversion, required_version))
def dbsetup(config):
logger = logging.getLogger(__name__)
required_version = int(config['migrate.required_version'])
try:
dbversion = migrate.versioning.api.db_version(
config['sqlalchemy.uri'], config['database.repository'])
if dbversion < required_version:
logger.info("""Database version is %d but required version \
is %d. Trying automatic upgrade.""" % (dbversion, required_version))
try:
migrate.versioning.api.upgrade(
config['sqlalchemy.uri'], config['database.repository'],
required_version)
except e:
logger.error("Automatic upgrade failed.", e)
raise
elif dbversion > required_version:
logger.error("""Database version is %d which is higher than \
the required version %d. I cannot handle this situation without possible \
data loss.""" % (dbversion, required_version))
sys.exit(1)
except NoSuchTableError, nste:
logger.info("""The database is not versioned. \
Trying automatic versioning.""")
try:
migrate.versioning.api.version_control(
config['sqlalchemy.uri'], config['database.repository'])
migrate.versioning.api.upgrade(
config.get('database', 'uri'),
config.get('database', 'repository'),
config['sqlalchemy.uri'], config['database.repository'],
required_version)
except:
print "Automatic upgrade failed."
logger.error("Automatic setup failed.")
raise
elif dbversion > required_version:
print("""Database version is %d which is higher than the required
version %d. I cannot handle this situation without possible data loss.""" %
(dbversion, required_version))
sys.exit(1)
except NoSuchTableError, nste:
print """The database is not versioned. Trying automatic versioning."""
try:
migrate.versioning.api.version_control(
config.get('database', 'uri'),
config.get('database', 'repository'))
migrate.versioning.api.upgrade(
config.get('database', 'uri'),
config.get('database', 'repository', required_version))
except:
print "Automatic setup failed."
raise
meta = MetaData(config.get('database', 'uri'))
#meta.engine.echo = True
client_table = Table('client', meta, schema = dbschema, autoload = True)
sysuser_table = Table('sysuser', meta, schema = dbschema, autoload = True)
domain_table = Table('domain', meta, schema = dbschema, autoload = True)
record_table = Table('record', meta, schema = dbschema, autoload = True)
supermaster_table = Table('supermaster', meta, schema = dbschema,
autoload = True)
mailaccount_table = Table('mailaccount', meta, schema = dbschema,
autoload = True)
mailaddress_table = Table('mailaddress', meta, schema = dbschema,
autoload = True)
mailtarget_table = Table('mailtarget', meta, schema = dbschema,
autoload = True)
meta = MetaData(config['sqlalchemy.uri'])
meta.bind.engine.echo = config['sqlalchemy.echo']
dbschema = None
if 'database.schema' in config:
dbschema = config['database.schema']
(client_table, sysuser_table, domain_table, \
record_table, supermaster_table, mailaccount_table, \
mailaddress_table, mailtarget_table) = \
[Table(tabname, meta, schema = dbschema,
autoload = True) for tabname in \
('client', 'sysuser', 'domain', 'record',
'supermaster', 'mailaccount', 'mailaddress', 'mailtarget')]
client_mapper = mapper(Client, client_table, {
'sysusers': relation(Sysuser, backref = 'client')})
sysuser_mapper = mapper(Sysuser, sysuser_table, {
'domains': relation(Domain, backref = 'sysuser')})
domain_mapper = mapper(Domain, domain_table, {
'records': relation(Record, cascade = 'all',
backref = 'domain')})
record_mapper = mapper(Record, record_table)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -197,9 +197,10 @@ Common options:
sys.exit()
self._handleoption(subcommand, o, a)
def __init__(self, args):
def __init__(self, args, config):
"""This initializes the command with the given command line
arguments and executes it."""
self.config = config
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
self._data = {}

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -24,3 +24,39 @@ This package provides modules for the command line interface of the
gnuviechadmin server administration suite."""
__all__ = ["client", "sysuser", "domain", "record"]
from logging import getLogger
from sys import exit
class CommandLineInterface(object):
def __init__(self, config, args):
self.log = getLogger(__name__)
self.config = config
if len(args) < 2:
self._usage(args[0])
exit(1)
self.commands = [command for command in self._get_commands() \
if command.name == args[1]]
self.cmdargs = args[2:]
def _usage(self, callee):
print """%s <command> [commandargs]
where command is one of
""" % callee
for command in self._get_commands():
print "%10s - %s" % (command.name, command.description)
def run(self):
for cmd in self.commands:
cmd(self.cmdargs, self.config)
def _get_commands(self):
from gnuviechadmin.cli.client import ClientCli
from gnuviechadmin.cli.sysuser import SysuserCli
from gnuviechadmin.cli.domain import DomainCli
from gnuviechadmin.cli.record import RecordCli
return [ClientCli, SysuserCli, DomainCli, RecordCli]

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -61,24 +61,25 @@ class ClientCli(CliCommand.CliCommand):
def _execute(self, subcommand):
self.logger.debug("execute %s with data %s", subcommand,
str(self._data))
from gnuviechadmin.backend import client
from gnuviechadmin import exceptions
from gnuviechadmin.backend.client import ClientHandler
from gnuviechadmin.exceptions import CreationFailedError
if subcommand == "create":
try:
myclient = client.ClientHandler(self._verbose).create(
**self._data)
myclient = ClientHandler(self.config,
self._verbose).create(**self._data)
if self._verbose:
print myclient
except exceptions.CreationFailedError, cfe:
except CreationFailedError, cfe:
self._usage()
print cfe
sys.exit(2)
elif subcommand == "list":
clients = client.ClientHandler(self._verbose).fetchall()
clients = ClientHandler(self.config, self._verbose).fetchall()
for client in clients:
print client
elif subcommand == "delete":
client.ClientHandler(self._verbose).delete(self._data["clientid"])
ClientHandler(self.config,
self._verbose).delete(self._data["clientid"])
def __init__(self, argv):
CliCommand.CliCommand.__init__(self, argv)
def __init__(self, argv, config):
CliCommand.CliCommand.__init__(self, argv, config)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -30,10 +30,8 @@ from email.MIMEText import MIMEText
from pyme import core
from pyme.constants.sig import mode
from gnuviechadmin.backend.settings import config
def send_mail(subject, text):
def send_mail(config, subject, text):
"""Send a signed and possibly encrypted mail.
This method sends a mail with the given text and subject and signs
@ -52,8 +50,8 @@ def send_mail(subject, text):
cipher = core.Data()
c = core.Context()
c.set_armor(1)
signer = config.get('common', 'mailfrom')
rcpt = config.get('common', 'mailto')
signer = config['mailfrom']
rcpt = config['mailto']
c.signers_clear()
for sigkey in [x for x in c.op_keylist_all(signer, 1)]:
if sigkey.can_sign:
@ -64,14 +62,12 @@ def send_mail(subject, text):
keylist = []
for key in c.op_keylist_all(rcpt, 0):
valid = 0
subkey = key.subkeys
while subkey:
for subkey in key.subkeys:
keyid = subkey.keyid
if keyid == None:
break
can_encrypt = subkey.can_encrypt
valid += can_encrypt
subkey = subkey.next
if valid:
keylist.append(key)
if keylist: