summaryrefslogtreecommitdiff
path: root/cloudinit/config/cc_apt_configure.py
diff options
context:
space:
mode:
authorChristian Ehrhardt <christian.ehrhardt@canonical.com>2016-08-10 16:43:14 +0200
committerScott Moser <smoser@brickies.net>2016-08-18 11:02:44 -0400
commitd861415ff9ab816b1183b8c58ec35348be4fd458 (patch)
treed3cc08c685f58208979c0fc6e941320cc1347b2b /cloudinit/config/cc_apt_configure.py
parent648dbbf6b090c81e989f1ab70bf99f4de16a6a70 (diff)
downloadvyos-cloud-init-d861415ff9ab816b1183b8c58ec35348be4fd458.tar.gz
vyos-cloud-init-d861415ff9ab816b1183b8c58ec35348be4fd458.zip
Apt: add new apt configuration format
This adds an improved apt configuration format that is fully backwards compatible with previous behavior. This is mostly copied from curtin's implementation. It does: * clean up and centralizes many of the top level 'apt_*' values that previously existed into a single top level 'apt'key. * support a 'source' in apt/sources/entry that has only a key * documents new features and adds tests. See the added doc/examples/cloud-config-apt.txt for more information.
Diffstat (limited to 'cloudinit/config/cc_apt_configure.py')
-rw-r--r--cloudinit/config/cc_apt_configure.py703
1 files changed, 547 insertions, 156 deletions
diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
index 05ad4b03..609dbb51 100644
--- a/cloudinit/config/cc_apt_configure.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -23,80 +23,182 @@ import os
import re
from cloudinit import gpg
+from cloudinit import log as logging
from cloudinit import templater
from cloudinit import util
-distros = ['ubuntu', 'debian']
-
-PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n"
-APT_CONFIG_FN = "/etc/apt/apt.conf.d/94cloud-init-config"
-APT_PROXY_FN = "/etc/apt/apt.conf.d/95cloud-init-proxy"
+LOG = logging.getLogger(__name__)
# this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar')
ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
+# place where apt stores cached repository data
+APT_LISTS = "/var/lib/apt/lists"
-def handle(name, cfg, cloud, log, _args):
- if util.is_false(cfg.get('apt_configure_enabled', True)):
- log.debug("Skipping module named %s, disabled by config.", name)
- return
-
- release = get_release()
- mirrors = find_apt_mirror_info(cloud, cfg)
- if not mirrors or "primary" not in mirrors:
- log.debug(("Skipping module named %s,"
- " no package 'mirror' located"), name)
- return
-
- # backwards compatibility
- mirror = mirrors["primary"]
- mirrors["mirror"] = mirror
-
- log.debug("Mirror info: %s" % mirrors)
-
- if not util.get_cfg_option_bool(cfg,
- 'apt_preserve_sources_list', False):
- generate_sources_list(cfg, release, mirrors, cloud, log)
- old_mirrors = cfg.get('apt_old_mirrors',
- {"primary": "archive.ubuntu.com/ubuntu",
- "security": "security.ubuntu.com/ubuntu"})
- rename_apt_lists(old_mirrors, mirrors)
+# Files to store proxy information
+APT_CONFIG_FN = "/etc/apt/apt.conf.d/94cloud-init-config"
+APT_PROXY_FN = "/etc/apt/apt.conf.d/90cloud-init-aptproxy"
+
+# Default keyserver to use
+DEFAULT_KEYSERVER = "keyserver.ubuntu.com"
+
+# Default archive mirrors
+PRIMARY_ARCH_MIRRORS = {"PRIMARY": "http://archive.ubuntu.com/ubuntu/",
+ "SECURITY": "http://security.ubuntu.com/ubuntu/"}
+PORTS_MIRRORS = {"PRIMARY": "http://ports.ubuntu.com/ubuntu-ports",
+ "SECURITY": "http://ports.ubuntu.com/ubuntu-ports"}
+PRIMARY_ARCHES = ['amd64', 'i386']
+PORTS_ARCHES = ['s390x', 'arm64', 'armhf', 'powerpc', 'ppc64el']
+
+
+def get_default_mirrors(arch=None, target=None):
+ """returns the default mirrors for the target. These depend on the
+ architecture, for more see:
+ https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports"""
+ if arch is None:
+ arch = util.get_architecture(target)
+ if arch in PRIMARY_ARCHES:
+ return PRIMARY_ARCH_MIRRORS.copy()
+ if arch in PORTS_ARCHES:
+ return PORTS_MIRRORS.copy()
+ raise ValueError("No default mirror known for arch %s" % arch)
+
+
+def handle(name, ocfg, cloud, log, _):
+ """process the config for apt_config. This can be called from
+ curthooks if a global apt config was provided or via the "apt"
+ standalone command."""
+ # keeping code close to curtin codebase via entry handler
+ target = None
+ if log is not None:
+ global LOG
+ LOG = log
+ # feed back converted config, but only work on the subset under 'apt'
+ ocfg = convert_to_v3_apt_format(ocfg)
+ cfg = ocfg.get('apt', {})
+
+ if not isinstance(cfg, dict):
+ raise ValueError("Expected dictionary for 'apt' config, found %s",
+ type(cfg))
+
+ LOG.debug("handling apt (module %s) with apt config '%s'", name, cfg)
+
+ release = util.lsb_release(target=target)['codename']
+ arch = util.get_architecture(target)
+ mirrors = find_apt_mirror_info(cfg, cloud, arch=arch)
+ LOG.debug("Apt Mirror info: %s", mirrors)
+
+ apply_debconf_selections(cfg, target)
+
+ if util.is_false(cfg.get('preserve_sources_list', False)):
+ generate_sources_list(cfg, release, mirrors, cloud)
+ rename_apt_lists(mirrors, target)
try:
apply_apt_config(cfg, APT_PROXY_FN, APT_CONFIG_FN)
- except Exception as e:
- log.warn("failed to proxy or apt config info: %s", e)
+ except (IOError, OSError):
+ LOG.exception("Failed to apply proxy or apt config info:")
- # Process 'apt_sources'
- if 'apt_sources' in cfg:
+ # Process 'apt_source -> sources {dict}'
+ if 'sources' in cfg:
params = mirrors
params['RELEASE'] = release
- params['MIRROR'] = mirror
+ params['MIRROR'] = mirrors["MIRROR"]
+ matcher = None
matchcfg = cfg.get('add_apt_repo_match', ADD_APT_REPO_MATCH)
if matchcfg:
matcher = re.compile(matchcfg).search
+
+ add_apt_sources(cfg['sources'], cloud, target=target,
+ template_params=params, aa_repo_match=matcher)
+
+
+def debconf_set_selections(selections, target=None):
+ util.subp(['debconf-set-selections'], data=selections, target=target,
+ capture=True)
+
+
+def dpkg_reconfigure(packages, target=None):
+ # For any packages that are already installed, but have preseed data
+ # we populate the debconf database, but the filesystem configuration
+ # would be preferred on a subsequent dpkg-reconfigure.
+ # so, what we have to do is "know" information about certain packages
+ # to unconfigure them.
+ unhandled = []
+ to_config = []
+ for pkg in packages:
+ if pkg in CONFIG_CLEANERS:
+ LOG.debug("unconfiguring %s", pkg)
+ CONFIG_CLEANERS[pkg](target)
+ to_config.append(pkg)
else:
- def matcher(x):
- return False
+ unhandled.append(pkg)
+
+ if len(unhandled):
+ LOG.warn("The following packages were installed and preseeded, "
+ "but cannot be unconfigured: %s", unhandled)
+
+ if len(to_config):
+ util.subp(['dpkg-reconfigure', '--frontend=noninteractive'] +
+ list(to_config), data=None, target=target, capture=True)
+
+
+def apply_debconf_selections(cfg, target=None):
+ """apply_debconf_selections - push content to debconf"""
+ # debconf_selections:
+ # set1: |
+ # cloud-init cloud-init/datasources multiselect MAAS
+ # set2: pkg pkg/value string bar
+ selsets = cfg.get('debconf_selections')
+ if not selsets:
+ LOG.debug("debconf_selections was not set in config")
+ return
- errors = add_apt_sources(cfg['apt_sources'], params,
- aa_repo_match=matcher)
- for e in errors:
- log.warn("Add source error: %s", ':'.join(e))
+ selections = '\n'.join(
+ [selsets[key] for key in sorted(selsets.keys())])
+ debconf_set_selections(selections.encode() + b"\n", target=target)
- dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False)
- if dconf_sel:
- log.debug("Setting debconf selections per cloud config")
- try:
- util.subp(('debconf-set-selections', '-'), dconf_sel)
- except Exception:
- util.logexc(log, "Failed to run debconf-set-selections")
+ # get a complete list of packages listed in input
+ pkgs_cfgd = set()
+ for key, content in selsets.items():
+ for line in content.splitlines():
+ if line.startswith("#"):
+ continue
+ pkg = re.sub(r"[:\s].*", "", line)
+ pkgs_cfgd.add(pkg)
+
+ pkgs_installed = util.get_installed_packages(target)
+
+ LOG.debug("pkgs_cfgd: %s", pkgs_cfgd)
+ need_reconfig = pkgs_cfgd.intersection(pkgs_installed)
+
+ if len(need_reconfig) == 0:
+ LOG.debug("no need for reconfig")
+ return
+
+ dpkg_reconfigure(need_reconfig, target=target)
+
+
+def clean_cloud_init(target):
+ """clean out any local cloud-init config"""
+ flist = glob.glob(
+ util.target_path(target, "/etc/cloud/cloud.cfg.d/*dpkg*"))
+
+ LOG.debug("cleaning cloud-init config from: %s", flist)
+ for dpkg_cfg in flist:
+ os.unlink(dpkg_cfg)
def mirrorurl_to_apt_fileprefix(mirror):
+ """mirrorurl_to_apt_fileprefix
+ Convert a mirror url to the file prefix used by apt on disk to
+ store cache information for that mirror.
+ To do so do:
+ - take off ???://
+ - drop tailing /
+ - convert in string / to _"""
string = mirror
- # take off http:// or ftp://
if string.endswith("/"):
string = string[0:-1]
pos = string.find("://")
@@ -106,174 +208,365 @@ def mirrorurl_to_apt_fileprefix(mirror):
return string
-def rename_apt_lists(old_mirrors, new_mirrors, lists_d="/var/lib/apt/lists"):
- for (name, omirror) in old_mirrors.items():
+def rename_apt_lists(new_mirrors, target=None):
+ """rename_apt_lists - rename apt lists to preserve old cache data"""
+ default_mirrors = get_default_mirrors(util.get_architecture(target))
+
+ pre = util.target_path(target, APT_LISTS)
+ for (name, omirror) in default_mirrors.items():
nmirror = new_mirrors.get(name)
if not nmirror:
continue
- oprefix = os.path.join(lists_d, mirrorurl_to_apt_fileprefix(omirror))
- nprefix = os.path.join(lists_d, mirrorurl_to_apt_fileprefix(nmirror))
+
+ oprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(omirror)
+ nprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(nmirror)
if oprefix == nprefix:
continue
olen = len(oprefix)
for filename in glob.glob("%s_*" % oprefix):
- util.rename(filename, "%s%s" % (nprefix, filename[olen:]))
-
-
-def get_release():
- (stdout, _stderr) = util.subp(['lsb_release', '-cs'])
- return stdout.strip()
-
-
-def generate_sources_list(cfg, codename, mirrors, cloud, log):
- params = {'codename': codename}
+ newname = "%s%s" % (nprefix, filename[olen:])
+ LOG.debug("Renaming apt list %s to %s", filename, newname)
+ try:
+ os.rename(filename, newname)
+ except OSError:
+ # since this is a best effort task, warn with but don't fail
+ LOG.warn("Failed to rename apt list:", exc_info=True)
+
+
+def mirror_to_placeholder(tmpl, mirror, placeholder):
+ """mirror_to_placeholder
+ replace the specified mirror in a template with a placeholder string
+ Checks for existance of the expected mirror and warns if not found"""
+ if mirror not in tmpl:
+ LOG.warn("Expected mirror '%s' not found in: %s", mirror, tmpl)
+ return tmpl.replace(mirror, placeholder)
+
+
+def map_known_suites(suite):
+ """there are a few default names which will be auto-extended.
+ This comes at the inability to use those names literally as suites,
+ but on the other hand increases readability of the cfg quite a lot"""
+ mapping = {'updates': '$RELEASE-updates',
+ 'backports': '$RELEASE-backports',
+ 'security': '$RELEASE-security',
+ 'proposed': '$RELEASE-proposed',
+ 'release': '$RELEASE'}
+ try:
+ retsuite = mapping[suite]
+ except KeyError:
+ retsuite = suite
+ return retsuite
+
+
+def disable_suites(disabled, src, release):
+ """reads the config for suites to be disabled and removes those
+ from the template"""
+ if not disabled:
+ return src
+
+ retsrc = src
+ for suite in disabled:
+ suite = map_known_suites(suite)
+ releasesuite = templater.render_string(suite, {'RELEASE': release})
+ LOG.debug("Disabling suite %s as %s", suite, releasesuite)
+
+ newsrc = ""
+ for line in retsrc.splitlines(True):
+ if line.startswith("#"):
+ newsrc += line
+ continue
+
+ # sources.list allow options in cols[1] which can have spaces
+ # so the actual suite can be [2] or later. example:
+ # deb [ arch=amd64,armel k=v ] http://example.com/debian
+ cols = line.split()
+ if len(cols) > 1:
+ pcol = 2
+ if cols[1].startswith("["):
+ for col in cols[1:]:
+ pcol += 1
+ if col.endswith("]"):
+ break
+
+ if cols[pcol] == releasesuite:
+ line = '# suite disabled by cloud-init: %s' % line
+ newsrc += line
+ retsrc = newsrc
+
+ return retsrc
+
+
+def generate_sources_list(cfg, release, mirrors, cloud):
+ """generate_sources_list
+ create a source.list file based on a custom or default template
+ by replacing mirrors and release in the template"""
+ aptsrc = "/etc/apt/sources.list"
+ params = {'RELEASE': release, 'codename': release}
for k in mirrors:
params[k] = mirrors[k]
+ params[k.lower()] = mirrors[k]
- custtmpl = cfg.get('apt_custom_sources_list', None)
- if custtmpl is not None:
- templater.render_string_to_file(custtmpl,
- '/etc/apt/sources.list', params)
- return
-
- template_fn = cloud.get_template_filename('sources.list.%s' %
- (cloud.distro.name))
- if not template_fn:
- template_fn = cloud.get_template_filename('sources.list')
+ tmpl = cfg.get('sources_list', None)
+ if tmpl is None:
+ LOG.info("No custom template provided, fall back to builtin")
+ template_fn = cloud.get_template_filename('sources.list.%s' %
+ (cloud.distro.name))
if not template_fn:
- log.warn("No template found, not rendering /etc/apt/sources.list")
+ template_fn = cloud.get_template_filename('sources.list')
+ if not template_fn:
+ LOG.warn("No template found, not rendering /etc/apt/sources.list")
return
+ tmpl = util.load_file(template_fn)
- templater.render_to_file(template_fn, '/etc/apt/sources.list', params)
+ rendered = templater.render_string(tmpl, params)
+ disabled = disable_suites(cfg.get('disable_suites'), rendered, release)
+ util.write_file(aptsrc, disabled, mode=0o644)
-def add_apt_key_raw(key):
+def add_apt_key_raw(key, target=None):
"""
actual adding of a key as defined in key argument
to the system
"""
+ LOG.debug("Adding key:\n'%s'", key)
try:
- util.subp(('apt-key', 'add', '-'), key)
+ util.subp(['apt-key', 'add', '-'], data=key.encode(), target=target)
except util.ProcessExecutionError:
- raise ValueError('failed to add apt GPG Key to apt keyring')
+ LOG.exception("failed to add apt GPG Key to apt keyring")
+ raise
-def add_apt_key(ent):
+def add_apt_key(ent, target=None):
"""
- add key to the system as defined in ent (if any)
- supports raw keys or keyid's
- The latter will as a first step fetch the raw key from a keyserver
+ Add key to the system as defined in ent (if any).
+ Supports raw keys or keyid's
+ The latter will as a first step fetched to get the raw key
"""
if 'keyid' in ent and 'key' not in ent:
- keyserver = "keyserver.ubuntu.com"
+ keyserver = DEFAULT_KEYSERVER
if 'keyserver' in ent:
keyserver = ent['keyserver']
- ent['key'] = gpg.get_key_by_id(ent['keyid'], keyserver)
+
+ ent['key'] = gpg.getkeybyid(ent['keyid'], keyserver)
if 'key' in ent:
- add_apt_key_raw(ent['key'])
+ add_apt_key_raw(ent['key'], target)
-def convert_to_new_format(srclist):
- """convert_to_new_format
- convert the old list based format to the new dict based one
- """
- srcdict = {}
- if isinstance(srclist, list):
- for srcent in srclist:
- if 'filename' not in srcent:
- # file collides for multiple !filename cases for compatibility
- # yet we need them all processed, so not same dictionary key
- srcent['filename'] = "cloud_config_sources.list"
- key = util.rand_dict_key(srcdict, "cloud_config_sources.list")
- else:
- # all with filename use that as key (matching new format)
- key = srcent['filename']
- srcdict[key] = srcent
- elif isinstance(srclist, dict):
- srcdict = srclist
- else:
- raise ValueError("unknown apt_sources format")
-
- return srcdict
+def update_packages(cloud):
+ cloud.distro.update_package_sources()
-def add_apt_sources(srclist, template_params=None, aa_repo_match=None):
+def add_apt_sources(srcdict, cloud, target=None, template_params=None,
+ aa_repo_match=None):
"""
add entries in /etc/apt/sources.list.d for each abbreviated
- sources.list entry in 'srclist'. When rendering template, also
+ sources.list entry in 'srcdict'. When rendering template, also
include the values in dictionary searchList
"""
if template_params is None:
template_params = {}
if aa_repo_match is None:
- def _aa_repo_match(x):
- return False
- aa_repo_match = _aa_repo_match
+ raise ValueError('did not get a valid repo matcher')
- errorlist = []
- srcdict = convert_to_new_format(srclist)
+ if not isinstance(srcdict, dict):
+ raise TypeError('unknown apt format: %s' % (srcdict))
for filename in srcdict:
ent = srcdict[filename]
+ LOG.debug("adding source/key '%s'", ent)
if 'filename' not in ent:
ent['filename'] = filename
- # keys can be added without specifying a source
- try:
- add_apt_key(ent)
- except ValueError as detail:
- errorlist.append([ent, detail])
+ add_apt_key(ent, target)
if 'source' not in ent:
- errorlist.append(["", "missing source"])
continue
source = ent['source']
source = templater.render_string(source, template_params)
- if not ent['filename'].startswith(os.path.sep):
+ if not ent['filename'].startswith("/"):
ent['filename'] = os.path.join("/etc/apt/sources.list.d/",
ent['filename'])
+ if not ent['filename'].endswith(".list"):
+ ent['filename'] += ".list"
if aa_repo_match(source):
try:
- util.subp(["add-apt-repository", source])
- except util.ProcessExecutionError as e:
- errorlist.append([source,
- ("add-apt-repository failed. " + str(e))])
+ util.subp(["add-apt-repository", source], target=target)
+ except util.ProcessExecutionError:
+ LOG.exception("add-apt-repository failed.")
+ raise
continue
+ sourcefn = util.target_path(target, ent['filename'])
try:
contents = "%s\n" % (source)
- util.write_file(ent['filename'], contents, omode="ab")
- except Exception:
- errorlist.append([source,
- "failed write to file %s" % ent['filename']])
+ util.write_file(sourcefn, contents, omode="a")
+ except IOError as detail:
+ LOG.exception("failed write to file %s: %s", sourcefn, detail)
+ raise
- return errorlist
+ update_packages(cloud)
+ return
-def find_apt_mirror_info(cloud, cfg):
- """find an apt_mirror given the cloud and cfg provided."""
- mirror = None
+def convert_v1_to_v2_apt_format(srclist):
+ """convert v1 apt format to v2 (dict in apt_sources)"""
+ srcdict = {}
+ if isinstance(srclist, list):
+ LOG.debug("apt config: convert V1 to V2 format (source list to dict)")
+ for srcent in srclist:
+ if 'filename' not in srcent:
+ # file collides for multiple !filename cases for compatibility
+ # yet we need them all processed, so not same dictionary key
+ srcent['filename'] = "cloud_config_sources.list"
+ key = util.rand_dict_key(srcdict, "cloud_config_sources.list")
+ else:
+ # all with filename use that as key (matching new format)
+ key = srcent['filename']
+ srcdict[key] = srcent
+ elif isinstance(srclist, dict):
+ srcdict = srclist
+ else:
+ raise ValueError("unknown apt_sources format")
+
+ return srcdict
+
+
+def convert_key(oldcfg, aptcfg, oldkey, newkey):
+ """convert an old key to the new one if the old one exists
+ returns true if a key was found and converted"""
+ if oldcfg.get(oldkey, None) is not None:
+ aptcfg[newkey] = oldcfg.get(oldkey)
+ del oldcfg[oldkey]
+ return True
+ return False
+
+
+def convert_mirror(oldcfg, aptcfg):
+ """convert old apt_mirror keys into the new more advanced mirror spec"""
+ keymap = [('apt_mirror', 'uri'),
+ ('apt_mirror_search', 'search'),
+ ('apt_mirror_search_dns', 'search_dns')]
+ converted = False
+ newmcfg = {'arches': ['default']}
+ for oldkey, newkey in keymap:
+ if convert_key(oldcfg, newmcfg, oldkey, newkey):
+ converted = True
+
+ # only insert new style config if anything was converted
+ if converted:
+ aptcfg['primary'] = [newmcfg]
+
+
+def convert_v2_to_v3_apt_format(oldcfg):
+ """convert old to new keys and adapt restructured mirror spec"""
+ oldkeys = ['apt_sources', 'apt_mirror', 'apt_mirror_search',
+ 'apt_mirror_search_dns', 'apt_proxy', 'apt_http_proxy',
+ 'apt_ftp_proxy', 'apt_https_proxy',
+ 'apt_preserve_sources_list', 'apt_custom_sources_list',
+ 'add_apt_repo_match']
+ needtoconvert = []
+ for oldkey in oldkeys:
+ if oldcfg.get(oldkey, None) is not None:
+ needtoconvert.append(oldkey)
+
+ # no old config, so no new one to be created
+ if not needtoconvert:
+ return oldcfg
+ LOG.debug("apt config: convert V2 to V3 format for keys '%s'",
+ ", ".join(needtoconvert))
+
+ if oldcfg.get('apt', None) is not None:
+ msg = ("Error in apt configuration: "
+ "old and new format of apt features are mutually exclusive "
+ "('apt':'%s' vs '%s' key)" % (oldcfg.get('apt', None),
+ ", ".join(needtoconvert)))
+ LOG.error(msg)
+ raise ValueError(msg)
+
+ # create new format from old keys
+ aptcfg = {}
+
+ # renames / moves under the apt key
+ convert_key(oldcfg, aptcfg, 'add_apt_repo_match', 'add_apt_repo_match')
+ convert_key(oldcfg, aptcfg, 'apt_proxy', 'proxy')
+ convert_key(oldcfg, aptcfg, 'apt_http_proxy', 'http_proxy')
+ convert_key(oldcfg, aptcfg, 'apt_https_proxy', 'https_proxy')
+ convert_key(oldcfg, aptcfg, 'apt_ftp_proxy', 'ftp_proxy')
+ convert_key(oldcfg, aptcfg, 'apt_custom_sources_list', 'sources_list')
+ convert_key(oldcfg, aptcfg, 'apt_preserve_sources_list',
+ 'preserve_sources_list')
+ # dict format not changed since v2, just renamed and moved
+ convert_key(oldcfg, aptcfg, 'apt_sources', 'sources')
+
+ convert_mirror(oldcfg, aptcfg)
+
+ for oldkey in oldkeys:
+ if oldcfg.get(oldkey, None) is not None:
+ raise ValueError("old apt key '%s' left after conversion" % oldkey)
+
+ # insert new format into config and return full cfg with only v3 content
+ oldcfg['apt'] = aptcfg
+ return oldcfg
+
+
+def convert_to_v3_apt_format(cfg):
+ """convert the old list based format to the new dict based one. After that
+ convert the old dict keys/format to v3 a.k.a 'new apt config'"""
+ # V1 -> V2, the apt_sources entry from list to dict
+ apt_sources = cfg.get('apt_sources', None)
+ if apt_sources is not None:
+ cfg['apt_sources'] = convert_v1_to_v2_apt_format(apt_sources)
+
+ # V2 -> V3, move all former globals under the "apt" key
+ # Restructure into new key names and mirror hierarchy
+ cfg = convert_v2_to_v3_apt_format(cfg)
+
+ return cfg
+
+
+def search_for_mirror(candidates):
+ """
+ Search through a list of mirror urls for one that works
+ This needs to return quickly.
+ """
+ if candidates is None:
+ return None
+
+ LOG.debug("search for mirror in candidates: '%s'", candidates)
+ for cand in candidates:
+ try:
+ if util.is_resolvable_url(cand):
+ LOG.debug("found working mirror: '%s'", cand)
+ return cand
+ except Exception:
+ pass
+ return None
- # this is less preferred way of specifying mirror preferred would be to
- # use the distro's search or package_mirror.
- mirror = cfg.get("apt_mirror", None)
- search = cfg.get("apt_mirror_search", None)
- if not mirror and search:
- mirror = util.search_for_mirror(search)
+def search_for_mirror_dns(configured, mirrortype, cfg, cloud):
+ """
+ Try to resolve a list of predefines DNS names to pick mirrors
+ """
+ mirror = None
- if (not mirror and
- util.get_cfg_option_bool(cfg, "apt_mirror_search_dns", False)):
+ if configured:
mydom = ""
doms = []
+ if mirrortype == "primary":
+ mirrordns = "mirror"
+ elif mirrortype == "security":
+ mirrordns = "security-mirror"
+ else:
+ raise ValueError("unknown mirror type")
+
# if we have a fqdn, then search its domain portion first
- (_hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ (_, fqdn) = util.get_hostname_fqdn(cfg, cloud)
mydom = ".".join(fqdn.split(".")[1:])
if mydom:
doms.append(".%s" % mydom)
@@ -282,38 +575,136 @@ def find_apt_mirror_info(cloud, cfg):
mirror_list = []
distro = cloud.distro.name
- mirrorfmt = "http://%s-mirror%s/%s" % (distro, "%s", distro)
+ mirrorfmt = "http://%s-%s%s/%s" % (distro, mirrordns, "%s", distro)
for post in doms:
mirror_list.append(mirrorfmt % (post))
- mirror = util.search_for_mirror(mirror_list)
+ mirror = search_for_mirror(mirror_list)
+
+ return mirror
+
+def update_mirror_info(pmirror, smirror, arch, cloud):
+ """sets security mirror to primary if not defined.
+ returns defaults if no mirrors are defined"""
+ if pmirror is not None:
+ if smirror is None:
+ smirror = pmirror
+ return {'PRIMARY': pmirror,
+ 'SECURITY': smirror}
+
+ # None specified at all, get default mirrors from cloud
mirror_info = cloud.datasource.get_package_mirror_info()
+ if mirror_info:
+ # get_package_mirror_info() returns a dictionary with
+ # arbitrary key/value pairs including 'primary' and 'security' keys.
+ # caller expects dict with PRIMARY and SECURITY.
+ m = mirror_info.copy()
+ m['PRIMARY'] = m['primary']
+ m['SECURITY'] = m['security']
+
+ return m
+
+ # if neither apt nor cloud configured mirrors fall back to
+ return get_default_mirrors(arch)
+
+
+def get_arch_mirrorconfig(cfg, mirrortype, arch):
+ """out of a list of potential mirror configurations select
+ and return the one matching the architecture (or default)"""
+ # select the mirror specification (if-any)
+ mirror_cfg_list = cfg.get(mirrortype, None)
+ if mirror_cfg_list is None:
+ return None
+
+ # select the specification matching the target arch
+ default = None
+ for mirror_cfg_elem in mirror_cfg_list:
+ arches = mirror_cfg_elem.get("arches")
+ if arch in arches:
+ return mirror_cfg_elem
+ if "default" in arches:
+ default = mirror_cfg_elem
+ return default
+
+
+def get_mirror(cfg, mirrortype, arch, cloud):
+ """pass the three potential stages of mirror specification
+ returns None is neither of them found anything otherwise the first
+ hit is returned"""
+ mcfg = get_arch_mirrorconfig(cfg, mirrortype, arch)
+ if mcfg is None:
+ return None
+
+ # directly specified
+ mirror = mcfg.get("uri", None)
+
+ # fallback to search if specified
+ if mirror is None:
+ # list of mirrors to try to resolve
+ mirror = search_for_mirror(mcfg.get("search", None))
+
+ # fallback to search_dns if specified
+ if mirror is None:
+ # list of mirrors to try to resolve
+ mirror = search_for_mirror_dns(mcfg.get("search_dns", None),
+ mirrortype, cfg, cloud)
+
+ return mirror
+
+
+def find_apt_mirror_info(cfg, cloud, arch=None):
+ """find_apt_mirror_info
+ find an apt_mirror given the cfg provided.
+ It can check for separate config of primary and security mirrors
+ If only primary is given security is assumed to be equal to primary
+ If the generic apt_mirror is given that is defining for both
+ """
- # this is a bit strange.
- # if mirror is set, then one of the legacy options above set it
- # but they do not cover security. so we need to get that from
- # get_package_mirror_info
- if mirror:
- mirror_info.update({'primary': mirror})
+ if arch is None:
+ arch = util.get_architecture()
+ LOG.debug("got arch for mirror selection: %s", arch)
+ pmirror = get_mirror(cfg, "primary", arch, cloud)
+ LOG.debug("got primary mirror: %s", pmirror)
+ smirror = get_mirror(cfg, "security", arch, cloud)
+ LOG.debug("got security mirror: %s", smirror)
+
+ mirror_info = update_mirror_info(pmirror, smirror, arch, cloud)
+
+ # less complex replacements use only MIRROR, derive from primary
+ mirror_info["MIRROR"] = mirror_info["PRIMARY"]
return mirror_info
def apply_apt_config(cfg, proxy_fname, config_fname):
+ """apply_apt_config
+ Applies any apt*proxy config from if specified
+ """
# Set up any apt proxy
- cfgs = (('apt_proxy', 'Acquire::HTTP::Proxy "%s";'),
- ('apt_http_proxy', 'Acquire::HTTP::Proxy "%s";'),
- ('apt_ftp_proxy', 'Acquire::FTP::Proxy "%s";'),
- ('apt_https_proxy', 'Acquire::HTTPS::Proxy "%s";'))
+ cfgs = (('proxy', 'Acquire::http::Proxy "%s";'),
+ ('http_proxy', 'Acquire::http::Proxy "%s";'),
+ ('ftp_proxy', 'Acquire::ftp::Proxy "%s";'),
+ ('https_proxy', 'Acquire::https::Proxy "%s";'))
proxies = [fmt % cfg.get(name) for (name, fmt) in cfgs if cfg.get(name)]
if len(proxies):
+ LOG.debug("write apt proxy info to %s", proxy_fname)
util.write_file(proxy_fname, '\n'.join(proxies) + '\n')
elif os.path.isfile(proxy_fname):
util.del_file(proxy_fname)
+ LOG.debug("no apt proxy configured, removed %s", proxy_fname)
- if cfg.get('apt_config', None):
- util.write_file(config_fname, cfg.get('apt_config'))
+ if cfg.get('conf', None):
+ LOG.debug("write apt config info to %s", config_fname)
+ util.write_file(config_fname, cfg.get('conf'))
elif os.path.isfile(config_fname):
util.del_file(config_fname)
+ LOG.debug("no apt config configured, removed %s", config_fname)
+
+
+CONFIG_CLEANERS = {
+ 'cloud-init': clean_cloud_init,
+}
+
+# vi: ts=4 expandtab syntax=python