20
23
PROPOSED_POCKET = """# Proposed
21
24
deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
26
CLOUD_ARCHIVE_POCKETS = {
28
'folsom': 'precise-updates/folsom',
29
'precise-folsom': 'precise-updates/folsom',
30
'precise-folsom/updates': 'precise-updates/folsom',
31
'precise-updates/folsom': 'precise-updates/folsom',
32
'folsom/proposed': 'precise-proposed/folsom',
33
'precise-folsom/proposed': 'precise-proposed/folsom',
34
'precise-proposed/folsom': 'precise-proposed/folsom',
36
'grizzly': 'precise-updates/grizzly',
37
'precise-grizzly': 'precise-updates/grizzly',
38
'precise-grizzly/updates': 'precise-updates/grizzly',
39
'precise-updates/grizzly': 'precise-updates/grizzly',
40
'grizzly/proposed': 'precise-proposed/grizzly',
41
'precise-grizzly/proposed': 'precise-proposed/grizzly',
42
'precise-proposed/grizzly': 'precise-proposed/grizzly',
44
'havana': 'precise-updates/havana',
45
'precise-havana': 'precise-updates/havana',
46
'precise-havana/updates': 'precise-updates/havana',
47
'precise-updates/havana': 'precise-updates/havana',
48
'havana/proposed': 'precise-proposed/havana',
49
'precise-havana/proposed': 'precise-proposed/havana',
50
'precise-proposed/havana': 'precise-proposed/havana',
52
'icehouse': 'precise-updates/icehouse',
53
'precise-icehouse': 'precise-updates/icehouse',
54
'precise-icehouse/updates': 'precise-updates/icehouse',
55
'precise-updates/icehouse': 'precise-updates/icehouse',
56
'icehouse/proposed': 'precise-proposed/icehouse',
57
'precise-icehouse/proposed': 'precise-proposed/icehouse',
58
'precise-proposed/icehouse': 'precise-proposed/icehouse',
61
# The order of this list is very important. Handlers should be listed in from
62
# least- to most-specific URL matching.
64
'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
65
'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
68
APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
69
APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
70
APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
73
class SourceConfigError(Exception):
77
class UnhandledSource(Exception):
81
class AptLockError(Exception):
85
class BaseFetchHandler(object):
87
"""Base class for FetchHandler implementations in fetch plugins"""
89
def can_handle(self, source):
90
"""Returns True if the source can be handled. Otherwise returns
91
a string explaining why it cannot"""
92
return "Wrong source type"
94
def install(self, source):
95
"""Try to download and unpack the source. Return the path to the
96
unpacked files or raise UnhandledSource."""
97
raise UnhandledSource("Wrong source type {}".format(source))
99
def parse_url(self, url):
102
def base_url(self, url):
103
"""Return url without querystring or fragment"""
104
parts = list(self.parse_url(url))
105
parts[4:] = ['' for i in parts[4:]]
106
return urlunparse(parts)
25
109
def filter_installed_packages(packages):
26
110
"""Returns a list of packages that require installation"""
113
# Tell apt to build an in-memory cache to prevent race conditions (if
114
# another process is already building the cache).
115
apt_pkg.config.set("Dir::Cache::pkgcache", "")
28
117
cache = apt_pkg.Cache()
30
119
for package in packages:
50
141
cmd.extend(packages)
51
142
log("Installing {} with options: {}".format(packages,
54
subprocess.check_call(cmd)
144
_run_apt_command(cmd, fatal)
147
def apt_upgrade(options=None, fatal=False, dist=False):
148
"""Upgrade all packages"""
150
options = ['--option=Dpkg::Options::=--force-confold']
152
cmd = ['apt-get', '--assume-yes']
155
cmd.append('dist-upgrade')
157
cmd.append('upgrade')
158
log("Upgrading with options: {}".format(options))
159
_run_apt_command(cmd, fatal)
59
162
def apt_update(fatal=False):
60
163
"""Update local apt cache"""
61
164
cmd = ['apt-get', 'update']
63
subprocess.check_call(cmd)
165
_run_apt_command(cmd, fatal)
68
168
def apt_purge(packages, fatal=False):
69
169
"""Purge one or more packages"""
70
cmd = ['apt-get', '-y', 'purge']
170
cmd = ['apt-get', '--assume-yes', 'purge']
71
171
if isinstance(packages, basestring):
72
172
cmd.append(packages)
74
174
cmd.extend(packages)
75
175
log("Purging {}".format(packages))
176
_run_apt_command(cmd, fatal)
179
def apt_hold(packages, fatal=False):
180
"""Hold one or more packages"""
181
cmd = ['apt-mark', 'hold']
182
if isinstance(packages, basestring):
186
log("Holding {}".format(packages))
77
189
subprocess.check_call(cmd)
82
194
def add_source(source, key=None):
83
if ((source.startswith('ppa:') or
84
source.startswith('http:'))):
196
log('Source is not present. Skipping')
199
if (source.startswith('ppa:') or
200
source.startswith('http') or
201
source.startswith('deb ') or
202
source.startswith('cloud-archive:')):
85
203
subprocess.check_call(['add-apt-repository', '--yes', source])
86
204
elif source.startswith('cloud:'):
87
205
apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
89
207
pocket = source.split(':')[-1]
208
if pocket not in CLOUD_ARCHIVE_POCKETS:
209
raise SourceConfigError(
210
'Unsupported cloud: source option %s' %
212
actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
90
213
with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
91
apt.write(CLOUD_ARCHIVE.format(pocket))
214
apt.write(CLOUD_ARCHIVE.format(actual_pocket))
92
215
elif source == 'proposed':
93
216
release = lsb_release()['DISTRIB_CODENAME']
94
217
with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
95
218
apt.write(PROPOSED_POCKET.format(release))
97
subprocess.check_call(['apt-key', 'import', key])
100
class SourceConfigError(Exception):
220
subprocess.check_call(['apt-key', 'adv', '--keyserver',
221
'hkp://keyserver.ubuntu.com:80', '--recv',
104
225
def configure_sources(update=False,
171
284
return install_remote(source)
174
class BaseFetchHandler(object):
175
"""Base class for FetchHandler implementations in fetch plugins"""
176
def can_handle(self, source):
177
"""Returns True if the source can be handled. Otherwise returns
178
a string explaining why it cannot"""
179
return "Wrong source type"
181
def install(self, source):
182
"""Try to download and unpack the source. Return the path to the
183
unpacked files or raise UnhandledSource."""
184
raise UnhandledSource("Wrong source type {}".format(source))
186
def parse_url(self, url):
189
def base_url(self, url):
190
"""Return url without querystring or fragment"""
191
parts = list(self.parse_url(url))
192
parts[4:] = ['' for i in parts[4:]]
193
return urlunparse(parts)
196
287
def plugins(fetch_handlers=None):
197
288
if not fetch_handlers:
198
289
fetch_handlers = FETCH_HANDLERS
200
291
for handler_name in fetch_handlers:
201
292
package, classname = handler_name.rsplit('.', 1)
203
handler_class = getattr(importlib.import_module(package), classname)
294
handler_class = getattr(
295
importlib.import_module(package),
204
297
plugin_list.append(handler_class())
205
298
except (ImportError, AttributeError):
206
299
# Skip missing plugins so that they can be ommitted from
207
300
# installation if desired
208
log("FetchHandler {} not found, skipping plugin".format(handler_name))
301
log("FetchHandler {} not found, skipping plugin".format(
209
303
return plugin_list
306
def _run_apt_command(cmd, fatal=False):
308
Run an APT command, checking output and retrying if the fatal flag is set
311
:param: cmd: str: The apt command to run.
312
:param: fatal: bool: Whether the command's output should be checked and
315
env = os.environ.copy()
317
if 'DEBIAN_FRONTEND' not in env:
318
env['DEBIAN_FRONTEND'] = 'noninteractive'
324
# If the command is considered "fatal", we need to retry if the apt
325
# lock was not acquired.
327
while result is None or result == APT_NO_LOCK:
329
result = subprocess.check_call(cmd, env=env)
330
except subprocess.CalledProcessError, e:
331
retry_count = retry_count + 1
332
if retry_count > APT_NO_LOCK_RETRY_COUNT:
334
result = e.returncode
335
log("Couldn't acquire DPKG lock. Will retry in {} seconds."
336
"".format(APT_NO_LOCK_RETRY_DELAY))
337
time.sleep(APT_NO_LOCK_RETRY_DELAY)
340
subprocess.call(cmd, env=env)