"""
Debian packages
===============
This module provides high-level tools for managing Debian/Ubuntu packages
and repositories.
"""
from __future__ import with_statement
from fabric.utils import puts
from fabtools.deb import (
add_apt_key,
apt_key_exists,
install,
is_installed,
uninstall,
update_index,
last_update_time,
)
from fabtools.files import is_file, watch
from fabtools.system import distrib_codename, distrib_release
from fabtools.utils import run_as_root
from fabtools import system
[docs]def key(keyid, filename=None, url=None, keyserver='subkeys.pgp.net', update=False):
"""
Require a PGP key for APT.
::
from fabtools import require
# Varnish signing key from URL
require.deb.key('C4DEFFEB', url='http://repo.varnish-cache.org/debian/GPG-key.txt')
# Nginx signing key from default key server (subkeys.pgp.net)
require.deb.key('7BD9BF62')
# From custom key server
require.deb.key('7BD9BF62', keyserver='keyserver.ubuntu.com')
# From file
require.deb.key('7BD9BF62', filename='nginx.asc')
"""
if not apt_key_exists(keyid):
add_apt_key(keyid=keyid, filename=filename, url=url, keyserver=keyserver, update=update)
[docs]def source(name, uri, distribution, *components):
"""
Require a package source.
::
from fabtools import require
# Official MongoDB packages
require.deb.source('mongodb', 'http://downloads-distro.mongodb.org/repo/ubuntu-upstart', 'dist', '10gen')
"""
from fabtools.require import file as require_file
path = '/etc/apt/sources.list.d/%(name)s.list' % locals()
components = ' '.join(components)
source_line = 'deb %(uri)s %(distribution)s %(components)s\n' % locals()
with watch(path) as config:
require_file(path=path, contents=source_line, use_sudo=True)
if config.changed:
puts('Added APT repository: %s' % source_line)
update_index()
[docs]def ppa(name, auto_accept=True, keyserver=None):
"""
Require a `PPA`_ package source.
Example::
from fabtools import require
# Node.js packages by Chris Lea
require.deb.ppa('ppa:chris-lea/node.js', keyserver='my.keyserver.com')
.. _PPA: https://help.launchpad.net/Packaging/PPA
"""
assert name.startswith('ppa:')
user, repo = name[4:].split('/', 2)
release = float(distrib_release())
if release >= 12.04:
repo = repo.replace('.', '_')
auto_accept = '--yes' if auto_accept else ''
else:
auto_accept = ''
if not isinstance(keyserver, basestring) and keyserver:
keyserver = keyserver[0]
if keyserver:
keyserver = '--keyserver ' + keyserver
else:
keyserver = ''
distrib = distrib_codename()
source = '/etc/apt/sources.list.d/%(user)s-%(repo)s-%(distrib)s.list' % locals()
if not is_file(source):
package('python-software-properties')
run_as_root('add-apt-repository %(auto_accept)s %(keyserver)s %(name)s' % locals(), pty=False)
update_index()
[docs]def package(pkg_name, update=False, version=None):
"""
Require a deb package to be installed.
Example::
from fabtools import require
# Require a package
require.deb.package('foo')
# Require a specific version
require.deb.package('firefox', version='11.0+build1-0ubuntu4')
"""
if not is_installed(pkg_name):
install(pkg_name, update=update, version=version)
[docs]def packages(pkg_list, update=False):
"""
Require several deb packages to be installed.
Example::
from fabtools import require
require.deb.packages([
'foo',
'bar',
'baz',
])
"""
pkg_list = [pkg for pkg in pkg_list if not is_installed(pkg)]
if pkg_list:
install(pkg_list, update)
[docs]def nopackage(pkg_name):
"""
Require a deb package to be uninstalled.
Example::
from fabtools import require
require.deb.nopackage('apache2')
"""
if is_installed(pkg_name):
uninstall(pkg_name)
[docs]def nopackages(pkg_list):
"""
Require several deb packages to be uninstalled.
Example::
from fabtools import require
require.deb.nopackages([
'perl',
'php5',
'ruby',
])
"""
pkg_list = [pkg for pkg in pkg_list if is_installed(pkg)]
if pkg_list:
uninstall(pkg_list)
def _to_seconds(var):
sec = 0
MINUTE = 60
HOUR = 60 * MINUTE
DAY = 24 * HOUR
WEEK = 7 * DAY
MONTH = 31 * DAY
try:
for key, value in var.items():
if key in ('second', 'seconds'):
sec += value
elif key in ('minute', 'minutes'):
sec += value * MINUTE
elif key in ('hour', 'hours'):
sec += value * HOUR
elif key in ('day', 'days'):
sec += value * DAY
elif key in ('week', 'weeks'):
sec += value * WEEK
elif key in ('month', 'months'):
sec += value * MONTH
else:
raise ValueError("Unknown time unit '%s'" % key)
return sec
except AttributeError:
return var
[docs]def uptodate_index(quiet=True, max_age=86400):
"""
Require an up-to-date package index.
This will update the package index (using ``apt-get update``) if the last
update occured more than *max_age* ago.
*max_age* can be specified either as an integer (a value in seconds),
or as a dictionary whose keys are units (``seconds``, ``minutes``,
``hours``, ``days``, ``weeks``, ``months``) and values are integers.
The default value is 1 hour.
Examples: ::
from fabtools import require
# Update index if last time was more than 1 day ago
require.deb.uptodate_index(max_age={'day': 1})
# Update index if last time was more than 1 hour and 30 minutes ago
require.deb.uptodate_index(max_age={'hour': 1, 'minutes': 30})
"""
from fabtools.require import file as require_file
require_file('/etc/apt/apt.conf.d/15fabtools-update-stamp', contents='''\
APT::Update::Post-Invoke-Success {"touch /var/lib/apt/periodic/fabtools-update-success-stamp 2>/dev/null || true";};
''', use_sudo=True)
if system.time() - last_update_time() > _to_seconds(max_age):
update_index(quiet=quiet)