#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
Roles in this namespace are meant to provision packages installed via the `PIP <http://www.pip-installer.org/>`_ package manager for Debian distributions.
'''
from fabric.api import settings
from provy.core import Role
from provy.more.debian.package.aptitude import AptitudeRole
import xmlrpclib
[docs]class PipRole(Role):
'''
This role provides package management operations with `PIP <http://www.pip-installer.org/>`_ within Debian distributions.
By default, all commands executed with this role will be executed with sudo, unless you set a different user (refer to the :meth:`set_user` method below).
You can also change the class parameters below in the class directly to have a global effect (use carefully!).
:var use_sudo: If :data:`False`, the packages will be installed as normal user. Defaults to :data:`True`.
:type use_sudo: :class:`bool`
:var user: User through which the packages will be installed. Defaults to :data:`None`, which means that, using together with the default use_sudo, will install packages globally.
:type user: :class:`str`
Example:
::
from provy.core import Role
from provy.more.debian import PipRole
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
role.ensure_package_installed('django', version='1.1.1')
'''
use_sudo = True
user = None
[docs] def provision(self):
'''
Installs pip dependencies. This method should be called upon if overriden in base classes, or PIP won't work properly in the remote server.
Example:
::
class MySampleRole(Role):
def provision(self):
self.provision_role(PipRole) # does not need to be called if using with block.
'''
with self.using(AptitudeRole) as role:
role.ensure_up_to_date()
role.ensure_package_installed('python-setuptools')
role.ensure_package_installed('python-dev')
self.execute("easy_install pip", sudo=True, stdout=False, user=None)
[docs] def is_package_installed(self, package_name, version=None):
'''
Returns :data:`True` if the given package is installed via pip in the remote server, :data:`False` otherwise.
:param package_name: Name of the package to verify
:type package_name: :class:`str`
:param version: Version to check for. Defaults to :data:`None`, which makes it check for any version.
:type version: :class:`str`
:return: Whether the package is installed or not.
:rtype: :class:`bool`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
if role.is_package_installed('django', version='1.1.1'):
pass
'''
with settings(warn_only=True):
package_info = self.extract_package_data_from_input(package_name)
if not version:
package_name = package_info['name']
package_string = self.execute("pip freeze | tr '[A-Z]' '[a-z]' | grep %s" % package_name, stdout=False, sudo=self.use_sudo, user=self.user)
if package_name in package_string:
installed_version = package_string.split('==')[-1]
if 'version' in package_info:
if '>=' == package_info['version_constraint']:
if installed_version < package_info['version']:
return False
elif version and installed_version != version:
return False
return True
[docs] def get_package_remote_version(self, package_name):
'''
Returns the version of the package currently installed via PIP in the remote server. If package is not installed, returns :data:`None`.
:param package_name: Name of the package to verify
:type package_name: :class:`str`
:return: The package version.
:rtype: :class:`str`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
version = role.get_package_remote_version('django')
if version and version == '1.1.1':
pass
'''
with settings(warn_only=True):
result = self.execute("pip freeze | tr '[A-Z]' '[a-z]' | grep %s" % package_name.lower(), stdout=False, sudo=self.use_sudo, user=self.user)
if result:
package, version = result.split('==')
return version
return None
[docs] def get_package_latest_version(self, package_name):
'''
Returns the latest available version of the package at the Python Package Index. If package is not available, returns :data:`None`.
:param package_name: Name of the package to verify
:type package_name: :class:`str`
:return: The package version.
:rtype: :class:`str`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
version = role.get_package_remote_version('django')
latest = role.get_package_latest_version('django')
if version != latest:
pass
# this check is not needed if you use ensure_package_up_to_date.
'''
pypi = xmlrpclib.ServerProxy('http://pypi.python.org/pypi')
available = pypi.package_releases(package_name)
if not available:
# Try to capitalize pkg name
available = pypi.package_releases(package_name.capitalize())
if not available:
return None
return available[0]
[docs] def package_can_be_updated(self, package_name):
'''
Returns :data:`True` if there is an update for the given package in the Python Package Index, False otherwise.
:param package_name: Name of the package to verify
:type package_name: :class:`str`
:return: Whether the package can be updated.
:rtype: :class:`bool`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
if role.package_can_be_updated('django'):
pass
# this check is not needed if you use ensure_package_up_to_date.
'''
remote_version = self.get_package_remote_version(package_name)
latest_version = self.get_package_latest_version(package_name)
return remote_version != latest_version
[docs] def ensure_package_installed(self, package_name, version=None):
'''
Makes sure the package is installed with the specified version (latest if :data:`None` specified).
This method does not verify and upgrade the package on subsequent provisions, though. Use :meth:`ensure_package_up_to_date` for this purpose instead.
:param package_name: Name of the package to install.
:type package_name: :class:`str`
:param version: If specified, installs this version of the package. Installs latest version otherwise. You can use >= or <= before version number to ensure package version.
:type version: :class:`str`
:return: Whether the package had to be installed or not.
:rtype: :class:`bool`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
role.ensure_package_installed('django', version='1.1.1')
'''
if version:
package_info = self.extract_package_data_from_input(version)
version_constraint = package_info.get('version_constraint', '==')
version = package_info.get('version', version)
if not self.is_package_installed(package_name, version):
self.log('%s version %s should be installed (via pip)! Rectifying that...' % (package_name, version))
self.execute('pip install %s%s%s' % (package_name, version_constraint, version), stdout=False, sudo=self.use_sudo, user=self.user)
self.log('%s version %s installed!' % (package_name, version))
return True
elif not self.is_package_installed(package_name):
self.log('%s is not installed (via pip)! Installing...' % package_name)
self.execute('pip install %s' % package_name, stdout=False, sudo=self.use_sudo, user=self.user)
self.log('%s installed!' % package_name)
return True
return False
[docs] def ensure_requirements_installed(self, requirements_file_name):
'''
Makes sure the requirements file provided is installed.
:param requirements_file_name: Path to the requirements file (can be provided as absolute path or relative to the directory where provy is run from).
:type requirements_file_name: :class:`str`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
role.ensure_requirements_installed('/path/to/requirements.txt')
'''
with open(requirements_file_name, 'r') as requirements_file:
for requirement in requirements_file.readlines():
self.ensure_package_installed(requirement.strip())
[docs] def ensure_package_up_to_date(self, package_name):
'''
Makes sure the package is installed and up-to-date with the latest version.
This method verifies if there is a newer version for this package every time the server is provisioned. If a new version is found, it is installed.
:param package_name: Name of the package to install.
:type package_name: :class:`str`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
role.ensure_package_is_up_to_date('django')
'''
is_installed = self.is_package_installed(package_name)
if is_installed and self.package_can_be_updated(package_name):
self.log('%s is installed (via pip)! Updating...' % package_name)
self.execute('pip install -U --no-dependencies %s' % package_name, stdout=False, sudo=self.use_sudo, user=self.user)
self.log('%s updated!' % package_name)
return True
elif not is_installed:
self.ensure_package_installed(package_name)
return True
self.log('%s is up to date (via pip).' % package_name)
return False
[docs] def set_user(self, user):
'''
Prepares the pip role instance to run its commands as a specific user.
:param user: The username with which the role should run its commands.
:type user: :class:`str`
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
role.ensure_package_installed('django') # runs as sudo
role.set_user('johndoe')
role.ensure_package_installed('django') # runs as "johndoe" user
'''
self.user = user
self.use_sudo = False
[docs] def set_sudo(self):
'''
Prepares the pip role instance to run its commands with sudo; This is useful when you had previously set a user, and want it to run back as sudo.
Example:
::
class MySampleRole(Role):
def provision(self):
with self.using(PipRole) as role:
role.ensure_package_installed('django') # runs as sudo
role.set_user('johndoe')
role.ensure_package_installed('django') # runs as "johndoe" user
role.set_sudo()
role.ensure_package_installed('django') # runs as sudo
'''
self.user = None
self.use_sudo = True