Actually *add* the database schema management utilities
authorMatthew Jordan <mjordan@digium.com>
Thu, 29 Aug 2013 12:30:07 +0000 (12:30 +0000)
committerMatthew Jordan <mjordan@digium.com>
Thu, 29 Aug 2013 12:30:07 +0000 (12:30 +0000)
In r397874, the scripts were removed... but not replaced. Thanks to
Michael Young for noticing this!
........

Merged revisions 397911 from http://svn.asterisk.org/svn/asterisk/branches/12

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@397912 65c4cc65-6c06-0410-ace0-fbb531ad65f3

contrib/ast-db-manage/README.md [new file with mode: 0644]
contrib/ast-db-manage/config.ini.sample [new file with mode: 0644]
contrib/ast-db-manage/config/env.py [new file with mode: 0644]
contrib/ast-db-manage/config/script.py.mako [new file with mode: 0644]
contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py [new file with mode: 0644]
contrib/ast-db-manage/voicemail.ini.sample [new file with mode: 0644]
contrib/ast-db-manage/voicemail/env.py [new file with mode: 0644]
contrib/ast-db-manage/voicemail/script.py.mako [new file with mode: 0644]
contrib/ast-db-manage/voicemail/versions/a2e9769475e_create_tables.py [new file with mode: 0644]

diff --git a/contrib/ast-db-manage/README.md b/contrib/ast-db-manage/README.md
new file mode 100644 (file)
index 0000000..3444dd5
--- /dev/null
@@ -0,0 +1,63 @@
+Asterisk Database Manager
+=========================
+
+Asterisk includes optional database integration for a variety of features.
+The purpose of this effort is to assist in managing the database schema
+for Asterisk database integration.
+
+This is implemented as a set of repositories that contain database schema
+migrations, using [Alembic](http://alembic.readthedocs.org).  The existing
+repositories include:
+
+ * `config` - Tables used for Asterisk realtime configuration
+ * `voicemail` - Tables used for `ODBC_STOARGE` of voicemail messages
+
+Alembic uses SQLAlchemy, which has support for
+[many databases](http://docs.sqlalchemy.org/en/rel_0_8/dialects/index.html).
+
+IMPORTANT NOTE: This is brand new and the initial migrations are still subject
+to change.  Only use this for testing purposes for now.
+
+Example Usage
+-------------
+
+First, create an ini file that contains database connection details.  For help
+with connection string details, see the
+[SQLAlchemy docs](http://docs.sqlalchemy.org/en/rel_0_8/core/engines.html#database-urls).
+
+    $ cp config.ini.sample config.ini
+    ... edit config.ini and change sqlalchemy.url ...
+
+Next, bring the database up to date with the current schema.
+
+    $ alembic -c config.ini upgrade head
+
+In the future, as additional database migrations are added, you can run
+alembic again to migrate the existing tables to the latest schema.
+
+    $ alembic -c config.ini upgrade head
+
+The migrations support both upgrading and downgrading.  You could go all the
+way back to where you started with no tables by downgrading back to the base
+revision.
+
+    $ alembic -c config.ini downgrade base
+
+`base` and `head` are special revisions.  You can refer to specific revisions
+to upgrade or downgrade to, as well.
+
+    $ alembic -c config.ini upgrade 4da0c5f79a9c
+
+Offline Mode
+------------
+
+If you would like to just generate the SQL statements that would have been
+executed, you can use alembic's offline mode.
+
+    $ alembic -c config.ini upgrade head --sql
+
+Adding Database Migrations
+--------------------------
+
+The best way to learn about how to add additional database migrations is to
+refer to the [Alembic documentation](http://alembic.readthedocs.org).
diff --git a/contrib/ast-db-manage/config.ini.sample b/contrib/ast-db-manage/config.ini.sample
new file mode 100644 (file)
index 0000000..d6cfacd
--- /dev/null
@@ -0,0 +1,48 @@
+[alembic]
+# path to migration scripts
+script_location = config
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+sqlalchemy.url = mysql://root:password@localhost/asterisk
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/contrib/ast-db-manage/config/env.py b/contrib/ast-db-manage/config/env.py
new file mode 100644 (file)
index 0000000..f72400b
--- /dev/null
@@ -0,0 +1,71 @@
+from __future__ import with_statement
+from alembic import context
+from sqlalchemy import engine_from_config, pool
+from logging.config import fileConfig
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = None
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+def run_migrations_offline():
+    """Run migrations in 'offline' mode.
+
+    This configures the context with just a URL
+    and not an Engine, though an Engine is acceptable
+    here as well.  By skipping the Engine creation
+    we don't even need a DBAPI to be available.
+
+    Calls to context.execute() here emit the given string to the
+    script output.
+
+    """
+    url = config.get_main_option("sqlalchemy.url")
+    context.configure(url=url)
+
+    with context.begin_transaction():
+        context.run_migrations()
+
+def run_migrations_online():
+    """Run migrations in 'online' mode.
+
+    In this scenario we need to create an Engine
+    and associate a connection with the context.
+
+    """
+    engine = engine_from_config(
+                config.get_section(config.config_ini_section),
+                prefix='sqlalchemy.',
+                poolclass=pool.NullPool)
+
+    connection = engine.connect()
+    context.configure(
+                connection=connection,
+                target_metadata=target_metadata
+                )
+
+    try:
+        with context.begin_transaction():
+            context.run_migrations()
+    finally:
+        connection.close()
+
+if context.is_offline_mode():
+    run_migrations_offline()
+else:
+    run_migrations_online()
+
diff --git a/contrib/ast-db-manage/config/script.py.mako b/contrib/ast-db-manage/config/script.py.mako
new file mode 100644 (file)
index 0000000..9570201
--- /dev/null
@@ -0,0 +1,22 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision}
+Create Date: ${create_date}
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+def upgrade():
+    ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+    ${downgrades if downgrades else "pass"}
diff --git a/contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py b/contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py
new file mode 100644 (file)
index 0000000..f6bed95
--- /dev/null
@@ -0,0 +1,292 @@
+#
+# Asterisk -- An open source telephony toolkit.
+#
+# Copyright (C) 2013, Russell Bryant
+#
+# Russell Bryant <russell@rusellbryant.net>
+#
+# See http://www.asterisk.org for more information about
+# the Asterisk project. Please do not directly contact
+# any of the maintainers of this project for assistance;
+# the project provides a web site, mailing lists and IRC
+# channels for your use.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License Version 2. See the LICENSE file
+# at the top of the source tree.
+#
+
+"""Create tables
+
+Revision ID: 4da0c5f79a9c
+Revises: None
+Create Date: 2013-07-28 12:28:03.091587
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '4da0c5f79a9c'
+down_revision = None
+
+from alembic import op
+import sqlalchemy as sa
+
+
+YESNO_VALUES = ['yes', 'no']
+TYPE_VALUES = ['friend', 'user', 'peer']
+
+SIP_TRANSPORT_VALUES = ['udp', 'tcp', 'tls', 'ws', 'wss', 'udp,tcp', 'tcp,udp']
+SIP_DTMFMODE_VALUES = ['rfc2833', 'info', 'shortinfo', 'inband', 'auto']
+SIP_DIRECTMEDIA_VALUES = ['yes', 'no', 'nonat', 'update']
+SIP_PROGRESSINBAND_VALUES = ['yes', 'no', 'never']
+SIP_SESSION_TIMERS_VALUES = ['accept', 'refuse', 'originate']
+SIP_SESSION_REFRESHER_VALUES = ['uac', 'uas']
+SIP_CALLINGPRES_VALUES = ['allowed_not_screened', 'allowed_passed_screen',
+                          'allowed_failed_screen', 'allowed',
+                          'prohib_not_screened', 'prohib_passed_screen',
+                          'prohib_failed_screen', 'prohib']
+
+IAX_REQUIRECALLTOKEN_VALUES = ['yes', 'no', 'auto']
+IAX_ENCRYPTION_VALUES = ['yes', 'no', 'aes128']
+IAX_TRANSFER_VALUES = ['yes', 'no', 'mediaonly']
+
+MOH_MODE_VALUES = ['custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3']
+
+
+def upgrade():
+    op.create_table(
+        'sippeers',
+        sa.Column('id', sa.Integer, primary_key=True, nullable=False,
+                  autoincrement=True),
+        sa.Column('name', sa.String(40), nullable=False, unique=True),
+        sa.Column('ipaddr', sa.String(45)),
+        sa.Column('port', sa.Integer),
+        sa.Column('regseconds', sa.Integer),
+        sa.Column('defaultuser', sa.String(40)),
+        sa.Column('fullcontact', sa.String(80)),
+        sa.Column('regserver', sa.String(20)),
+        sa.Column('useragent', sa.String(20)),
+        sa.Column('lastms', sa.Integer),
+        sa.Column('host', sa.String(40)),
+        sa.Column('type', sa.Enum(*TYPE_VALUES)),
+        sa.Column('context', sa.String(40)),
+        sa.Column('permit', sa.String(95)),
+        sa.Column('deny', sa.String(95)),
+        sa.Column('secret', sa.String(40)),
+        sa.Column('md5secret', sa.String(40)),
+        sa.Column('remotesecret', sa.String(40)),
+        sa.Column('transport', sa.Enum(*SIP_TRANSPORT_VALUES)),
+        sa.Column('dtmfmode', sa.Enum(*SIP_DTMFMODE_VALUES)),
+        sa.Column('directmedia', sa.Enum(*SIP_DIRECTMEDIA_VALUES)),
+        sa.Column('nat', sa.String(29)),
+        sa.Column('callgroup', sa.String(40)),
+        sa.Column('pickupgroup', sa.String(40)),
+        sa.Column('language', sa.String(40)),
+        sa.Column('disallow', sa.String(200)),
+        sa.Column('allow', sa.String(200)),
+        sa.Column('insecure', sa.String(40)),
+        sa.Column('trustrpid', sa.Enum(*YESNO_VALUES)),
+        sa.Column('progressinband', sa.Enum(*SIP_PROGRESSINBAND_VALUES)),
+        sa.Column('promiscredir', sa.Enum(*YESNO_VALUES)),
+        sa.Column('useclientcode', sa.Enum(*YESNO_VALUES)),
+        sa.Column('accountcode', sa.String(40)),
+        sa.Column('setvar', sa.String(200)),
+        sa.Column('callerid', sa.String(40)),
+        sa.Column('amaflags', sa.String(40)),
+        sa.Column('callcounter', sa.Enum(*YESNO_VALUES)),
+        sa.Column('busylevel', sa.Integer),
+        sa.Column('allowoverlap', sa.Enum(*YESNO_VALUES)),
+        sa.Column('allowsubscribe', sa.Enum(*YESNO_VALUES)),
+        sa.Column('videosupport', sa.Enum(*YESNO_VALUES)),
+        sa.Column('maxcallbitrate', sa.Integer),
+        sa.Column('rfc2833compensate', sa.Enum(*YESNO_VALUES)),
+        sa.Column('mailbox', sa.String(40)),
+        sa.Column('session-timers', sa.Enum(*SIP_SESSION_TIMERS_VALUES)),
+        sa.Column('session-expires', sa.Integer),
+        sa.Column('session-minse', sa.Integer),
+        sa.Column('session-refresher', sa.Enum(*SIP_SESSION_REFRESHER_VALUES)),
+        sa.Column('t38pt_usertpsource', sa.String(40)),
+        sa.Column('regexten', sa.String(40)),
+        sa.Column('fromdomain', sa.String(40)),
+        sa.Column('fromuser', sa.String(40)),
+        sa.Column('qualify', sa.String(40)),
+        sa.Column('defaultip', sa.String(45)),
+        sa.Column('rtptimeout', sa.Integer),
+        sa.Column('rtpholdtimeout', sa.Integer),
+        sa.Column('sendrpid', sa.Enum(*YESNO_VALUES)),
+        sa.Column('outboundproxy', sa.String(40)),
+        sa.Column('callbackextension', sa.String(40)),
+        sa.Column('timert1', sa.Integer),
+        sa.Column('timerb', sa.Integer),
+        sa.Column('qualifyfreq', sa.Integer),
+        sa.Column('constantssrc', sa.Enum(*YESNO_VALUES)),
+        sa.Column('contactpermit', sa.String(95)),
+        sa.Column('contactdeny', sa.String(95)),
+        sa.Column('usereqphone', sa.Enum(*YESNO_VALUES)),
+        sa.Column('textsupport', sa.Enum(*YESNO_VALUES)),
+        sa.Column('faxdetect', sa.Enum(*YESNO_VALUES)),
+        sa.Column('buggymwi', sa.Enum(*YESNO_VALUES)),
+        sa.Column('auth', sa.String(40)),
+        sa.Column('fullname', sa.String(40)),
+        sa.Column('trunkname', sa.String(40)),
+        sa.Column('cid_number', sa.String(40)),
+        sa.Column('callingpres', sa.Enum(*SIP_CALLINGPRES_VALUES)),
+        sa.Column('mohinterpret', sa.String(40)),
+        sa.Column('mohsuggest', sa.String(40)),
+        sa.Column('parkinglot', sa.String(40)),
+        sa.Column('hasvoicemail', sa.Enum(*YESNO_VALUES)),
+        sa.Column('subscribemwi', sa.Enum(*YESNO_VALUES)),
+        sa.Column('vmexten', sa.String(40)),
+        sa.Column('autoframing', sa.Enum(*YESNO_VALUES)),
+        sa.Column('rtpkeepalive', sa.Integer),
+        sa.Column('call-limit', sa.Integer),
+        sa.Column('g726nonstandard', sa.Enum(*YESNO_VALUES)),
+        sa.Column('ignoresdpversion', sa.Enum(*YESNO_VALUES)),
+        sa.Column('allowtransfer', sa.Enum(*YESNO_VALUES)),
+        sa.Column('dynamic', sa.Enum(*YESNO_VALUES)),
+        sa.Column('path', sa.String(256)),
+        sa.Column('supportpath', sa.Enum(*YESNO_VALUES))
+    )
+    op.create_index('sippeers_name', 'sippeers', ['name'])
+    op.create_index('sippeers_name_host', 'sippeers', ['name', 'host'])
+    op.create_index('sippeers_ipaddr_port', 'sippeers', ['ipaddr', 'port'])
+    op.create_index('sippeers_host_port', 'sippeers', ['host', 'port'])
+
+    op.create_table(
+        'iaxfriends',
+        sa.Column('id', sa.Integer, primary_key=True, nullable=False,
+                  autoincrement=True),
+        sa.Column('name', sa.String(40), nullable=False, unique=True),
+        sa.Column('type', sa.Enum(*TYPE_VALUES)),
+        sa.Column('username', sa.String(40)),
+        sa.Column('mailbox', sa.String(40)),
+        sa.Column('secret', sa.String(40)),
+        sa.Column('dbsecret', sa.String(40)),
+        sa.Column('context', sa.String(40)),
+        sa.Column('regcontext', sa.String(40)),
+        sa.Column('host', sa.String(40)),
+        sa.Column('ipaddr', sa.String(40)),
+        sa.Column('port', sa.Integer),
+        sa.Column('defaultip', sa.String(20)),
+        sa.Column('sourceaddress', sa.String(20)),
+        sa.Column('mask', sa.String(20)),
+        sa.Column('regexten', sa.String(40)),
+        sa.Column('regseconds', sa.Integer),
+        sa.Column('accountcode', sa.String(20)),
+        sa.Column('mohinterpret', sa.String(20)),
+        sa.Column('mohsuggest', sa.String(20)),
+        sa.Column('inkeys', sa.String(40)),
+        sa.Column('outkeys', sa.String(40)),
+        sa.Column('language', sa.String(10)),
+        sa.Column('callerid', sa.String(100)),
+        sa.Column('cid_number', sa.String(40)),
+        sa.Column('sendani', sa.Enum(*YESNO_VALUES)),
+        sa.Column('fullname', sa.String(40)),
+        sa.Column('trunk', sa.Enum(*YESNO_VALUES)),
+        sa.Column('auth', sa.String(20)),
+        sa.Column('maxauthreq', sa.Integer),
+        sa.Column('requirecalltoken', sa.Enum(*IAX_REQUIRECALLTOKEN_VALUES)),
+        sa.Column('encryption', sa.Enum(*IAX_ENCRYPTION_VALUES)),
+        sa.Column('transfer', sa.Enum(*IAX_TRANSFER_VALUES)),
+        sa.Column('jitterbuffer', sa.Enum(*YESNO_VALUES)),
+        sa.Column('forcejitterbuffer', sa.Enum(*YESNO_VALUES)),
+        sa.Column('disallow', sa.String(200)),
+        sa.Column('allow', sa.String(200)),
+        sa.Column('codecpriority', sa.String(40)),
+        sa.Column('qualify', sa.String(10)),
+        sa.Column('qualifysmoothing', sa.Enum(*YESNO_VALUES)),
+        sa.Column('qualifyfreqok', sa.String(10)),
+        sa.Column('qualifyfreqnotok', sa.String(10)),
+        sa.Column('timezone', sa.String(20)),
+        sa.Column('adsi', sa.Enum(*YESNO_VALUES)),
+        sa.Column('amaflags', sa.String(20)),
+        sa.Column('setvar', sa.String(200))
+    )
+    op.create_index('iaxfriends_name', 'iaxfriends', ['name'])
+    op.create_index('iaxfriends_name_host', 'iaxfriends', ['name', 'host'])
+    op.create_index('iaxfriends_name_ipaddr_port', 'iaxfriends',
+                    ['name', 'ipaddr', 'port'])
+    op.create_index('iaxfriends_ipaddr_port', 'iaxfriends', ['ipaddr', 'port'])
+    op.create_index('iaxfriends_host_port', 'iaxfriends', ['host', 'port'])
+
+    op.create_table(
+        'voicemail',
+        sa.Column('uniqueid', sa.Integer, primary_key=True, nullable=False,
+                  autoincrement=True),
+        sa.Column('context', sa.String(80), nullable=False),
+        sa.Column('mailbox', sa.String(80), nullable=False),
+        sa.Column('password', sa.String(80), nullable=False),
+        sa.Column('fullname', sa.String(80)),
+        sa.Column('alias', sa.String(80)),
+        sa.Column('email', sa.String(80)),
+        sa.Column('pager', sa.String(80)),
+        sa.Column('attach', sa.Enum(*YESNO_VALUES)),
+        sa.Column('attachfmt', sa.String(10)),
+        sa.Column('serveremail', sa.String(80)),
+        sa.Column('language', sa.String(20)),
+        sa.Column('tz', sa.String(30)),
+        sa.Column('deletevoicemail', sa.Enum(*YESNO_VALUES)),
+        sa.Column('saycid', sa.Enum(*YESNO_VALUES)),
+        sa.Column('sendvoicemail', sa.Enum(*YESNO_VALUES)),
+        sa.Column('review', sa.Enum(*YESNO_VALUES)),
+        sa.Column('tempgreetwarn', sa.Enum(*YESNO_VALUES)),
+        sa.Column('operator', sa.Enum(*YESNO_VALUES)),
+        sa.Column('envelope', sa.Enum(*YESNO_VALUES)),
+        sa.Column('sayduration', sa.Integer),
+        sa.Column('forcename', sa.Enum(*YESNO_VALUES)),
+        sa.Column('forcegreetings', sa.Enum(*YESNO_VALUES)),
+        sa.Column('callback', sa.String(80)),
+        sa.Column('dialout', sa.String(80)),
+        sa.Column('exitcontext', sa.String(80)),
+        sa.Column('maxmsg', sa.Integer),
+        sa.Column('volgain', sa.Numeric(precision=5, scale=2)),
+        sa.Column('imapuser', sa.String(80)),
+        sa.Column('imappassword', sa.String(80)),
+        sa.Column('imapserver', sa.String(80)),
+        sa.Column('imapport', sa.String(8)),
+        sa.Column('imapflags', sa.String(80)),
+        sa.Column('stamp', sa.DateTime())
+    )
+    op.create_index('voicemail_mailbox', 'voicemail', ['mailbox'])
+    op.create_index('voicemail_context', 'voicemail', ['context'])
+    op.create_index('voicemail_mailbox_context', 'voicemail', ['mailbox', 'context'])
+    op.create_index('voicemail_imapuser', 'voicemail', ['imapuser'])
+
+    op.create_table(
+        'meetme',
+        sa.Column('bookid', sa.Integer, primary_key=True, nullable=False,
+                  autoincrement=True),
+        sa.Column('confno', sa.String(80), nullable=False),
+        sa.Column('starttime', sa.DateTime()),
+        sa.Column('endtime', sa.DateTime()),
+        sa.Column('pin', sa.String(20)),
+        sa.Column('adminpin', sa.String(20)),
+        sa.Column('opts', sa.String(20)),
+        sa.Column('adminopts', sa.String(20)),
+        sa.Column('recordingfilename', sa.String(80)),
+        sa.Column('recordingformat', sa.String(10)),
+        sa.Column('maxusers', sa.Integer),
+        sa.Column('members', sa.Integer, nullable=False, default=0)
+    )
+    op.create_index('meetme_confno_starttime_endtime', 'meetme',
+                    ['confno', 'starttime', 'endtime'])
+
+    op.create_table(
+        'musiconhold',
+        sa.Column('name', sa.String(80), primary_key=True, nullable=False),
+        sa.Column('mode', sa.Enum(*MOH_MODE_VALUES)),
+        sa.Column('directory', sa.String(255)),
+        sa.Column('application', sa.String(255)),
+        sa.Column('digit', sa.String(1)),
+        sa.Column('sort', sa.String(10)),
+        sa.Column('format', sa.String(10)),
+        sa.Column('stamp', sa.DateTime())
+    )
+
+
+def downgrade():
+    op.drop_table('sippeers')
+    op.drop_table('iaxfriends')
+    op.drop_table('voicemail')
+    op.drop_table('meetme')
+    op.drop_table('musiconhold')
diff --git a/contrib/ast-db-manage/voicemail.ini.sample b/contrib/ast-db-manage/voicemail.ini.sample
new file mode 100644 (file)
index 0000000..6742d01
--- /dev/null
@@ -0,0 +1,48 @@
+[alembic]
+# path to migration scripts
+script_location = voicemail
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+sqlalchemy.url = mysql://user:pass@localhost/voicemail
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/contrib/ast-db-manage/voicemail/env.py b/contrib/ast-db-manage/voicemail/env.py
new file mode 100644 (file)
index 0000000..f72400b
--- /dev/null
@@ -0,0 +1,71 @@
+from __future__ import with_statement
+from alembic import context
+from sqlalchemy import engine_from_config, pool
+from logging.config import fileConfig
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = None
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+def run_migrations_offline():
+    """Run migrations in 'offline' mode.
+
+    This configures the context with just a URL
+    and not an Engine, though an Engine is acceptable
+    here as well.  By skipping the Engine creation
+    we don't even need a DBAPI to be available.
+
+    Calls to context.execute() here emit the given string to the
+    script output.
+
+    """
+    url = config.get_main_option("sqlalchemy.url")
+    context.configure(url=url)
+
+    with context.begin_transaction():
+        context.run_migrations()
+
+def run_migrations_online():
+    """Run migrations in 'online' mode.
+
+    In this scenario we need to create an Engine
+    and associate a connection with the context.
+
+    """
+    engine = engine_from_config(
+                config.get_section(config.config_ini_section),
+                prefix='sqlalchemy.',
+                poolclass=pool.NullPool)
+
+    connection = engine.connect()
+    context.configure(
+                connection=connection,
+                target_metadata=target_metadata
+                )
+
+    try:
+        with context.begin_transaction():
+            context.run_migrations()
+    finally:
+        connection.close()
+
+if context.is_offline_mode():
+    run_migrations_offline()
+else:
+    run_migrations_online()
+
diff --git a/contrib/ast-db-manage/voicemail/script.py.mako b/contrib/ast-db-manage/voicemail/script.py.mako
new file mode 100644 (file)
index 0000000..9570201
--- /dev/null
@@ -0,0 +1,22 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision}
+Create Date: ${create_date}
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+def upgrade():
+    ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+    ${downgrades if downgrades else "pass"}
diff --git a/contrib/ast-db-manage/voicemail/versions/a2e9769475e_create_tables.py b/contrib/ast-db-manage/voicemail/versions/a2e9769475e_create_tables.py
new file mode 100644 (file)
index 0000000..3dc4d47
--- /dev/null
@@ -0,0 +1,58 @@
+#
+# Asterisk -- An open source telephony toolkit.
+#
+# Copyright (C) 2013, Russell Bryant
+#
+# Russell Bryant <russell@rusellbryant.net>
+#
+# See http://www.asterisk.org for more information about
+# the Asterisk project. Please do not directly contact
+# any of the maintainers of this project for assistance;
+# the project provides a web site, mailing lists and IRC
+# channels for your use.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License Version 2. See the LICENSE file
+# at the top of the source tree.
+#
+
+"""Create tables
+
+Revision ID: a2e9769475e
+Revises: None
+Create Date: 2013-07-29 23:43:09.431668
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'a2e9769475e'
+down_revision = None
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.create_table(
+        'voicemail_messages',
+        sa.Column('dir', sa.String(255), nullable=False),
+        sa.Column('msgnum', sa.Integer, nullable=False),
+        sa.Column('context', sa.String(80)),
+        sa.Column('macrocontext', sa.String(80)),
+        sa.Column('callerid', sa.String(80)),
+        sa.Column('origtime', sa.Integer),
+        sa.Column('duration', sa.Integer),
+        sa.Column('recording', sa.LargeBinary),
+        sa.Column('flag', sa.String(30)),
+        sa.Column('category', sa.String(30)),
+        sa.Column('mailboxuser', sa.String(30)),
+        sa.Column('mailboxcontext', sa.String(30)),
+        sa.Column('msg_id', sa.String(40))
+    )
+    op.create_primary_key('voicemail_messages_dir_msgnum',
+            'voicemail_messages', ['dir', 'msgnum'])
+    op.create_index('voicemail_messages_dir', 'voicemail_messages', ['dir'])
+
+
+def downgrade():
+    op.drop_table('voicemail_messages')