~ibmcharmers/charms/trusty/layer-ibm-mobilefirst-server/devel

« back to all changes in this revision

Viewing changes to deps/layer/layer-apt/lib/charms/apt.py

  • Committer: Suchitra Venugopal
  • Date: 2016-09-06 09:48:53 UTC
  • Revision ID: suchvenu@in.ibm.com-20160906094853-1n09myeisek096nm
IBM MobileFirst Server

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2015-2016 Canonical Ltd.
 
2
#
 
3
# This file is part of the Apt layer for Juju.
 
4
#
 
5
# This program is free software: you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License version 3, as
 
7
# published by the Free Software Foundation.
 
8
#
 
9
# This program is distributed in the hope that it will be useful, but
 
10
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
11
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
12
# PURPOSE.  See the GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
16
 
 
17
'''
 
18
charms.reactive helpers for dealing with deb packages.
 
19
 
 
20
Add apt package sources using add_source(). Queue deb packages for
 
21
installation with install(). Configure and work with your software
 
22
once the apt.installed.{packagename} state is set.
 
23
'''
 
24
import itertools
 
25
import subprocess
 
26
 
 
27
from charmhelpers import fetch
 
28
from charmhelpers.core import hookenv, unitdata
 
29
from charms import reactive
 
30
 
 
31
 
 
32
__all__ = ['add_source', 'update', 'queue_install', 'install_queued',
 
33
           'installed', 'purge', 'ensure_package_status']
 
34
 
 
35
 
 
36
def add_source(source, key=None):
 
37
    '''Add an apt source.
 
38
 
 
39
    Sets the apt.needs_update state.
 
40
 
 
41
    A source may be either a line that can be added directly to
 
42
    sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
 
43
    Personal Package Archives, or a distribution component to enable.
 
44
 
 
45
    The package signing key should be an ASCII armoured GPG key. While
 
46
    GPG key ids are also supported, the retrieval mechanism is insecure.
 
47
    There is no need to specify the package signing key for PPAs or for
 
48
    the main Ubuntu archives.
 
49
    '''
 
50
    # Maybe we should remember which sources have been added already
 
51
    # so we don't waste time re-adding them. Is this time significant?
 
52
    fetch.add_source(source, key)
 
53
    reactive.set_state('apt.needs_update')
 
54
 
 
55
 
 
56
def queue_install(packages, options=None):
 
57
    """Queue one or more deb packages for install.
 
58
 
 
59
    The `apt.installed.{name}` state is set once the package is installed.
 
60
 
 
61
    If a package has already been installed it will not be reinstalled.
 
62
 
 
63
    If a package has already been queued it will not be requeued, and
 
64
    the install options will not be changed.
 
65
 
 
66
    Sets the apt.queued_installs state.
 
67
    """
 
68
    # Filter installed packages.
 
69
    store = unitdata.kv()
 
70
    queued_packages = store.getrange('apt.install_queue.', strip=True)
 
71
    packages = {package: options for package in packages
 
72
                if not (package in queued_packages or
 
73
                        reactive.helpers.is_state('apt.installed.' + package))}
 
74
    if packages:
 
75
        unitdata.kv().update(packages, prefix='apt.install_queue.')
 
76
        reactive.set_state('apt.queued_installs')
 
77
 
 
78
 
 
79
def installed():
 
80
    '''Return the set of deb packages completed install'''
 
81
    return set(state.split('.', 2)[2] for state in reactive.bus.get_states()
 
82
               if state.startswith('apt.installed.'))
 
83
 
 
84
 
 
85
def purge(packages):
 
86
    """Purge one or more deb packages from the system"""
 
87
    fetch.apt_purge(packages, fatal=True)
 
88
    store = unitdata.kv()
 
89
    store.unsetrange(packages, prefix='apt.install_queue.')
 
90
    for package in packages:
 
91
        reactive.remove_state('apt.installed.{}'.format(package))
 
92
 
 
93
 
 
94
def update():
 
95
    """Update the apt cache.
 
96
 
 
97
    Removes the apt.needs_update state.
 
98
    """
 
99
    status_set(None, 'Updating apt cache')
 
100
    fetch.apt_update(fatal=True)  # Friends don't let friends set fatal=False
 
101
    reactive.remove_state('apt.needs_update')
 
102
 
 
103
 
 
104
def install_queued():
 
105
    '''Installs queued deb packages.
 
106
 
 
107
    Removes the apt.queued_installs state and sets the apt.installed state.
 
108
 
 
109
    On failure, sets the unit's workload state to 'blocked' and returns
 
110
    False. Package installs remain queued.
 
111
 
 
112
    On success, sets the apt.installed.{packagename} state for each
 
113
    installed package and returns True.
 
114
    '''
 
115
    store = unitdata.kv()
 
116
    queue = sorted((options, package)
 
117
                   for package, options in store.getrange('apt.install_queue.',
 
118
                                                          strip=True).items())
 
119
 
 
120
    installed = set()
 
121
    for options, batch in itertools.groupby(queue, lambda x: x[0]):
 
122
        packages = [b[1] for b in batch]
 
123
        try:
 
124
            status_set(None, 'Installing {}'.format(','.join(packages)))
 
125
            fetch.apt_install(packages, options, fatal=True)
 
126
            store.unsetrange(packages, prefix='apt.install_queue.')
 
127
            installed.update(packages)
 
128
        except subprocess.CalledProcessError:
 
129
            status_set('blocked',
 
130
                       'Unable to install packages {}'
 
131
                       .format(','.join(packages)))
 
132
            return False  # Without setting reactive state.
 
133
 
 
134
    for package in installed:
 
135
        reactive.set_state('apt.installed.{}'.format(package))
 
136
 
 
137
    reactive.remove_state('apt.queued_installs')
 
138
    return True
 
139
 
 
140
 
 
141
def ensure_package_status():
 
142
    '''Hold or unhold packages per the package_status configuration option.
 
143
 
 
144
    All packages installed using this module and handlers are affected.
 
145
 
 
146
    An mechanism may be added in the future to override this for a
 
147
    subset of installed packages.
 
148
    '''
 
149
    packages = installed()
 
150
    if not packages:
 
151
        return
 
152
    config = hookenv.config()
 
153
    package_status = config['package_status']
 
154
    changed = reactive.helpers.data_changed('apt.package_status',
 
155
                                            (package_status, sorted(packages)))
 
156
    if changed:
 
157
        if package_status == 'hold':
 
158
            hookenv.log('Holding packages {}'.format(','.join(packages)))
 
159
            fetch.apt_hold(packages)
 
160
        else:
 
161
            hookenv.log('Unholding packages {}'.format(','.join(packages)))
 
162
            fetch.apt_unhold(packages)
 
163
    reactive.remove_state('apt.needs_hold')
 
164
 
 
165
 
 
166
def status_set(state, message):
 
167
    """Set the unit's workload status.
 
168
 
 
169
    Set state == None to keep the same state and just change the message.
 
170
    """
 
171
    if state is None:
 
172
        state = hookenv.status_get()[0]
 
173
        if state == 'unknown':
 
174
            state = 'maintenance'  # Guess
 
175
    if state in ('error', 'blocked'):
 
176
        lvl = hookenv.WARNING
 
177
    else:
 
178
        lvl = hookenv.INFO
 
179
    hookenv.status_set(state, message)
 
180
    hookenv.log('{}: {}'.format(state, message), lvl)