reorganising installer code into a package, adding a new wrapping bloodhound_setup script - towards #809
git-svn-id: https://svn.apache.org/repos/asf/bloodhound/trunk@1621579 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/installer/bhsetup/__init__.py b/installer/bhsetup/__init__.py
new file mode 100644
index 0000000..534df97
--- /dev/null
+++ b/installer/bhsetup/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
diff --git a/installer/bhsetup/bloodhound_setup.py b/installer/bhsetup/bloodhound_setup.py
new file mode 100644
index 0000000..747ff3a
--- /dev/null
+++ b/installer/bhsetup/bloodhound_setup.py
@@ -0,0 +1,478 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Initial configuration for Bloodhound"""
+
+import os
+import pkg_resources
+import shutil
+import sys
+from bhsetup.createdigest import htdigest_create
+from getpass import getpass
+from optparse import OptionParser
+
+try:
+ from trac.admin.console import TracAdmin
+ from trac.config import Configuration
+ from trac.util import translation
+ from trac.util.translation import _, get_negotiated_locale, has_babel
+except ImportError, e:
+ print("Requirements must be installed before running "
+ "bloodhound_setup.py.\n"
+ "You can install them with the following command:\n"
+ " pip install -r requirements.txt\n")
+ sys.exit(1)
+
+try:
+ import psycopg2
+except ImportError:
+ psycopg2 = None
+
+try:
+ import MySQLdb as mysqldb
+except ImportError:
+ mysqldb = None
+
+LANG = os.environ.get('LANG')
+
+MAXBACKUPNUMBER = 64 # Max attempts to create backup file
+
+SUPPORTED_DBTYPES = ('sqlite', 'postgres', 'mysql')
+DEFAULT_DB_USER = 'bloodhound'
+DEFAULT_DB_NAME = 'bloodhound'
+DEFAULT_ADMIN_USER = 'admin'
+
+BH_PROJECT_SITE = 'https://issues.apache.org/bloodhound/'
+BASE_CONFIG = {'components': {'bhtheme.*': 'enabled',
+ 'bhdashboard.*': 'enabled',
+ 'multiproduct.*': 'enabled',
+ 'permredirect.*': 'enabled',
+ 'themeengine.api.*': 'enabled',
+ 'themeengine.web_ui.*': 'enabled',
+ 'bhsearch.*': 'enabled',
+ 'bhrelations.*': 'enabled',
+ 'trac.ticket.web_ui.ticketmodule': 'disabled',
+ 'trac.ticket.report.reportmodule': 'disabled',
+ },
+ 'header_logo': {'src': '',},
+ 'mainnav': {'roadmap': 'disabled',
+ 'search': 'disabled',
+ 'timeline': 'disabled',},
+ 'metanav': {'about': 'disabled',},
+ 'theme': {'theme': 'bloodhound',},
+ 'trac': {'mainnav': ','.join(['dashboard', 'wiki', 'browser',
+ 'tickets', 'newticket', 'timeline',
+ 'roadmap', 'search']),
+ 'environment_factory': '',
+ 'request_factory': '',},
+ 'project': {'footer': ('Get involved with '
+ '<a href="%(site)s">Apache Bloodhound</a>'
+ % {'site': BH_PROJECT_SITE,}),},
+ 'labels': {'application_short': 'Bloodhound',
+ 'application_full': 'Apache Bloodhound',
+ 'footer_left_prefix': '',
+ 'footer_left_postfix': '',
+ 'footer_right': ''},
+ 'bhsearch': {'is_default': 'true', 'enable_redirect': 'true'},
+}
+
+ACCOUNTS_CONFIG = {'account-manager': {'account_changes_notify_addresses' : '',
+ 'authentication_url' : '',
+ 'db_htdigest_realm' : '',
+ 'force_passwd_change' :'true',
+ 'hash_method' : 'HtDigestHashMethod',
+ 'htdigest_file' : '',
+ 'htdigest_realm' : '',
+ 'htpasswd_file' : '',
+ 'htpasswd_hash_type' : 'crypt',
+ 'password_store' : 'HtDigestStore',
+ 'persistent_sessions' : 'False',
+ 'refresh_passwd' : 'False',
+ 'user_lock_max_time' : '0',
+ 'verify_email' : 'True',
+ },
+ 'components': {'acct_mgr.admin.*' : 'enabled',
+ 'acct_mgr.api.accountmanager' : 'enabled',
+ 'acct_mgr.guard.accountguard' : 'enabled',
+ 'acct_mgr.htfile.htdigeststore' : 'enabled',
+ 'acct_mgr.macros.*': 'enabled',
+ 'acct_mgr.web_ui.accountmodule' : 'enabled',
+ 'acct_mgr.web_ui.loginmodule' : 'enabled',
+ 'trac.web.auth.loginmodule' : 'disabled',
+ },
+ }
+
+class BloodhoundSetup(object):
+ """Creates a Bloodhound environment"""
+
+ def __init__(self, opts):
+ if isinstance(opts, dict):
+ options = dict(opts)
+ else:
+ options = vars(opts)
+ self.options = options
+
+ if 'project' not in options:
+ options['project'] = 'main'
+ if 'envsdir' not in options:
+ options['envsdir'] = os.path.join('bloodhound',
+ 'environments')
+
+ # Flags used when running the functional test suite
+ self.apply_bhwiki_upgrades = True
+
+ def _generate_db_str(self, options):
+ """Builds an appropriate db string for trac-admin for sqlite and
+ postgres options. Also allows for a user to provide their own db
+ string to allow database initialisation beyond these."""
+ dbdata = {'type': options.get('dbtype', 'sqlite'),
+ 'user': options.get('dbuser'),
+ 'pass': options.get('dbpass'),
+ 'host': options.get('dbhost', 'localhost'),
+ 'port': options.get('dbport'),
+ 'name': options.get('dbname', 'bloodhound'),
+ }
+
+ db = options.get('dbstring')
+ if db is None:
+ if dbdata['type'] in ('postgres', 'mysql') \
+ and dbdata['user'] is not None \
+ and dbdata['pass'] is not None:
+ if dbdata['port'] is not None:
+ db = '%(type)s://%(user)s:%(pass)s@%(host)s:%(port)s/%(name)s'
+ else: # no port specified = default port
+ db = '%(type)s://%(user)s:%(pass)s@%(host)s/%(name)s'
+ else:
+ db = '%%(type)s:%s' % os.path.join('db', '%(name)s.db')
+ return db % dbdata
+
+ def setup(self, **kwargs):
+ """Do the setup. A kwargs dictionary may be passed to override base
+ options, potentially allowing for multiple environment creation."""
+
+ if has_babel:
+ import babel
+ try:
+ locale = get_negotiated_locale([LANG])
+ locale = locale or babel.Locale.default()
+ except babel.UnknownLocaleError:
+ pass
+ translation.activate(locale)
+
+ options = dict(self.options)
+ options.update(kwargs)
+ if psycopg2 is None and options.get('dbtype') == 'postgres':
+ print "psycopg2 needs to be installed to initialise a postgresql db"
+ return False
+ elif mysqldb is None and options.get('dbtype') == 'mysql':
+ print "MySQLdb needs to be installed to initialise a mysql db"
+ return False
+
+ environments_path = options['envsdir']
+ if not os.path.exists(environments_path):
+ os.makedirs(environments_path)
+
+ new_env = os.path.join(environments_path, options['project'])
+ tracini = os.path.abspath(os.path.join(new_env, 'conf', 'trac.ini'))
+ baseini = os.path.abspath(os.path.join(new_env, 'conf', 'base.ini'))
+ options['inherit'] = '"' + baseini + '"'
+
+ options['db'] = self._generate_db_str(options)
+ if 'repo_type' not in options or options['repo_type'] is None:
+ options['repo_type'] = ''
+ if 'repo_path' not in options or options['repo_path'] is None:
+ options['repo_path'] = ''
+ if (len(options['repo_type']) > 0) ^ (len(options['repo_path']) > 0):
+ print "Error: Specifying a repository requires both the "\
+ "repository-type and the repository-path options."
+ return False
+
+ custom_prefix = 'default_product_prefix'
+ if custom_prefix in options and options[custom_prefix]:
+ default_product_prefix = options[custom_prefix]
+ else:
+ default_product_prefix = '@'
+
+ digestfile = os.path.abspath(os.path.join(new_env,
+ options['digestfile']))
+ realm = options['realm']
+ adminuser = options['adminuser']
+ adminpass = options['adminpass']
+
+ # create base options:
+ accounts_config = dict(ACCOUNTS_CONFIG)
+ accounts_config['account-manager']['htdigest_file'] = digestfile
+ accounts_config['account-manager']['htdigest_realm'] = realm
+
+ trac = TracAdmin(os.path.abspath(new_env))
+ if not trac.env_check():
+ try:
+ rv = trac.do_initenv('%(project)s %(db)s '
+ '%(repo_type)s %(repo_path)s '
+ '--inherit=%(inherit)s '
+ '--nowiki'
+ % options)
+ if rv == 2:
+ raise SystemExit
+ except SystemExit:
+ print ("Error: Unable to initialise the environment.")
+ return False
+ else:
+ print ("Warning: Environment already exists at %s." % new_env)
+ self.writeconfig(tracini, [{'inherit': {'file': baseini},},])
+
+ base_config = dict(BASE_CONFIG)
+ base_config['trac']['environment_factory'] = \
+ 'multiproduct.hooks.MultiProductEnvironmentFactory'
+ base_config['trac']['request_factory'] = \
+ 'multiproduct.hooks.ProductRequestFactory'
+ if default_product_prefix != '@':
+ base_config['multiproduct'] = dict(
+ default_product_prefix=default_product_prefix
+ )
+
+ self.writeconfig(baseini, [base_config, accounts_config])
+
+ if os.path.exists(digestfile):
+ backupfile(digestfile)
+ htdigest_create(digestfile, adminuser, realm, adminpass)
+
+ print "Adding TRAC_ADMIN permissions to the admin user %s" % adminuser
+ trac.onecmd('permission add %s TRAC_ADMIN' % adminuser)
+
+ # get fresh TracAdmin instance (original does not know about base.ini)
+ bloodhound = TracAdmin(os.path.abspath(new_env))
+
+ # final upgrade
+ print "Running upgrades"
+ bloodhound.onecmd('upgrade')
+ pages = []
+ pages.append(pkg_resources.resource_filename('bhdashboard',
+ 'default-pages'))
+ pages.append(pkg_resources.resource_filename('bhsearch',
+ 'default-pages'))
+ bloodhound.onecmd('wiki load %s' % " ".join(pages))
+
+ print "Running wiki upgrades"
+ bloodhound.onecmd('wiki upgrade')
+
+ if self.apply_bhwiki_upgrades:
+ print "Running wiki Bloodhound upgrades"
+ bloodhound.onecmd('wiki bh-upgrade')
+ else:
+ print "Skipping Bloodhound wiki upgrades"
+
+ print "Loading default product wiki"
+ bloodhound.onecmd('product admin %s wiki load %s' %
+ (default_product_prefix,
+ " ".join(pages)))
+
+ print "Running default product wiki upgrades"
+ bloodhound.onecmd('product admin %s wiki upgrade' %
+ default_product_prefix)
+
+ if self.apply_bhwiki_upgrades:
+ print "Running default product Bloodhound wiki upgrades"
+ bloodhound.onecmd('product admin %s wiki bh-upgrade' %
+ default_product_prefix)
+ else:
+ print "Skipping default product Bloodhound wiki upgrades"
+
+ print """
+You can now start Bloodhound by running:
+
+ tracd --port=8000 %s
+
+And point your browser at http://localhost:8000/%s
+""" % (os.path.abspath(new_env), options['project'])
+ return True
+
+ def writeconfig(self, filepath, dicts=[]):
+ """Writes or updates a config file. A list of dictionaries is used so
+ that options for different aspects of the configuration can be kept
+ separate while being able to update the same sections. Note that the
+ result is order dependent where two dictionaries update the same
+ option.
+ """
+ config = Configuration(filepath)
+ file_changed = False
+ for data in dicts:
+ for section, options in data.iteritems():
+ for key, value in options.iteritems():
+ if config.get(section, key, None) != value:
+ # This should be expected to generate a false positive
+ # when two dictionaries update the same option
+ file_changed = True
+ config.set(section, key, value)
+ if file_changed:
+ if os.path.exists(filepath):
+ backupfile(filepath)
+ config.save()
+
+def backupfile(filepath):
+ """Very basic backup routine"""
+ print "Warning: Updating %s." % filepath
+ backuppath = None
+ if not os.path.exists(filepath + '_bak'):
+ backuppath = filepath + '_bak'
+ else:
+ backuptemplate = filepath + '_bak_%d'
+ for i in xrange(MAXBACKUPNUMBER):
+ if not os.path.exists(backuptemplate % i):
+ backuppath = backuptemplate % i
+ break
+ if backuppath is not None:
+ shutil.copyfile(filepath, backuppath)
+ print "Backup created at %s." % backuppath
+ else:
+ print "No backup created (too many other backups found)"
+ return backuppath
+
+def handle_options():
+ """Parses the command line, with basic prompting for choices where options
+ are not specified."""
+ parser = OptionParser()
+
+ # Base Trac Options
+ parser.add_option('--project', dest='project',
+ help='Set the top project name', default='main')
+ parser.add_option('--source_directory', dest='sourcedir',
+ help='Specify root source code directory',
+ default=os.path.normpath(os.path.join(os.getcwd(), '../'))),
+ parser.add_option('--environments_directory', dest='envsdir',
+ help='Set the directory to contain environments',
+ default=os.path.join('bloodhound', 'environments'))
+ parser.add_option('-d', '--database-type', dest='dbtype',
+ help="Specify as either 'sqlite', 'postgres' or 'mysql'",
+ default='')
+ parser.add_option('--database-string', dest='dbstring',
+ help=('Advanced: provide a custom database string, '
+ 'overriding other database options'),
+ default=None)
+ parser.add_option('--database-name', dest='dbname',
+ help='Specify the database name',
+ default='bloodhound')
+ parser.add_option('-u', '--user', dest='dbuser',
+ help='Specify the db user (required for postgres and mysql)',
+ default='')
+ parser.add_option('-p', '--password', dest='dbpass',
+ help='Specify the db password (required for postgres and mysql)')
+ parser.add_option('--database-host', dest='dbhost',
+ help='Specify the database host (optional for postgres and mysql)',
+ default='localhost')
+ parser.add_option('--database-port', dest='dbport',
+ help='Specify the database port (optional for postgres and mysql)',
+ default='5432')
+
+ # Account Manager Options
+ parser.add_option('--admin-password', dest='adminpass',
+ help='create an admin user in an htdigest file')
+ parser.add_option('--digest-realm', dest='realm', default='bloodhound',
+ help='authentication realm for htdigest file')
+ parser.add_option('--admin-user', dest='adminuser', default='',
+ help='admin user name for htdigest file')
+ parser.add_option('--digest-file', dest='digestfile',
+ default='bloodhound.htdigest',
+ help='filename for the htdigest file')
+
+ # Repository Options
+ parser.add_option('--repository-type', dest='repo_type',
+ help='specify the repository type - ')
+ parser.add_option('--repository-path', dest='repo_path',
+ help='specify the repository type')
+
+ # Multiproduct options
+ parser.add_option('--default-product-prefix', dest='default_product_prefix',
+ help='Specify prefix for default product (defaults to @')
+
+ (options, args) = parser.parse_args()
+ if args:
+ print "Unprocessed options/arguments: ", args
+
+ def ask_question(question, default=None):
+ """Basic question asking functionality"""
+ if default:
+ answer = raw_input(question % default)
+ else:
+ answer = raw_input(question)
+ return answer if answer else default
+
+ def ask_password(user):
+ """Asks for a password to be provided for setting purposes"""
+ attempts = 3
+ for attempt in range(attempts):
+ if attempt > 0:
+ print "Passwords empty or did not match. Please try again",
+ print "(attempt %d/%d)""" % (attempt+1, attempts)
+ password1 = getpass('Enter a new password for "%s": ' % user)
+ password2 = getpass('Please reenter the password: ')
+ if password1 and password1 == password2:
+ return password1
+ print "Passwords did not match. Quiting."
+ sys.exit(1)
+
+ if options.dbtype.lower() not in SUPPORTED_DBTYPES:
+ answer = ask_question("""
+This installer is able to install Apache Bloodhound with either SQLite,
+PostgreSQL or MySQL databases. SQLite is an easier option for installing
+Bloodhound as SQLite support is built into Python and requires no special
+permissions to run. However, PostgreSQL and MySQL are generally expected to
+be more robust for production use.
+What type of database do you want to instant to (%s)?
+[%%s]: """ % '/'.join(SUPPORTED_DBTYPES), default='sqlite')
+ answer = answer.lower()
+ if answer in SUPPORTED_DBTYPES:
+ options.dbtype = answer
+ else:
+ print "Unrecognized dbtype \"%s\". Quiting." % answer
+ sys.exit(1)
+ else:
+ options.dbtype = options.dbtype.lower()
+
+ if options.dbtype in ('postgres','mysql'):
+ if not options.dbuser:
+ options.dbuser = ask_question("""
+For PostgreSQL/MySQL you need to have PostgreSQL/MySQL installed and you need
+to have created a database user to connect to the database with. Setting this
+up may require admin access rights to the server.
+DB user name [%s]: """, DEFAULT_DB_USER)
+
+ if not options.dbpass:
+ options.dbpass = ask_password(options.dbuser)
+
+ if not options.dbname:
+ options.dbname = ask_question("""
+For PostgreSQL/MySQL setup, you need to specify a database that you have
+created for Bloodhound to use. This installer currently assumes that this
+database will be empty. DB name [%s]: """, DEFAULT_DB_NAME)
+ if not options.adminuser:
+ options.adminuser = ask_question("""
+Please supply a username for the admin user [%s]: """, DEFAULT_ADMIN_USER)
+ if not options.adminpass:
+ options.adminpass = ask_password(options.adminuser)
+
+ return options
+
+
+def run():
+ options = handle_options()
+ bsetup = BloodhoundSetup(options)
+ bsetup.setup()
+
+if __name__ == '__main__':
+ run()
diff --git a/installer/createdigest.py b/installer/bhsetup/createdigest.py
similarity index 100%
rename from installer/createdigest.py
rename to installer/bhsetup/createdigest.py
diff --git a/installer/bloodhound_setup.py b/installer/bloodhound_setup.py
index 2da3a75..ebfb08e 100644
--- a/installer/bloodhound_setup.py
+++ b/installer/bloodhound_setup.py
@@ -18,458 +18,7 @@
# under the License.
"""Initial configuration for Bloodhound"""
-import os
-import pkg_resources
-import shutil
-import sys
-from createdigest import htdigest_create
-from getpass import getpass
-from optparse import OptionParser
-
-try:
- from trac.admin.console import TracAdmin
- from trac.config import Configuration
- from trac.util import translation
- from trac.util.translation import _, get_negotiated_locale, has_babel
-except ImportError, e:
- print("Requirements must be installed before running "
- "bloodhound_setup.py.\n"
- "You can install them with the following command:\n"
- " pip install -r requirements.txt\n")
- sys.exit(1)
-
-try:
- import psycopg2
-except ImportError:
- psycopg2 = None
-
-try:
- import MySQLdb as mysqldb
-except ImportError:
- mysqldb = None
-
-LANG = os.environ.get('LANG')
-
-MAXBACKUPNUMBER = 64 # Max attempts to create backup file
-
-SUPPORTED_DBTYPES = ('sqlite', 'postgres', 'mysql')
-DEFAULT_DB_USER = 'bloodhound'
-DEFAULT_DB_NAME = 'bloodhound'
-DEFAULT_ADMIN_USER = 'admin'
-
-BH_PROJECT_SITE = 'https://issues.apache.org/bloodhound/'
-BASE_CONFIG = {'components': {'bhtheme.*': 'enabled',
- 'bhdashboard.*': 'enabled',
- 'multiproduct.*': 'enabled',
- 'permredirect.*': 'enabled',
- 'themeengine.api.*': 'enabled',
- 'themeengine.web_ui.*': 'enabled',
- 'bhsearch.*': 'enabled',
- 'bhrelations.*': 'enabled',
- 'trac.ticket.web_ui.ticketmodule': 'disabled',
- 'trac.ticket.report.reportmodule': 'disabled',
- },
- 'header_logo': {'src': '',},
- 'mainnav': {'roadmap': 'disabled',
- 'search': 'disabled',
- 'timeline': 'disabled',},
- 'metanav': {'about': 'disabled',},
- 'theme': {'theme': 'bloodhound',},
- 'trac': {'mainnav': ','.join(['dashboard', 'wiki', 'browser',
- 'tickets', 'newticket', 'timeline',
- 'roadmap', 'search']),
- 'environment_factory': '',
- 'request_factory': '',},
- 'project': {'footer': ('Get involved with '
- '<a href="%(site)s">Apache Bloodhound</a>'
- % {'site': BH_PROJECT_SITE,}),},
- 'labels': {'application_short': 'Bloodhound',
- 'application_full': 'Apache Bloodhound',
- 'footer_left_prefix': '',
- 'footer_left_postfix': '',
- 'footer_right': ''},
- 'bhsearch': {'is_default': 'true', 'enable_redirect': 'true'},
-}
-
-ACCOUNTS_CONFIG = {'account-manager': {'account_changes_notify_addresses' : '',
- 'authentication_url' : '',
- 'db_htdigest_realm' : '',
- 'force_passwd_change' :'true',
- 'hash_method' : 'HtDigestHashMethod',
- 'htdigest_file' : '',
- 'htdigest_realm' : '',
- 'htpasswd_file' : '',
- 'htpasswd_hash_type' : 'crypt',
- 'password_store' : 'HtDigestStore',
- 'persistent_sessions' : 'False',
- 'refresh_passwd' : 'False',
- 'user_lock_max_time' : '0',
- 'verify_email' : 'True',
- },
- 'components': {'acct_mgr.admin.*' : 'enabled',
- 'acct_mgr.api.accountmanager' : 'enabled',
- 'acct_mgr.guard.accountguard' : 'enabled',
- 'acct_mgr.htfile.htdigeststore' : 'enabled',
- 'acct_mgr.macros.*': 'enabled',
- 'acct_mgr.web_ui.accountmodule' : 'enabled',
- 'acct_mgr.web_ui.loginmodule' : 'enabled',
- 'trac.web.auth.loginmodule' : 'disabled',
- },
- }
-
-class BloodhoundSetup(object):
- """Creates a Bloodhound environment"""
-
- def __init__(self, opts):
- if isinstance(opts, dict):
- options = dict(opts)
- else:
- options = vars(opts)
- self.options = options
-
- if 'project' not in options:
- options['project'] = 'main'
- if 'envsdir' not in options:
- options['envsdir'] = os.path.join('bloodhound',
- 'environments')
-
- # Flags used when running the functional test suite
- self.apply_bhwiki_upgrades = True
-
- def _generate_db_str(self, options):
- """Builds an appropriate db string for trac-admin for sqlite and
- postgres options. Also allows for a user to provide their own db
- string to allow database initialisation beyond these."""
- dbdata = {'type': options.get('dbtype', 'sqlite'),
- 'user': options.get('dbuser'),
- 'pass': options.get('dbpass'),
- 'host': options.get('dbhost', 'localhost'),
- 'port': options.get('dbport'),
- 'name': options.get('dbname', 'bloodhound'),
- }
-
- db = options.get('dbstring')
- if db is None:
- if dbdata['type'] in ('postgres', 'mysql') \
- and dbdata['user'] is not None \
- and dbdata['pass'] is not None:
- if dbdata['port'] is not None:
- db = '%(type)s://%(user)s:%(pass)s@%(host)s:%(port)s/%(name)s'
- else: # no port specified = default port
- db = '%(type)s://%(user)s:%(pass)s@%(host)s/%(name)s'
- else:
- db = '%%(type)s:%s' % os.path.join('db', '%(name)s.db')
- return db % dbdata
-
- def setup(self, **kwargs):
- """Do the setup. A kwargs dictionary may be passed to override base
- options, potentially allowing for multiple environment creation."""
-
- if has_babel:
- import babel
- try:
- locale = get_negotiated_locale([LANG])
- locale = locale or babel.Locale.default()
- except babel.UnknownLocaleError:
- pass
- translation.activate(locale)
-
- options = dict(self.options)
- options.update(kwargs)
- if psycopg2 is None and options.get('dbtype') == 'postgres':
- print "psycopg2 needs to be installed to initialise a postgresql db"
- return False
- elif mysqldb is None and options.get('dbtype') == 'mysql':
- print "MySQLdb needs to be installed to initialise a mysql db"
- return False
-
- environments_path = options['envsdir']
- if not os.path.exists(environments_path):
- os.makedirs(environments_path)
-
- new_env = os.path.join(environments_path, options['project'])
- tracini = os.path.abspath(os.path.join(new_env, 'conf', 'trac.ini'))
- baseini = os.path.abspath(os.path.join(new_env, 'conf', 'base.ini'))
- options['inherit'] = '"' + baseini + '"'
-
- options['db'] = self._generate_db_str(options)
- if 'repo_type' not in options or options['repo_type'] is None:
- options['repo_type'] = ''
- if 'repo_path' not in options or options['repo_path'] is None:
- options['repo_path'] = ''
- if (len(options['repo_type']) > 0) ^ (len(options['repo_path']) > 0):
- print "Error: Specifying a repository requires both the "\
- "repository-type and the repository-path options."
- return False
-
- custom_prefix = 'default_product_prefix'
- if custom_prefix in options and options[custom_prefix]:
- default_product_prefix = options[custom_prefix]
- else:
- default_product_prefix = '@'
-
- digestfile = os.path.abspath(os.path.join(new_env,
- options['digestfile']))
- realm = options['realm']
- adminuser = options['adminuser']
- adminpass = options['adminpass']
-
- # create base options:
- accounts_config = dict(ACCOUNTS_CONFIG)
- accounts_config['account-manager']['htdigest_file'] = digestfile
- accounts_config['account-manager']['htdigest_realm'] = realm
-
- trac = TracAdmin(os.path.abspath(new_env))
- if not trac.env_check():
- try:
- rv = trac.do_initenv('%(project)s %(db)s '
- '%(repo_type)s %(repo_path)s '
- '--inherit=%(inherit)s '
- '--nowiki'
- % options)
- if rv == 2:
- raise SystemExit
- except SystemExit:
- print ("Error: Unable to initialise the environment.")
- return False
- else:
- print ("Warning: Environment already exists at %s." % new_env)
- self.writeconfig(tracini, [{'inherit': {'file': baseini},},])
-
- base_config = dict(BASE_CONFIG)
- base_config['trac']['environment_factory'] = \
- 'multiproduct.hooks.MultiProductEnvironmentFactory'
- base_config['trac']['request_factory'] = \
- 'multiproduct.hooks.ProductRequestFactory'
- if default_product_prefix != '@':
- base_config['multiproduct'] = dict(
- default_product_prefix=default_product_prefix
- )
-
- self.writeconfig(baseini, [base_config, accounts_config])
-
- if os.path.exists(digestfile):
- backupfile(digestfile)
- htdigest_create(digestfile, adminuser, realm, adminpass)
-
- print "Adding TRAC_ADMIN permissions to the admin user %s" % adminuser
- trac.onecmd('permission add %s TRAC_ADMIN' % adminuser)
-
- # get fresh TracAdmin instance (original does not know about base.ini)
- bloodhound = TracAdmin(os.path.abspath(new_env))
-
- # final upgrade
- print "Running upgrades"
- bloodhound.onecmd('upgrade')
- pages = []
- pages.append(pkg_resources.resource_filename('bhdashboard',
- 'default-pages'))
- pages.append(pkg_resources.resource_filename('bhsearch',
- 'default-pages'))
- bloodhound.onecmd('wiki load %s' % " ".join(pages))
-
- print "Running wiki upgrades"
- bloodhound.onecmd('wiki upgrade')
-
- if self.apply_bhwiki_upgrades:
- print "Running wiki Bloodhound upgrades"
- bloodhound.onecmd('wiki bh-upgrade')
- else:
- print "Skipping Bloodhound wiki upgrades"
-
- print "Loading default product wiki"
- bloodhound.onecmd('product admin %s wiki load %s' %
- (default_product_prefix,
- " ".join(pages)))
-
- print "Running default product wiki upgrades"
- bloodhound.onecmd('product admin %s wiki upgrade' %
- default_product_prefix)
-
- if self.apply_bhwiki_upgrades:
- print "Running default product Bloodhound wiki upgrades"
- bloodhound.onecmd('product admin %s wiki bh-upgrade' %
- default_product_prefix)
- else:
- print "Skipping default product Bloodhound wiki upgrades"
-
- print """
-You can now start Bloodhound by running:
-
- tracd --port=8000 %s
-
-And point your browser at http://localhost:8000/%s
-""" % (os.path.abspath(new_env), options['project'])
- return True
-
- def writeconfig(self, filepath, dicts=[]):
- """Writes or updates a config file. A list of dictionaries is used so
- that options for different aspects of the configuration can be kept
- separate while being able to update the same sections. Note that the
- result is order dependent where two dictionaries update the same
- option.
- """
- config = Configuration(filepath)
- file_changed = False
- for data in dicts:
- for section, options in data.iteritems():
- for key, value in options.iteritems():
- if config.get(section, key, None) != value:
- # This should be expected to generate a false positive
- # when two dictionaries update the same option
- file_changed = True
- config.set(section, key, value)
- if file_changed:
- if os.path.exists(filepath):
- backupfile(filepath)
- config.save()
-
-def backupfile(filepath):
- """Very basic backup routine"""
- print "Warning: Updating %s." % filepath
- backuppath = None
- if not os.path.exists(filepath + '_bak'):
- backuppath = filepath + '_bak'
- else:
- backuptemplate = filepath + '_bak_%d'
- for i in xrange(MAXBACKUPNUMBER):
- if not os.path.exists(backuptemplate % i):
- backuppath = backuptemplate % i
- break
- if backuppath is not None:
- shutil.copyfile(filepath, backuppath)
- print "Backup created at %s." % backuppath
- else:
- print "No backup created (too many other backups found)"
- return backuppath
-
-def handle_options():
- """Parses the command line, with basic prompting for choices where options
- are not specified."""
- parser = OptionParser()
-
- # Base Trac Options
- parser.add_option('--project', dest='project',
- help='Set the top project name', default='main')
- parser.add_option('--source_directory', dest='sourcedir',
- help='Specify root source code directory',
- default=os.path.normpath(os.path.join(os.getcwd(), '../'))),
- parser.add_option('--environments_directory', dest='envsdir',
- help='Set the directory to contain environments',
- default=os.path.join('bloodhound', 'environments'))
- parser.add_option('-d', '--database-type', dest='dbtype',
- help="Specify as either 'sqlite', 'postgres' or 'mysql'",
- default='')
- parser.add_option('--database-string', dest='dbstring',
- help=('Advanced: provide a custom database string, '
- 'overriding other database options'),
- default=None)
- parser.add_option('--database-name', dest='dbname',
- help='Specify the database name',
- default='bloodhound')
- parser.add_option('-u', '--user', dest='dbuser',
- help='Specify the db user (required for postgres and mysql)',
- default='')
- parser.add_option('-p', '--password', dest='dbpass',
- help='Specify the db password (required for postgres and mysql)')
- parser.add_option('--database-host', dest='dbhost',
- help='Specify the database host (optional for postgres and mysql)',
- default='localhost')
- parser.add_option('--database-port', dest='dbport',
- help='Specify the database port (optional for postgres and mysql)',
- default='5432')
-
- # Account Manager Options
- parser.add_option('--admin-password', dest='adminpass',
- help='create an admin user in an htdigest file')
- parser.add_option('--digest-realm', dest='realm', default='bloodhound',
- help='authentication realm for htdigest file')
- parser.add_option('--admin-user', dest='adminuser', default='',
- help='admin user name for htdigest file')
- parser.add_option('--digest-file', dest='digestfile',
- default='bloodhound.htdigest',
- help='filename for the htdigest file')
-
- # Repository Options
- parser.add_option('--repository-type', dest='repo_type',
- help='specify the repository type - ')
- parser.add_option('--repository-path', dest='repo_path',
- help='specify the repository type')
-
- # Multiproduct options
- parser.add_option('--default-product-prefix', dest='default_product_prefix',
- help='Specify prefix for default product (defaults to @')
-
- (options, args) = parser.parse_args()
- if args:
- print "Unprocessed options/arguments: ", args
-
- def ask_question(question, default=None):
- """Basic question asking functionality"""
- if default:
- answer = raw_input(question % default)
- else:
- answer = raw_input(question)
- return answer if answer else default
-
- def ask_password(user):
- """Asks for a password to be provided for setting purposes"""
- attempts = 3
- for attempt in range(attempts):
- if attempt > 0:
- print "Passwords empty or did not match. Please try again",
- print "(attempt %d/%d)""" % (attempt+1, attempts)
- password1 = getpass('Enter a new password for "%s": ' % user)
- password2 = getpass('Please reenter the password: ')
- if password1 and password1 == password2:
- return password1
- print "Passwords did not match. Quiting."
- sys.exit(1)
-
- if options.dbtype.lower() not in SUPPORTED_DBTYPES:
- answer = ask_question("""
-This installer is able to install Apache Bloodhound with either SQLite,
-PostgreSQL or MySQL databases. SQLite is an easier option for installing
-Bloodhound as SQLite support is built into Python and requires no special
-permissions to run. However, PostgreSQL and MySQL are generally expected to
-be more robust for production use.
-What type of database do you want to instant to (%s)?
-[%%s]: """ % '/'.join(SUPPORTED_DBTYPES), default='sqlite')
- answer = answer.lower()
- if answer in SUPPORTED_DBTYPES:
- options.dbtype = answer
- else:
- print "Unrecognized dbtype \"%s\". Quiting." % answer
- sys.exit(1)
- else:
- options.dbtype = options.dbtype.lower()
-
- if options.dbtype in ('postgres','mysql'):
- if not options.dbuser:
- options.dbuser = ask_question("""
-For PostgreSQL/MySQL you need to have PostgreSQL/MySQL installed and you need
-to have created a database user to connect to the database with. Setting this
-up may require admin access rights to the server.
-DB user name [%s]: """, DEFAULT_DB_USER)
-
- if not options.dbpass:
- options.dbpass = ask_password(options.dbuser)
-
- if not options.dbname:
- options.dbname = ask_question("""
-For PostgreSQL/MySQL setup, you need to specify a database that you have
-created for Bloodhound to use. This installer currently assumes that this
-database will be empty. DB name [%s]: """, DEFAULT_DB_NAME)
- if not options.adminuser:
- options.adminuser = ask_question("""
-Please supply a username for the admin user [%s]: """, DEFAULT_ADMIN_USER)
- if not options.adminpass:
- options.adminpass = ask_password(options.adminuser)
-
- return options
-
+from bhsetup import bloodhound_setup
if __name__ == '__main__':
- options = handle_options()
- bsetup = BloodhoundSetup(options)
- bsetup.setup()
+ bloodhound_setup.run()
diff --git a/installer/tests.py b/installer/tests.py
index b20c61e..846e033 100644
--- a/installer/tests.py
+++ b/installer/tests.py
@@ -23,7 +23,7 @@
import shutil
import os
from tempfile import mkdtemp, NamedTemporaryFile
-from bloodhound_setup import backupfile, BloodhoundSetup
+from bhsetup.bloodhound_setup import backupfile, BloodhoundSetup
from functools import partial
class BackupfileTest(unittest.TestCase):