3
# Copyright (C) 2009-2010 Canonical Ltd.
4
# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
6
# Author: Scott Moser <scott.moser@canonical.com>
7
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License version 3, as
11
# published by the Free Software Foundation.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
import cloudinit.util as util
26
import cloudinit.CloudConfig as cc
30
def handle(name, cfg, cloud, log, _args):
31
update = util.get_cfg_option_bool(cfg, 'apt_update', False)
32
upgrade = util.get_cfg_option_bool(cfg, 'apt_upgrade', False)
34
release = get_release()
36
mirrors = find_apt_mirror_info(cloud, cfg, log)
38
if not mirrors or "primary" not in mirrors:
39
log.debug(("Skipping module named %s,"
40
" no package 'mirror' located"), name)
43
# backwards compatibility
44
mirror = mirrors["primary"]
45
mirrors["mirror"] = mirror
47
log.debug("mirror info: %s" % mirrors)
49
if not util.get_cfg_option_bool(cfg, \
50
'apt_preserve_sources_list', False):
51
generate_sources_list(release, mirrors)
52
old_mirrors = cfg.get('apt_old_mirrors',
53
{"primary": "archive.ubuntu.com/ubuntu",
54
"security": "security.ubuntu.com/ubuntu"})
55
rename_apt_lists(old_mirrors, mirrors)
58
proxy = cfg.get("apt_proxy", None)
59
proxy_filename = "/etc/apt/apt.conf.d/95cloud-init-proxy"
62
contents = "Acquire::HTTP::Proxy \"%s\";\n"
63
with open(proxy_filename, "w") as fp:
64
fp.write(contents % proxy)
65
except Exception as e:
66
log.warn("Failed to write proxy to %s" % proxy_filename)
67
elif os.path.isfile(proxy_filename):
68
os.unlink(proxy_filename)
70
# process 'apt_sources'
71
if 'apt_sources' in cfg:
73
params['RELEASE'] = release
74
params['MIRROR'] = mirror
75
errors = add_sources(cloud, cfg['apt_sources'], params)
77
log.warn("Source Error: %s\n" % ':'.join(e))
79
dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False)
81
log.debug("setting debconf selections per cloud config")
83
util.subp(('debconf-set-selections', '-'), dconf_sel)
85
log.error("Failed to run debconf-set-selections")
86
log.debug(traceback.format_exc())
88
pkglist = util.get_cfg_option_list_or_str(cfg, 'packages', [])
91
if update or len(pkglist) or upgrade:
93
cc.update_package_sources()
94
except subprocess.CalledProcessError as e:
95
log.warn("apt-get update failed")
96
log.debug(traceback.format_exc())
101
cc.apt_get("upgrade")
102
except subprocess.CalledProcessError as e:
103
log.warn("apt upgrade failed")
104
log.debug(traceback.format_exc())
109
cc.install_packages(pkglist)
110
except subprocess.CalledProcessError as e:
111
log.warn("Failed to install packages: %s " % pkglist)
112
log.debug(traceback.format_exc())
121
def mirror2lists_fileprefix(mirror):
123
# take of http:// or ftp://
124
if string.endswith("/"):
125
string = string[0:-1]
126
pos = string.find("://")
128
string = string[pos + 3:]
129
string = string.replace("/", "_")
133
def rename_apt_lists(old_mirrors, new_mirrors, lists_d="/var/lib/apt/lists"):
134
for (name, omirror) in old_mirrors.iteritems():
135
nmirror = new_mirrors.get(name)
138
oprefix = os.path.join(lists_d, mirror2lists_fileprefix(omirror))
139
nprefix = os.path.join(lists_d, mirror2lists_fileprefix(nmirror))
140
if oprefix == nprefix:
143
for filename in glob.glob("%s_*" % oprefix):
144
os.rename(filename, "%s%s" % (nprefix, filename[olen:]))
148
stdout, _stderr = subprocess.Popen(['lsb_release', '-cs'],
149
stdout=subprocess.PIPE).communicate()
150
return(str(stdout).strip())
153
def generate_sources_list(codename, mirrors):
154
params = {'codename': codename}
156
params[k] = mirrors[k]
157
util.render_to_file('sources.list', '/etc/apt/sources.list', params)
160
def add_sources(srclist, searchList=None):
162
add entries in /etc/apt/sources.list.d for each abbreviated
163
sources.list entry in 'srclist'. When rendering template, also
164
include the values in dictionary searchList
166
if searchList is None:
171
if 'source' not in ent:
172
elst.append(["", "missing source"])
175
source = ent['source']
176
if source.startswith("ppa:"):
178
util.subp(["add-apt-repository", source])
180
elst.append([source, "add-apt-repository failed"])
183
source = util.render_string(source, searchList)
185
if 'filename' not in ent:
186
ent['filename'] = 'cloud_config_sources.list'
188
if not ent['filename'].startswith("/"):
189
ent['filename'] = "%s/%s" % \
190
("/etc/apt/sources.list.d/", ent['filename'])
192
if ('keyid' in ent and 'key' not in ent):
193
ks = "keyserver.ubuntu.com"
194
if 'keyserver' in ent:
195
ks = ent['keyserver']
197
ent['key'] = util.getkeybyid(ent['keyid'], ks)
199
elst.append([source, "failed to get key from %s" % ks])
204
util.subp(('apt-key', 'add', '-'), ent['key'])
206
elst.append([source, "failed add key"])
209
util.write_file(ent['filename'], source + "\n", omode="ab")
211
elst.append([source, "failed write to file %s" % ent['filename']])
216
def find_apt_mirror_info(cloud, cfg, log):
217
""" find an apt_mirror given the cloud and cfg provided """
219
# TODO: distro and defaults should be configurable
222
# this is used if cfg['system_info']['package_mirrors'] is not present
225
'primary': "http://archive.ubuntu.com/ubuntu",
226
'security': "http://security.ubuntu.com/ubuntu"
231
# this is less preferred way of specifying mirror preferred would be to
232
# use the distro's search or package_mirror.
233
mirror = cfg.get("apt_mirror", None)
235
search = cfg.get("apt_mirror_search", None)
236
if not mirror and search:
237
mirror = util.search_for_mirror(search)
240
util.get_cfg_option_bool(cfg, "apt_mirror_search_dns", False)):
245
# if we have a fqdn, then search its domain portion first
246
(_hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
247
mydom = ".".join(fqdn.split(".")[1:])
249
doms.append(".%s" % mydom)
251
doms.extend((".localdomain", "",))
254
mirrorfmt = "http://%s-mirror%s/%s" % (distro, "%s", distro)
256
mirror_list.append(mirrorfmt % (post))
258
mirror = util.search_for_mirror(mirror_list)
261
pmirrors = cfg['system_info']['package_mirrors']
262
az = cloud.datasource.get_availability_zone()
263
mirror_info = get_package_mirror_info(package_mirrors=pmirrors,
264
availability_zone=az)
265
except Exception as e:
267
log.warn("Failed to get mirror info, falling back to default" %
269
mirror_info = def_mirror_info
271
# this is a bit strange.
272
# if mirror is set, then one of the legacy options above set it
273
# but they do not cover security. so we need to get that from
274
# get_package_mirror_info
276
mirror_info.update({'primary': mirror})
280
## put together from trunk's cloudinit/distros/__init__.py and
281
## cloudinit/sources/__init__.py
282
def get_package_mirror_info(package_mirrors,
283
availability_zone=None, arch=None):
285
arch = get_primary_arch()
286
arch_info = _get_arch_package_mirror_info(package_mirrors, arch)
288
info = _get_package_mirror_info(mirror_info=arch_info,
289
availability_zone=availability_zone)
292
## taken from trunk's cloudinit/distros/debian.py (Distro)
293
def get_primary_arch():
294
(arch, _err) = util.subp(['dpkg', '--print-architecture'])
295
return str(arch).strip()
297
## taken from trunk's cloudinit/distros/__init__.py ##
298
def _get_package_mirror_info(mirror_info, availability_zone=None,
299
mirror_filter=util.search_for_mirror):
300
# given a arch specific 'mirror_info' entry (from package_mirrors)
301
# search through the 'search' entries, and fallback appropriately
302
# return a dict with only {name: mirror} entries.
304
ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" %
305
"north|northeast|east|southeast|south|southwest|west|northwest")
308
if availability_zone:
309
subst['availability_zone'] = availability_zone
311
if availability_zone and re.match(ec2_az_re, availability_zone):
312
subst['ec2_region'] = "%s" % availability_zone[0:-1]
315
for (name, mirror) in mirror_info.get('failsafe', {}).iteritems():
316
results[name] = mirror
318
for (name, searchlist) in mirror_info.get('search', {}).iteritems():
320
for tmpl in searchlist:
322
mirrors.append(tmpl % subst)
326
found = mirror_filter(mirrors)
328
results[name] = found
330
#LOG.debug("filtered distro mirror info: %s" % results)
334
## taken from trunk's cloudinit/distros/__init__.py
335
def _get_arch_package_mirror_info(package_mirrors, arch):
336
# pull out the specific arch from a 'package_mirrors' config option
338
for item in package_mirrors:
339
arches = item.get("arches")
342
if "default" in arches: