0001
0002
0003"""
0004Provides the two commands for preparing an application:
0005``prepare-app`` and ``setup-app``
0006"""
0007
0008import os
0009import sys
0010if sys.version_info < (2, 4):
0011 from paste.script.util import string24 as string
0012else:
0013 import string
0014import new
0015from cStringIO import StringIO
0016from paste.script.command import Command, BadCommand, run as run_command
0017import paste.script.templates
0018from paste.script import copydir
0019import pkg_resources
0020Cheetah = None
0021from ConfigParser import ConfigParser
0022from paste.util import import_string
0023from paste.deploy import appconfig
0024from paste.script.util import uuid
0025from paste.script.util import secret
0026
0027class AbstractInstallCommand(Command):
0028
0029 default_interactive = 1
0030
0031 default_sysconfigs = [
0032 (False, '/etc/paste/sysconfig.py'),
0033 (False, '/usr/local/etc/paste/sysconfig.py'),
0034 (True, 'paste.script.default_sysconfig'),
0035 ]
0036 if os.environ.get('HOME'):
0037 default_sysconfigs.insert(
0038 0, (False, os.path.join(os.environ['HOME'], '.paste', 'config',
0039 'sysconfig.py')))
0040 if os.environ.get('PASTE_SYSCONFIG'):
0041 default_sysconfigs.insert(
0042 0, (False, os.environ['PASTE_SYSCONFIG']))
0043
0044 def run(self, args):
0045
0046
0047 self.sysconfigs = self.default_sysconfigs
0048 new_args = []
0049 while args:
0050 if args[0].startswith('--no-default-sysconfig'):
0051 self.sysconfigs = []
0052 args.pop(0)
0053 continue
0054 if args[0].startswith('--sysconfig='):
0055 self.sysconfigs.insert(
0056 0, (True, args.pop(0)[len('--sysconfig='):]))
0057 continue
0058 if args[0] == '--sysconfig':
0059 args.pop(0)
0060 if not args:
0061 raise BadCommand, (
0062 "You gave --sysconfig as the last argument without "
0063 "a value")
0064 self.sysconfigs.insert(0, (True, args.pop(0)))
0065 continue
0066 new_args.append(args.pop(0))
0067 self.load_sysconfigs()
0068 return super(AbstractInstallCommand, self).run(new_args)
0069
0070
0071 def standard_parser(cls, **kw):
0072 parser = super(AbstractInstallCommand, cls).standard_parser(**kw)
0073 parser.add_option('--sysconfig',
0074 action="append",
0075 dest="sysconfigs",
0076 help="System configuration file")
0077 parser.add_option('--no-default-sysconfig',
0078 action='store_true',
0079 dest='no_default_sysconfig',
0080 help="Don't load the default sysconfig files")
0081 parser.add_option(
0082 '--easy-install',
0083 action='append',
0084 dest='easy_install_op',
0085 metavar='OP',
0086 help='An option to add if invoking easy_install (like --easy-install=exclude-scripts)')
0087 parser.add_option(
0088 '--no-install',
0089 action='store_true',
0090 dest='no_install',
0091 help="Don't try to install the package (it must already be installed)")
0092 parser.add_option(
0093 '-f', '--find-links',
0094 action='append',
0095 dest='easy_install_find_links',
0096 metavar='URL',
0097 help='Passed through to easy_install')
0098
0099 return parser
0100
0101 standard_parser = classmethod(standard_parser)
0102
0103
0104
0105
0106
0107 def load_sysconfigs(self):
0108 configs = self.sysconfigs[:]
0109 configs.reverse()
0110 self.sysconfig_modules = []
0111 for index, (explicit, name) in enumerate(configs):
0112
0113
0114
0115
0116 if name.endswith('.py'):
0117 if not os.path.exists(name):
0118 if explicit:
0119 raise BadCommand, (
0120 "sysconfig file %s does not exist"
0121 % name)
0122 else:
0123 continue
0124 globs = {}
0125 execfile(name, globs)
0126 mod = new.module('__sysconfig_%i__' % index)
0127 for name, value in globs.items():
0128 setattr(mod, name, value)
0129 mod.__file__ = name
0130 else:
0131 try:
0132 mod = import_string.simple_import(name)
0133 except ImportError, e:
0134 if explicit:
0135 raise
0136 else:
0137 continue
0138 mod.paste_command = self
0139 self.sysconfig_modules.insert(0, mod)
0140
0141
0142 parser = self.parser
0143 self.call_sysconfig_functions('add_custom_options', parser)
0144
0145 def get_sysconfig_option(self, name, default=None):
0146 """
0147 Return the value of the given option in the first sysconfig
0148 module in which it is found, or ``default`` (None) if not
0149 found in any.
0150 """
0151 for mod in self.sysconfig_modules:
0152 if hasattr(mod, name):
0153 return getattr(mod, name)
0154 return default
0155
0156 def get_sysconfig_options(self, name):
0157 """
0158 Return the option value for the given name in all the
0159 sysconfig modules in which is is found (``[]`` if none).
0160 """
0161 return [getattr(mod, name) for mod in self.sysconfig_modules
0162 if hasattr(mod, name)]
0163
0164 def call_sysconfig_function(self, name, *args, **kw):
0165 """
0166 Call the specified function in the first sysconfig module it
0167 is defined in. ``NameError`` if no function is found.
0168 """
0169 val = self.get_sysconfig_option(name)
0170 if val is None:
0171 raise NameError, (
0172 "Method %s not found in any sysconfig module" % name)
0173 return val(*args, **kw)
0174
0175 def call_sysconfig_functions(self, name, *args, **kw):
0176 """
0177 Call all the named functions in the sysconfig modules,
0178 returning a list of the return values.
0179 """
0180 return [method(*args, **kw) for method in
0181 self.get_sysconfig_options(name)]
0182
0183 def sysconfig_install_vars(self, installer):
0184 """
0185 Return the folded results of calling the
0186 ``install_variables()`` functions.
0187 """
0188 result = {}
0189 all_vars = self.call_sysconfig_functions(
0190 'install_variables', installer)
0191 all_vars.reverse()
0192 for vardict in all_vars:
0193 result.update(vardict)
0194 return result
0195
0196
0197
0198
0199
0200 def get_distribution(self, req):
0201 """
0202 This gets a distribution object, and installs the distribution
0203 if required.
0204 """
0205 try:
0206 dist = pkg_resources.get_distribution(req)
0207 if self.verbose:
0208 print 'Distribution already installed:'
0209 print ' ', dist, 'from', dist.location
0210 return dist
0211 except pkg_resources.DistributionNotFound:
0212 if self.options.no_install:
0213 print "Because --no-install was given, we won't try to install the package %s" % req
0214 raise
0215 options = ['-v', '-m']
0216 for op in self.options.easy_install_op or []:
0217 if not op.startswith('-'):
0218 op = '--'+op
0219 options.append(op)
0220 for op in self.options.easy_install_find_links or []:
0221 options.append('--find-links=%s' % op)
0222 if self.simulate:
0223 raise BadCommand(
0224 "Must install %s, but in simulation mode" % req)
0225 print "Must install %s" % req
0226 from setuptools.command import easy_install
0227 from setuptools import setup
0228 setup(script_args=['-q', 'easy_install']
0229 + options + [req])
0230 return pkg_resources.get_distribution(req)
0231
0232 def get_installer(self, distro, ep_group, ep_name):
0233 installer_class = distro.load_entry_point(
0234 'paste.app_install', ep_name)
0235 installer = installer_class(
0236 distro, ep_group, ep_name)
0237 return installer
0238
0239
0240class MakeConfigCommand(AbstractInstallCommand):
0241
0242 default_verbosity = 1
0243 max_args = None
0244 min_args = 1
0245 summary = "Install a package and create a fresh config file/directory"
0246 usage = "PACKAGE_NAME [CONFIG_FILE] [VAR=VALUE]"
0247
0248 description = """\
0249 Note: this is an experimental command, and it will probably change
0250 in several ways by the next release.
0251
0252 make-config is part of a two-phase installation process (the
0253 second phase is setup-app). make-config installs the package
0254 (using easy_install) and asks it to create a bare configuration
0255 file or directory (possibly filling in defaults from the extra
0256 variables you give).
0257 """
0258
0259 parser = AbstractInstallCommand.standard_parser(
0260 simulate=True, quiet=True, no_interactive=True)
0261 parser.add_option('--info',
0262 action="store_true",
0263 dest="show_info",
0264 help="Show information on the package (after installing it), but do not write a config.")
0265 parser.add_option('--name',
0266 action='store',
0267 dest='ep_name',
0268 help='The name of the application contained in the distribution (default "main")')
0269 parser.add_option('--entry-group',
0270 action='store',
0271 dest='ep_group',
0272 default='paste.app_factory',
0273 help='The entry point group to install (i.e., the kind of application; default paste.app_factory')
0274 parser.add_option('--edit',
0275 action='store_true',
0276 dest='edit',
0277 help='Edit the configuration file after generating it (using $EDITOR)')
0278 parser.add_option('--setup',
0279 action='store_true',
0280 dest='run_setup',
0281 help='Run setup-app immediately after generating (and possibly editing) the configuration file')
0282
0283 def command(self):
0284 self.requirement = self.args[0]
0285 if '#' in self.requirement:
0286 if self.options.ep_name is not None:
0287 raise BadCommand(
0288 "You may not give both --name and a requirement with "
0289 "#name")
0290 self.requirement, self.options.ep_name = self.requirement.split('#', 1)
0291 if not self.options.ep_name:
0292 self.options.ep_name = 'main'
0293 self.distro = self.get_distribution(self.requirement)
0294 self.installer = self.get_installer(
0295 self.distro, self.options.ep_group, self.options.ep_name)
0296 if self.options.show_info:
0297 if len(self.args) > 1:
0298 raise BadCommand(
0299 "With --info you can only give one argument")
0300 return self.show_info()
0301 if len(self.args) < 2:
0302
0303 options = filter(None, self.call_sysconfig_functions(
0304 'default_config_filename', self.installer))
0305 if not options:
0306 raise BadCommand(
0307 "You must give a configuration filename")
0308 self.config_file = options[0]
0309 else:
0310 self.config_file = self.args[1]
0311 self.check_config_file()
0312 self.project_name = self.distro.project_name
0313 self.vars = self.sysconfig_install_vars(self.installer)
0314 self.vars.update(self.parse_vars(self.args[2:]))
0315 self.vars['project_name'] = self.project_name
0316 self.vars['requirement'] = self.requirement
0317 self.vars['ep_name'] = self.options.ep_name
0318 self.vars['ep_group'] = self.options.ep_group
0319 self.vars.setdefault('app_name', self.project_name.lower())
0320 self.vars.setdefault('app_instance_uuid', uuid.uuid4())
0321 self.vars.setdefault('app_instance_secret', secret.secret_string())
0322 if self.verbose > 1:
0323 print_vars = self.vars.items()
0324 print_vars.sort()
0325 print 'Variables for installation:'
0326 for name, value in print_vars:
0327 print ' %s: %r' % (name, value)
0328 self.installer.write_config(self, self.config_file, self.vars)
0329 edit_success = True
0330 if self.options.edit:
0331 edit_success = self.run_editor()
0332 setup_configs = self.installer.editable_config_files(self.config_file)
0333
0334
0335 setup_config = setup_configs[0]
0336 if self.options.run_setup:
0337 if not edit_success:
0338 print 'Config-file editing was not successful.'
0339 if self.ask('Run setup-app anyway?', default=False):
0340 self.run_setup(setup_config)
0341 else:
0342 self.run_setup(setup_config)
0343 else:
0344 filenames = self.installer.editable_config_files(self.config_file)
0345 assert not isinstance(filenames, basestring), (
0346 "editable_config_files returned a string, not a list")
0347 if not filenames and filenames is not None:
0348 print 'No config files need editing'
0349 else:
0350 print 'Now you should edit the config files'
0351 if filenames:
0352 for fn in filenames:
0353 print ' %s' % fn
0354
0355 def show_info(self):
0356 text = self.installer.description(None)
0357 print text
0358
0359 def check_config_file(self):
0360 if self.installer.expect_config_directory is None:
0361 return
0362 fn = self.config_file
0363 if self.installer.expect_config_directory:
0364 if os.path.splitext(fn)[1]:
0365 raise BadCommand(
0366 "The CONFIG_FILE argument %r looks like a filename, "
0367 "and a directory name is expected" % fn)
0368 else:
0369 if fn.endswith('/') or not os.path.splitext(fn):
0370 raise BadCommand(
0371 "The CONFIG_FILE argument %r looks like a directory "
0372 "name and a filename is expected" % fn)
0373
0374 def run_setup(self, filename):
0375 run_command(['setup-app', filename])
0376
0377 def run_editor(self):
0378 filenames = self.installer.editable_config_files(self.config_file)
0379 if filenames is None:
0380 print 'Warning: the config file is not known (--edit ignored)'
0381 return False
0382 if not filenames:
0383 print 'Warning: no config files need editing (--edit ignored)'
0384 return True
0385 if len(filenames) > 1:
0386 print 'Warning: there is more than one editable config file (--edit ignored)'
0387 return False
0388 if not os.environ.get('EDITOR'):
0389 print 'Error: you must set $EDITOR if using --edit'
0390 return False
0391 if self.verbose:
0392 print '%s %s' % (os.environ['EDITOR'], filenames[0])
0393 retval = os.system('$EDITOR %s' % filenames[0])
0394 if retval:
0395 print 'Warning: editor %s returned with error code %i' % (
0396 os.environ['EDITOR'], retval)
0397 return False
0398 return True
0399
0400class SetupCommand(AbstractInstallCommand):
0401
0402 default_verbosity = 1
0403 max_args = 1
0404 min_args = 1
0405 summary = "Setup an application, given a config file"
0406 usage = "CONFIG_FILE"
0407
0408 description = """\
0409 Note: this is an experimental command, and it will probably change
0410 in several ways by the next release.
0411
0412 Setup an application according to its configuration file. This is
0413 the second part of a two-phase web application installation
0414 process (the first phase is prepare-app). The setup process may
0415 consist of things like creating directories and setting up
0416 databases.
0417 """
0418
0419 parser = AbstractInstallCommand.standard_parser(
0420 simulate=True, quiet=True, interactive=True)
0421 parser.add_option('--name',
0422 action='store',
0423 dest='section_name',
0424 default=None,
0425 help='The name of the section to set up (default: app:main)')
0426
0427 def command(self):
0428 config_spec = self.args[0]
0429 section = self.options.section_name
0430 if section is None:
0431 if '#' in config_spec:
0432 config_spec, section = config_spec.split('#', 1)
0433 else:
0434 section = 'main'
0435 if not ':' in section:
0436 plain_section = section
0437 section = 'app:'+section
0438 else:
0439 plain_section = section.split(':', 1)[0]
0440 if not config_spec.startswith('config:'):
0441 config_spec = 'config:' + config_spec
0442 if plain_section != 'main':
0443 config_spec += '#' + plain_section
0444 config_file = config_spec[len('config:'):].split('#', 1)[0]
0445 config_file = os.path.join(os.getcwd(), config_file)
0446 self.logging_file_config(config_file)
0447 conf = appconfig(config_spec, relative_to=os.getcwd())
0448 ep_name = conf.context.entry_point_name
0449 ep_group = conf.context.protocol
0450 dist = conf.context.distribution
0451 if dist is None:
0452 raise BadCommand(
0453 "The section %r is not the application (probably a filter). You should add #section_name, where section_name is the section that configures your application" % plain_section)
0454 installer = self.get_installer(dist, ep_group, ep_name)
0455 installer.setup_config(
0456 self, config_file, section, self.sysconfig_install_vars(installer))
0457 self.call_sysconfig_functions(
0458 'post_setup_hook', installer, config_file)
0459
0460
0461class Installer(object):
0462
0463 """
0464 Abstract base class for installers, and also a generic
0465 installer that will run off config files in the .egg-info
0466 directory of a distribution.
0467
0468 Packages that simply refer to this installer can provide a file
0469 ``*.egg-info/paste_deploy_config.ini_tmpl`` that will be
0470 interpreted by Cheetah. They can also provide ``websetup``
0471 modules with a ``setup_app(command, conf, vars)`` (or the
0472 now-deprecated ``setup_config(command, filename, section, vars)``)
0473 function, that will be called.
0474
0475 In the future other functions or configuration files may be
0476 called.
0477 """
0478
0479
0480
0481
0482
0483 expect_config_directory = False
0484
0485
0486
0487 default_config_filename = None
0488
0489
0490
0491 use_cheetah = True
0492
0493 def __init__(self, dist, ep_group, ep_name):
0494 self.dist = dist
0495 self.ep_group = ep_group
0496 self.ep_name = ep_name
0497
0498 def description(self, config):
0499 return 'An application'
0500
0501 def write_config(self, command, filename, vars):
0502 """
0503 Writes the content to the filename (directory or single file).
0504 You should use the ``command`` object, which respects things
0505 like simulation and interactive. ``vars`` is a dictionary
0506 of user-provided variables.
0507 """
0508 command.ensure_file(filename, self.config_content(command, vars))
0509
0510 def editable_config_files(self, filename):
0511 """
0512 Return a list of filenames; this is primarily used when the
0513 filename is treated as a directory and several configuration
0514 files are created. The default implementation returns the
0515 file itself. Return None if you don't know what files should
0516 be edited on installation.
0517 """
0518 if not self.expect_config_directory:
0519 return [filename]
0520 else:
0521 return None
0522
0523 def config_content(self, command, vars):
0524 """
0525 Called by ``self.write_config``, this returns the text content
0526 for the config file, given the provided variables.
0527
0528 The default implementation reads
0529 ``Package.egg-info/paste_deploy_config.ini_tmpl`` and fills it
0530 with the variables.
0531 """
0532 global Cheetah
0533 meta_name = 'paste_deploy_config.ini_tmpl'
0534 if not self.dist.has_metadata(meta_name):
0535 if command.verbose:
0536 print 'No %s found' % meta_name
0537 return self.simple_config(vars)
0538 return self.template_renderer(
0539 self.dist.get_metadata(meta_name), vars, filename=meta_name)
0540
0541 def template_renderer(self, content, vars, filename=None):
0542 """
0543 Subclasses may override this to provide different template
0544 substitution (e.g., use a different template engine).
0545 """
0546 if self.use_cheetah:
0547 import Cheetah.Template
0548 tmpl = Cheetah.Template.Template(content,
0549 searchList=[vars])
0550 return copydir.careful_sub(
0551 tmpl, vars, filename)
0552 else:
0553 tmpl = string.Template(content)
0554 return tmpl.substitute(vars)
0555
0556 def simple_config(self, vars):
0557 """
0558 Return a very simple configuration file for this application.
0559 """
0560 if self.ep_name != 'main':
0561 ep_name = '#'+self.ep_name
0562 else:
0563 ep_name = ''
0564 return ('[app:main]\n'
0565 'use = egg:%s%s\n'
0566 % (self.dist.project_name, ep_name))
0567
0568 def setup_config(self, command, filename, section, vars):
0569 """
0570 Called to setup an application, given its configuration
0571 file/directory.
0572
0573 The default implementation calls
0574 ``package.websetup.setup_config(command, filename, section,
0575 vars)`` or ``package.websetup.setup_app(command, config,
0576 vars)``
0577
0578 With ``setup_app`` the ``config`` object is a dictionary with
0579 the extra attributes ``global_conf``, ``local_conf`` and
0580 ``filename``
0581 """
0582 modules = [
0583 line.strip()
0584 for line in self.dist.get_metadata_lines('top_level.txt')
0585 if line.strip() and not line.strip().startswith('#')]
0586 if not modules:
0587 print 'No modules are listed in top_level.txt'
0588 print 'Try running python setup.py egg_info to regenerate that file'
0589 for mod_name in modules:
0590 mod_name = mod_name + '.websetup'
0591 mod = import_string.try_import_module(mod_name)
0592 if mod is None:
0593 continue
0594 if command.verbose:
0595 print 'Running setup_config() from %s' % mod_name
0596 if hasattr(mod, 'setup_app'):
0597 self._call_setup_app(
0598 mod.setup_app, command, filename, section, vars)
0599 elif hasattr(mod, 'setup_config'):
0600 mod.setup_config(command, filename, section, vars)
0601 else:
0602 print 'No setup_app() or setup_config() function in %s (%s)' % (
0603 mod.__name__, mod.__file__)
0604
0605 def _call_setup_app(self, func, command, filename, section, vars):
0606 filename = os.path.abspath(filename)
0607 if ':' in section:
0608 section = section.split(':', 1)[1]
0609 conf = 'config:%s#%s' % (filename, section)
0610 conf = appconfig(conf)
0611 conf.filename = filename
0612 func(command, conf, vars)