======================
 Installation helpers
======================

As icemac.addressbook is not installable using `easy_install`, there are
some helpers for installation.

check_prerequisites
===================

This function tests whether the installation might be successful.

Existing buildout.cfg
---------------------

If there is already a `buildout.cfg` file, an error message is
displayed and ``False`` is returned.

In the current directory already exists a `buildout.cfg`:

>>> from icemac.addressbook.install import check_prerequisites
>>> ls(sample_buildout)
d  bin
-  buildout.cfg
d  develop-eggs
d  eggs
d  parts
>>> check_prerequisites()
ERROR: buildout.cfg already exists.
       Please (re-)move the existing one and restart install.
False

If there is no `buildout.cfg` file, ``True`` is returned:

>>> remove(sample_buildout, 'buildout.cfg')
>>> check_prerequisites()
True

Wrong python version
--------------------

icemac.addressbook currently only runs with python 2.5. If another
version is used, an error is displayed. We expect here that the version
of the python which runs the tests matches the requirement, so
``True`` is returned:

>>> check_prerequisites()
True

Faking the python version, results in the described error message:

>>> import sys
>>> orig_version_info = sys.version_info
>>> sys.version_info = (2, 4, 6, 'final', 0)
>>> check_prerequisites()
ERROR: icemac.addressbook currently supports python 2.5 and 2.6
       But you try to install it using python 2.4.6.
False
>>> sys.version_info = (2, 7, 0, 'final', 0)
>>> check_prerequisites()
ERROR: icemac.addressbook currently supports python 2.5 and 2.6
       But you try to install it using python 2.7.0.
False
>>> sys.version_info = orig_version_info


Configurator
============

An instance of this class reads the default configuration file
(`install.default.ini`). Whose values are used when the user does not
enter a value.

Setup
-----

We need an `install.default.ini` in the working directory as
``Configurator`` searches only there:

>>> write(sample_buildout, 'install.default.ini',
... """
... [install]
... eggs_dir = py-eggs
...
... [admin]
... login = me
... password = secret
...
... [server]
... host = my.computer.local
... port = 13090
... username =
...
... [log]
... handler = FileHandler
... max_size = 1000
... when = midnight
... interval = 1
... backups = 5
...
... [packages]
...
... [migration]
... do_migration = no
... stop_server = no
... start_server = no
... """)

Create a configurator and read the file above:

>>> from icemac.addressbook.install import Configurator
>>> config = Configurator()
>>> config.load()
>>> config.get('install', 'eggs_dir')
'py-eggs'


ask_user
--------

This is a helper method, which ask the user for input, it displays the
default value as set in the configuration file. The entered value is
stored in the configuration and is also returned. When the user does
not enter a value (only hits `enter`), the default value is used
instead:

>>> call_with_user_input(
...     '\n', config.ask_user, 'Server port', 'server', 'port')
Server port: [13090]
'13090'
>>> config.get('server', 'port')
'13090'

When the user enters a value, it is returned and stored:

>>> call_with_user_input(
...     '4711\n', config.ask_user, 'Server port', 'server', 'port')
Server port: [13090]
'4711'
>>> config.get('server', 'port')
'4711'

The next time the stored value is presented as default:

>>> call_with_user_input(
...     '0815\n', config.ask_user, 'Server port', 'server', 'port')
Server port: [4711]
'0815'
>>> config.get('server', 'port')
'0815'

When a list of possible values is defined, `ask_user` retries to get
an answer until the user entered a value contained in the list:

>>> call_with_user_input(
...   'maybe\n'
...   '???\n'
...   'yes\n', config.ask_user, 'Really', 'migration', 'do_migration',
...   values=('yes', 'no'))
 Really: [no]
ERROR: 'maybe' is not in ('yes', 'no').
Please choose a value out of the list.
 Really: [no]
ERROR: '???' is not in ('yes', 'no').
Please choose a value out of the list.
 Really: [no]
'yes'
>>> config.get('migration', 'do_migration')
'yes'


load
----

(Re-) loads the configuration from the configuration file(s):

>>> config.load()
>>> config.get('server', 'port')
'13090'

Stores the path to the old instance, when it is not set it resets it:

>>> config.get('migration', 'old_instance')
''

When a user config is set on the configurator this file is read. When
the file does not exist an error is raised:

>>> config.user_config = 'i-do-not-exist.ini'
>>> config.load()
Traceback (most recent call last):
IOError: 'i-do-not-exist.ini' does not exist.

The values in the user configuration take precedence over the ones in
the default configuration:

>>> config.get('install', 'eggs_dir')
'py-eggs'
>>> mkdir('prev_version')
>>> conf_path = join(sample_buildout, 'prev_version')
>>> write(conf_path, 'install.user.ini',
... """
... [install]
... eggs_dir = my-eggs
... """)
>>> config.user_config = join(conf_path, 'install.user.ini')
>>> config.load()
>>> config.get('install', 'eggs_dir')
'my-eggs'
>>> config.get('migration', 'old_instance')
'/.../sample-buildout/prev_version'

Setting the user configuration file path again to None resets the
configuration value:

>>> config.user_config = None
>>> config.load()
>>> config.get('migration', 'old_instance')
''

>>> config.user_config = join(conf_path, 'install.user.ini')
>>> config.load()


print_intro
-----------

Prints the installation introduction:

>>> config.print_intro()
Welcome to icemac.addressbook installation
<BLANKLINE>
Hint: to use the default value (the one in [brackets]),  enter no value.


get_global_options
------------------

Ask the user about global options and stores them:

>>> call_with_user_input('/Users/mac\n', config.get_global_options)
 Directory to store python eggs: [my-eggs]
>>> config.eggs_dir
'/Users/mac'
>>> config.get('install', 'eggs_dir')
'/Users/mac'

get_server_options
------------------

Ask the user about the server and store the answers:

>>> call_with_user_input(
...     'admin\n'
...     'geheim\n'
...     'localhost\n'
...     '8080\n'
...     'mac\n', config.get_server_options)
Log-in name for the administrator: [me]
Password for the administrator: [secret]
Hostname: [my.computer.local]
Port number: [13090]
Username whether process should run as different user otherwise emtpy: []
>>> config.admin_login
'admin'
>>> config.get('admin', 'login')
'admin'
>>> config.admin_passwd
'geheim'
>>> config.get('admin', 'password')
'geheim'
>>> config.host
'localhost'
>>> config.get('server', 'host')
'localhost'
>>> config.port
'8080'
>>> config.get('server', 'port')
'8080'
>>> config.username
'mac'
>>> config.get('server', 'username')
'mac'

get_log_options
---------------

Ask the user about logging and store his answers.  When the user
enters a wrong handler name he is asked to enter it again. Choosing
`TimedRotatingFileHandler` three additional questions are asked:

>>> call_with_user_input(
... """does not matter
... TimedRotatingFileHandler
... D
... 7
... 8
... """, config.get_log_options)
 Please choose Log-Handler:
    Details see http://docs.python.org/library/logging.html#handler-objects
 Log-Handler, choose between FileHandler, RotatingFileHandler, TimedRotatingFileHandler: [FileHandler]
ERROR: 'does not matter' is not in ('FileHandler', 'RotatingFileHandler', 'TimedRotatingFileHandler').
Please choose a value out of the list.
 Log-Handler, choose between FileHandler, RotatingFileHandler, TimedRotatingFileHandler: [FileHandler]
 Type of rotation interval, choose between S, M, H, D, W, midnight: [midnight]
 Rotation interval size: [1]
 Number of log file backups: [5]
>>> config.log_handler
'TimedRotatingFileHandler'
>>> config.get('log', 'handler')
'TimedRotatingFileHandler'
>>> config.log_when
'D'
>>> config.get('log', 'when')
'D'
>>> config.log_interval
'7'
>>> config.get('log', 'interval')
'7'
>>> config.log_backups
'8'
>>> config.get('log', 'backups')
'8'

Choosing `RotatingFileHandler` two more questions are asked:

>>> call_with_user_input(
... """RotatingFileHandler
... 12345
... 2
... """, config.get_log_options)
Please choose Log-Handler:
    Details see http://docs.python.org/library/logging.html#handler-objects
 Log-Handler, choose between FileHandler, RotatingFileHandler,
 TimedRotatingFileHandler: [TimedRotatingFileHandler]
 Maximum file size before rotating in bytes: [1000]
 Number of log file backups: [8]
>>> config.log_handler
'RotatingFileHandler'
>>> config.get('log', 'handler')
'RotatingFileHandler'
>>> config.log_max_bytes
'12345'
>>> config.get('log', 'max_size')
'12345'
>>> config.log_backups
'2'
>>> config.get('log', 'backups')
'2'

Choosing `FileHandler` no more questions are asked:
>>> call_with_user_input(
... """FileHandler\n""", config.get_log_options)
 Please choose Log-Handler:
    Details see http://docs.python.org/library/logging.html#handler-objects
 Log-Handler, choose between FileHandler, RotatingFileHandler,
 TimedRotatingFileHandler: [RotatingFileHandler]
>>> config.log_handler
'FileHandler'
>>> config.get('log', 'handler')
'FileHandler'

print_additional_packages_intro
-------------------------------

Prints the introduction text for additional packages:

>>> config.print_additional_packages_intro()
 When you need additional packages (e. g. import readers)
 enter the package names here separated by a newline.
 When you are done enter an empty line.
 Known packages:
   icemac.ab.importer -- Import of CSV files
   icemac.ab.importxls -- Import of XLS (Excel) files


get_additional_packages
-----------------------

The user can enter the names of additional packages. Those packages
are later added as runtime and test dependenies. They need a
`configure.zcml` as they get integrated using that file:

>>> call_with_user_input(
...     'icemac.ab.importxls\n'
...     'icemac.ab.importcsv\n', config.get_additional_packages)
 Package 1: []
 Package 2: []
 Package 3: []
>>> config.packages
['icemac.ab.importxls', 'icemac.ab.importcsv']
>>> config.get('packages', 'package_1')
'icemac.ab.importxls'
>>> config.get('packages', 'package_2')
'icemac.ab.importcsv'
>>> config.get('packages', 'package_3')
''

get_migration_options
---------------------

Ask the user about the migration of existing content and store the
values. When the user decides no to do the migration no additional
questions are asked:

>>> call_with_user_input(
...     'no\n', config.get_migration_options)
 Migrate old address book content to new instance: [no]
>>> config.do_migration
'no'
>>> config.get('migration', 'do_migration')
'no'

When the user chooses to migrate he is asked some additional
questions:

>>> call_with_user_input(
...     'yes\n'
...     'no\n'
...     'yes\n', config.get_migration_options)
 Migrate old address book content to new instance: [no]
The old address book instance must not run during migration. When it runs as demon process the migration script can stop it otherwise you have to stop it manually.
 Old instance is running as a demon process: [no]
 New instance should be started as a demon process after migration: [no]
>>> config.do_migration
'yes'
>>> config.get('migration', 'do_migration')
'yes'
>>> config.stop_server
'no'
>>> config.get('migration', 'stop_server')
'no'
>>> config.start_server
'yes'
>>> config.get('migration', 'start_server')
'yes'


create_admin_zcml
-----------------

Creates the `admin.zcml` file which contains the password of the
global administrator:

>>> config.admin_login = 'root'
>>> config.admin_passwd = 'keins'
>>> config.create_admin_zcml()
creating admin.zcml ...
>>> cat('admin.zcml')
<configure xmlns="http://namespaces.zope.org/zope">
  <principal
    id="icemac.addressbook.global.Administrator"
    title="global administrator"
    login="root"
    password_manager="Plain Text"
    password="keins" />
  <grant
    role="zope.Manager"
    principal="icemac.addressbook.global.Administrator" />
</configure>

create_buildout_cfg
-------------------

Creates the `buildout.cfg` file which contains the user configurations
for the server. The contents of the file depend a bit on the choosen
log handler:

>>> config.eggs_dir = '/var/lib/eggs'
>>> config.host = 'server.local'
>>> config.port = '8081'
>>> config.username = ''
>>> config.log_handler = 'FileHandler'
>>> config.packages = ['icemac.ab.reporting', 'icemac.ab.relations']
>>> config.create_buildout_cfg()
creating buildout.cfg ...
>>> cat('buildout.cfg')
[buildout]
newest = true
allow-picked-versions = true
extends = profiles/prod.cfg
eggs-directory = /var/lib/eggs
<BLANKLINE>
[site.zcml]
permissions_zcml = <include package="icemac.ab.reporting" />
	<include package="icemac.ab.relations" />
<BLANKLINE>
[deploy.ini]
log-handler-args = 'a'
host = server.local
log-handler = FileHandler
port = 8081
<BLANKLINE>
[app]
eggs += icemac.ab.reporting
      icemac.ab.relations
<BLANKLINE>
[test]
eggs += icemac.ab.reporting
      icemac.ab.relations

Using different log handler results in different ``log-handler-args``:

>>> config.log_handler = 'RotatingFileHandler'
>>> config.log_max_bytes = '200000'
>>> config.log_backups = '10'
>>> config.create_buildout_cfg()
creating buildout.cfg ...
>>> cat('buildout.cfg')
[buildout]...
log-handler-args = 'a', 200000, 10...


>>> config.log_handler = 'TimedRotatingFileHandler'
>>> config.log_when = 'W'
>>> config.log_interval = '2'
>>> config.log_backups = '1'
>>> config.create_buildout_cfg()
creating buildout.cfg ...
>>> cat('buildout.cfg')
[buildout]...
log-handler-args = 'W', 2, 1...

When the username is not empty a zdaemon.conf section is added:

>>> config.username = 'mac'
>>> config.create_buildout_cfg()
creating buildout.cfg ...
>>> cat('buildout.cfg')
[zdaemon.conf]
user = user mac
[buildout]...

store
-----

Stores the current configuration values in a file named
`install.user.ini`:

>>> config.store()
saving config ...
>>> cat('install.user.ini')
[log]
interval = 7
when = D
handler = FileHandler
backups = 2
max_size = 12345
<BLANKLINE>
[admin]
login = admin
password = geheim
<BLANKLINE>
[server]
username = mac
host = localhost
port = 8080
<BLANKLINE>
[migration]
do_migration = yes
stop_server = no
start_server = yes
old_instance = /.../sample-buildout/prev_version
<BLANKLINE>
[install]
eggs_dir = /Users/mac
<BLANKLINE>
[packages]
package_1 = icemac.ab.importxls
package_2 = icemac.ab.importcsv
package_3 =


Complete run
------------

Do a complete run of the configurator with only default values:

>>> config = Configurator()
>>> call_with_user_input('', config)
Welcome to icemac.addressbook installation
<BLANKLINE>
Hint: to use the default value (the one in [brackets]),  enter no value.
<BLANKLINE>
 Directory to store python eggs: [py-eggs]
 Log-in name for the administrator: [me]
 Password for the administrator: [secret]
 Hostname: [my.computer.local]
 Port number: [13090]
 Username whether process should run as different user otherwise emtpy: []
 Please choose Log-Handler:
    Details see http://docs.python.org/library/logging.html#handler-objects
 Log-Handler, choose between FileHandler, RotatingFileHandler, TimedRotatingFileHandler: [FileHandler]
 When you need additional packages (e. g. import readers)
 enter the package names here separated by a newline.
 When you are done enter an empty line.
 Known packages:
   icemac.ab.importer -- Import of CSV files
   icemac.ab.importxls -- Import of XLS (Excel) files
 Package 1: []
  Migrate old address book content to new instance: [no]
creating admin.zcml ...
creating buildout.cfg ...
saving config ...

>>> cat('buildout.cfg')
[buildout]
newest = true
allow-picked-versions = true
extends = profiles/prod.cfg
eggs-directory = py-eggs
<BLANKLINE>
[deploy.ini]
log-handler-args = 'a'
host = my.computer.local
log-handler = FileHandler
port = 13090

>>> cat('install.user.ini')
[log]
interval = 1
when = midnight
handler = FileHandler
backups = 5
max_size = 1000
<BLANKLINE>
[admin]
login = me
password = secret
<BLANKLINE>
[server]
username =
host = my.computer.local
port = 13090
<BLANKLINE>
[migration]
do_migration = no
stop_server = no
start_server = no
old_instance =
<BLANKLINE>
[install]
eggs_dir = py-eggs
<BLANKLINE>
[packages]
package_1 =

>>> cat('admin.zcml')
<configure xmlns="http://namespaces.zope.org/zope">
  <principal
    id="icemac.addressbook.global.Administrator"
    title="global administrator"
    login="me"
    password_manager="Plain Text"
    password="secret" />
  <grant
    role="zope.Manager"
    principal="icemac.addressbook.global.Administrator" />
</configure>
