Merge lp:~ubuntu-server/curtin/bug-1574113-derived-repositories into lp:~curtin-dev/curtin/trunk

Proposed by Christian Ehrhardt 
Status: Merged
Merged at revision: 410
Proposed branch: lp:~ubuntu-server/curtin/bug-1574113-derived-repositories
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 4136 lines (+3606/-197)
25 files modified
curtin/__init__.py (+2/-0)
curtin/commands/apt_config.py (+658/-0)
curtin/commands/curthooks.py (+50/-168)
curtin/commands/main.py (+1/-1)
curtin/gpg.py (+74/-0)
curtin/util.py (+161/-17)
doc/devel/README-vmtest.txt (+59/-4)
doc/topics/apt_source.rst (+158/-0)
examples/apt-source.yaml (+267/-0)
examples/tests/apt_config_command.yaml (+85/-0)
examples/tests/apt_source_custom.yaml (+97/-0)
examples/tests/apt_source_modify.yaml (+92/-0)
examples/tests/apt_source_modify_arches.yaml (+102/-0)
examples/tests/apt_source_modify_disable_suite.yaml (+92/-0)
examples/tests/apt_source_preserve.yaml (+98/-0)
examples/tests/apt_source_search.yaml (+97/-0)
examples/tests/test_old_apt_features.yaml (+10/-0)
examples/tests/test_old_apt_features_ports.yaml (+10/-0)
tests/unittests/test_apt_custom_sources_list.py (+170/-0)
tests/unittests/test_apt_source.py (+929/-0)
tests/unittests/test_util.py (+2/-2)
tests/vmtests/__init__.py (+19/-5)
tests/vmtests/test_apt_config_cmd.py (+55/-0)
tests/vmtests/test_apt_source.py (+238/-0)
tests/vmtests/test_old_apt_features.py (+80/-0)
To merge this branch: bzr merge lp:~ubuntu-server/curtin/bug-1574113-derived-repositories
Reviewer Review Type Date Requested Status
Scott Moser (community) Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+301362@code.launchpad.net

Commit message

Adding apt features to curtin.

This allows to define derived repositories by being able to control various apt configurations, proxies, mirrors, ppa's, sources.list content and pgp keys trusted by apt.
See examples/apt-source.yaml for examples and explanation of the individual capabilities.

Along the actual features a huge list of related unit- and vm-tests is added.

Description of the change

Adding apt_source features for the install stage of curtin.
This is inherited from cloud-init, but simplified (as we don't have to care about some features and old compatibility).
And then added plenty of unit- and vm-tests for the functionality.

See the current spec discussion at https://docs.google.com/document/d/1B5sCAOYcwQNwpfalu452GnDmHKRcRbC0P61A8y5SpZQ/edit#heading=h.kwm2ll6tdqwk

To post a comment you must log in.
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

This is the "new" MP based on https://code.launchpad.net/~paelzer/curtin/bug-1574113-derived-repositories/+merge/296118

I'll keep the old MP open for now to have the discussion history available (not sure if it would go away when rejecting the old MP).

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
624. By Christian Ehrhardt 

fixup codeing style to pass checks

Running: pep8 curtin/ tests/
curtin/commands/apt_config.py:399:80: E501 line too long (80 > 79 characters)
tests/unittests/test_apt_source.py:52:1: E302 expected 2 blank lines, found 1
tests/unittests/test_apt_source.py:59:1: W293 blank line contains whitespace
tests/unittests/test_apt_source.py:438:5: E303 too many blank lines (2)

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

Reviewed on phone (probably not a good habit). See inline comments.

625. By Christian Ehrhardt 

no fqdn can be quite common in early environemnts, silence the LOG entry a bit

626. By Christian Ehrhardt 

fixup most of the dns vmtest and disable it until fully fixed

627. By Christian Ehrhardt 

make get_default_mirrors take arch instead of target

628. By Christian Ehrhardt 

make lsb_release not use caching if target is provided

Eventually with target provided the release could even "change", so never
use cache when target is given.
If any one could implement a per-target cache, but even that might not be
100% true at all times.

629. By Christian Ehrhardt 

move target out of update_mirror_info as it was only used for arch

630. By Christian Ehrhardt 

drop search_dns feature

This is following smosers guidance.
In a discussion it became clear that this doesn't make much sense in a curtin
environment at all.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
631. By Christian Ehrhardt 

rework usage of target and chroot

- reduce passing of target where only arch is needed
- unify the adding of chroot (from various sources) into subp

Based on a nice diff by smoser but fixed some remaining issues with in_chroot.

632. By Scott Moser

merge from trunk. address removal of RunInChroot

633. By Christian Ehrhardt 

APT: rework examples and documentation

Now that we have settled on the format ensure that the examples are on par.
Also rework and simplify the examples for readability.
Drop the filename key which is working (for transparency and compatibility),
but not meant to be documented as this might change.

634. By Christian Ehrhardt 

merge trunk to ver 409

635. By Christian Ehrhardt 

document Test calss variables

I wanted to "just" document boot_cloudconf, but found that none of the vmtest
base class variables we usually use is documented so far.
So I added some short description of all of these along the furtunately already
existing doc of the environment variables.

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

make check:
Ran 234 tests in 2.612s
Ran 234 tests in 2.224s

tox
  py27: commands succeeded
  py3: commands succeeded
  py3-flake8: commands succeeded
  py3-pylint: commands succeeded
  py27-pylint: commands succeeded
  trusty-check: commands succeeded
  congratulations :)

vmtests:
Ran 727 tests in 1803.671s

All tests passed, pushed repo again after merging trunk and finalizing some doc polishing as discussed on #curtin.

Revision history for this message
Scott Moser (smoser) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'curtin/__init__.py'
2--- curtin/__init__.py 2015-10-26 17:35:29 +0000
3+++ curtin/__init__.py 2016-08-02 09:15:49 +0000
4@@ -33,6 +33,8 @@
5 'SUBCOMMAND_SYSTEM_INSTALL',
6 # subcommand 'system-upgrade' is present
7 'SUBCOMMAND_SYSTEM_UPGRADE',
8+ # supports new format of apt configuration
9+ 'APT_CONFIG_V1',
10 ]
11
12 # vi: ts=4 expandtab syntax=python
13
14=== added file 'curtin/commands/apt_config.py'
15--- curtin/commands/apt_config.py 1970-01-01 00:00:00 +0000
16+++ curtin/commands/apt_config.py 2016-08-02 09:15:49 +0000
17@@ -0,0 +1,658 @@
18+# Copyright (C) 2016 Canonical Ltd.
19+#
20+# Author: Christian Ehrhardt <christian.ehrhardt@canonical.com>
21+#
22+# Curtin is free software: you can redistribute it and/or modify it under
23+# the terms of the GNU Affero General Public License as published by the
24+# Free Software Foundation, either version 3 of the License, or (at your
25+# option) any later version.
26+#
27+# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY
28+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
29+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
30+# more details.
31+#
32+# You should have received a copy of the GNU Affero General Public License
33+# along with Curtin. If not, see <http://www.gnu.org/licenses/>.
34+"""
35+apt.py
36+Handle the setup of apt related tasks like proxies, mirrors, repositories.
37+"""
38+
39+import argparse
40+import glob
41+import os
42+import re
43+import sys
44+import yaml
45+
46+from curtin.log import LOG
47+from curtin import (config, util, gpg)
48+
49+from . import populate_one_subcmd
50+
51+# this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar')
52+ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
53+
54+# place where apt stores cached repository data
55+APT_LISTS = "/var/lib/apt/lists"
56+
57+# Files to store proxy information
58+APT_CONFIG_FN = "/etc/apt/apt.conf.d/94curtin-config"
59+APT_PROXY_FN = "/etc/apt/apt.conf.d/90curtin-aptproxy"
60+
61+# Default keyserver to use
62+DEFAULT_KEYSERVER = "keyserver.ubuntu.com"
63+
64+# Default archive mirrors
65+PRIMARY_ARCH_MIRRORS = {"PRIMARY": "http://archive.ubuntu.com/ubuntu/",
66+ "SECURITY": "http://security.ubuntu.com/ubuntu/"}
67+PORTS_MIRRORS = {"PRIMARY": "http://ports.ubuntu.com/ubuntu-ports",
68+ "SECURITY": "http://ports.ubuntu.com/ubuntu-ports"}
69+PRIMARY_ARCHES = ['amd64', 'i386']
70+PORTS_ARCHES = ['s390x', 'arm64', 'armhf', 'powerpc', 'ppc64el']
71+
72+
73+def get_default_mirrors(arch=None):
74+ """returns the default mirrors for the target. These depend on the
75+ architecture, for more see:
76+ https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports"""
77+ if arch is None:
78+ arch = util.get_architecture()
79+ if arch in PRIMARY_ARCHES:
80+ return PRIMARY_ARCH_MIRRORS
81+ if arch in PORTS_ARCHES:
82+ return PORTS_MIRRORS
83+ raise ValueError("No default mirror known for arch %s" % arch)
84+
85+
86+def handle_apt(cfg, target=None):
87+ """ handle_apt
88+ process the config for apt_config. This can be called from
89+ curthooks if a global apt config was provided or via the "apt"
90+ standalone command.
91+ """
92+ release = util.lsb_release(target=target)['codename']
93+ arch = util.get_architecture(target)
94+ mirrors = find_apt_mirror_info(cfg, arch)
95+ LOG.debug("Apt Mirror info: %s", mirrors)
96+
97+ apply_debconf_selections(cfg, target)
98+
99+ if not config.value_as_boolean(cfg.get('preserve_sources_list',
100+ True)):
101+ generate_sources_list(cfg, release, mirrors, target)
102+ rename_apt_lists(mirrors, target)
103+
104+ try:
105+ apply_apt_proxy_config(cfg, target + APT_PROXY_FN,
106+ target + APT_CONFIG_FN)
107+ except (IOError, OSError):
108+ LOG.exception("Failed to apply proxy or apt config info:")
109+
110+ # Process 'apt_source -> sources {dict}'
111+ if 'sources' in cfg:
112+ params = mirrors
113+ params['RELEASE'] = release
114+ params['MIRROR'] = mirrors["MIRROR"]
115+
116+ matcher = None
117+ matchcfg = cfg.get('add_apt_repo_match', ADD_APT_REPO_MATCH)
118+ if matchcfg:
119+ matcher = re.compile(matchcfg).search
120+
121+ add_apt_sources(cfg['sources'], target,
122+ template_params=params, aa_repo_match=matcher)
123+
124+
125+def apply_debconf_selections(cfg, target=None):
126+ """apply_debconf_selections - push content to debconf"""
127+ # debconf_selections:
128+ # set1: |
129+ # cloud-init cloud-init/datasources multiselect MAAS
130+ # set2: pkg pkg/value string bar
131+ selsets = cfg.get('debconf_selections')
132+ if not selsets:
133+ LOG.debug("debconf_selections was not set in config")
134+ return
135+
136+ # for each entry in selections, chroot and apply them.
137+ # keep a running total of packages we've seen.
138+ pkgs_cfgd = set()
139+
140+ for key, content in selsets.items():
141+ LOG.debug("setting for %s, %s", key, content)
142+ util.subp(['debconf-set-selections'],
143+ data=content.encode(), target=target)
144+ for line in content.splitlines():
145+ if line.startswith("#"):
146+ continue
147+ pkg = re.sub(r"[:\s].*", "", line)
148+ pkgs_cfgd.add(pkg)
149+
150+ pkgs_installed = util.get_installed_packages(target)
151+
152+ LOG.debug("pkgs_cfgd: %s", pkgs_cfgd)
153+ LOG.debug("pkgs_installed: %s", pkgs_installed)
154+ need_reconfig = pkgs_cfgd.intersection(pkgs_installed)
155+
156+ if len(need_reconfig) == 0:
157+ LOG.debug("no need for reconfig")
158+ return
159+
160+ # For any packages that are already installed, but have preseed data
161+ # we populate the debconf database, but the filesystem configuration
162+ # would be preferred on a subsequent dpkg-reconfigure.
163+ # so, what we have to do is "know" information about certain packages
164+ # to unconfigure them.
165+ unhandled = []
166+ to_config = []
167+ for pkg in need_reconfig:
168+ if pkg in CONFIG_CLEANERS:
169+ LOG.debug("unconfiguring %s", pkg)
170+ CONFIG_CLEANERS[pkg](target)
171+ to_config.append(pkg)
172+ else:
173+ unhandled.append(pkg)
174+
175+ if len(unhandled):
176+ LOG.warn("The following packages were installed and preseeded, "
177+ "but cannot be unconfigured: %s", unhandled)
178+
179+ if len(to_config):
180+ util.subp(['dpkg-reconfigure' '--frontend=noninteractive'] +
181+ list(to_config), data=None, target=target)
182+
183+
184+def clean_cloud_init(target):
185+ """clean out any local cloud-init config"""
186+ flist = glob.glob(
187+ util.target_path(target, "/etc/cloud/cloud.cfg.d/*dpkg*"))
188+
189+ LOG.debug("cleaning cloud-init config from: %s", flist)
190+ for dpkg_cfg in flist:
191+ os.unlink(dpkg_cfg)
192+
193+
194+def mirrorurl_to_apt_fileprefix(mirror):
195+ """ mirrorurl_to_apt_fileprefix
196+ Convert a mirror url to the file prefix used by apt on disk to
197+ store cache information for that mirror.
198+ To do so do:
199+ - take off ???://
200+ - drop tailing /
201+ - convert in string / to _
202+ """
203+ string = mirror
204+ if string.endswith("/"):
205+ string = string[0:-1]
206+ pos = string.find("://")
207+ if pos >= 0:
208+ string = string[pos + 3:]
209+ string = string.replace("/", "_")
210+ return string
211+
212+
213+def rename_apt_lists(new_mirrors, target=None):
214+ """rename_apt_lists - rename apt lists to preserve old cache data"""
215+ default_mirrors = get_default_mirrors(util.get_architecture(target))
216+
217+ pre = util.target_path(target, APT_LISTS)
218+ for (name, omirror) in default_mirrors.items():
219+ nmirror = new_mirrors.get(name)
220+ if not nmirror:
221+ continue
222+
223+ oprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(omirror)
224+ nprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(nmirror)
225+ if oprefix == nprefix:
226+ continue
227+ olen = len(oprefix)
228+ for filename in glob.glob("%s_*" % oprefix):
229+ newname = "%s%s" % (nprefix, filename[olen:])
230+ LOG.debug("Renaming apt list %s to %s", filename, newname)
231+ try:
232+ os.rename(filename, newname)
233+ except OSError:
234+ # since this is a best effort task, warn with but don't fail
235+ LOG.warn("Failed to rename apt list:", exc_info=True)
236+
237+
238+def mirror_to_placeholder(tmpl, mirror, placeholder):
239+ """ mirror_to_placeholder
240+ replace the specified mirror in a template with a placeholder string
241+ Checks for existance of the expected mirror and warns if not found
242+ """
243+ if mirror not in tmpl:
244+ LOG.warn("Expected mirror '%s' not found in: %s", mirror, tmpl)
245+ return tmpl.replace(mirror, placeholder)
246+
247+
248+def map_known_suites(suite):
249+ """there are a few default names which will be auto-extended.
250+ This comes at the inability to use those names literally as suites,
251+ but on the other hand increases readability of the cfg quite a lot"""
252+ mapping = {'updates': '$RELEASE-updates',
253+ 'backports': '$RELEASE-backports',
254+ 'security': '$RELEASE-security',
255+ 'proposed': '$RELEASE-proposed',
256+ 'release': '$RELEASE'}
257+ try:
258+ retsuite = mapping[suite]
259+ except KeyError:
260+ retsuite = suite
261+ return retsuite
262+
263+
264+def disable_suites(disabled, src, release):
265+ """reads the config for suites to be disabled and removes those
266+ from the template"""
267+ if not disabled:
268+ return src
269+
270+ retsrc = src
271+ for suite in disabled:
272+ suite = map_known_suites(suite)
273+ releasesuite = util.render_string(suite, {'RELEASE': release})
274+ LOG.debug("Disabling suite %s as %s", suite, releasesuite)
275+
276+ newsrc = ""
277+ for line in retsrc.splitlines(True):
278+ if line.startswith("#"):
279+ newsrc += line
280+ continue
281+
282+ # sources.list allow options in cols[1] which can have spaces
283+ # so the actual suite can be [2] or later
284+ cols = line.split()
285+ pcol = 2
286+ if cols[1].startswith("["):
287+ for col in cols[1:]:
288+ pcol += 1
289+ if col.endswith("]"):
290+ break
291+
292+ if cols[pcol] == releasesuite:
293+ line = '# suite disabled by curtin: %s' % line
294+ newsrc += line
295+ retsrc = newsrc
296+
297+ return retsrc
298+
299+
300+def generate_sources_list(cfg, release, mirrors, target=None):
301+ """ generate_sources_list
302+ create a source.list file based on a custom or default template
303+ by replacing mirrors and release in the template
304+ """
305+ default_mirrors = get_default_mirrors(util.get_architecture(target))
306+ aptsrc = "/etc/apt/sources.list"
307+ params = {'RELEASE': release}
308+ for k in mirrors:
309+ params[k] = mirrors[k]
310+
311+ tmpl = cfg.get('sources_list', None)
312+ if tmpl is None:
313+ LOG.info("No custom template provided, fall back to modify"
314+ "mirrors in %s on the target system", aptsrc)
315+ tmpl = util.load_file(util.target_path(target, aptsrc))
316+ # Strategy if no custom template was provided:
317+ # - Only replacing mirrors
318+ # - no reason to replace "release" as it is from target anyway
319+ # - The less we depend upon, the more stable this is against changes
320+ # - warn if expected original content wasn't found
321+ tmpl = mirror_to_placeholder(tmpl, default_mirrors['PRIMARY'],
322+ "$MIRROR")
323+ tmpl = mirror_to_placeholder(tmpl, default_mirrors['SECURITY'],
324+ "$SECURITY")
325+
326+ orig = util.target_path(target, aptsrc)
327+ if os.path.exists(orig):
328+ os.rename(orig, orig + ".curtin.old")
329+
330+ rendered = util.render_string(tmpl, params)
331+ disabled = disable_suites(cfg.get('disable_suites'), rendered, release)
332+ util.write_file(util.target_path(target, aptsrc), disabled, mode=0o644)
333+
334+ # protect the just generated sources.list from cloud-init
335+ cloudfile = "/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg"
336+ # this has to work with older cloud-init as well, so use old key
337+ cloudconf = yaml.dump({'apt_preserve_sources_list': True}, indent=1)
338+ try:
339+ util.write_file(util.target_path(target, cloudfile),
340+ cloudconf, mode=0o644)
341+ except IOError:
342+ LOG.exception("Failed to protect source.list from cloud-init in (%s)",
343+ util.target_path(target, cloudfile))
344+ raise
345+
346+
347+def add_apt_key_raw(key, target=None):
348+ """
349+ actual adding of a key as defined in key argument
350+ to the system
351+ """
352+ LOG.debug("Adding key:\n'%s'", key)
353+ try:
354+ util.subp(['apt-key', 'add', '-'], data=key.encode(), target=target)
355+ except util.ProcessExecutionError:
356+ LOG.exception("failed to add apt GPG Key to apt keyring")
357+ raise
358+
359+
360+def add_apt_key(ent, target=None):
361+ """
362+ Add key to the system as defined in ent (if any).
363+ Supports raw keys or keyid's
364+ The latter will as a first step fetched to get the raw key
365+ """
366+ if 'keyid' in ent and 'key' not in ent:
367+ keyserver = DEFAULT_KEYSERVER
368+ if 'keyserver' in ent:
369+ keyserver = ent['keyserver']
370+
371+ ent['key'] = gpg.getkeybyid(ent['keyid'], keyserver)
372+
373+ if 'key' in ent:
374+ add_apt_key_raw(ent['key'], target)
375+
376+
377+def add_apt_sources(srcdict, target=None, template_params=None,
378+ aa_repo_match=None):
379+ """
380+ add entries in /etc/apt/sources.list.d for each abbreviated
381+ sources.list entry in 'srcdict'. When rendering template, also
382+ include the values in dictionary searchList
383+ """
384+ if template_params is None:
385+ template_params = {}
386+
387+ if aa_repo_match is None:
388+ raise ValueError('did not get a valid repo matcher')
389+
390+ if not isinstance(srcdict, dict):
391+ raise TypeError('unknown apt format: %s' % (srcdict))
392+
393+ for filename in srcdict:
394+ ent = srcdict[filename]
395+ if 'filename' not in ent:
396+ ent['filename'] = filename
397+
398+ add_apt_key(ent, target)
399+
400+ if 'source' not in ent:
401+ continue
402+ source = ent['source']
403+ source = util.render_string(source, template_params)
404+
405+ if not ent['filename'].startswith("/"):
406+ ent['filename'] = os.path.join("/etc/apt/sources.list.d/",
407+ ent['filename'])
408+ if not ent['filename'].endswith(".list"):
409+ ent['filename'] += ".list"
410+
411+ if aa_repo_match(source):
412+ try:
413+ with util.ChrootableTarget(
414+ target, sys_resolvconf=True) as in_chroot:
415+ in_chroot.subp(["add-apt-repository", source])
416+ except util.ProcessExecutionError:
417+ LOG.exception("add-apt-repository failed.")
418+ raise
419+ continue
420+
421+ sourcefn = util.target_path(target, ent['filename'])
422+ try:
423+ contents = "%s\n" % (source)
424+ util.write_file(sourcefn, contents, omode="a")
425+ except IOError as detail:
426+ LOG.exception("failed write to file %s: %s", sourcefn, detail)
427+ raise
428+
429+ util.apt_update(target=target, force=True,
430+ comment="apt-source changed config")
431+
432+ return
433+
434+
435+def search_for_mirror(candidates):
436+ """
437+ Search through a list of mirror urls for one that works
438+ This needs to return quickly.
439+ """
440+ if candidates is None:
441+ return None
442+
443+ LOG.debug("search for mirror in candidates: '%s'", candidates)
444+ for cand in candidates:
445+ try:
446+ if util.is_resolvable_url(cand):
447+ LOG.debug("found working mirror: '%s'", cand)
448+ return cand
449+ except Exception:
450+ pass
451+ return None
452+
453+
454+def update_mirror_info(pmirror, smirror, arch):
455+ """sets security mirror to primary if not defined.
456+ returns defaults if no mirrors are defined"""
457+ if pmirror is not None:
458+ if smirror is None:
459+ smirror = pmirror
460+ return {'PRIMARY': pmirror,
461+ 'SECURITY': smirror}
462+ return get_default_mirrors(arch)
463+
464+
465+def get_arch_mirrorconfig(cfg, mirrortype, arch):
466+ """out of a list of potential mirror configurations select
467+ and return the one matching the architecture (or default)"""
468+ # select the mirror specification (if-any)
469+ mirror_cfg_list = cfg.get(mirrortype, None)
470+ if mirror_cfg_list is None:
471+ return None
472+
473+ # select the specification matching the target arch
474+ default = None
475+ for mirror_cfg_elem in mirror_cfg_list:
476+ arches = mirror_cfg_elem.get("arches")
477+ if arch in arches:
478+ return mirror_cfg_elem
479+ if "default" in arches:
480+ default = mirror_cfg_elem
481+ return default
482+
483+
484+def get_mirror(cfg, mirrortype, arch):
485+ """pass the three potential stages of mirror specification
486+ returns None is neither of them found anything otherwise the first
487+ hit is returned"""
488+ mcfg = get_arch_mirrorconfig(cfg, mirrortype, arch)
489+ if mcfg is None:
490+ return None
491+
492+ # directly specified
493+ mirror = mcfg.get("uri", None)
494+
495+ # fallback to search if specified
496+ if mirror is None:
497+ # list of mirrors to try to resolve
498+ mirror = search_for_mirror(mcfg.get("search", None))
499+
500+ return mirror
501+
502+
503+def find_apt_mirror_info(cfg, arch=None):
504+ """find_apt_mirror_info
505+ find an apt_mirror given the cfg provided.
506+ It can check for separate config of primary and security mirrors
507+ If only primary is given security is assumed to be equal to primary
508+ If the generic apt_mirror is given that is defining for both
509+ """
510+
511+ if arch is None:
512+ arch = util.get_architecture()
513+ LOG.debug("got arch for mirror selection: %s", arch)
514+ pmirror = get_mirror(cfg, "primary", arch)
515+ LOG.debug("got primary mirror: %s", pmirror)
516+ smirror = get_mirror(cfg, "security", arch)
517+ LOG.debug("got security mirror: %s", smirror)
518+
519+ # Note: curtin has no cloud-datasource fallback
520+
521+ mirror_info = update_mirror_info(pmirror, smirror, arch)
522+
523+ # less complex replacements use only MIRROR, derive from primary
524+ mirror_info["MIRROR"] = mirror_info["PRIMARY"]
525+
526+ return mirror_info
527+
528+
529+def apply_apt_proxy_config(cfg, proxy_fname, config_fname):
530+ """apply_apt_proxy_config
531+ Applies any apt*proxy config from if specified
532+ """
533+ # Set up any apt proxy
534+ cfgs = (('proxy', 'Acquire::http::Proxy "%s";'),
535+ ('http_proxy', 'Acquire::http::Proxy "%s";'),
536+ ('ftp_proxy', 'Acquire::ftp::Proxy "%s";'),
537+ ('https_proxy', 'Acquire::https::Proxy "%s";'))
538+
539+ proxies = [fmt % cfg.get(name) for (name, fmt) in cfgs if cfg.get(name)]
540+ if len(proxies):
541+ LOG.debug("write apt proxy info to %s", proxy_fname)
542+ util.write_file(proxy_fname, '\n'.join(proxies) + '\n')
543+ elif os.path.isfile(proxy_fname):
544+ util.del_file(proxy_fname)
545+ LOG.debug("no apt proxy configured, removed %s", proxy_fname)
546+
547+ if cfg.get('conf', None):
548+ LOG.debug("write apt config info to %s", config_fname)
549+ util.write_file(config_fname, cfg.get('conf'))
550+ elif os.path.isfile(config_fname):
551+ util.del_file(config_fname)
552+ LOG.debug("no apt config configured, removed %s", config_fname)
553+
554+
555+def apt_command(args):
556+ """ Main entry point for curtin apt-config standalone command
557+ This does not read the global config as handled by curthooks, but
558+ instead one can specify a different "target" and a new cfg via --config
559+ """
560+ cfg = config.load_command_config(args, {})
561+
562+ if args.target is not None:
563+ target = args.target
564+ else:
565+ state = util.load_command_environment()
566+ target = state['target']
567+
568+ if target is None:
569+ sys.stderr.write("Unable to find target. "
570+ "Use --target or set TARGET_MOUNT_POINT\n")
571+ sys.exit(2)
572+
573+ apt_cfg = cfg.get("apt")
574+ # if no apt config section is available, do nothing
575+ if apt_cfg is not None:
576+ LOG.debug("Handling apt to target %s with config %s",
577+ target, apt_cfg)
578+ try:
579+ with util.ChrootableTarget(target, sys_resolvconf=True):
580+ handle_apt(apt_cfg, target)
581+ except (RuntimeError, TypeError, ValueError, IOError):
582+ LOG.exception("Failed to configure apt features '%s'", apt_cfg)
583+ sys.exit(1)
584+ else:
585+ LOG.info("No apt config provided, skipping")
586+
587+ sys.exit(0)
588+
589+
590+def translate_old_apt_features(cfg):
591+ """translate the few old apt related features into the new config format"""
592+ predef_apt_cfg = cfg.get("apt")
593+ if predef_apt_cfg is None:
594+ cfg['apt'] = {}
595+ predef_apt_cfg = cfg.get("apt")
596+
597+ if cfg.get('apt_proxy') is not None:
598+ if predef_apt_cfg.get('proxy') is not None:
599+ msg = ("Error in apt_proxy configuration: "
600+ "old and new format of apt features "
601+ "are mutually exclusive")
602+ LOG.error(msg)
603+ raise ValueError(msg)
604+
605+ cfg['apt']['proxy'] = cfg.get('apt_proxy')
606+ LOG.debug("Transferred %s into new format: %s", cfg.get('apt_proxy'),
607+ cfg.get('apte'))
608+ del cfg['apt_proxy']
609+
610+ if cfg.get('apt_mirrors') is not None:
611+ if predef_apt_cfg.get('mirrors') is not None:
612+ msg = ("Error in apt_mirror configuration: "
613+ "old and new format of apt features "
614+ "are mutually exclusive")
615+ LOG.error(msg)
616+ raise ValueError(msg)
617+
618+ old = cfg.get('apt_mirrors')
619+ cfg['apt']['primary'] = [{"arches": ["default"],
620+ "uri": old.get('ubuntu_archive')}]
621+ cfg['apt']['security'] = [{"arches": ["default"],
622+ "uri": old.get('ubuntu_security')}]
623+ LOG.debug("Transferred %s into new format: %s", cfg.get('apt_mirror'),
624+ cfg.get('apt'))
625+ del cfg['apt_mirrors']
626+ # to work this also needs to disable the default protection
627+ psl = predef_apt_cfg.get('preserve_sources_list')
628+ if psl is not None:
629+ if config.value_as_boolean(psl) is True:
630+ msg = ("Error in apt_mirror configuration: "
631+ "apt_mirrors and preserve_sources_list: True "
632+ "are mutually exclusive")
633+ LOG.error(msg)
634+ raise ValueError(msg)
635+ cfg['apt']['preserve_sources_list'] = False
636+
637+ if cfg.get('debconf_selections') is not None:
638+ if predef_apt_cfg.get('debconf_selections') is not None:
639+ msg = ("Error in debconf_selections configuration: "
640+ "old and new format of apt features "
641+ "are mutually exclusive")
642+ LOG.error(msg)
643+ raise ValueError(msg)
644+
645+ selsets = cfg.get('debconf_selections')
646+ cfg['apt']['debconf_selections'] = selsets
647+ LOG.info("Transferred %s into new format: %s",
648+ cfg.get('debconf_selections'),
649+ cfg.get('apt'))
650+ del cfg['debconf_selections']
651+
652+ return cfg
653+
654+
655+CMD_ARGUMENTS = (
656+ ((('-c', '--config'),
657+ {'help': 'read configuration from cfg', 'action': util.MergedCmdAppend,
658+ 'metavar': 'FILE', 'type': argparse.FileType("rb"),
659+ 'dest': 'cfgopts', 'default': []}),
660+ (('-t', '--target'),
661+ {'help': 'chroot to target. default is env[TARGET_MOUNT_POINT]',
662+ 'action': 'store', 'metavar': 'TARGET',
663+ 'default': os.environ.get('TARGET_MOUNT_POINT')}),)
664+)
665+
666+
667+def POPULATE_SUBCMD(parser):
668+ """Populate subcommand option parsing for apt-config"""
669+ populate_one_subcmd(parser, CMD_ARGUMENTS, apt_command)
670+
671+CONFIG_CLEANERS = {
672+ 'cloud-init': clean_cloud_init,
673+}
674+
675+# vi: ts=4 expandtab syntax=python
676
677=== modified file 'curtin/commands/curthooks.py'
678--- curtin/commands/curthooks.py 2016-07-28 20:11:26 +0000
679+++ curtin/commands/curthooks.py 2016-08-02 09:15:49 +0000
680@@ -16,10 +16,8 @@
681 # along with Curtin. If not, see <http://www.gnu.org/licenses/>.
682
683 import copy
684-import glob
685 import os
686 import platform
687-import re
688 import sys
689 import shutil
690 import textwrap
691@@ -32,6 +30,7 @@
692 from curtin import util
693 from curtin import net
694 from curtin.reporter import events
695+from curtin.commands import apt_config
696
697 from . import populate_one_subcmd
698
699@@ -90,45 +89,15 @@
700 info.get('perms', "0644")))
701
702
703-def apt_config(cfg, target):
704- # cfg['apt_proxy']
705-
706- proxy_cfg_path = os.path.sep.join(
707- [target, '/etc/apt/apt.conf.d/90curtin-aptproxy'])
708- if cfg.get('apt_proxy'):
709- util.write_file(
710- proxy_cfg_path,
711- content='Acquire::HTTP::Proxy "%s";\n' % cfg['apt_proxy'])
712+def do_apt_config(cfg, target):
713+ cfg = apt_config.translate_old_apt_features(cfg)
714+ apt_cfg = cfg.get("apt")
715+ if apt_cfg is not None:
716+ LOG.info("curthooks handling apt to target %s with config %s",
717+ target, apt_cfg)
718+ apt_config.handle_apt(apt_cfg, target)
719 else:
720- if os.path.isfile(proxy_cfg_path):
721- os.unlink(proxy_cfg_path)
722-
723- # cfg['apt_mirrors']
724- # apt_mirrors:
725- # ubuntu_archive: http://local.archive/ubuntu
726- # ubuntu_security: http://local.archive/ubuntu
727- sources_list = os.path.sep.join([target, '/etc/apt/sources.list'])
728- if (isinstance(cfg.get('apt_mirrors'), dict) and
729- os.path.isfile(sources_list)):
730- repls = [
731- ('ubuntu_archive', r'http://\S*[.]*archive.ubuntu.com/\S*'),
732- ('ubuntu_security', r'http://security.ubuntu.com/\S*'),
733- ]
734- content = None
735- for name, regex in repls:
736- mirror = cfg['apt_mirrors'].get(name)
737- if not mirror:
738- continue
739-
740- if content is None:
741- with open(sources_list) as fp:
742- content = fp.read()
743- util.write_file(sources_list + ".dist", content)
744-
745- content = re.sub(regex, mirror + " ", content)
746-
747- if content is not None:
748- util.write_file(sources_list, content)
749+ LOG.info("No apt config provided, skipping")
750
751
752 def disable_overlayroot(cfg, target):
753@@ -140,15 +109,6 @@
754 shutil.move(local_conf, local_conf + ".old")
755
756
757-def clean_cloud_init(target):
758- flist = glob.glob(
759- os.path.sep.join([target, "/etc/cloud/cloud.cfg.d/*dpkg*"]))
760-
761- LOG.debug("cleaning cloud-init config from: %s" % flist)
762- for dpkg_cfg in flist:
763- os.unlink(dpkg_cfg)
764-
765-
766 def _maybe_remove_legacy_eth0(target,
767 path="/etc/network/interfaces.d/eth0.cfg"):
768 """Ubuntu cloud images previously included a 'eth0.cfg' that had
769@@ -250,118 +210,45 @@
770 mapping = copy.deepcopy(KERNEL_MAPPING)
771 config.merge_config(mapping, kernel_cfg.get('mapping', {}))
772
773- with util.ChrootableTarget(target) as in_chroot:
774-
775- if kernel_package:
776- util.install_packages([kernel_package], target=target)
777- return
778-
779- # uname[2] is kernel name (ie: 3.16.0-7-generic)
780- # version gets X.Y.Z, flavor gets anything after second '-'.
781- kernel = os.uname()[2]
782- codename, err = in_chroot.subp(
783- ['lsb_release', '--codename', '--short'], capture=True)
784- codename = codename.strip()
785- version, abi, flavor = kernel.split('-', 2)
786-
787- try:
788- map_suffix = mapping[codename][version]
789- except KeyError:
790- LOG.warn("Couldn't detect kernel package to install for %s."
791- % kernel)
792- if kernel_fallback is not None:
793- util.install_packages([kernel_fallback], target=target)
794- return
795-
796- package = "linux-{flavor}{map_suffix}".format(
797- flavor=flavor, map_suffix=map_suffix)
798-
799- if util.has_pkg_available(package, target):
800- if util.has_pkg_installed(package, target):
801- LOG.debug("Kernel package '%s' already installed", package)
802- else:
803- LOG.debug("installing kernel package '%s'", package)
804- util.install_packages([package], target=target)
805- else:
806- if kernel_fallback is not None:
807- LOG.info("Kernel package '%s' not available. "
808- "Installing fallback package '%s'.",
809- package, kernel_fallback)
810- util.install_packages([kernel_fallback], target=target)
811- else:
812- LOG.warn("Kernel package '%s' not available and no fallback."
813- " System may not boot.", package)
814-
815-
816-def apply_debconf_selections(cfg, target):
817- # debconf_selections:
818- # set1: |
819- # cloud-init cloud-init/datasources multiselect MAAS
820- # set2: pkg pkg/value string bar
821- selsets = cfg.get('debconf_selections')
822- if not selsets:
823- LOG.debug("debconf_selections was not set in config")
824- return
825-
826- # for each entry in selections, chroot and apply them.
827- # keep a running total of packages we've seen.
828- pkgs_cfgd = set()
829- for key, content in selsets.items():
830- LOG.debug("setting for %s, %s" % (key, content))
831- util.subp(['debconf-set-selections'], data=content.encode(),
832- target=target)
833- for line in content.splitlines():
834- if line.startswith("#"):
835- continue
836- pkg = re.sub(r"[:\s].*", "", line)
837- pkgs_cfgd.add(pkg)
838-
839- pkgs_installed = get_installed_packages(target)
840-
841- LOG.debug("pkgs_cfgd: %s" % pkgs_cfgd)
842- LOG.debug("pkgs_installed: %s" % pkgs_installed)
843- need_reconfig = pkgs_cfgd.intersection(pkgs_installed)
844-
845- if len(need_reconfig) == 0:
846- LOG.debug("no need for reconfig")
847- return
848-
849- # For any packages that are already installed, but have preseed data
850- # we populate the debconf database, but the filesystem configuration
851- # would be preferred on a subsequent dpkg-reconfigure.
852- # so, what we have to do is "know" information about certain packages
853- # to unconfigure them.
854- unhandled = []
855- to_config = []
856- for pkg in need_reconfig:
857- if pkg in CONFIG_CLEANERS:
858- LOG.debug("unconfiguring %s" % pkg)
859- CONFIG_CLEANERS[pkg](target)
860- to_config.append(pkg)
861- else:
862- unhandled.append(pkg)
863-
864- if len(unhandled):
865- LOG.warn("The following packages were installed and preseeded, "
866- "but cannot be unconfigured: %s", unhandled)
867-
868- util.subp(['dpkg-reconfigure', '--frontend=noninteractive'] +
869- list(to_config), data=None, target=target)
870-
871-
872-def get_installed_packages(target=None):
873- (out, _) = util.subp(['dpkg-query', '--list'], target=target, capture=True)
874-
875- pkgs_inst = set()
876- for line in out.splitlines():
877- try:
878- (state, pkg, other) = line.split(None, 2)
879- except ValueError:
880- continue
881- if state.startswith("hi") or state.startswith("ii"):
882- pkgs_inst.add(re.sub(":.*", "", pkg))
883-
884- return pkgs_inst
885+ if kernel_package:
886+ util.install_packages([kernel_package], target=target)
887+ return
888+
889+ # uname[2] is kernel name (ie: 3.16.0-7-generic)
890+ # version gets X.Y.Z, flavor gets anything after second '-'.
891+ kernel = os.uname()[2]
892+ codename, _ = util.subp(['lsb_release', '--codename', '--short'],
893+ capture=True, target=target)
894+ codename = codename.strip()
895+ version, abi, flavor = kernel.split('-', 2)
896+
897+ try:
898+ map_suffix = mapping[codename][version]
899+ except KeyError:
900+ LOG.warn("Couldn't detect kernel package to install for %s."
901+ % kernel)
902+ if kernel_fallback is not None:
903+ util.install_packages([kernel_fallback], target=target)
904+ return
905+
906+ package = "linux-{flavor}{map_suffix}".format(
907+ flavor=flavor, map_suffix=map_suffix)
908+
909+ if util.has_pkg_available(package, target):
910+ if util.has_pkg_installed(package, target):
911+ LOG.debug("Kernel package '%s' already installed", package)
912+ else:
913+ LOG.debug("installing kernel package '%s'", package)
914+ util.install_packages([package], target=target)
915+ else:
916+ if kernel_fallback is not None:
917+ LOG.info("Kernel package '%s' not available. "
918+ "Installing fallback package '%s'.",
919+ package, kernel_fallback)
920+ util.install_packages([kernel_fallback], target=target)
921+ else:
922+ LOG.warn("Kernel package '%s' not available and no fallback."
923+ " System may not boot.", package)
924
925
926 def setup_grub(cfg, target):
927@@ -731,7 +618,7 @@
928 }
929
930 needed_packages = []
931- installed_packages = get_installed_packages(target)
932+ installed_packages = util.get_installed_packages(target)
933 for cust_cfg, pkg_reqs in custom_configs.items():
934 if cust_cfg not in cfg:
935 continue
936@@ -811,7 +698,7 @@
937 name=stack_prefix, reporting_enabled=True, level="INFO",
938 description="writing config files and configuring apt"):
939 write_files(cfg, target)
940- apt_config(cfg, target)
941+ do_apt_config(cfg, target)
942 disable_overlayroot(cfg, target)
943
944 # packages may be needed prior to installing kernel
945@@ -834,7 +721,6 @@
946 setup_zipl(cfg, target)
947 install_kernel(cfg, target)
948 run_zipl(cfg, target)
949- apply_debconf_selections(cfg, target)
950
951 restore_dist_interfaces(cfg, target)
952
953@@ -897,8 +783,4 @@
954 populate_one_subcmd(parser, CMD_ARGUMENTS, curthooks)
955
956
957-CONFIG_CLEANERS = {
958- 'cloud-init': clean_cloud_init,
959-}
960-
961 # vi: ts=4 expandtab syntax=python
962
963=== modified file 'curtin/commands/main.py'
964--- curtin/commands/main.py 2016-04-04 20:12:01 +0000
965+++ curtin/commands/main.py 2016-08-02 09:15:49 +0000
966@@ -27,7 +27,7 @@
967
968 SUB_COMMAND_MODULES = [
969 'apply_net', 'block-meta', 'block-wipe', 'curthooks', 'extract',
970- 'hook', 'in-target', 'install', 'mkfs', 'net-meta',
971+ 'hook', 'in-target', 'install', 'mkfs', 'net-meta', 'apt-config',
972 'pack', 'swap', 'system-install', 'system-upgrade']
973
974
975
976=== added file 'curtin/gpg.py'
977--- curtin/gpg.py 1970-01-01 00:00:00 +0000
978+++ curtin/gpg.py 2016-08-02 09:15:49 +0000
979@@ -0,0 +1,74 @@
980+# Copyright (C) 2016 Canonical Ltd.
981+#
982+# Author: Scott Moser <scott.moser@canonical.com>
983+# Christian Ehrhardt <christian.ehrhardt@canonical.com>
984+#
985+# Curtin is free software: you can redistribute it and/or modify it under
986+# the terms of the GNU Affero General Public License as published by the
987+# Free Software Foundation, either version 3 of the License, or (at your
988+# option) any later version.
989+#
990+# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY
991+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
992+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
993+# more details.
994+#
995+# You should have received a copy of the GNU Affero General Public License
996+# along with Curtin. If not, see <http://www.gnu.org/licenses/>.
997+""" gpg.py
998+gpg related utilities to get raw keys data by their id
999+"""
1000+
1001+from curtin import util
1002+
1003+from .log import LOG
1004+
1005+
1006+def export_armour(key):
1007+ """Export gpg key, armoured key gets returned"""
1008+ try:
1009+ (armour, _) = util.subp(["gpg", "--export", "--armour", key],
1010+ capture=True)
1011+ except util.ProcessExecutionError as error:
1012+ # debug, since it happens for any key not on the system initially
1013+ LOG.debug('Failed to export armoured key "%s": %s', key, error)
1014+ armour = None
1015+ return armour
1016+
1017+
1018+def recv_key(key, keyserver):
1019+ """Receive gpg key from the specified keyserver"""
1020+ LOG.debug('Receive gpg key "%s"', key)
1021+ try:
1022+ util.subp(["gpg", "--keyserver", keyserver, "--recv", key],
1023+ capture=True)
1024+ except util.ProcessExecutionError as error:
1025+ raise ValueError(('Failed to import key "%s" '
1026+ 'from server "%s" - error %s') %
1027+ (key, keyserver, error))
1028+
1029+
1030+def delete_key(key):
1031+ """Delete the specified key from the local gpg ring"""
1032+ try:
1033+ util.subp(["gpg", "--batch", "--yes", "--delete-keys", key],
1034+ capture=True)
1035+ except util.ProcessExecutionError as error:
1036+ LOG.warn('Failed delete key "%s": %s', key, error)
1037+
1038+
1039+def getkeybyid(keyid, keyserver='keyserver.ubuntu.com'):
1040+ """get gpg keyid from keyserver"""
1041+ armour = export_armour(keyid)
1042+ if not armour:
1043+ try:
1044+ recv_key(keyid, keyserver=keyserver)
1045+ armour = export_armour(keyid)
1046+ except ValueError:
1047+ LOG.exception('Failed to obtain gpg key %s', keyid)
1048+ raise
1049+ finally:
1050+ # delete just imported key to leave environment as it was before
1051+ delete_key(keyid)
1052+
1053+ return armour
1054
1055=== modified file 'curtin/util.py'
1056--- curtin/util.py 2016-07-28 20:03:42 +0000
1057+++ curtin/util.py 2016-08-02 09:15:49 +0000
1058@@ -16,18 +16,30 @@
1059 # along with Curtin. If not, see <http://www.gnu.org/licenses/>.
1060
1061 import argparse
1062+import collections
1063 import errno
1064 import glob
1065 import json
1066 import os
1067 import platform
1068+import re
1069 import shutil
1070+import socket
1071 import subprocess
1072 import stat
1073 import sys
1074 import tempfile
1075 import time
1076
1077+# avoid the dependency to python3-six as used in cloud-init
1078+try:
1079+ from urlparse import urlparse
1080+except ImportError:
1081+ # python3
1082+ # avoid triggering pylint, https://github.com/PyCQA/pylint/issues/769
1083+ # pylint:disable=import-error,no-name-in-module
1084+ from urllib.parse import urlparse
1085+
1086 try:
1087 string_types = (basestring,)
1088 except NameError:
1089@@ -40,6 +52,11 @@
1090
1091 _LSB_RELEASE = {}
1092
1093+_DNS_REDIRECT_IP = None
1094+
1095+# matcher used in template rendering functions
1096+BASIC_MATCHER = re.compile(r'\$\{([A-Za-z0-9_.]+)\}|\$([A-Za-z0-9_.]+)')
1097+
1098
1099 def _subp(args, data=None, rcs=None, env=None, capture=False, shell=False,
1100 logstring=False, decode="replace", target=None):
1101@@ -487,6 +504,21 @@
1102 return False
1103
1104
1105+def get_installed_packages(target=None):
1106+ (out, _) = subp(['dpkg-query', '--list'], target=target, capture=True)
1107+
1108+ pkgs_inst = set()
1109+ for line in out.splitlines():
1110+ try:
1111+ (state, pkg, other) = line.split(None, 2)
1112+ except ValueError:
1113+ continue
1114+ if state.startswith("hi") or state.startswith("ii"):
1115+ pkgs_inst.add(re.sub(":.*", "", pkg))
1116+
1117+ return pkgs_inst
1118+
1119+
1120 def has_pkg_installed(pkg, target=None):
1121 try:
1122 out, _ = subp(['dpkg-query', '--show', '--showformat',
1123@@ -839,27 +871,37 @@
1124 return (isinstance(exc, IOError) and exc.errno == errno.ENOENT)
1125
1126
1127-def lsb_release():
1128+def _lsb_release(target=None):
1129 fmap = {'Codename': 'codename', 'Description': 'description',
1130 'Distributor ID': 'id', 'Release': 'release'}
1131+
1132+ data = {}
1133+ try:
1134+ out, _ = subp(['lsb_release', '--all'], capture=True, target=target)
1135+ for line in out.splitlines():
1136+ fname, _, val = line.partition(":")
1137+ if fname in fmap:
1138+ data[fmap[fname]] = val.strip()
1139+ missing = [k for k in fmap.values() if k not in data]
1140+ if len(missing):
1141+ LOG.warn("Missing fields in lsb_release --all output: %s",
1142+ ','.join(missing))
1143+
1144+ except ProcessExecutionError as err:
1145+ LOG.warn("Unable to get lsb_release --all: %s", err)
1146+ data = {v: "UNAVAILABLE" for v in fmap.values()}
1147+
1148+ return data
1149+
1150+
1151+def lsb_release(target=None):
1152+ if target_path(target) != "/":
1153+ # do not use or update cache if target is provided
1154+ return _lsb_release(target)
1155+
1156 global _LSB_RELEASE
1157 if not _LSB_RELEASE:
1158- data = {}
1159- try:
1160- out, err = subp(['lsb_release', '--all'], capture=True)
1161- for line in out.splitlines():
1162- fname, tok, val = line.partition(":")
1163- if fname in fmap:
1164- data[fmap[fname]] = val.strip()
1165- missing = [k for k in fmap.values() if k not in data]
1166- if len(missing):
1167- LOG.warn("Missing fields in lsb_release --all output: %s",
1168- ','.join(missing))
1169-
1170- except ProcessExecutionError as e:
1171- LOG.warn("Unable to get lsb_release --all: %s", e)
1172- data = {v: "UNAVAILABLE" for v in fmap.values()}
1173-
1174+ data = _lsb_release()
1175 _LSB_RELEASE.update(data)
1176 return _LSB_RELEASE
1177
1178@@ -889,6 +931,108 @@
1179 return platform2arch.get(platform.machine(), platform.machine())
1180
1181
1182+def basic_template_render(content, params):
1183+ """This does simple replacement of bash variable like templates.
1184+
1185+ It identifies patterns like ${a} or $a and can also identify patterns like
1186+ ${a.b} or $a.b which will look for a key 'b' in the dictionary rooted
1187+ by key 'a'.
1188+ """
1189+
1190+ def replacer(match):
1191+ """ replacer
1192+ replacer used in regex match to replace content
1193+ """
1194+ # Only 1 of the 2 groups will actually have a valid entry.
1195+ name = match.group(1)
1196+ if name is None:
1197+ name = match.group(2)
1198+ if name is None:
1199+ raise RuntimeError("Match encountered but no valid group present")
1200+ path = collections.deque(name.split("."))
1201+ selected_params = params
1202+ while len(path) > 1:
1203+ key = path.popleft()
1204+ if not isinstance(selected_params, dict):
1205+ raise TypeError("Can not traverse into"
1206+ " non-dictionary '%s' of type %s while"
1207+ " looking for subkey '%s'"
1208+ % (selected_params,
1209+ selected_params.__class__.__name__,
1210+ key))
1211+ selected_params = selected_params[key]
1212+ key = path.popleft()
1213+ if not isinstance(selected_params, dict):
1214+ raise TypeError("Can not extract key '%s' from non-dictionary"
1215+ " '%s' of type %s"
1216+ % (key, selected_params,
1217+ selected_params.__class__.__name__))
1218+ return str(selected_params[key])
1219+
1220+ return BASIC_MATCHER.sub(replacer, content)
1221+
1222+
1223+def render_string(content, params):
1224+ """ render_string
1225+ render a string following replacement rules as defined in
1226+ basic_template_render returning the string
1227+ """
1228+ if not params:
1229+ params = {}
1230+ return basic_template_render(content, params)
1231+
1232+
1233+def is_resolvable(name):
1234+ """determine if a url is resolvable, return a boolean
1235+ This also attempts to be resilent against dns redirection.
1236+
1237+ Note, that normal nsswitch resolution is used here. So in order
1238+ to avoid any utilization of 'search' entries in /etc/resolv.conf
1239+ we have to append '.'.
1240+
1241+ The top level 'invalid' domain is invalid per RFC. And example.com
1242+ should also not exist. The random entry will be resolved inside
1243+ the search list.
1244+ """
1245+ global _DNS_REDIRECT_IP
1246+ if _DNS_REDIRECT_IP is None:
1247+ badips = set()
1248+ badnames = ("does-not-exist.example.com.", "example.invalid.")
1249+ badresults = {}
1250+ for iname in badnames:
1251+ try:
1252+ result = socket.getaddrinfo(iname, None, 0, 0,
1253+ socket.SOCK_STREAM,
1254+ socket.AI_CANONNAME)
1255+ badresults[iname] = []
1256+ for (_, _, _, cname, sockaddr) in result:
1257+ badresults[iname].append("%s: %s" % (cname, sockaddr[0]))
1258+ badips.add(sockaddr[0])
1259+ except (socket.gaierror, socket.error):
1260+ pass
1261+ _DNS_REDIRECT_IP = badips
1262+ if badresults:
1263+ LOG.debug("detected dns redirection: %s", badresults)
1264+
1265+ try:
1266+ result = socket.getaddrinfo(name, None)
1267+ # check first result's sockaddr field
1268+ addr = result[0][4][0]
1269+ if addr in _DNS_REDIRECT_IP:
1270+ LOG.debug("dns %s in _DNS_REDIRECT_IP", name)
1271+ return False
1272+ LOG.debug("dns %s resolved to '%s'", name, result)
1273+ return True
1274+ except (socket.gaierror, socket.error):
1275+ LOG.debug("dns %s failed to resolve", name)
1276+ return False
1277+
1278+
1279+def is_resolvable_url(url):
1280+ """determine if this url is resolvable (existing or ip)."""
1281+ return is_resolvable(urlparse(url).hostname)
1282+
1283+
1284 def target_path(target, path=None):
1285 # return 'path' inside target, accepting target as None
1286 if target in (None, ""):
1287
1288=== modified file 'doc/devel/README-vmtest.txt'
1289--- doc/devel/README-vmtest.txt 2016-07-18 16:27:49 +0000
1290+++ doc/devel/README-vmtest.txt 2016-08-02 09:15:49 +0000
1291@@ -90,13 +90,13 @@
1292 The tests themselves don't actually have to run as root, but the
1293 test setup does.
1294 * the 'tools' directory must be in your path.
1295- * test will set apt_proxy in the guests to the value of
1296- 'apt_proxy' environment variable. If that is not set it will
1297+ * test will set apt: { proxy } in the guests to the value of
1298+ 'apt_proxy' environment variable. If that is not set it will
1299 look at the host's apt config and read 'Acquire::HTTP::Proxy'
1300
1301 == Environment Variables ==
1302 Some environment variables affect the running of vmtest
1303- * apt_proxy:
1304+ * apt_proxy:
1305 test will set apt_proxy in the guests to the value of 'apt_proxy'.
1306 If that is not set it will look at the host's apt config and read
1307 'Acquire::HTTP::Proxy'
1308@@ -146,7 +146,7 @@
1309
1310 * CURTIN_VMTEST_INSTALL_TIMEOUT: default 3000
1311 timeout before giving up on installation.
1312-
1313+
1314 * CURTIN_VMTEST_PARALLEL: default ''
1315 only supported through ./tools/jenkins-runner .
1316 -1 : then run one per core.
1317@@ -164,3 +164,58 @@
1318 Environment 'boolean' values:
1319 For boolean environment variables the value is considered True
1320 if it is any value other than case insensitive 'false', '' or "0"
1321+
1322+
1323+== Test Class Variables ==
1324+The base VMBaseClass defines several variables that help creating a new test
1325+easily. Among those the common ones are:
1326+
1327+Generic:
1328+- arch_skip
1329+ If a test is not supported on an architecture it can list the arch in this
1330+ variable to auto-skip the test if executed on that arch.
1331+- conf_file
1332+ The configuration that will be processed by this vmtest.
1333+- extra_kern_args
1334+ Extra arguments to the guest kernel on boot.
1335+
1336+Data Collection:
1337+- collect_scripts
1338+ The commands run when booting into the installed environment to collect the
1339+ data for the test to verify a proper execution.
1340+- boot_cloudconf
1341+ Extra cloud-init config content for the install phase.
1342+ This allows to gather content of the install phase if needed for test
1343+ verification.
1344+
1345+
1346+Disk Setup:
1347+- disk_block_size = 512
1348+- disk_driver = 'virtio-blk'
1349+ Used to set up the disks for the virtual environment used by the vmtest.
1350+ Will set the values used in extra_disks if not specified there.
1351+- extra_disks
1352+ A list of extra disks to be created for the testcase. The definition is
1353+ dpath:size:driver:block_size
1354+- multipath = False
1355+- multipath_num_paths = 2
1356+ Details for the (virtual) multipath setup
1357+- nvme_disks
1358+ a shortcut to provide extra disks with the nvme driver
1359+
1360+Timeouts:
1361+- boot_timeout
1362+- install_timeout
1363+ Usually set via CURTIN_VMTEST_BOOT_TIMEOUT/CURTIN_VMTEST_INSTALL_TIMEOUT
1364+ (see above) environment var, but can be overwritten by a testcase if needed.
1365+
1366+Checks:
1367+- disk_to_check
1368+ A disk name that is verified to be existing after the installation.
1369+- fstab_expected
1370+
1371+Release:
1372+- release = None
1373+- krel = None
1374+ Those two define the distribution and kernel release to be tested and are
1375+ usually set by importing and inheriting from tests/vmtests/releases.py
1376
1377=== added file 'doc/topics/apt_source.rst'
1378--- doc/topics/apt_source.rst 1970-01-01 00:00:00 +0000
1379+++ doc/topics/apt_source.rst 2016-08-02 09:15:49 +0000
1380@@ -0,0 +1,158 @@
1381+==========
1382+APT Source
1383+==========
1384+
1385+This part of curtin is meant to allow influencing the apt behaviour and configuration.
1386+
1387+By default - if no apt config is provided - it does nothing. That keeps behavior compatible on upgrades.
1388+
1389+The feature has an optional target argument which - by default - is used to modify the environment that curtin currently installs (@TARGET_MOUNT_POINT).
1390+
1391+Features
1392+--------
1393+
1394+* Add PGP keys to the APT trusted keyring
1395+
1396+ - add via short keyid
1397+
1398+ - add via long key fingerprint
1399+
1400+ - specify a custom keyserver to pull from
1401+
1402+ - add raw keys (which makes you independent of keyservers)
1403+
1404+* Influence global apt configuration
1405+
1406+ - adding ppa's
1407+
1408+ - replacing mirror, security mirror and release in sources.list
1409+
1410+ - able to provide a fully custom template for sources.list
1411+
1412+ - add arbitrary apt.conf settings
1413+
1414+ - provide debconf configurations
1415+
1416+ - disabling suites (=pockets)
1417+
1418+ - per architecture mirror definition
1419+
1420+
1421+Configuration
1422+-------------
1423+
1424+The general configuration of the apt feature is under an element called ``apt``.
1425+
1426+This can have various "global" subelements as listed in the examples below.
1427+The file ``apt-source.yaml`` holds more examples.
1428+
1429+These global configurations are valid throughput all of the apt feature.
1430+So for exmaple a global specification of a ``primary`` mirror will apply to all rendered sources entries.
1431+
1432+Then there is a section ``sources`` which can hold any number of source subelements itself.
1433+The key is the filename and will be prepended by /etc/apt/sources.list.d/ if it doesn't start with a ``/``.
1434+There are certain cases - where no content is written into a source.list file where the filename will be ignored - yet it can still be used as index for merging.
1435+
1436+The values inside the entries consist of the following optional entries::
1437+* ``source``: a sources.list entry (some variable replacements apply)
1438+
1439+* ``keyid``: providing a key to import via shortid or fingerprint
1440+
1441+* ``key``: providing a raw PGP key
1442+
1443+* ``keyserver``: specify an alternate keyserver to pull keys from that were specified by keyid
1444+
1445+The section "sources" is is a dictionary (unlike most block/net configs which are lists). This format allows merging between multiple input files than a list like::
1446+ sources:
1447+ s1: {'key': 'key1', 'source': 'source1'}
1448+
1449+ sources:
1450+ s2: {'key': 'key2'}
1451+ s1: {'keyserver': 'foo'}
1452+
1453+ This would be merged into
1454+ s1: {'key': 'key1', 'source': 'source1', keyserver: 'foo'}
1455+ s2: {'key': 'key2'}
1456+
1457+Here is just one of the most common examples for this feature: install with curtin in an isolated environment (derived repository):
1458+
1459+For that we need to:
1460+* insert the PGP key of the local repository to be trusted
1461+
1462+ - since you are locked down you can't pull from keyserver.ubuntu.com
1463+
1464+ - if you have an internal keyserver you could pull from there, but let us assume you don't even have that; so you have to provide the raw key
1465+
1466+ - in the example I'll use the key of the "Ubuntu CD Image Automatic Signing Key" which makes no sense as it is in the trusted keyring anyway, but it is a good example. (Also the key is shortened to stay readable)
1467+
1468+::
1469+
1470+ -----BEGIN PGP PUBLIC KEY BLOCK-----
1471+ Version: GnuPG v1
1472+ mQGiBEFEnz8RBAC7LstGsKD7McXZgd58oN68KquARLBl6rjA2vdhwl77KkPPOr3O
1473+ RwIbDAAKCRBAl26vQ30FtdxYAJsFjU+xbex7gevyGQ2/mhqidES4MwCggqQyo+w1
1474+ Twx6DKLF+3rF5nf1F3Q=
1475+ =PBAe
1476+ -----END PGP PUBLIC KEY BLOCK-----
1477+
1478+* replace the mirrors used to some mirrors available inside the isolated environment for apt to pull repository data from.
1479+
1480+ - lets consider we have a local mirror at ``mymirror.local`` but otherwise following the usual paths
1481+
1482+ - make an example with a partial mirror that doesn't mirror the backports suite, so backports have to be disabled
1483+
1484+That would be specified as
1485+::
1486+
1487+ apt:
1488+ primary:
1489+ - arches [default]
1490+ uri: http://mymirror.local/ubuntu/
1491+ disable_suites: [backports]
1492+ sources:
1493+ localrepokey:
1494+ key: | # full key as block
1495+ -----BEGIN PGP PUBLIC KEY BLOCK-----
1496+ Version: GnuPG v1
1497+
1498+ mQGiBEFEnz8RBAC7LstGsKD7McXZgd58oN68KquARLBl6rjA2vdhwl77KkPPOr3O
1499+ RwIbDAAKCRBAl26vQ30FtdxYAJsFjU+xbex7gevyGQ2/mhqidES4MwCggqQyo+w1
1500+ Twx6DKLF+3rF5nf1F3Q=
1501+ =PBAe
1502+ -----END PGP PUBLIC KEY BLOCK-----
1503+
1504+The file examples/apt-source.yaml holds various further examples that can be configured with this feature.
1505+
1506+
1507+Common snippets
1508+---------------
1509+This is a collection of additional ideas people can use the feature for customizing their to-be-installed system.
1510+
1511+* enable proposed on installing
1512+ apt:
1513+ sources:
1514+ proposed.list: deb $MIRROR $RELEASE-proposed main restricted universe multiverse
1515+
1516+* Make debug symbols available
1517+ apt:
1518+  sources:
1519+   ddebs.list: |
1520+   deb http://ddebs.ubuntu.com $RELEASE main restricted universe multiverse
1521+   deb http://ddebs.ubuntu.com $RELEASE-updates main restricted universe multiverse
1522+  deb http://ddebs.ubuntu.com $RELEASE-security main restricted universe multiverse
1523+ deb http://ddebs.ubuntu.com $RELEASE-proposed main restricted universe multiverse
1524+
1525+
1526+Timing
1527+------
1528+The feature is implemented at the stage of curthooks_commands, which runs just after curtin has extracted the image to the target.
1529+Additionally it can be ran as standalong command "curtin -v --config <yourconfigfile> apt-config".
1530+
1531+This will pick up the target from the environment variable that is set by curtin, if you want to use it to a different target or outside of usual curtin handling you can add ``--target <path>`` to it to overwrite the target path.
1532+This target should have at least a minimal system with apt, apt-add-repository and dpkg being installed for the functionality to work.
1533+
1534+
1535+Dependencies
1536+------------
1537+Cloud-init might need to resolve dependencies and install packages in the ephemeral environment to run curtin.
1538+Therefore it is recommended to not only provide an apt configuration to curtin for the target, but also one to the install environment via cloud-init.
1539
1540=== added file 'examples/apt-source.yaml'
1541--- examples/apt-source.yaml 1970-01-01 00:00:00 +0000
1542+++ examples/apt-source.yaml 2016-08-02 09:15:49 +0000
1543@@ -0,0 +1,267 @@
1544+# YAML example of an apt config.
1545+apt:
1546+ # The apt config consists of two major "areas".
1547+ #
1548+ # On one hand there is the global configuration for the apt feature.
1549+ #
1550+ # On one hand (down in this file) there is the source dictionary which allows
1551+ # to define various entries to be considered by apt.
1552+
1553+ ##############################################################################
1554+ # Section 1: global apt configuration
1555+ #
1556+ # The following examples number the top keys to ease identification in
1557+ # discussions.
1558+
1559+ # 1.1 preserve_sources_list
1560+ #
1561+ # Preserves the existing /etc/apt/sources.list
1562+ # Default: True - do not overwrite sources_list. If staying at true
1563+ # then any "mirrors" configuration will have no effect.
1564+ # Set to False to affect sources.list with the configuration. Without setting
1565+ # this to false only "extra" source specifications will be written into
1566+ # /etc/apt/sources.list.d/*
1567+ preserve_sources_list: false
1568+
1569+ # 1.2 disable_suites
1570+ #
1571+ # This is an empty list by default, so nothing is disabled.
1572+ #
1573+ # If given, those suites are removed from sources.list after all other
1574+ # modifications have been made.
1575+ # Suites are even disabled if no other modification was made,
1576+ # but not if is preserve_sources_list is active.
1577+ # There is a special alias “$RELEASE” as in the sources that will be replace
1578+ # by the matching release.
1579+ #
1580+ # To ease configuration and improve readability the following common ubuntu
1581+ # suites will be automatically mapped to their full definition.
1582+ # updates => $RELEASE-updates
1583+ # backports => $RELEASE-backports
1584+ # security => $RELEASE-security
1585+ # proposed => $RELEASE-proposed
1586+ # release => $RELEASE
1587+ #
1588+ # There is no harm in specifying a suite to be disabled that is not found in
1589+ # the source.list file (just a no-op then)
1590+ #
1591+ # Note: Lines don’t get deleted, but disabled by being converted to a comment.
1592+ # The following example disables all usual defaults except $RELEASE-security.
1593+ # On top it disables a custom suite called "mysuite"
1594+ disable_suites: [$RELEASE-updates, backports, $RELEASE, mysuite]
1595+
1596+ # 1.3 primary/security
1597+ #
1598+ # define a custom (e.g. localized) mirror that will be used in sources.list
1599+ # and any custom sources entries for deb / deb-src lines.
1600+ #
1601+ # One can set primary and security mirror to different uri's
1602+ # the child elements to the keys primary and secondary are equivalent
1603+ primary:
1604+ # arches is list of architectures the following config applies to
1605+ # the special keyword "default" applies to any architecture not explicitly
1606+ # listed.
1607+ - arches: [amd64, i386, default]
1608+ # uri is just defining the target as-is
1609+ uri: http://us.archive.ubuntu.com/ubuntu
1610+ #
1611+ # via search one can define lists that are
1612+ # tried one by one. The first with a working DNS resolution (or if it is an
1613+ # IP) will be picked. That way one can keep one configuration for multiple
1614+ # subenvironments that select the working one.
1615+ search:
1616+ - http://cool.but-sometimes-unreachable.com/ubuntu
1617+ - http://us.archive.ubuntu.com/ubuntu
1618+ #
1619+ # If multiple of a category are given
1620+ # 1. uri
1621+ # 2. search
1622+ # the first defining a valid mirror wins (in the order as defined here,
1623+ # not the order as listed in the config).
1624+ #
1625+ - arches: [s390x, arm64]
1626+ # as above, allowing to have one config for different per arch mirrors
1627+ # security is optional, if not defined it is set to the same value as primary
1628+ security:
1629+ uri: http://security.ubuntu.com/ubuntu
1630+ [...]
1631+
1632+ # if no mirrors are specified at all, or all lookups fail it will use:
1633+ # primary: http://archive.ubuntu.com/ubuntu
1634+ # security: http://security.ubuntu.com/ubuntu
1635+
1636+ # 1.4 sources_list
1637+ #
1638+ # Provide a custom template for rendering sources.list
1639+ # without one provided curtin will try to modify the sources.list it finds
1640+ # in the target at /etc/apt/sources.list.
1641+ # Within these sources.list templates you can use the following replacement
1642+ # variables (all have sane Ubuntu defaults, but mirrors can be overwritten
1643+ # as needed (see above)):
1644+ # => $RELEASE, $MIRROR, $PRIMARY, $SECURITY
1645+ sources_list: | # written by curtin custom template
1646+ deb $MIRROR $RELEASE main restricted
1647+ deb-src $MIRROR $RELEASE main restricted
1648+ deb $PRIMARY $RELEASE universe restricted
1649+ deb $SECURITY $RELEASE-security multiverse
1650+
1651+ # 1.5 conf
1652+ #
1653+ # Any apt config string that will be made available to apt
1654+ # see the APT.CONF(5) man page for details what can be specified
1655+ conf: | # APT config
1656+ APT {
1657+ Get {
1658+ Assume-Yes "true";
1659+ Fix-Broken "true";
1660+ };
1661+ };
1662+
1663+ # 1.6 (http_|ftp_|https_)proxy
1664+ #
1665+ # Proxies are the most common apt.conf option, so that for simplified use
1666+ # there is a shortcut for those. Those get automatically translated into the
1667+ # correct Acquire::*::Proxy statements.
1668+ #
1669+ # note: proxy actually being a short synonym to http_proxy
1670+ proxy: http://[[user][:pass]@]host[:port]/
1671+ http_proxy: http://[[user][:pass]@]host[:port]/
1672+ ftp_proxy: ftp://[[user][:pass]@]host[:port]/
1673+ https_proxy: https://[[user][:pass]@]host[:port]/
1674+
1675+ # 1.7 add_apt_repo_match
1676+ #
1677+ # 'source' entries in apt-sources that match this python regex
1678+ # expression will be passed to add-apt-repository
1679+ # The following example is also the builtin default if nothing is specified
1680+ add_apt_repo_match: '^[\w-]+:\w'
1681+
1682+
1683+ ##############################################################################
1684+ # Section 2: source list entries
1685+ #
1686+ # This is a dictionary (unlike most block/net which are lists)
1687+ #
1688+ # The key of each source entry is the filename and will be prepended by
1689+ # /etc/apt/sources.list.d/ if it doesn't start with a '/'.
1690+ # If it doesn't end with .list it will be appended so that apt picks up it's
1691+ # configuration.
1692+ #
1693+ # Whenever there is no content to be written into such a file, the key is
1694+ # not used as filename - yet it can still be used as index for merging
1695+ # configuration.
1696+ #
1697+ # The values inside the entries consost of the following optional entries:
1698+ # 'source': a sources.list entry (some variable replacements apply)
1699+ # 'keyid': providing a key to import via shortid or fingerprint
1700+ # 'key': providing a raw PGP key
1701+ # 'keyserver': specify an alternate keyserver to pull keys from that
1702+ # were specified by keyid
1703+
1704+ # This allows merging between multiple input files than a list like:
1705+ # cloud-config1
1706+ # sources:
1707+ # s1: {'key': 'key1', 'source': 'source1'}
1708+ # cloud-config2
1709+ # sources:
1710+ # s2: {'key': 'key2'}
1711+ # s1: {'keyserver': 'foo'}
1712+ # This would be merged to
1713+ # sources:
1714+ # s1:
1715+ # keyserver: foo
1716+ # key: key1
1717+ # source: source1
1718+ # s2:
1719+ # key: key2
1720+ #
1721+ # The following examples number the subfeatures per sources entry to ease
1722+ # identification in discussions.
1723+
1724+
1725+ sources:
1726+ curtin-dev-ppa.list:
1727+ # 2.1 source
1728+ #
1729+ # Creates a file in /etc/apt/sources.list.d/ for the sources list entry
1730+ # based on the key: "/etc/apt/sources.list.d/curtin-dev-ppa.list"
1731+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
1732+
1733+ # 2.2 keyid
1734+ #
1735+ # Importing a gpg key for a given key id. Used keyserver defaults to
1736+ # keyserver.ubuntu.com
1737+ keyid: F430BBA5 # GPG key ID published on a key server
1738+
1739+ ignored1:
1740+ # 2.3 PPA shortcut
1741+ #
1742+ # Setup correct apt sources.list line and Auto-Import the signing key
1743+ # from LP
1744+ #
1745+ # See https://help.launchpad.net/Packaging/PPA for more information
1746+ # this requires 'add-apt-repository'. This will create a file in
1747+ # /etc/apt/sources.list.d automatically, therefore the key here is
1748+ # ignored as filename in those cases.
1749+ source: "ppa:curtin-dev/test-archive" # Quote the string
1750+
1751+ my-repo2.list:
1752+ # 2.4 replacement variables
1753+ #
1754+ # sources can use $MIRROR, $PRIMARY, $SECURITY and $RELEASE replacement
1755+ # variables.
1756+ # They will be replaced with the default or specified mirrors and the
1757+ # running release.
1758+ # The entry below would be possibly turned into:
1759+ # source: deb http://archive.ubuntu.com/ubuntu xenial multiverse
1760+ source: deb $MIRROR $RELEASE multiverse
1761+
1762+ my-repo3.list:
1763+ # this would have the same end effect as 'ppa:curtin-dev/test-archive'
1764+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
1765+ keyid: F430BBA5 # GPG key ID published on the key server
1766+ filename: curtin-dev-ppa.list
1767+
1768+ ignored2:
1769+ # 2.5 key only
1770+ #
1771+ # this would only import the key without adding a ppa or other source spec
1772+ # since this doesn't generate a source.list file the filename key is ignored
1773+ keyid: F430BBA5 # GPG key ID published on a key server
1774+
1775+ ignored3:
1776+ # 2.6 key id alternatives
1777+ #
1778+ # Keyid's can also be specified via their long fingerprints
1779+ keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77
1780+
1781+ ignored4:
1782+ # 2.7 alternative keyservers
1783+ #
1784+ # One can also specify alternative keyservers to fetch keys from.
1785+ keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77
1786+ keyserver: pgp.mit.edu
1787+
1788+
1789+ my-repo4.list:
1790+ # 2.8 raw key
1791+ #
1792+ # The apt signing key can also be specified by providing a pgp public key
1793+ # block. Providing the PGP key this way is the most robust method for
1794+ # specifying a key, as it removes dependency on a remote key server.
1795+ #
1796+ # As with keyid's this can be specified with or without some actual source
1797+ # content.
1798+ key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK-----
1799+ -----BEGIN PGP PUBLIC KEY BLOCK-----
1800+ Version: SKS 1.0.10
1801+
1802+ mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6
1803+ qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj
1804+ 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y
1805+ IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H
1806+ 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP
1807+ t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o
1808+ uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey
1809+ =Y2oI
1810+ -----END PGP PUBLIC KEY BLOCK-----
1811
1812=== added file 'examples/tests/apt_config_command.yaml'
1813--- examples/tests/apt_config_command.yaml 1970-01-01 00:00:00 +0000
1814+++ examples/tests/apt_config_command.yaml 2016-08-02 09:15:49 +0000
1815@@ -0,0 +1,85 @@
1816+# This pushes curtin through a automatic installation
1817+# where no storage configuration is necessary.
1818+# exercising the standalong curtin apt-config command
1819+-placeholder_simple_install: unused
1820+bucket:
1821+ - &run_with_stdin |
1822+ #!/bin/sh
1823+ input="$1"
1824+ shift
1825+ printf "%s\n" "$input" | "$@"
1826+
1827+ - &run_apt_config_file |
1828+ #!/bin/sh
1829+ # take the first argument, write it to a tmp file and execute
1830+ # curtin apt --config=<tmpfile> "$@"
1831+ set -e
1832+ config="$1"
1833+ shift
1834+ TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX")
1835+ trap cleanup EXIT
1836+ cleanup() { [ -z "${TEMP_D}" || rm -Rf "${TEMP_D}"; }
1837+ cfg_file="${TEMP_D}/curtin-apt.conf"
1838+ printf "%s\n" "$config" > "${cfg_file}"
1839+ curtin apt-config "--config=${cfg_file}" "$@"
1840+
1841+ - &apt_config_ppa |
1842+ # this is just a large string
1843+ apt:
1844+ sources:
1845+ ignored:
1846+ source: "ppa:curtin-dev/test-archive"
1847+ curtin-test1.list:
1848+ source: "deb $MIRROR $RELEASE-proposed main"
1849+
1850+ - &apt_config_source |
1851+ apt:
1852+ preserve_sources_list: false
1853+ sources:
1854+ ignored:
1855+ source: "ppa:curtin-dev/test-archive"
1856+ # apt-add-repositroy adds the key anyway, but lets pass that code
1857+ # path of adding once more in this scope
1858+ key: |
1859+ -----BEGIN PGP PUBLIC KEY BLOCK-----
1860+ Version: GnuPG v1
1861+
1862+ mQINBFazYtEBEADXrW53tDOvwcnHwchLapTKK89+wBWR2qQKXx5Mymtjkrb688Fs
1863+ ciXcCsvClnNGJ9bEhrJTucyb7WF0KcDVQcvOd0C4HOSEAc0DANBu1Mdp/tmCWuiW
1864+ 1TbbhomyHAcHNdbuSZeMDh5xi9M3DYPVq72PwYwjrE4lotVxHeX5nYEH304U+5nJ
1865+ tBNpVon91k3ItymQ6Jii+9gVoQ7ujiH1/Gw4/J/1/5zQ3C1mOjq68vLunz5iw1Kn
1866+ 7TMVyID6qwq2UFEgudpseLfFZcb/p7KgI0m3S/OViwzSc44m63ggTPMmbeHW51xA
1867+ 1rpUChSU+cm0cJ4tNtAcYHRYRltWAo/3J1OzB6Ut5P7vIC5r+QcCyyMbku9NjYaw
1868+ dWX4DDKqW3is3qJ/7EeOKPL4N8wuKwuWUC7s2wqsIZL8EmsvR+ZOnTJ3bHZFvsLg
1869+ p/OKqmhxMGYXiXOWDOEJ+vwboPxrvhD90JZl8weNGPnpla+EkxRDBSpEb31Vgt5X
1870+ AIoxE7XxwfuXS3MGMA7fSqkGPGHfSLYQFFk+CAIeTUV+ypKW94hIxXKgqRxa7dxz
1871+ Ymqs+wgIGaWJCnx7z1Kpd3HD9iTAYjyWyhlQ/Tjt43kwUBdALhTL0vYUTGQyTgKt
1872+ tAriVf5bqHb6Hj5PS5YZQ/+YoCUI2OTrAWWNyH9rIEZGsFc30oJFPHj3fQARAQAB
1873+ tCNMYXVuY2hwYWQgUFBBIGZvciBjdXJ0aW4gZGV2ZWxvcGVyc4kCOAQTAQIAIgUC
1874+ VrNi0QIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQVf58jAFlAT4TGg//
1875+ SV7vWmkJqr5TSlT9JqCBfmtFjgudxTGG8XM2zwnta+m/3YVOMo0ZjyGL4fUKjCmN
1876+ eh6eYihwpRtfdawziaEOydDxNfdjwscV4Qcy7FjHX+DQnNzQyzK+WgWRJwNWloCw
1877+ skg2tF+EDRajalTRjHJAn+5zAilXVn71T/hhOCxkF0PBiH9s/e7pW/KcgBEC1MYV
1878+ Fs0fLST8SYhsIxttVRWuRkJDrtEY1zeVhkvk+PN6UuCY6/gyRSQ1rhhBF3ePqiba
1879+ CmLiUjnJMEm1OJOkuD33IMNPKQi99TZhr8y3AGCcrmAQtJsYLvVDPcsOsjGQHXP4
1880+ 2qQXK+jE/AAUycCQ6tgrAqCcUNQiClP8xUPkZOiDNvVMiPvIj/s79ShkoRaWLMb7
1881+ n9jyDOhs3L7dtmKQwHWq9qJ56fzx1L0/jxSanzm+ZJ/Q7t6E/GFxY1RsAk7xtI1C
1882+ SzSmrGKmtlbWlOyqqQb6zhULIJpaXvh/GaYyo0xI3rA+QvPDt/fgUJEBiSidwabW
1883+ Q8JU9iI5HXQxbVq1gSdy/z31fue5JuZSqjnjCjgho/UrXa4i1RPtqsY3FoTk7Hmo
1884+ C1z2cJc8HQI8JnEX/4qJXvPMRM2JsMD9DqvgsUJG5M9Qchy8cymYY+xeiBVYzJI+
1885+ WHCq6LHqnVxYZ+RM858lSsD6wetN44vguIjL3qJJ+wU=
1886+ curtin-test1.list:
1887+ source: "deb $MIRROR $RELEASE-proposed main"
1888+
1889+# into ephemeral environment
1890+early_commands:
1891+ 00_add_archive: [sh, -c, *run_with_stdin, "curtin-apt",
1892+ *apt_config_ppa, curtin, apt-config, --config=-, --target=/]
1893+ # tests itself by installing a packet only available in that ppa
1894+ 00_install_package: [apt-get, install, --assume-yes, smello]
1895+
1896+# into target environment
1897+late_commands:
1898+ 00_add_archive: [sh, -c, *run_apt_config_file, "curtin-apt-file",
1899+ *apt_config_source]
1900+ 00_install_package: [curtin, in-target, --, apt-get, install, --assume-yes, smello]
1901
1902=== added file 'examples/tests/apt_source_custom.yaml'
1903--- examples/tests/apt_source_custom.yaml 1970-01-01 00:00:00 +0000
1904+++ examples/tests/apt_source_custom.yaml 2016-08-02 09:15:49 +0000
1905@@ -0,0 +1,97 @@
1906+showtrace: true
1907+apt:
1908+ preserve_sources_list: false
1909+ primary:
1910+ - arches: [default]
1911+ uri: http://us.archive.ubuntu.com/ubuntu
1912+ security:
1913+ - arches: [default]
1914+ uri: http://security.ubuntu.com/ubuntu
1915+ sources_list: | # written by curtin custom template
1916+ deb $MIRROR $RELEASE main restricted
1917+ deb-src $MIRROR $RELEASE main restricted
1918+ deb $PRIMARY $RELEASE universe restricted
1919+ deb $SECURITY $RELEASE-security multiverse
1920+ # nice line to check in test
1921+ conf: | # APT config
1922+ ACQUIRE {
1923+ Retries "3";
1924+ };
1925+ sources:
1926+ curtin-dev-ppa.list:
1927+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
1928+ keyid: F430BBA5
1929+ ignored1:
1930+ source: "ppa:curtin-dev/test-archive"
1931+ my-repo2.list:
1932+ source: deb $MIRROR $RELEASE multiverse
1933+ ignored3:
1934+ keyid: 0E72 9061 0D2F 6DC4 D65E A921 9A31 4EC5 F470 A0AC
1935+ my-repo4.list:
1936+ source: deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main
1937+ key: |
1938+ -----BEGIN PGP PUBLIC KEY BLOCK-----
1939+ Version: GnuPG v1
1940+
1941+ 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
2000+ 7oh88hTkC1w=
2001+ =UNSw
2002+ -----END PGP PUBLIC KEY BLOCK-----
2003
2004=== added file 'examples/tests/apt_source_modify.yaml'
2005--- examples/tests/apt_source_modify.yaml 1970-01-01 00:00:00 +0000
2006+++ examples/tests/apt_source_modify.yaml 2016-08-02 09:15:49 +0000
2007@@ -0,0 +1,92 @@
2008+showtrace: true
2009+apt:
2010+ preserve_sources_list: false
2011+ primary:
2012+ - arches: [default]
2013+ uri: http://us.archive.ubuntu.com/ubuntu
2014+ security:
2015+ - arches: [default]
2016+ uri: http://security.ubuntu.com/ubuntu
2017+ conf: | # APT config
2018+ ACQUIRE {
2019+ Retries "3";
2020+ };
2021+ sources:
2022+ curtin-dev-ppa.list:
2023+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
2024+ keyid: F430BBA5
2025+ ignored1:
2026+ source: "ppa:curtin-dev/test-archive"
2027+ # intentionally dropped the .list here, has to be added by the code
2028+ my-repo2:
2029+ source: deb $MIRROR $RELEASE multiverse
2030+ ignored3:
2031+ keyid: 0E72 9061 0D2F 6DC4 D65E A921 9A31 4EC5 F470 A0AC
2032+ my-repo4.list:
2033+ source: deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main
2034+ key: |
2035+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2036+ Version: GnuPG v1
2037+
2038+ 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
2097+ 7oh88hTkC1w=
2098+ =UNSw
2099+ -----END PGP PUBLIC KEY BLOCK-----
2100
2101=== added file 'examples/tests/apt_source_modify_arches.yaml'
2102--- examples/tests/apt_source_modify_arches.yaml 1970-01-01 00:00:00 +0000
2103+++ examples/tests/apt_source_modify_arches.yaml 2016-08-02 09:15:49 +0000
2104@@ -0,0 +1,102 @@
2105+showtrace: true
2106+apt:
2107+ preserve_sources_list: false
2108+ primary:
2109+ # we don't know on which arch this will run, so we can't put the "right"
2110+ # config in an arch, but we can provide various confusing alternatives
2111+ # and orders and it has to pick default out of them
2112+ - arches: [x86_2048, x86_4096, x86_8192, amd18.5, "foobar"]
2113+ uri: http://notthis.com/ubuntu
2114+ - arches: ["*"]
2115+ uri: http://notthis.com/ubuntu
2116+ - arches: [default]
2117+ uri: http://us.archive.ubuntu.com/ubuntu
2118+ - arches: []
2119+ uri: http://notthis.com/ubuntu
2120+ security:
2121+ - arches: [default]
2122+ uri: http://security.ubuntu.com/ubuntu
2123+ - arches: ["supersecurearchthatdoesnexist"]
2124+ uri: http://notthat.com/ubuntu
2125+ conf: | # APT config
2126+ ACQUIRE {
2127+ Retries "3";
2128+ };
2129+ sources:
2130+ curtin-dev-ppa.list:
2131+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
2132+ keyid: F430BBA5
2133+ ignored1:
2134+ source: "ppa:curtin-dev/test-archive"
2135+ my-repo2.list:
2136+ source: deb $MIRROR $RELEASE multiverse
2137+ ignored3:
2138+ keyid: 0E72 9061 0D2F 6DC4 D65E A921 9A31 4EC5 F470 A0AC
2139+ my-repo4.list:
2140+ source: deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main
2141+ key: |
2142+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2143+ Version: GnuPG v1
2144+
2145+ mQINBFXJ3NcBEAC85PMdaKdItkdjCT1vRJrdwNqj4lN5mu6z4dDVfeZlmozRDBGb
2146+ ENSOWCiYz3meANO7bKthQQCqAETSBV72rrDCqFZUpXeyG3zCN98Z/UdJ8zpQD9uw
2147+ mq2CaAqWMk6ty+PkHQ4gtIc390lGfRbHNoZ5HaWJNVOK7FCB2hBmnTZW7AViYiYa
2148+ YswOjYxaCkwQ/DsMOPD7S5OjwbLucs2YGjkBm7YF1nnXNzyt+BwieKQW/sQ2+ga1
2149+ mkgLW1BTQN3+JreBpeHy/yrRdK4dOZZUar4WPZitZzOW2eNpaaf6hKNA14LB/96a
2150+ tEguK8VazoqSQGvNV/R3PjIYmurVP3/Z9bEVgOKhMCflgwKCYgx+tBUypN3zFWv9
2151+ pgVq3iHx1MFCvoP9FsNB7I6jzOxlQP4z25BzR3ympx/QexkFw5CBFXhdrU+qNVBl
2152+ SSnz69aLEjCRXqBOnQEr0irs/e/35+yLJdEuw89vSwWwrzbV5r1Y7uxinEGWSydT
2153+ qddj97uKOWeMmnp20Be4+nhDDW/BMiTFI4Y3bYeDTrftbWMaSEmtSTw5HHxtAFtg
2154+ X9Hyx0Q3eN1w3gRZgIdm0xYTe7bNTofFRdfXzB/9wtNIcaW10+IlODShFHPCnh+d
2155+ i56a8LCdZcXiiLfCIhEcnqmM37BVvhjIQKSyOU1eMEgX148aVEz36OVuMwARAQAB
2156+ tCdDaHJpc3RpYW4gRWhyaGFyZHQgPGNwYWVsemVyQGdtYWlsLmNvbT6JAjgEEwEC
2157+ ACIFAlXJ3NcCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJELo+KTOCgLJC
2158+ BugP/ir0ES3wCzvHMnkz2UlXt9FR4KqY0L9uFmwu9VYpmfAploEVIOi2HcuxpcRp
2159+ hgoQlUtkz3lRhUeZzCxuB1ljM2JKTJiezP1tFTTGCbVYhPyA0LmUiHDWylG7FzPb
2160+ TX96HY/G0jf+m4CfR8q3HNHjeDi4VeA2ppBxdHcVE5I7HihDgRPJd+CvCa3nYdAb
2161+ nXDKlQZz5aZc7AgrRVamr4mshkzWuwNNCwOt3AIgHDkU/HzA5xlXfwHxOoP6scWH
2162+ /T7vFsd/vOikBphGseWPgKm6w1zyQ5Dk/wjRL8UeSJZW+Rh4PuBMbxg01lAZpPTq
2163+ tu/bePeNty3g5bhwO6oHMpWhprn3dO37R680qo6UnBPzICeuBUnSYgpPnsQC9maz
2164+ FEjiBtMsXSanU5vww7TpxY1JHjk5KFcmKx4sBeablznsm+GuVaDFN8R4eDjrM14r
2165+ SOzA9cV0bSQr4dMqA9fZFSx6qLTacIeMfptybW3zaDX/pJOeBBWRAtoAfZIFbBnu
2166+ /ZxDDgiQtZzpVK4UkYk5rjjtV/CPVXx64AnTHi35YfUn14KkE+k3odHdvPfBiv9+
2167+ NxfkTuV/koOgpD3+lTIYXyVHS9gwvhfRD/YfdrnVGl7bRZe68j7bfWDuQuSqIhSA
2168+ jpeJslJCawnqv6fVB6buj6jjcgHIxqCVn99chaPFSblEIPfXtDVDaHJpc3RpYW4g
2169+ RWhyaGFyZHQgPGNocmlzdGlhbi5laHJoYXJkdEBjYW5vbmljYWwuY29tPokCOAQT
2170+ AQIAIgUCVsbUOgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQuj4pM4KA
2171+ skJNPg/7BF/iXHHdSBigWKXCCvQz58uInoc/R4beIegxRCMq7wkYEey4B7Fd35zY
2172+ zv9CBOTV3hZePMCg9jxl4ki2kSsrZSCIEJw4L/aXDtJtx3HT18uTW0QKoU3nK/ro
2173+ OtthVqBqmiSEi40UUU+5MGrUjwLSm+PjaaSapjK/lddf0KbXBB78/BtR/XT0gxWM
2174+ +o68Oei9Nj1S3h6UndJwNAQ1xaDWmU2T7CRJet3F+cXZd3aDuS2axOTSTZbraSq7
2175+ zdl1xUiKtzXZIp8X1ewne+dzkewZuWj7DOwOBEFK26UhxCjKd5mUr7jpWQ4ampFX
2176+ 6xfd/MK8SJFY+iHOBKyzq9po40tE23dqWuaHB+T3MxOgQ9JHCo9x22XNvEuKZW/V
2177+ 4WaoGHVkR+jtWNC8Qv/xCMHL3CEvAklKJR68WDhozwUYTgNt5vCoJOviMlbhDSwf
2178+ 0zVXpQwMR//4c0QSA0+BPpIEPDnx5vTIHBVXHy4bBBHU2Vi87QIDS0AtiBpNcspN
2179+ 6AG0ktuldkE/pqfSTJ2A9HpHZyU+8boagRS5/z102Pjtmf/mzUkcHmfRb9o0DE15
2180+ X5fqpA3lYyx9eHIAgH4eaB1+G20Ez/EY5hr8IMS2nNBSem491UW6DXDYRu6eBLrR
2181+ sRmtrJ6DlTZFRFlqVZ47bce/SbeM/xljvRkBxWG6RtDRsTyNVI65Ag0EVcnc1wEQ
2182+ ANzk9W058tSHqf05UEtJGrN0K8DLriCvPd7QdFA8yVIZM3WD+m0AMBGXjd8BT5c2
2183+ lt0GmhB8klonHZvPiVLTRTLcSsc3NBopr1HL1bWsgOczwWiXSrc62oGAHUOQT/bv
2184+ vS6KIkZgez+qtCo/DCOGJrADaoJBiBCLSsZgowpzazZZDPUF7rAsfcryVCFvftK0
2185+ wAe1OdvUG77NHrMrE1oX3zh82hTqR5azBre6Y81lNwxxug/Xl/RHjNhEOYohcsLS
2186+ /xl0m2X831fHzcGGpoISRgrfel+M4RoC7KsLrwVhrF8koCD/ZQlevfLpuRl5LNpO
2187+ s1ZtEi8ZvLliih+H+BOlBD0zUc3zZrrks/NCpm1eZba0Z6L48r4TIHW08SGlHx7o
2188+ SrXgkq3mtoM8C4uDiLwjav5KxiF7n68s/9LF82aAr7YjNXd+xYZNjsmmFlYj9CGI
2189+ lL4jVt4v4EtTONa6pbtCNv5ezOLDZ6BBcQ36xdkrWzdpjQjL2mnh3sqIAGIPu7tH
2190+ N8euQ5L1zIvIjVqYlR1eJssp96QYPWYxF7TosfML4BUhCP631IWfuD9X/K2LzDmv
2191+ B2gVZo9fbhSC+P7GYVG+tV4VLAMbspAxRXXL69+j98aeV5g59f8OFQPbGpKE/SAY
2192+ eIXtq8DD+PYUXXq3VUI2brVLv42LBVdSJpKNKG3decIBABEBAAGJAh8EGAECAAkF
2193+ AlXJ3NcCGwwACgkQuj4pM4KAskKzeg/9FxXJLV3eWwY4nn2VhwYTHnHtSUpi8usk
2194+ RzIa3Mcj6OEVjU2LZaT3UQF8h6dLM9y+CemcwyjMqm1RQ5+ogfrItby1AaBXwCvm
2195+ XCUGw2zFOAnyzSHHoDFj27sllFxDmfSiBY5KP8M+/ywHKZDkRb6EjzMPx5oKFeGW
2196+ Hmqaj5FDmTeWChSIHd1ZxobashFauOZDbS/ijRRMsVGFulU2Nb/4QJK73g3orfhY
2197+ 5mq1TMkQ5Kcbqh4OmYYYayLtJQcpa6ZVopaRhAJFe30P83zW9pM5LQDpP9JIyY+S
2198+ DjasEY4ekYtw6oCKAjpqlwaaNDjl27OkJ7R7laFKy4grZ2TSB/2KTjn/Ea3CH/pA
2199+ SrpVis1LvC90XytbBnsEKYXU55H943wmBc6oj+itQhx4WyIiv+UgtHI/DbnYbUru
2200+ 71wpfapqGBXYfu/zAra8PITngOFuizeYu+idemu55ANO3keJPKr3ZBUSBBpNFauT
2201+ VUUCSnrLt+kpSLopYESiNdsPW/aQTFgFvA4BkBJTIMQsQZXicuXUePYlg5xFzXOv
2202+ XgiqkjRA9xBI5JAIUgLRk3ulVFt2bIsTG9XgtGyphEs86Q0MOIMo0WbZGtAYDrZO
2203+ DITbm2KzVLGVLn/ZJiW11RSHPNiwgg66/puKdFWrSogYYDJdDEUJtLIhypZ+ORxe
2204+ 7oh88hTkC1w=
2205+ =UNSw
2206+ -----END PGP PUBLIC KEY BLOCK-----
2207
2208=== added file 'examples/tests/apt_source_modify_disable_suite.yaml'
2209--- examples/tests/apt_source_modify_disable_suite.yaml 1970-01-01 00:00:00 +0000
2210+++ examples/tests/apt_source_modify_disable_suite.yaml 2016-08-02 09:15:49 +0000
2211@@ -0,0 +1,92 @@
2212+showtrace: true
2213+apt:
2214+ preserve_sources_list: false
2215+ primary:
2216+ - arches: [default]
2217+ uri: http://us.archive.ubuntu.com/ubuntu
2218+ security:
2219+ - arches: [default]
2220+ uri: http://security.ubuntu.com/ubuntu
2221+ disable_suites: [$RELEASE-updates]
2222+ conf: | # APT config
2223+ ACQUIRE {
2224+ Retries "3";
2225+ };
2226+ sources:
2227+ curtin-dev-ppa.list:
2228+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
2229+ keyid: F430BBA5
2230+ ignored1:
2231+ source: "ppa:curtin-dev/test-archive"
2232+ my-repo2.list:
2233+ source: deb $MIRROR $RELEASE multiverse
2234+ ignored3:
2235+ keyid: 0E72 9061 0D2F 6DC4 D65E A921 9A31 4EC5 F470 A0AC
2236+ my-repo4.list:
2237+ source: deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main
2238+ key: |
2239+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2240+ Version: GnuPG v1
2241+
2242+ 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
2301+ 7oh88hTkC1w=
2302+ =UNSw
2303+ -----END PGP PUBLIC KEY BLOCK-----
2304
2305=== added file 'examples/tests/apt_source_preserve.yaml'
2306--- examples/tests/apt_source_preserve.yaml 1970-01-01 00:00:00 +0000
2307+++ examples/tests/apt_source_preserve.yaml 2016-08-02 09:15:49 +0000
2308@@ -0,0 +1,98 @@
2309+showtrace: true
2310+apt:
2311+ # this is like the other apt_source test but with preserve true
2312+ # this is the default now preserve_sources_list: true
2313+ primary:
2314+ - arches: [default]
2315+ uri: http://us.archive.ubuntu.com/ubuntu
2316+ security:
2317+ - arches: [default]
2318+ uri: http://security.ubuntu.com/ubuntu
2319+ sources_list: | # written by curtin custom template
2320+ deb $MIRROR $RELEASE main restricted
2321+ deb-src $MIRROR $RELEASE main restricted
2322+ deb $PRIMARY $RELEASE universe restricted
2323+ deb $SECURITY $RELEASE-security multiverse
2324+ # nice line to check in test
2325+ conf: | # APT config
2326+ ACQUIRE {
2327+ Retries "3";
2328+ };
2329+ sources:
2330+ curtin-dev-ppa.list:
2331+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
2332+ keyid: F430BBA5
2333+ ignored1:
2334+ source: "ppa:curtin-dev/test-archive"
2335+ my-repo2.list:
2336+ source: deb $MIRROR $RELEASE multiverse
2337+ ignored3:
2338+ keyid: 0E72 9061 0D2F 6DC4 D65E A921 9A31 4EC5 F470 A0AC
2339+ my-repo4.list:
2340+ source: deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main
2341+ key: |
2342+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2343+ Version: GnuPG v1
2344+
2345+ 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
2404+ 7oh88hTkC1w=
2405+ =UNSw
2406+ -----END PGP PUBLIC KEY BLOCK-----
2407
2408=== added file 'examples/tests/apt_source_search.yaml'
2409--- examples/tests/apt_source_search.yaml 1970-01-01 00:00:00 +0000
2410+++ examples/tests/apt_source_search.yaml 2016-08-02 09:15:49 +0000
2411@@ -0,0 +1,97 @@
2412+showtrace: true
2413+apt:
2414+ preserve_sources_list: false
2415+ primary:
2416+ - arches: [default]
2417+ search:
2418+ - http://does.not.exist/ubuntu
2419+ - http://does.also.not.exist/ubuntu
2420+ - http://us.archive.ubuntu.com/ubuntu
2421+ security:
2422+ - arches: [default]
2423+ search:
2424+ - http://does.not.exist/ubuntu
2425+ - http://does.also.not.exist/ubuntu
2426+ - http://security.ubuntu.com/ubuntu
2427+ conf: | # APT config
2428+ ACQUIRE {
2429+ Retries "3";
2430+ };
2431+ sources:
2432+ curtin-dev-ppa.list:
2433+ source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main"
2434+ keyid: F430BBA5
2435+ ignored1:
2436+ source: "ppa:curtin-dev/test-archive"
2437+ my-repo2.list:
2438+ source: deb $MIRROR $RELEASE multiverse
2439+ ignored3:
2440+ keyid: 0E72 9061 0D2F 6DC4 D65E A921 9A31 4EC5 F470 A0AC
2441+ my-repo4.list:
2442+ source: deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main
2443+ key: |
2444+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2445+ Version: GnuPG v1
2446+
2447+ mQINBFXJ3NcBEAC85PMdaKdItkdjCT1vRJrdwNqj4lN5mu6z4dDVfeZlmozRDBGb
2448+ ENSOWCiYz3meANO7bKthQQCqAETSBV72rrDCqFZUpXeyG3zCN98Z/UdJ8zpQD9uw
2449+ mq2CaAqWMk6ty+PkHQ4gtIc390lGfRbHNoZ5HaWJNVOK7FCB2hBmnTZW7AViYiYa
2450+ YswOjYxaCkwQ/DsMOPD7S5OjwbLucs2YGjkBm7YF1nnXNzyt+BwieKQW/sQ2+ga1
2451+ mkgLW1BTQN3+JreBpeHy/yrRdK4dOZZUar4WPZitZzOW2eNpaaf6hKNA14LB/96a
2452+ tEguK8VazoqSQGvNV/R3PjIYmurVP3/Z9bEVgOKhMCflgwKCYgx+tBUypN3zFWv9
2453+ pgVq3iHx1MFCvoP9FsNB7I6jzOxlQP4z25BzR3ympx/QexkFw5CBFXhdrU+qNVBl
2454+ SSnz69aLEjCRXqBOnQEr0irs/e/35+yLJdEuw89vSwWwrzbV5r1Y7uxinEGWSydT
2455+ qddj97uKOWeMmnp20Be4+nhDDW/BMiTFI4Y3bYeDTrftbWMaSEmtSTw5HHxtAFtg
2456+ X9Hyx0Q3eN1w3gRZgIdm0xYTe7bNTofFRdfXzB/9wtNIcaW10+IlODShFHPCnh+d
2457+ i56a8LCdZcXiiLfCIhEcnqmM37BVvhjIQKSyOU1eMEgX148aVEz36OVuMwARAQAB
2458+ tCdDaHJpc3RpYW4gRWhyaGFyZHQgPGNwYWVsemVyQGdtYWlsLmNvbT6JAjgEEwEC
2459+ ACIFAlXJ3NcCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJELo+KTOCgLJC
2460+ BugP/ir0ES3wCzvHMnkz2UlXt9FR4KqY0L9uFmwu9VYpmfAploEVIOi2HcuxpcRp
2461+ hgoQlUtkz3lRhUeZzCxuB1ljM2JKTJiezP1tFTTGCbVYhPyA0LmUiHDWylG7FzPb
2462+ TX96HY/G0jf+m4CfR8q3HNHjeDi4VeA2ppBxdHcVE5I7HihDgRPJd+CvCa3nYdAb
2463+ nXDKlQZz5aZc7AgrRVamr4mshkzWuwNNCwOt3AIgHDkU/HzA5xlXfwHxOoP6scWH
2464+ /T7vFsd/vOikBphGseWPgKm6w1zyQ5Dk/wjRL8UeSJZW+Rh4PuBMbxg01lAZpPTq
2465+ tu/bePeNty3g5bhwO6oHMpWhprn3dO37R680qo6UnBPzICeuBUnSYgpPnsQC9maz
2466+ FEjiBtMsXSanU5vww7TpxY1JHjk5KFcmKx4sBeablznsm+GuVaDFN8R4eDjrM14r
2467+ SOzA9cV0bSQr4dMqA9fZFSx6qLTacIeMfptybW3zaDX/pJOeBBWRAtoAfZIFbBnu
2468+ /ZxDDgiQtZzpVK4UkYk5rjjtV/CPVXx64AnTHi35YfUn14KkE+k3odHdvPfBiv9+
2469+ NxfkTuV/koOgpD3+lTIYXyVHS9gwvhfRD/YfdrnVGl7bRZe68j7bfWDuQuSqIhSA
2470+ jpeJslJCawnqv6fVB6buj6jjcgHIxqCVn99chaPFSblEIPfXtDVDaHJpc3RpYW4g
2471+ RWhyaGFyZHQgPGNocmlzdGlhbi5laHJoYXJkdEBjYW5vbmljYWwuY29tPokCOAQT
2472+ AQIAIgUCVsbUOgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQuj4pM4KA
2473+ skJNPg/7BF/iXHHdSBigWKXCCvQz58uInoc/R4beIegxRCMq7wkYEey4B7Fd35zY
2474+ zv9CBOTV3hZePMCg9jxl4ki2kSsrZSCIEJw4L/aXDtJtx3HT18uTW0QKoU3nK/ro
2475+ OtthVqBqmiSEi40UUU+5MGrUjwLSm+PjaaSapjK/lddf0KbXBB78/BtR/XT0gxWM
2476+ +o68Oei9Nj1S3h6UndJwNAQ1xaDWmU2T7CRJet3F+cXZd3aDuS2axOTSTZbraSq7
2477+ zdl1xUiKtzXZIp8X1ewne+dzkewZuWj7DOwOBEFK26UhxCjKd5mUr7jpWQ4ampFX
2478+ 6xfd/MK8SJFY+iHOBKyzq9po40tE23dqWuaHB+T3MxOgQ9JHCo9x22XNvEuKZW/V
2479+ 4WaoGHVkR+jtWNC8Qv/xCMHL3CEvAklKJR68WDhozwUYTgNt5vCoJOviMlbhDSwf
2480+ 0zVXpQwMR//4c0QSA0+BPpIEPDnx5vTIHBVXHy4bBBHU2Vi87QIDS0AtiBpNcspN
2481+ 6AG0ktuldkE/pqfSTJ2A9HpHZyU+8boagRS5/z102Pjtmf/mzUkcHmfRb9o0DE15
2482+ X5fqpA3lYyx9eHIAgH4eaB1+G20Ez/EY5hr8IMS2nNBSem491UW6DXDYRu6eBLrR
2483+ sRmtrJ6DlTZFRFlqVZ47bce/SbeM/xljvRkBxWG6RtDRsTyNVI65Ag0EVcnc1wEQ
2484+ ANzk9W058tSHqf05UEtJGrN0K8DLriCvPd7QdFA8yVIZM3WD+m0AMBGXjd8BT5c2
2485+ lt0GmhB8klonHZvPiVLTRTLcSsc3NBopr1HL1bWsgOczwWiXSrc62oGAHUOQT/bv
2486+ vS6KIkZgez+qtCo/DCOGJrADaoJBiBCLSsZgowpzazZZDPUF7rAsfcryVCFvftK0
2487+ wAe1OdvUG77NHrMrE1oX3zh82hTqR5azBre6Y81lNwxxug/Xl/RHjNhEOYohcsLS
2488+ /xl0m2X831fHzcGGpoISRgrfel+M4RoC7KsLrwVhrF8koCD/ZQlevfLpuRl5LNpO
2489+ s1ZtEi8ZvLliih+H+BOlBD0zUc3zZrrks/NCpm1eZba0Z6L48r4TIHW08SGlHx7o
2490+ SrXgkq3mtoM8C4uDiLwjav5KxiF7n68s/9LF82aAr7YjNXd+xYZNjsmmFlYj9CGI
2491+ lL4jVt4v4EtTONa6pbtCNv5ezOLDZ6BBcQ36xdkrWzdpjQjL2mnh3sqIAGIPu7tH
2492+ N8euQ5L1zIvIjVqYlR1eJssp96QYPWYxF7TosfML4BUhCP631IWfuD9X/K2LzDmv
2493+ B2gVZo9fbhSC+P7GYVG+tV4VLAMbspAxRXXL69+j98aeV5g59f8OFQPbGpKE/SAY
2494+ eIXtq8DD+PYUXXq3VUI2brVLv42LBVdSJpKNKG3decIBABEBAAGJAh8EGAECAAkF
2495+ AlXJ3NcCGwwACgkQuj4pM4KAskKzeg/9FxXJLV3eWwY4nn2VhwYTHnHtSUpi8usk
2496+ RzIa3Mcj6OEVjU2LZaT3UQF8h6dLM9y+CemcwyjMqm1RQ5+ogfrItby1AaBXwCvm
2497+ XCUGw2zFOAnyzSHHoDFj27sllFxDmfSiBY5KP8M+/ywHKZDkRb6EjzMPx5oKFeGW
2498+ Hmqaj5FDmTeWChSIHd1ZxobashFauOZDbS/ijRRMsVGFulU2Nb/4QJK73g3orfhY
2499+ 5mq1TMkQ5Kcbqh4OmYYYayLtJQcpa6ZVopaRhAJFe30P83zW9pM5LQDpP9JIyY+S
2500+ DjasEY4ekYtw6oCKAjpqlwaaNDjl27OkJ7R7laFKy4grZ2TSB/2KTjn/Ea3CH/pA
2501+ SrpVis1LvC90XytbBnsEKYXU55H943wmBc6oj+itQhx4WyIiv+UgtHI/DbnYbUru
2502+ 71wpfapqGBXYfu/zAra8PITngOFuizeYu+idemu55ANO3keJPKr3ZBUSBBpNFauT
2503+ VUUCSnrLt+kpSLopYESiNdsPW/aQTFgFvA4BkBJTIMQsQZXicuXUePYlg5xFzXOv
2504+ XgiqkjRA9xBI5JAIUgLRk3ulVFt2bIsTG9XgtGyphEs86Q0MOIMo0WbZGtAYDrZO
2505+ DITbm2KzVLGVLn/ZJiW11RSHPNiwgg66/puKdFWrSogYYDJdDEUJtLIhypZ+ORxe
2506+ 7oh88hTkC1w=
2507+ =UNSw
2508+ -----END PGP PUBLIC KEY BLOCK-----
2509
2510=== added file 'examples/tests/test_old_apt_features.yaml'
2511--- examples/tests/test_old_apt_features.yaml 1970-01-01 00:00:00 +0000
2512+++ examples/tests/test_old_apt_features.yaml 2016-08-02 09:15:49 +0000
2513@@ -0,0 +1,10 @@
2514+showtrace: true
2515+# apt_proxy gets configured by tools/launch and tests/vmtests/__init__.py
2516+apt_mirrors:
2517+# we need a mirror that works (even in CI) but isn't the default
2518+ ubuntu_archive: http://us.archive.ubuntu.com/ubuntu
2519+ ubuntu_security: http://archive.ubuntu.com/ubuntu
2520+# set some key that surely is available to a non-default value
2521+debconf_selections:
2522+ set1: |
2523+ debconf debconf/priority select low
2524
2525=== added file 'examples/tests/test_old_apt_features_ports.yaml'
2526--- examples/tests/test_old_apt_features_ports.yaml 1970-01-01 00:00:00 +0000
2527+++ examples/tests/test_old_apt_features_ports.yaml 2016-08-02 09:15:49 +0000
2528@@ -0,0 +1,10 @@
2529+showtrace: true
2530+# apt_proxy gets configured by tools/launch and tests/vmtests/__init__.py
2531+apt_mirrors:
2532+# For ports there is no non-default alternative we could use
2533+ ubuntu_archive: http://ports.ubuntu.com/ubuntu-ports
2534+ ubuntu_security: http://ports.ubuntu.com/ubuntu-ports
2535+# set some key that surely is available to a non-default value
2536+debconf_selections:
2537+ set1: |
2538+ debconf debconf/priority select low
2539
2540=== added file 'tests/unittests/test_apt_custom_sources_list.py'
2541--- tests/unittests/test_apt_custom_sources_list.py 1970-01-01 00:00:00 +0000
2542+++ tests/unittests/test_apt_custom_sources_list.py 2016-08-02 09:15:49 +0000
2543@@ -0,0 +1,170 @@
2544+""" test_apt_custom_sources_list
2545+Test templating of custom sources list
2546+"""
2547+import logging
2548+import os
2549+import shutil
2550+import tempfile
2551+
2552+from unittest import TestCase
2553+
2554+import yaml
2555+import mock
2556+from mock import call
2557+
2558+from curtin import util
2559+from curtin.commands import apt_config
2560+
2561+LOG = logging.getLogger(__name__)
2562+
2563+TARGET = "/"
2564+
2565+# Input and expected output for the custom template
2566+YAML_TEXT_CUSTOM_SL = """
2567+preserve_sources_list: false
2568+primary:
2569+ - arches: [default]
2570+ uri: http://test.ubuntu.com/ubuntu/
2571+security:
2572+ - arches: [default]
2573+ uri: http://testsec.ubuntu.com/ubuntu/
2574+sources_list: |
2575+
2576+ ## Note, this file is written by curtin at install time. It should not end
2577+ ## up on the installed system itself.
2578+ #
2579+ # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
2580+ # newer versions of the distribution.
2581+ deb $MIRROR $RELEASE main restricted
2582+ deb-src $MIRROR $RELEASE main restricted
2583+ deb $PRIMARY $RELEASE universe restricted
2584+ deb $SECURITY $RELEASE-security multiverse
2585+ # FIND_SOMETHING_SPECIAL
2586+"""
2587+
2588+EXPECTED_CONVERTED_CONTENT = """
2589+## Note, this file is written by curtin at install time. It should not end
2590+## up on the installed system itself.
2591+#
2592+# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
2593+# newer versions of the distribution.
2594+deb http://test.ubuntu.com/ubuntu/ fakerel main restricted
2595+deb-src http://test.ubuntu.com/ubuntu/ fakerel main restricted
2596+deb http://test.ubuntu.com/ubuntu/ fakerel universe restricted
2597+deb http://testsec.ubuntu.com/ubuntu/ fakerel-security multiverse
2598+# FIND_SOMETHING_SPECIAL
2599+"""
2600+
2601+# mocked to be independent to the unittest system
2602+MOCKED_APT_SRC_LIST = """
2603+deb http://test.ubuntu.com/ubuntu/ notouched main restricted
2604+deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted
2605+deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted
2606+deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted
2607+"""
2608+
2609+EXPECTED_BASE_CONTENT = ("""
2610+deb http://test.ubuntu.com/ubuntu/ notouched main restricted
2611+deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted
2612+deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted
2613+deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted
2614+""")
2615+
2616+EXPECTED_MIRROR_CONTENT = ("""
2617+deb http://test.ubuntu.com/ubuntu/ notouched main restricted
2618+deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted
2619+deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted
2620+deb http://test.ubuntu.com/ubuntu/ notouched-security main restricted
2621+""")
2622+
2623+EXPECTED_PRIMSEC_CONTENT = ("""
2624+deb http://test.ubuntu.com/ubuntu/ notouched main restricted
2625+deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted
2626+deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted
2627+deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted
2628+""")
2629+
2630+
2631+class TestAptSourceConfigSourceList(TestCase):
2632+ """TestAptSourceConfigSourceList - Class to test sources list rendering"""
2633+ def setUp(self):
2634+ super(TestAptSourceConfigSourceList, self).setUp()
2635+ self.new_root = tempfile.mkdtemp()
2636+ self.addCleanup(shutil.rmtree, self.new_root)
2637+ # self.patchUtils(self.new_root)
2638+
2639+ @staticmethod
2640+ def _apt_source_list(cfg, expected):
2641+ "_apt_source_list - Test rendering from template (generic)"
2642+
2643+ arch = util.get_architecture()
2644+ # would fail inside the unittest context
2645+ with mock.patch.object(util, 'get_architecture',
2646+ return_value=arch) as mockga:
2647+ with mock.patch.object(util, 'write_file') as mockwrite:
2648+ # keep it side effect free and avoid permission errors
2649+ with mock.patch.object(os, 'rename'):
2650+ # make test independent to executing system
2651+ with mock.patch.object(util, 'load_file',
2652+ return_value=MOCKED_APT_SRC_LIST):
2653+ with mock.patch.object(util, 'lsb_release',
2654+ return_value={'codename':
2655+ 'fakerel'}):
2656+ apt_config.handle_apt(cfg, TARGET)
2657+
2658+ mockga.assert_called_with("/")
2659+
2660+ cloudfile = '/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg'
2661+ cloudconf = yaml.dump({'apt_preserve_sources_list': True}, indent=1)
2662+ calls = [call(util.target_path(TARGET, '/etc/apt/sources.list'),
2663+ expected,
2664+ mode=0o644),
2665+ call(util.target_path(TARGET, cloudfile),
2666+ cloudconf,
2667+ mode=0o644)]
2668+ mockwrite.assert_has_calls(calls)
2669+
2670+ def test_apt_source_list(self):
2671+ """test_apt_source_list - Test with neither custom sources nor parms"""
2672+ cfg = {'preserve_sources_list': False}
2673+
2674+ self._apt_source_list(cfg, EXPECTED_BASE_CONTENT)
2675+
2676+ def test_apt_source_list_psm(self):
2677+ """test_apt_source_list_psm - Test specifying prim+sec mirrors"""
2678+ cfg = {'preserve_sources_list': False,
2679+ 'primary': [{'arches': ["default"],
2680+ 'uri': 'http://test.ubuntu.com/ubuntu/'}],
2681+ 'security': [{'arches': ["default"],
2682+ 'uri': 'http://testsec.ubuntu.com/ubuntu/'}]}
2683+
2684+ self._apt_source_list(cfg, EXPECTED_PRIMSEC_CONTENT)
2685+
2686+ @staticmethod
2687+ def test_apt_srcl_custom():
2688+ """test_apt_srcl_custom - Test rendering a custom source template"""
2689+ cfg = yaml.safe_load(YAML_TEXT_CUSTOM_SL)
2690+
2691+ arch = util.get_architecture()
2692+ # would fail inside the unittest context
2693+ with mock.patch.object(util, 'get_architecture',
2694+ return_value=arch) as mockga:
2695+ with mock.patch.object(util, 'write_file') as mockwrite:
2696+ # keep it side effect free and avoid permission errors
2697+ with mock.patch.object(os, 'rename'):
2698+ with mock.patch.object(util, 'lsb_release',
2699+ return_value={'codename':
2700+ 'fakerel'}):
2701+ apt_config.handle_apt(cfg, TARGET)
2702+
2703+ mockga.assert_called_with("/")
2704+ cloudfile = '/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg'
2705+ cloudconf = yaml.dump({'apt_preserve_sources_list': True}, indent=1)
2706+ calls = [call(util.target_path(TARGET, '/etc/apt/sources.list'),
2707+ EXPECTED_CONVERTED_CONTENT, mode=0o644),
2708+ call(util.target_path(TARGET, cloudfile), cloudconf,
2709+ mode=0o644)]
2710+ mockwrite.assert_has_calls(calls)
2711+
2712+
2713+# vi: ts=4 expandtab
2714
2715=== added file 'tests/unittests/test_apt_source.py'
2716--- tests/unittests/test_apt_source.py 1970-01-01 00:00:00 +0000
2717+++ tests/unittests/test_apt_source.py 2016-08-02 09:15:49 +0000
2718@@ -0,0 +1,929 @@
2719+""" test_apt_source
2720+Testing various config variations of the apt_source custom config
2721+"""
2722+import glob
2723+import os
2724+import re
2725+import shutil
2726+import socket
2727+import tempfile
2728+
2729+from unittest import TestCase
2730+
2731+import mock
2732+from mock import call
2733+
2734+from curtin import util
2735+from curtin import gpg
2736+from curtin.commands import apt_config
2737+
2738+
2739+EXPECTEDKEY = u"""-----BEGIN PGP PUBLIC KEY BLOCK-----
2740+Version: GnuPG v1
2741+
2742+mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ
2743+NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy
2744+8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0
2745+HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG
2746+CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29
2747+OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ
2748+FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm
2749+S0ORP6HXET3+jC8BMG4tBWCTK/XEZw==
2750+=ACB2
2751+-----END PGP PUBLIC KEY BLOCK-----"""
2752+
2753+ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
2754+
2755+TARGET = "/"
2756+
2757+
2758+def load_tfile(filename):
2759+ """ load_tfile
2760+ load file and return content after decoding
2761+ """
2762+ try:
2763+ content = util.load_file(filename, mode="r")
2764+ except Exception as error:
2765+ print('failed to load file content for test: %s' % error)
2766+ raise
2767+
2768+ return content
2769+
2770+
2771+class PseudoChrootableTarget(util.ChrootableTarget):
2772+ # no-ops the mounting and modifying that ChrootableTarget does
2773+ def __enter__(self):
2774+ return self
2775+
2776+ def __exit__(self, exc_type, exc_value, traceback):
2777+ return
2778+
2779+ChrootableTargetStr = "curtin.commands.apt_config.util.ChrootableTarget"
2780+
2781+
2782+class TestAptSourceConfig(TestCase):
2783+ """ TestAptSourceConfig
2784+ Main Class to test apt configs
2785+ """
2786+ def setUp(self):
2787+ super(TestAptSourceConfig, self).setUp()
2788+ self.tmp = tempfile.mkdtemp()
2789+ self.addCleanup(shutil.rmtree, self.tmp)
2790+ self.aptlistfile = os.path.join(self.tmp, "single-deb.list")
2791+ self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list")
2792+ self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list")
2793+ self.join = os.path.join
2794+ self.matcher = re.compile(ADD_APT_REPO_MATCH).search
2795+
2796+ @staticmethod
2797+ def _add_apt_sources(*args, **kwargs):
2798+ with mock.patch.object(util, 'apt_update'):
2799+ apt_config.add_apt_sources(*args, **kwargs)
2800+
2801+ @staticmethod
2802+ def _get_default_params():
2803+ """ get_default_params
2804+ Get the most basic default mrror and release info to be used in tests
2805+ """
2806+ params = {}
2807+ params['RELEASE'] = util.lsb_release()['codename']
2808+ arch = util.get_architecture()
2809+ params['MIRROR'] = apt_config.get_default_mirrors(arch)["PRIMARY"]
2810+ return params
2811+
2812+ def _myjoin(self, *args, **kwargs):
2813+ """ _myjoin - redir into writable tmpdir"""
2814+ if (args[0] == "/etc/apt/sources.list.d/" and
2815+ args[1] == "cloud_config_sources.list" and
2816+ len(args) == 2):
2817+ return self.join(self.tmp, args[0].lstrip("/"), args[1])
2818+ else:
2819+ return self.join(*args, **kwargs)
2820+
2821+ def _apt_src_basic(self, filename, cfg):
2822+ """ _apt_src_basic
2823+ Test Fix deb source string, has to overwrite mirror conf in params
2824+ """
2825+ params = self._get_default_params()
2826+
2827+ self._add_apt_sources(cfg, TARGET, template_params=params,
2828+ aa_repo_match=self.matcher)
2829+
2830+ self.assertTrue(os.path.isfile(filename))
2831+
2832+ contents = load_tfile(filename)
2833+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2834+ ("deb", "http://test.ubuntu.com/ubuntu",
2835+ "karmic-backports",
2836+ "main universe multiverse restricted"),
2837+ contents, flags=re.IGNORECASE))
2838+
2839+ def test_apt_src_basic(self):
2840+ """test_apt_src_basic - Test fix deb source string"""
2841+ cfg = {self.aptlistfile: {'source':
2842+ ('deb http://test.ubuntu.com/ubuntu'
2843+ ' karmic-backports'
2844+ ' main universe multiverse restricted')}}
2845+ self._apt_src_basic(self.aptlistfile, cfg)
2846+
2847+ def test_apt_src_basic_tri(self):
2848+ """test_apt_src_basic_tri - Test multiple fix deb source strings"""
2849+ cfg = {self.aptlistfile: {'source':
2850+ ('deb http://test.ubuntu.com/ubuntu'
2851+ ' karmic-backports'
2852+ ' main universe multiverse restricted')},
2853+ self.aptlistfile2: {'source':
2854+ ('deb http://test.ubuntu.com/ubuntu'
2855+ ' precise-backports'
2856+ ' main universe multiverse restricted')},
2857+ self.aptlistfile3: {'source':
2858+ ('deb http://test.ubuntu.com/ubuntu'
2859+ ' lucid-backports'
2860+ ' main universe multiverse restricted')}}
2861+ self._apt_src_basic(self.aptlistfile, cfg)
2862+
2863+ # extra verify on two extra files of this test
2864+ contents = load_tfile(self.aptlistfile2)
2865+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2866+ ("deb", "http://test.ubuntu.com/ubuntu",
2867+ "precise-backports",
2868+ "main universe multiverse restricted"),
2869+ contents, flags=re.IGNORECASE))
2870+ contents = load_tfile(self.aptlistfile3)
2871+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2872+ ("deb", "http://test.ubuntu.com/ubuntu",
2873+ "lucid-backports",
2874+ "main universe multiverse restricted"),
2875+ contents, flags=re.IGNORECASE))
2876+
2877+ def _apt_src_replacement(self, filename, cfg):
2878+ """ apt_src_replace
2879+ Test Autoreplacement of MIRROR and RELEASE in source specs
2880+ """
2881+ params = self._get_default_params()
2882+ self._add_apt_sources(cfg, TARGET, template_params=params,
2883+ aa_repo_match=self.matcher)
2884+
2885+ self.assertTrue(os.path.isfile(filename))
2886+
2887+ contents = load_tfile(filename)
2888+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2889+ ("deb", params['MIRROR'], params['RELEASE'],
2890+ "multiverse"),
2891+ contents, flags=re.IGNORECASE))
2892+
2893+ def test_apt_src_replace(self):
2894+ """test_apt_src_replace - Test Autoreplacement of MIRROR and RELEASE"""
2895+ cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}}
2896+ self._apt_src_replacement(self.aptlistfile, cfg)
2897+
2898+ def test_apt_src_replace_fn(self):
2899+ """test_apt_src_replace_fn - Test filename being overwritten in dict"""
2900+ cfg = {'ignored': {'source': 'deb $MIRROR $RELEASE multiverse',
2901+ 'filename': self.aptlistfile}}
2902+ # second file should overwrite the dict key
2903+ self._apt_src_replacement(self.aptlistfile, cfg)
2904+
2905+ def _apt_src_replace_tri(self, cfg):
2906+ """ _apt_src_replace_tri
2907+ Test three autoreplacements of MIRROR and RELEASE in source specs with
2908+ generic part
2909+ """
2910+ self._apt_src_replacement(self.aptlistfile, cfg)
2911+
2912+ # extra verify on two extra files of this test
2913+ params = self._get_default_params()
2914+ contents = load_tfile(self.aptlistfile2)
2915+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2916+ ("deb", params['MIRROR'], params['RELEASE'],
2917+ "main"),
2918+ contents, flags=re.IGNORECASE))
2919+ contents = load_tfile(self.aptlistfile3)
2920+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2921+ ("deb", params['MIRROR'], params['RELEASE'],
2922+ "universe"),
2923+ contents, flags=re.IGNORECASE))
2924+
2925+ def test_apt_src_replace_tri(self):
2926+ """test_apt_src_replace_tri - Test multiple replacements/overwrites"""
2927+ cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'},
2928+ 'notused': {'source': 'deb $MIRROR $RELEASE main',
2929+ 'filename': self.aptlistfile2},
2930+ self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}}
2931+ self._apt_src_replace_tri(cfg)
2932+
2933+ def _apt_src_keyid(self, filename, cfg, keynum):
2934+ """ _apt_src_keyid
2935+ Test specification of a source + keyid
2936+ """
2937+ params = self._get_default_params()
2938+
2939+ with mock.patch("curtin.util.subp",
2940+ return_value=('fakekey 1234', '')) as mockobj:
2941+ self._add_apt_sources(cfg, TARGET, template_params=params,
2942+ aa_repo_match=self.matcher)
2943+
2944+ # check if it added the right ammount of keys
2945+ calls = []
2946+ for _ in range(keynum):
2947+ calls.append(call(['apt-key', 'add', '-'], data=b'fakekey 1234',
2948+ target=TARGET))
2949+ mockobj.assert_has_calls(calls, any_order=True)
2950+
2951+ self.assertTrue(os.path.isfile(filename))
2952+
2953+ contents = load_tfile(filename)
2954+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2955+ ("deb",
2956+ ('http://ppa.launchpad.net/smoser/'
2957+ 'cloud-init-test/ubuntu'),
2958+ "xenial", "main"),
2959+ contents, flags=re.IGNORECASE))
2960+
2961+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
2962+ def test_apt_src_keyid(self):
2963+ """test_apt_src_keyid - Test source + keyid with filename being set"""
2964+ cfg = {self.aptlistfile: {'source': ('deb '
2965+ 'http://ppa.launchpad.net/'
2966+ 'smoser/cloud-init-test/ubuntu'
2967+ ' xenial main'),
2968+ 'keyid': "03683F77"}}
2969+ self._apt_src_keyid(self.aptlistfile, cfg, 1)
2970+
2971+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
2972+ def test_apt_src_keyid_tri(self):
2973+ """test_apt_src_keyid_tri - Test multiple src+keyid+filen overwrites"""
2974+ cfg = {self.aptlistfile: {'source': ('deb '
2975+ 'http://ppa.launchpad.net/'
2976+ 'smoser/cloud-init-test/ubuntu'
2977+ ' xenial main'),
2978+ 'keyid': "03683F77"},
2979+ 'ignored': {'source': ('deb '
2980+ 'http://ppa.launchpad.net/'
2981+ 'smoser/cloud-init-test/ubuntu'
2982+ ' xenial universe'),
2983+ 'keyid': "03683F77",
2984+ 'filename': self.aptlistfile2},
2985+ self.aptlistfile3: {'source': ('deb '
2986+ 'http://ppa.launchpad.net/'
2987+ 'smoser/cloud-init-test/ubuntu'
2988+ ' xenial multiverse'),
2989+ 'keyid': "03683F77"}}
2990+
2991+ self._apt_src_keyid(self.aptlistfile, cfg, 3)
2992+ contents = load_tfile(self.aptlistfile2)
2993+ self.assertTrue(re.search(r"%s %s %s %s\n" %
2994+ ("deb",
2995+ ('http://ppa.launchpad.net/smoser/'
2996+ 'cloud-init-test/ubuntu'),
2997+ "xenial", "universe"),
2998+ contents, flags=re.IGNORECASE))
2999+ contents = load_tfile(self.aptlistfile3)
3000+ self.assertTrue(re.search(r"%s %s %s %s\n" %
3001+ ("deb",
3002+ ('http://ppa.launchpad.net/smoser/'
3003+ 'cloud-init-test/ubuntu'),
3004+ "xenial", "multiverse"),
3005+ contents, flags=re.IGNORECASE))
3006+
3007+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
3008+ def test_apt_src_key(self):
3009+ """test_apt_src_key - Test source + key"""
3010+ params = self._get_default_params()
3011+ cfg = {self.aptlistfile: {'source': ('deb '
3012+ 'http://ppa.launchpad.net/'
3013+ 'smoser/cloud-init-test/ubuntu'
3014+ ' xenial main'),
3015+ 'key': "fakekey 4321"}}
3016+
3017+ with mock.patch.object(util, 'subp') as mockobj:
3018+ self._add_apt_sources(cfg, TARGET, template_params=params,
3019+ aa_repo_match=self.matcher)
3020+
3021+ mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 4321',
3022+ target=TARGET)
3023+
3024+ self.assertTrue(os.path.isfile(self.aptlistfile))
3025+
3026+ contents = load_tfile(self.aptlistfile)
3027+ self.assertTrue(re.search(r"%s %s %s %s\n" %
3028+ ("deb",
3029+ ('http://ppa.launchpad.net/smoser/'
3030+ 'cloud-init-test/ubuntu'),
3031+ "xenial", "main"),
3032+ contents, flags=re.IGNORECASE))
3033+
3034+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
3035+ def test_apt_src_keyonly(self):
3036+ """test_apt_src_keyonly - Test key without source"""
3037+ params = self._get_default_params()
3038+ cfg = {self.aptlistfile: {'key': "fakekey 4242"}}
3039+
3040+ with mock.patch.object(util, 'subp') as mockobj:
3041+ self._add_apt_sources(cfg, TARGET, template_params=params,
3042+ aa_repo_match=self.matcher)
3043+
3044+ mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 4242',
3045+ target=TARGET)
3046+
3047+ # filename should be ignored on key only
3048+ self.assertFalse(os.path.isfile(self.aptlistfile))
3049+
3050+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
3051+ def test_apt_src_keyidonly(self):
3052+ """test_apt_src_keyidonly - Test keyid without source"""
3053+ params = self._get_default_params()
3054+ cfg = {self.aptlistfile: {'keyid': "03683F77"}}
3055+
3056+ with mock.patch.object(util, 'subp',
3057+ return_value=('fakekey 1212', '')) as mockobj:
3058+ self._add_apt_sources(cfg, TARGET, template_params=params,
3059+ aa_repo_match=self.matcher)
3060+
3061+ mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 1212',
3062+ target=TARGET)
3063+
3064+ # filename should be ignored on key only
3065+ self.assertFalse(os.path.isfile(self.aptlistfile))
3066+
3067+ def apt_src_keyid_real(self, cfg, expectedkey):
3068+ """apt_src_keyid_real
3069+ Test specification of a keyid without source including
3070+ up to addition of the key (add_apt_key_raw mocked to keep the
3071+ environment as is)
3072+ """
3073+ params = self._get_default_params()
3074+
3075+ with mock.patch.object(apt_config, 'add_apt_key_raw') as mockkey:
3076+ with mock.patch.object(gpg, 'getkeybyid',
3077+ return_value=expectedkey) as mockgetkey:
3078+ self._add_apt_sources(cfg, TARGET, template_params=params,
3079+ aa_repo_match=self.matcher)
3080+
3081+ keycfg = cfg[self.aptlistfile]
3082+ mockgetkey.assert_called_with(keycfg['keyid'],
3083+ keycfg.get('keyserver',
3084+ 'keyserver.ubuntu.com'))
3085+ mockkey.assert_called_with(expectedkey, TARGET)
3086+
3087+ # filename should be ignored on key only
3088+ self.assertFalse(os.path.isfile(self.aptlistfile))
3089+
3090+ def test_apt_src_keyid_real(self):
3091+ """test_apt_src_keyid_real - Test keyid including key add"""
3092+ keyid = "03683F77"
3093+ cfg = {self.aptlistfile: {'keyid': keyid}}
3094+
3095+ self.apt_src_keyid_real(cfg, EXPECTEDKEY)
3096+
3097+ def test_apt_src_longkeyid_real(self):
3098+ """test_apt_src_longkeyid_real Test long keyid including key add"""
3099+ keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77"
3100+ cfg = {self.aptlistfile: {'keyid': keyid}}
3101+
3102+ self.apt_src_keyid_real(cfg, EXPECTEDKEY)
3103+
3104+ def test_apt_src_longkeyid_ks_real(self):
3105+ """test_apt_src_longkeyid_ks_real Test long keyid from other ks"""
3106+ keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77"
3107+ cfg = {self.aptlistfile: {'keyid': keyid,
3108+ 'keyserver': 'keys.gnupg.net'}}
3109+
3110+ self.apt_src_keyid_real(cfg, EXPECTEDKEY)
3111+
3112+ def test_apt_src_keyid_keyserver(self):
3113+ """test_apt_src_keyid_keyserver - Test custom keyserver"""
3114+ keyid = "03683F77"
3115+ params = self._get_default_params()
3116+ cfg = {self.aptlistfile: {'keyid': keyid,
3117+ 'keyserver': 'test.random.com'}}
3118+
3119+ # in some test environments only *.ubuntu.com is reachable
3120+ # so mock the call and check if the config got there
3121+ with mock.patch.object(gpg, 'getkeybyid',
3122+ return_value="fakekey") as mockgetkey:
3123+ with mock.patch.object(apt_config, 'add_apt_key_raw') as mockadd:
3124+ self._add_apt_sources(cfg, TARGET, template_params=params,
3125+ aa_repo_match=self.matcher)
3126+
3127+ mockgetkey.assert_called_with('03683F77', 'test.random.com')
3128+ mockadd.assert_called_with('fakekey', TARGET)
3129+
3130+ # filename should be ignored on key only
3131+ self.assertFalse(os.path.isfile(self.aptlistfile))
3132+
3133+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
3134+ def test_apt_src_ppa(self):
3135+ """test_apt_src_ppa - Test specification of a ppa"""
3136+ params = self._get_default_params()
3137+ cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'}}
3138+
3139+ with mock.patch("curtin.util.subp") as mockobj:
3140+ self._add_apt_sources(cfg, TARGET, template_params=params,
3141+ aa_repo_match=self.matcher)
3142+ mockobj.assert_any_call(['add-apt-repository',
3143+ 'ppa:smoser/cloud-init-test'], target=TARGET)
3144+
3145+ # adding ppa should ignore filename (uses add-apt-repository)
3146+ self.assertFalse(os.path.isfile(self.aptlistfile))
3147+
3148+ @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
3149+ def test_apt_src_ppa_tri(self):
3150+ """test_apt_src_ppa_tri - Test specification of multiple ppa's"""
3151+ params = self._get_default_params()
3152+ cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'},
3153+ self.aptlistfile2: {'source': 'ppa:smoser/cloud-init-test2'},
3154+ self.aptlistfile3: {'source': 'ppa:smoser/cloud-init-test3'}}
3155+
3156+ with mock.patch("curtin.util.subp") as mockobj:
3157+ self._add_apt_sources(cfg, TARGET, template_params=params,
3158+ aa_repo_match=self.matcher)
3159+ calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'],
3160+ target=TARGET),
3161+ call(['add-apt-repository', 'ppa:smoser/cloud-init-test2'],
3162+ target=TARGET),
3163+ call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'],
3164+ target=TARGET)]
3165+ mockobj.assert_has_calls(calls, any_order=True)
3166+
3167+ # adding ppa should ignore all filenames (uses add-apt-repository)
3168+ self.assertFalse(os.path.isfile(self.aptlistfile))
3169+ self.assertFalse(os.path.isfile(self.aptlistfile2))
3170+ self.assertFalse(os.path.isfile(self.aptlistfile3))
3171+
3172+ def test_mir_apt_list_rename(self):
3173+ """test_mir_apt_list_rename - Test find mirror and apt list renaming"""
3174+ pre = "/var/lib/apt/lists"
3175+ # filenames are archive dependent
3176+ arch = util.get_architecture()
3177+ if arch in apt_config.PRIMARY_ARCHES:
3178+ component = "ubuntu"
3179+ archive = "archive.ubuntu.com"
3180+ else:
3181+ component = "ubuntu-ports"
3182+ archive = "ports.ubuntu.com"
3183+
3184+ cfg = {'primary': [{'arches': ["default"],
3185+ 'uri':
3186+ 'http://test.ubuntu.com/%s/' % component}],
3187+ 'security': [{'arches': ["default"],
3188+ 'uri':
3189+ 'http://testsec.ubuntu.com/%s/' % component}]}
3190+ post = ("%s_dists_%s-updates_InRelease" %
3191+ (component, util.lsb_release()['codename']))
3192+ fromfn = ("%s/%s_%s" % (pre, archive, post))
3193+ tofn = ("%s/test.ubuntu.com_%s" % (pre, post))
3194+
3195+ mirrors = apt_config.find_apt_mirror_info(cfg, arch)
3196+
3197+ self.assertEqual(mirrors['MIRROR'],
3198+ "http://test.ubuntu.com/%s/" % component)
3199+ self.assertEqual(mirrors['PRIMARY'],
3200+ "http://test.ubuntu.com/%s/" % component)
3201+ self.assertEqual(mirrors['SECURITY'],
3202+ "http://testsec.ubuntu.com/%s/" % component)
3203+
3204+ # get_architecture would fail inside the unittest context
3205+ with mock.patch.object(util, 'get_architecture', return_value=arch):
3206+ with mock.patch.object(os, 'rename') as mockren:
3207+ with mock.patch.object(glob, 'glob',
3208+ return_value=[fromfn]):
3209+ apt_config.rename_apt_lists(mirrors, TARGET)
3210+
3211+ mockren.assert_any_call(fromfn, tofn)
3212+
3213+ @mock.patch("curtin.commands.apt_config.util.get_architecture")
3214+ def test_mir_apt_list_rename_non_slash(self, m_get_architecture):
3215+ target = os.path.join(self.tmp, "rename_non_slash")
3216+ apt_lists_d = os.path.join(target, "./" + apt_config.APT_LISTS)
3217+
3218+ m_get_architecture.return_value = 'amd64'
3219+
3220+ mirror_path = "some/random/path/"
3221+ primary = "http://test.ubuntu.com/" + mirror_path
3222+ security = "http://test-security.ubuntu.com/" + mirror_path
3223+ mirrors = {'PRIMARY': primary, 'SECURITY': security}
3224+
3225+ # these match default archive prefixes
3226+ opri_pre = "archive.ubuntu.com_ubuntu_dists_xenial"
3227+ osec_pre = "security.ubuntu.com_ubuntu_dists_xenial"
3228+ # this one won't match and should not be renamed defaults.
3229+ other_pre = "dl.google.com_linux_chrome_deb_dists_stable"
3230+ # these are our new expected prefixes
3231+ npri_pre = "test.ubuntu.com_some_random_path_dists_xenial"
3232+ nsec_pre = "test-security.ubuntu.com_some_random_path_dists_xenial"
3233+
3234+ files = [
3235+ # orig prefix, new prefix, suffix
3236+ (opri_pre, npri_pre, "_main_binary-amd64_Packages"),
3237+ (opri_pre, npri_pre, "_main_binary-amd64_InRelease"),
3238+ (opri_pre, npri_pre, "-updates_main_binary-amd64_Packages"),
3239+ (opri_pre, npri_pre, "-updates_main_binary-amd64_InRelease"),
3240+ (other_pre, other_pre, "_main_binary-amd64_Packages"),
3241+ (other_pre, other_pre, "_Release"),
3242+ (other_pre, other_pre, "_Release.gpg"),
3243+ (osec_pre, nsec_pre, "_InRelease"),
3244+ (osec_pre, nsec_pre, "_main_binary-amd64_Packages"),
3245+ (osec_pre, nsec_pre, "_universe_binary-amd64_Packages"),
3246+ ]
3247+
3248+ expected = sorted([npre + suff for opre, npre, suff in files])
3249+ # create files
3250+ for (opre, npre, suff) in files:
3251+ fpath = os.path.join(apt_lists_d, opre + suff)
3252+ util.write_file(fpath, content=fpath)
3253+
3254+ apt_config.rename_apt_lists(mirrors, target)
3255+ found = sorted(os.listdir(apt_lists_d))
3256+ self.assertEqual(expected, found)
3257+
3258+ @staticmethod
3259+ def test_apt_proxy():
3260+ """test_apt_proxy - Test apt_*proxy configuration"""
3261+ cfg = {"proxy": "foobar1",
3262+ "http_proxy": "foobar2",
3263+ "ftp_proxy": "foobar3",
3264+ "https_proxy": "foobar4"}
3265+
3266+ with mock.patch.object(util, 'write_file') as mockobj:
3267+ apt_config.apply_apt_proxy_config(cfg, "proxyfn", "notused")
3268+
3269+ mockobj.assert_called_with('proxyfn',
3270+ ('Acquire::http::Proxy "foobar1";\n'
3271+ 'Acquire::http::Proxy "foobar2";\n'
3272+ 'Acquire::ftp::Proxy "foobar3";\n'
3273+ 'Acquire::https::Proxy "foobar4";\n'))
3274+
3275+ def test_mirror(self):
3276+ """test_mirror - Test defining a mirror"""
3277+ pmir = "http://us.archive.ubuntu.com/ubuntu/"
3278+ smir = "http://security.ubuntu.com/ubuntu/"
3279+ cfg = {"primary": [{'arches': ["default"],
3280+ "uri": pmir}],
3281+ "security": [{'arches': ["default"],
3282+ "uri": smir}]}
3283+
3284+ mirrors = apt_config.find_apt_mirror_info(cfg, 'amd64')
3285+
3286+ self.assertEqual(mirrors['MIRROR'],
3287+ pmir)
3288+ self.assertEqual(mirrors['PRIMARY'],
3289+ pmir)
3290+ self.assertEqual(mirrors['SECURITY'],
3291+ smir)
3292+
3293+ def test_mirror_default(self):
3294+ """test_mirror_default - Test without defining a mirror"""
3295+ arch = util.get_architecture()
3296+ default_mirrors = apt_config.get_default_mirrors(arch)
3297+ pmir = default_mirrors["PRIMARY"]
3298+ smir = default_mirrors["SECURITY"]
3299+ mirrors = apt_config.find_apt_mirror_info({}, arch)
3300+
3301+ self.assertEqual(mirrors['MIRROR'],
3302+ pmir)
3303+ self.assertEqual(mirrors['PRIMARY'],
3304+ pmir)
3305+ self.assertEqual(mirrors['SECURITY'],
3306+ smir)
3307+
3308+ def test_mirror_arches(self):
3309+ """test_mirror_arches - Test arches selection of mirror"""
3310+ pmir = "http://us.archive.ubuntu.com/ubuntu/"
3311+ smir = "http://security.ubuntu.com/ubuntu/"
3312+ cfg = {"primary": [{'arches': ["default"],
3313+ "uri": "notthis"},
3314+ {'arches': [util.get_architecture()],
3315+ "uri": pmir}],
3316+ "security": [{'arches': [util.get_architecture()],
3317+ "uri": smir},
3318+ {'arches': ["default"],
3319+ "uri": "nothat"}]}
3320+
3321+ mirrors = apt_config.find_apt_mirror_info(cfg, 'amd64')
3322+
3323+ self.assertEqual(mirrors['MIRROR'],
3324+ pmir)
3325+ self.assertEqual(mirrors['PRIMARY'],
3326+ pmir)
3327+ self.assertEqual(mirrors['SECURITY'],
3328+ smir)
3329+
3330+ def test_mirror_arches_default(self):
3331+ """test_mirror_arches - Test falling back to default arch"""
3332+ pmir = "http://us.archive.ubuntu.com/ubuntu/"
3333+ smir = "http://security.ubuntu.com/ubuntu/"
3334+ cfg = {"primary": [{'arches': ["default"],
3335+ "uri": pmir},
3336+ {'arches': ["thisarchdoesntexist"],
3337+ "uri": "notthis"}],
3338+ "security": [{'arches': ["thisarchdoesntexist"],
3339+ "uri": "nothat"},
3340+ {'arches': ["default"],
3341+ "uri": smir}]}
3342+
3343+ mirrors = apt_config.find_apt_mirror_info(cfg, 'amd64')
3344+
3345+ self.assertEqual(mirrors['MIRROR'],
3346+ pmir)
3347+ self.assertEqual(mirrors['PRIMARY'],
3348+ pmir)
3349+ self.assertEqual(mirrors['SECURITY'],
3350+ smir)
3351+
3352+ @mock.patch("curtin.commands.apt_config.util.get_architecture")
3353+ def test_get_default_mirrors_non_intel_no_arg(self, m_get_architecture):
3354+ arch = 'ppc64el'
3355+ m_get_architecture.return_value = arch
3356+ expected = {'PRIMARY': 'http://ports.ubuntu.com/ubuntu-ports',
3357+ 'SECURITY': 'http://ports.ubuntu.com/ubuntu-ports'}
3358+ self.assertEqual(expected, apt_config.get_default_mirrors(arch))
3359+
3360+ @mock.patch("curtin.commands.apt_config.util.get_architecture")
3361+ def test_get_default_mirrors_non_intel_with_arch(self, m_get_architecture):
3362+ arch = 'ppc64el'
3363+ found = apt_config.get_default_mirrors(arch)
3364+
3365+ expected = {'PRIMARY': 'http://ports.ubuntu.com/ubuntu-ports',
3366+ 'SECURITY': 'http://ports.ubuntu.com/ubuntu-ports'}
3367+ self.assertEqual(expected, found)
3368+
3369+ def test_mirror_arches_sysdefault(self):
3370+ """test_mirror_arches - Test arches falling back to sys default"""
3371+ arch = util.get_architecture()
3372+ default_mirrors = apt_config.get_default_mirrors(arch)
3373+ pmir = default_mirrors["PRIMARY"]
3374+ smir = default_mirrors["SECURITY"]
3375+ cfg = {"primary": [{'arches': ["thisarchdoesntexist_64"],
3376+ "uri": "notthis"},
3377+ {'arches': ["thisarchdoesntexist"],
3378+ "uri": "notthiseither"}],
3379+ "security": [{'arches': ["thisarchdoesntexist"],
3380+ "uri": "nothat"},
3381+ {'arches': ["thisarchdoesntexist_64"],
3382+ "uri": "nothateither"}]}
3383+
3384+ mirrors = apt_config.find_apt_mirror_info(cfg, 'amd64')
3385+
3386+ self.assertEqual(mirrors['MIRROR'],
3387+ pmir)
3388+ self.assertEqual(mirrors['PRIMARY'],
3389+ pmir)
3390+ self.assertEqual(mirrors['SECURITY'],
3391+ smir)
3392+
3393+ def test_mirror_search(self):
3394+ """test_mirror_search - Test searching mirrors in a list
3395+ mock checks to avoid relying on network connectivity"""
3396+ pmir = "http://us.archive.ubuntu.com/ubuntu/"
3397+ smir = "http://security.ubuntu.com/ubuntu/"
3398+ cfg = {"primary": [{'arches': ["default"],
3399+ "search": ["pfailme", pmir]}],
3400+ "security": [{'arches': ["default"],
3401+ "search": ["sfailme", smir]}]}
3402+
3403+ with mock.patch.object(apt_config, 'search_for_mirror',
3404+ side_effect=[pmir, smir]) as mocksearch:
3405+ mirrors = apt_config.find_apt_mirror_info(cfg, 'amd64')
3406+
3407+ calls = [call(["pfailme", pmir]),
3408+ call(["sfailme", smir])]
3409+ mocksearch.assert_has_calls(calls)
3410+
3411+ self.assertEqual(mirrors['MIRROR'],
3412+ pmir)
3413+ self.assertEqual(mirrors['PRIMARY'],
3414+ pmir)
3415+ self.assertEqual(mirrors['SECURITY'],
3416+ smir)
3417+
3418+ def test_mirror_search_many2(self):
3419+ """test_mirror_search_many3 - Test both mirrors specs at once"""
3420+ pmir = "http://us.archive.ubuntu.com/ubuntu/"
3421+ smir = "http://security.ubuntu.com/ubuntu/"
3422+ cfg = {"primary": [{'arches': ["default"],
3423+ "uri": pmir,
3424+ "search": ["pfailme", "foo"]}],
3425+ "security": [{'arches': ["default"],
3426+ "uri": smir,
3427+ "search": ["sfailme", "bar"]}]}
3428+
3429+ arch = 'amd64'
3430+
3431+ # should be called only once per type, despite two mirror configs
3432+ with mock.patch.object(apt_config, 'get_mirror',
3433+ return_value="http://mocked/foo") as mockgm:
3434+ mirrors = apt_config.find_apt_mirror_info(cfg, arch)
3435+ calls = [call(cfg, 'primary', arch), call(cfg, 'security', arch)]
3436+ mockgm.assert_has_calls(calls)
3437+
3438+ # should not be called, since primary is specified
3439+ with mock.patch.object(apt_config, 'search_for_mirror') as mockse:
3440+ mirrors = apt_config.find_apt_mirror_info(cfg, arch)
3441+ mockse.assert_not_called()
3442+
3443+ self.assertEqual(mirrors['MIRROR'],
3444+ pmir)
3445+ self.assertEqual(mirrors['PRIMARY'],
3446+ pmir)
3447+ self.assertEqual(mirrors['SECURITY'],
3448+ smir)
3449+
3450+ def test_url_resolvable(self):
3451+ """test_url_resolvable - Test resolving urls"""
3452+
3453+ with mock.patch.object(util, 'is_resolvable') as mockresolve:
3454+ util.is_resolvable_url("http://1.2.3.4/ubuntu")
3455+ mockresolve.assert_called_with("1.2.3.4")
3456+
3457+ with mock.patch.object(util, 'is_resolvable') as mockresolve:
3458+ util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu")
3459+ mockresolve.assert_called_with("us.archive.ubuntu.com")
3460+
3461+ bad = [(None, None, None, "badname", ["10.3.2.1"])]
3462+ good = [(None, None, None, "goodname", ["10.2.3.4"])]
3463+ with mock.patch.object(socket, 'getaddrinfo',
3464+ side_effect=[bad, bad, good,
3465+ good]) as mocksock:
3466+ ret = util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu")
3467+ ret2 = util.is_resolvable_url("http://1.2.3.4/ubuntu")
3468+ calls = [call('does-not-exist.example.com.', None, 0, 0, 1, 2),
3469+ call('example.invalid.', None, 0, 0, 1, 2),
3470+ call('us.archive.ubuntu.com', None),
3471+ call('1.2.3.4', None)]
3472+ mocksock.assert_has_calls(calls)
3473+ self.assertTrue(ret)
3474+ self.assertTrue(ret2)
3475+
3476+ # side effect need only bad ret after initial call
3477+ with mock.patch.object(socket, 'getaddrinfo',
3478+ side_effect=[bad]) as mocksock:
3479+ ret3 = util.is_resolvable_url("http://failme.com/ubuntu")
3480+ calls = [call('failme.com', None)]
3481+ mocksock.assert_has_calls(calls)
3482+ self.assertFalse(ret3)
3483+
3484+ def test_disable_suites(self):
3485+ """test_disable_suites - disable_suites with many configurations"""
3486+ release = "xenial"
3487+ orig = """deb http://ubuntu.com//ubuntu xenial main
3488+deb http://ubuntu.com//ubuntu xenial-updates main
3489+deb http://ubuntu.com//ubuntu xenial-security main
3490+deb-src http://ubuntu.com//ubuntu universe multiverse
3491+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3492+
3493+ # disable nothing
3494+ disabled = []
3495+ expect = """deb http://ubuntu.com//ubuntu xenial main
3496+deb http://ubuntu.com//ubuntu xenial-updates main
3497+deb http://ubuntu.com//ubuntu xenial-security main
3498+deb-src http://ubuntu.com//ubuntu universe multiverse
3499+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3500+ result = apt_config.disable_suites(disabled, orig, release)
3501+ self.assertEqual(expect, result)
3502+
3503+ # single disable release suite
3504+ disabled = ["$RELEASE"]
3505+ expect = """\
3506+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial main
3507+deb http://ubuntu.com//ubuntu xenial-updates main
3508+deb http://ubuntu.com//ubuntu xenial-security main
3509+deb-src http://ubuntu.com//ubuntu universe multiverse
3510+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3511+ result = apt_config.disable_suites(disabled, orig, release)
3512+ self.assertEqual(expect, result)
3513+
3514+ # single disable other suite
3515+ disabled = ["$RELEASE-updates"]
3516+ expect = """deb http://ubuntu.com//ubuntu xenial main
3517+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-updates main
3518+deb http://ubuntu.com//ubuntu xenial-security main
3519+deb-src http://ubuntu.com//ubuntu universe multiverse
3520+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3521+ result = apt_config.disable_suites(disabled, orig, release)
3522+ self.assertEqual(expect, result)
3523+
3524+ # multi disable
3525+ disabled = ["$RELEASE-updates", "$RELEASE-security"]
3526+ expect = """deb http://ubuntu.com//ubuntu xenial main
3527+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-updates main
3528+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-security main
3529+deb-src http://ubuntu.com//ubuntu universe multiverse
3530+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3531+ result = apt_config.disable_suites(disabled, orig, release)
3532+ self.assertEqual(expect, result)
3533+
3534+ # multi line disable (same suite multiple times in input)
3535+ disabled = ["$RELEASE-updates", "$RELEASE-security"]
3536+ orig = """deb http://ubuntu.com//ubuntu xenial main
3537+deb http://ubuntu.com//ubuntu xenial-updates main
3538+deb http://ubuntu.com//ubuntu xenial-security main
3539+deb-src http://ubuntu.com//ubuntu universe multiverse
3540+deb http://UBUNTU.com//ubuntu xenial-updates main
3541+deb http://UBUNTU.COM//ubuntu xenial-updates main
3542+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3543+ expect = """deb http://ubuntu.com//ubuntu xenial main
3544+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-updates main
3545+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-security main
3546+deb-src http://ubuntu.com//ubuntu universe multiverse
3547+# suite disabled by curtin: deb http://UBUNTU.com//ubuntu xenial-updates main
3548+# suite disabled by curtin: deb http://UBUNTU.COM//ubuntu xenial-updates main
3549+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3550+ result = apt_config.disable_suites(disabled, orig, release)
3551+ self.assertEqual(expect, result)
3552+
3553+ # comment in input
3554+ disabled = ["$RELEASE-updates", "$RELEASE-security"]
3555+ orig = """deb http://ubuntu.com//ubuntu xenial main
3556+deb http://ubuntu.com//ubuntu xenial-updates main
3557+deb http://ubuntu.com//ubuntu xenial-security main
3558+deb-src http://ubuntu.com//ubuntu universe multiverse
3559+#foo
3560+#deb http://UBUNTU.com//ubuntu xenial-updates main
3561+deb http://UBUNTU.COM//ubuntu xenial-updates main
3562+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3563+ expect = """deb http://ubuntu.com//ubuntu xenial main
3564+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-updates main
3565+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-security main
3566+deb-src http://ubuntu.com//ubuntu universe multiverse
3567+#foo
3568+#deb http://UBUNTU.com//ubuntu xenial-updates main
3569+# suite disabled by curtin: deb http://UBUNTU.COM//ubuntu xenial-updates main
3570+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3571+ result = apt_config.disable_suites(disabled, orig, release)
3572+ self.assertEqual(expect, result)
3573+
3574+ # single disable custom suite
3575+ disabled = ["foobar"]
3576+ orig = """deb http://ubuntu.com//ubuntu xenial main
3577+deb http://ubuntu.com//ubuntu xenial-updates main
3578+deb http://ubuntu.com//ubuntu xenial-security main
3579+deb http://ubuntu.com/ubuntu/ foobar main"""
3580+ expect = """deb http://ubuntu.com//ubuntu xenial main
3581+deb http://ubuntu.com//ubuntu xenial-updates main
3582+deb http://ubuntu.com//ubuntu xenial-security main
3583+# suite disabled by curtin: deb http://ubuntu.com/ubuntu/ foobar main"""
3584+ result = apt_config.disable_suites(disabled, orig, release)
3585+ self.assertEqual(expect, result)
3586+
3587+ # single disable non existing suite
3588+ disabled = ["foobar"]
3589+ orig = """deb http://ubuntu.com//ubuntu xenial main
3590+deb http://ubuntu.com//ubuntu xenial-updates main
3591+deb http://ubuntu.com//ubuntu xenial-security main
3592+deb http://ubuntu.com/ubuntu/ notfoobar main"""
3593+ expect = """deb http://ubuntu.com//ubuntu xenial main
3594+deb http://ubuntu.com//ubuntu xenial-updates main
3595+deb http://ubuntu.com//ubuntu xenial-security main
3596+deb http://ubuntu.com/ubuntu/ notfoobar main"""
3597+ result = apt_config.disable_suites(disabled, orig, release)
3598+ self.assertEqual(expect, result)
3599+
3600+ # single disable suite with option
3601+ disabled = ["$RELEASE-updates"]
3602+ orig = """deb http://ubuntu.com//ubuntu xenial main
3603+deb [a=b] http://ubu.com//ubu xenial-updates main
3604+deb http://ubuntu.com//ubuntu xenial-security main
3605+deb-src http://ubuntu.com//ubuntu universe multiverse
3606+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3607+ expect = """deb http://ubuntu.com//ubuntu xenial main
3608+# suite disabled by curtin: deb [a=b] http://ubu.com//ubu xenial-updates main
3609+deb http://ubuntu.com//ubuntu xenial-security main
3610+deb-src http://ubuntu.com//ubuntu universe multiverse
3611+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3612+ result = apt_config.disable_suites(disabled, orig, release)
3613+ self.assertEqual(expect, result)
3614+
3615+ # single disable suite with more options and auto $RELEASE expansion
3616+ disabled = ["updates"]
3617+ orig = """deb http://ubuntu.com//ubuntu xenial main
3618+deb [a=b c=d] http://ubu.com//ubu xenial-updates main
3619+deb http://ubuntu.com//ubuntu xenial-security main
3620+deb-src http://ubuntu.com//ubuntu universe multiverse
3621+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3622+ expect = """deb http://ubuntu.com//ubuntu xenial main
3623+# suite disabled by curtin: deb [a=b c=d] \
3624+http://ubu.com//ubu xenial-updates main
3625+deb http://ubuntu.com//ubuntu xenial-security main
3626+deb-src http://ubuntu.com//ubuntu universe multiverse
3627+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3628+ result = apt_config.disable_suites(disabled, orig, release)
3629+ self.assertEqual(expect, result)
3630+
3631+ # single disable suite while options at others
3632+ disabled = ["$RELEASE-security"]
3633+ orig = """deb http://ubuntu.com//ubuntu xenial main
3634+deb [arch=foo] http://ubuntu.com//ubuntu xenial-updates main
3635+deb http://ubuntu.com//ubuntu xenial-security main
3636+deb-src http://ubuntu.com//ubuntu universe multiverse
3637+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3638+ expect = """deb http://ubuntu.com//ubuntu xenial main
3639+deb [arch=foo] http://ubuntu.com//ubuntu xenial-updates main
3640+# suite disabled by curtin: deb http://ubuntu.com//ubuntu xenial-security main
3641+deb-src http://ubuntu.com//ubuntu universe multiverse
3642+deb http://ubuntu.com/ubuntu/ xenial-proposed main"""
3643+ result = apt_config.disable_suites(disabled, orig, release)
3644+ self.assertEqual(expect, result)
3645+
3646+#
3647+# vi: ts=4 expandtab
3648
3649=== modified file 'tests/unittests/test_util.py'
3650--- tests/unittests/test_util.py 2016-07-28 19:33:01 +0000
3651+++ tests/unittests/test_util.py 2016-08-02 09:15:49 +0000
3652@@ -121,13 +121,13 @@
3653 rdata = {'id': 'Ubuntu', 'description': 'Ubuntu 14.04.2 LTS',
3654 'codename': 'trusty', 'release': '14.04'}
3655
3656- def fake_subp(cmd, capture=False):
3657+ def fake_subp(cmd, capture=False, target=None):
3658 return output, 'No LSB modules are available.'
3659
3660 mock_subp.side_effect = fake_subp
3661 found = util.lsb_release()
3662 mock_subp.assert_called_with(
3663- ['lsb_release', '--all'], capture=True)
3664+ ['lsb_release', '--all'], capture=True, target=None)
3665 self.assertEqual(found, rdata)
3666
3667 @mock.patch("curtin.util.subp")
3668
3669=== modified file 'tests/vmtests/__init__.py'
3670--- tests/vmtests/__init__.py 2016-08-01 18:26:06 +0000
3671+++ tests/vmtests/__init__.py 2016-08-02 09:15:49 +0000
3672@@ -357,6 +357,7 @@
3673 extra_kern_args = None
3674 fstab_expected = {}
3675 image_store_class = ImageStore
3676+ boot_cloudconf = None
3677 install_timeout = INSTALL_TIMEOUT
3678 interactive = False
3679 multipath = False
3680@@ -365,6 +366,7 @@
3681 recorded_errors = 0
3682 recorded_failures = 0
3683 uefi = False
3684+ proxy = None
3685
3686 # these get set from base_vm_classes
3687 release = None
3688@@ -394,7 +396,8 @@
3689 # set up tempdir
3690 cls.td = TempDir(
3691 name=cls.__name__,
3692- user_data=generate_user_data(collect_scripts=cls.collect_scripts))
3693+ user_data=generate_user_data(collect_scripts=cls.collect_scripts,
3694+ boot_cloudconf=cls.boot_cloudconf))
3695 logger.info('Using tempdir: %s , Image: %s', cls.td.tmpdir,
3696 img_verstr)
3697 cls.install_log = os.path.join(cls.td.logs, 'install-serial.log')
3698@@ -494,11 +497,11 @@
3699
3700 # proxy config
3701 configs = [cls.conf_file]
3702- proxy = get_apt_proxy()
3703- if get_apt_proxy is not None:
3704+ cls.proxy = get_apt_proxy()
3705+ if cls.proxy is not None:
3706 proxy_config = os.path.join(cls.td.install, 'proxy.cfg')
3707 with open(proxy_config, "w") as fp:
3708- fp.write(json.dumps({'apt_proxy': proxy}) + "\n")
3709+ fp.write(json.dumps({'apt_proxy': cls.proxy}) + "\n")
3710 configs.append(proxy_config)
3711
3712 uefi_flags = []
3713@@ -733,8 +736,14 @@
3714 # Misc functions that are useful for many tests
3715 def output_files_exist(self, files):
3716 for f in files:
3717+ logger.debug('checking file %s', f)
3718 self.assertTrue(os.path.exists(os.path.join(self.td.collect, f)))
3719
3720+ def output_files_dont_exist(self, files):
3721+ for f in files:
3722+ logger.debug('checking file %s', f)
3723+ self.assertFalse(os.path.exists(os.path.join(self.td.collect, f)))
3724+
3725 def check_file_strippedline(self, filename, search):
3726 with open(os.path.join(self.td.collect, filename), "r") as fp:
3727 data = list(i.strip() for i in fp.readlines())
3728@@ -963,7 +972,8 @@
3729 return None
3730
3731
3732-def generate_user_data(collect_scripts=None, apt_proxy=None):
3733+def generate_user_data(collect_scripts=None, apt_proxy=None,
3734+ boot_cloudconf=None):
3735 # this returns the user data for the *booted* system
3736 # its a cloud-config-archive type, which is
3737 # just a list of parts. the 'x-shellscript' parts
3738@@ -988,6 +998,10 @@
3739 'content': yaml.dump(base_cloudconfig, indent=1)},
3740 {'type': 'text/cloud-config', 'content': ssh_keys}]
3741
3742+ if boot_cloudconf is not None:
3743+ parts.append({'type': 'text/cloud-config', 'content':
3744+ yaml.dump(boot_cloudconf, indent=1)})
3745+
3746 output_dir = '/mnt/output'
3747 output_dir_macro = 'OUTPUT_COLLECT_D'
3748 output_device = '/dev/disk/by-id/virtio-%s' % OUTPUT_DISK_NAME
3749
3750=== added file 'tests/vmtests/test_apt_config_cmd.py'
3751--- tests/vmtests/test_apt_config_cmd.py 1970-01-01 00:00:00 +0000
3752+++ tests/vmtests/test_apt_config_cmd.py 2016-08-02 09:15:49 +0000
3753@@ -0,0 +1,55 @@
3754+""" test_apt_config_cmd
3755+ Collection of tests for the apt configuration features when called via the
3756+ apt-config standalone command.
3757+"""
3758+import textwrap
3759+
3760+from . import VMBaseClass
3761+from .releases import base_vm_classes as relbase
3762+
3763+
3764+class TestAptConfigCMD(VMBaseClass):
3765+ """TestAptConfigCMD - test standalone command"""
3766+ conf_file = "examples/tests/apt_config_command.yaml"
3767+ interactive = False
3768+ extra_disks = []
3769+ fstab_expected = {}
3770+ disk_to_check = []
3771+ collect_scripts = [textwrap.dedent("""
3772+ cd OUTPUT_COLLECT_D
3773+ cat /etc/fstab > fstab
3774+ ls /dev/disk/by-dname > ls_dname
3775+ find /etc/network/interfaces.d > find_interfacesd
3776+ cp /etc/apt/sources.list.d/curtin-dev-ubuntu-test-archive-*.list .
3777+ cp /etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg .
3778+ apt-cache policy | grep proposed > proposed-enabled
3779+ """)]
3780+
3781+ def test_cmd_proposed_enabled(self):
3782+ """check if proposed was enabled"""
3783+ self.output_files_exist(["proposed-enabled"])
3784+ self.check_file_regex("proposed-enabled",
3785+ r"500.*%s-proposed" % self.release)
3786+
3787+ def test_cmd_ppa_enabled(self):
3788+ """check if specified curtin-dev ppa was enabled"""
3789+ self.output_files_exist(
3790+ ["curtin-dev-ubuntu-test-archive-%s.list" % self.release])
3791+ self.check_file_regex("curtin-dev-ubuntu-test-archive-%s.list" %
3792+ self.release,
3793+ (r"http://ppa.launchpad.net/"
3794+ r"curtin-dev/test-archive/ubuntu"
3795+ r" %s main" % self.release))
3796+
3797+ def test_cmd_preserve_source(self):
3798+ """check if cloud-init was prevented from overwriting"""
3799+ self.output_files_exist(["curtin-preserve-sources.cfg"])
3800+ self.check_file_regex("curtin-preserve-sources.cfg",
3801+ "apt_preserve_sources_list.*true")
3802+
3803+
3804+class XenialTestAptConfigCMDCMD(relbase.xenial, TestAptConfigCMD):
3805+ """ XenialTestAptSrcModifyCMD
3806+ apt feature Test for Xenial using the standalone command
3807+ """
3808+ __test__ = True
3809
3810=== added file 'tests/vmtests/test_apt_source.py'
3811--- tests/vmtests/test_apt_source.py 1970-01-01 00:00:00 +0000
3812+++ tests/vmtests/test_apt_source.py 2016-08-02 09:15:49 +0000
3813@@ -0,0 +1,238 @@
3814+""" test_apt_source
3815+ Collection of tests for the apt configuration features
3816+"""
3817+import textwrap
3818+
3819+from . import VMBaseClass
3820+from .releases import base_vm_classes as relbase
3821+
3822+from unittest import SkipTest
3823+from curtin import util
3824+
3825+
3826+class TestAptSrcAbs(VMBaseClass):
3827+ """TestAptSrcAbs - Basic tests for apt features of curtin"""
3828+ interactive = False
3829+ extra_disks = []
3830+ fstab_expected = {}
3831+ disk_to_check = []
3832+ collect_scripts = [textwrap.dedent("""
3833+ cd OUTPUT_COLLECT_D
3834+ cat /etc/fstab > fstab
3835+ ls /dev/disk/by-dname > ls_dname
3836+ find /etc/network/interfaces.d > find_interfacesd
3837+ apt-key list "F430BBA5" > keyid-F430BBA5
3838+ apt-key list "0165013E" > keyppa-0165013E
3839+ apt-key list "F470A0AC" > keylongid-F470A0AC
3840+ apt-key list "8280B242" > keyraw-8280B242
3841+ ls -laF /etc/apt/sources.list.d/ > sources.list.d
3842+ cp /etc/apt/sources.list.d/curtin-dev-ppa.list .
3843+ cp /etc/apt/sources.list.d/my-repo2.list .
3844+ cp /etc/apt/sources.list.d/my-repo4.list .
3845+ cp /etc/apt/sources.list.d/curtin-dev-ubuntu-test-archive-*.list .
3846+ find /etc/apt/sources.list.d/ -maxdepth 1 -name "*ignore*" | wc -l > ic
3847+ apt-config dump | grep Retries > aptconf
3848+ cp /etc/apt/sources.list sources.list
3849+ cp /etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg .
3850+ """)]
3851+ mirror = "http://us.archive.ubuntu.com/ubuntu"
3852+ secmirror = "http://security.ubuntu.com/ubuntu"
3853+
3854+ def test_output_files_exist(self):
3855+ """test_output_files_exist - Check if all output files exist"""
3856+ self.output_files_exist(
3857+ ["fstab", "ic", "keyid-F430BBA5", "keylongid-F470A0AC",
3858+ "keyraw-8280B242", "keyppa-0165013E", "aptconf", "sources.list",
3859+ "curtin-dev-ppa.list", "my-repo2.list", "my-repo4.list"])
3860+ self.output_files_exist(
3861+ ["curtin-dev-ubuntu-test-archive-%s.list" % self.release])
3862+
3863+ def test_keys_imported(self):
3864+ """test_keys_imported - Check if all keys are imported correctly"""
3865+ self.check_file_regex("keyid-F430BBA5",
3866+ r"Launchpad PPA for Ubuntu Screen Profile")
3867+ self.check_file_regex("keylongid-F470A0AC",
3868+ r"Ryan Harper")
3869+ self.check_file_regex("keyppa-0165013E",
3870+ r"Launchpad PPA for curtin developers")
3871+ self.check_file_regex("keyraw-8280B242",
3872+ r"Christian Ehrhardt")
3873+
3874+ def test_preserve_source(self):
3875+ """test_preserve_source - no clobbering sources.list by cloud-init"""
3876+ self.output_files_exist(["curtin-preserve-sources.cfg"])
3877+ self.check_file_regex("curtin-preserve-sources.cfg",
3878+ "apt_preserve_sources_list.*true")
3879+
3880+ def test_source_files(self):
3881+ """test_source_files - Check generated .lists for correct content"""
3882+ # hard coded deb lines
3883+ self.check_file_strippedline("curtin-dev-ppa.list",
3884+ ("deb http://ppa.launchpad.net/curtin-dev"
3885+ "/test-archive/ubuntu xenial main"))
3886+ self.check_file_strippedline("my-repo4.list",
3887+ ("deb http://ppa.launchpad.net/curtin-dev"
3888+ "/test-archive/ubuntu xenial main"))
3889+ # mirror and release replacement in deb line
3890+ self.check_file_strippedline("my-repo2.list", "deb %s %s multiverse" %
3891+ (self.mirror, self.release))
3892+ # auto creation by apt-add-repository
3893+ self.check_file_regex("curtin-dev-ubuntu-test-archive-%s.list" %
3894+ self.release,
3895+ (r"http://ppa.launchpad.net/"
3896+ r"curtin-dev/test-archive/ubuntu"
3897+ r" %s main" % self.release))
3898+
3899+ def test_ignore_count(self):
3900+ """test_ignore_count - Check for files that should not be created"""
3901+ self.check_file_strippedline("ic", "0")
3902+
3903+ def test_apt_conf(self):
3904+ """test_apt_conf - Check if the selected apt conf was set"""
3905+ self.check_file_strippedline("aptconf", 'Acquire::Retries "3";')
3906+
3907+
3908+class TestAptSrcCustom(TestAptSrcAbs):
3909+ """TestAptSrcNormal - tests valid in the custom sources.list case"""
3910+ conf_file = "examples/tests/apt_source_custom.yaml"
3911+
3912+ def test_custom_source_list(self):
3913+ """test_custom_source_list - Check custom sources with replacement"""
3914+ # check that all replacements happened
3915+ self.check_file_strippedline("sources.list",
3916+ "deb %s %s main restricted" %
3917+ (self.mirror, self.release))
3918+ self.check_file_strippedline("sources.list",
3919+ "deb-src %s %s main restricted" %
3920+ (self.mirror, self.release))
3921+ self.check_file_strippedline("sources.list",
3922+ "deb %s %s universe restricted" %
3923+ (self.mirror, self.release))
3924+ self.check_file_strippedline("sources.list",
3925+ "deb %s %s-security multiverse" %
3926+ (self.secmirror, self.release))
3927+ # check for something that guarantees us to come from our test
3928+ self.check_file_strippedline("sources.list",
3929+ "# nice line to check in test")
3930+
3931+
3932+class TestAptSrcPreserve(TestAptSrcAbs):
3933+ """TestAptSrcPreserve - tests valid in the preserved sources.list case"""
3934+ conf_file = "examples/tests/apt_source_preserve.yaml"
3935+ boot_cloudconf = None
3936+
3937+ def test_preserved_source_list(self):
3938+ """test_preserved_source_list - Check sources to be preserved as-is"""
3939+ # curtin didn't touch it, so we should find what curtin set as default
3940+ self.check_file_regex("sources.list",
3941+ r"this file is written by cloud-init")
3942+
3943+ # overwrite inherited check to match situation here
3944+ def test_preserve_source(self):
3945+ """test_preserve_source - check apt_preserve_sources_list not set"""
3946+ self.output_files_dont_exist(["curtin-preserve-sources.cfg"])
3947+
3948+
3949+class TestAptSrcModify(TestAptSrcAbs):
3950+ """TestAptSrcModify - tests modifying sources.list"""
3951+ conf_file = "examples/tests/apt_source_modify.yaml"
3952+
3953+ def test_modified_source_list(self):
3954+ """test_modified_source_list - Check sources with replacement"""
3955+ # we set us.archive which is non default, check for that
3956+ # this will catch if a target ever changes the expected defaults we
3957+ # have to replace in case there is no custom template
3958+ self.check_file_regex("sources.list",
3959+ r"us.archive.ubuntu.com")
3960+ self.check_file_regex("sources.list",
3961+ r"security.ubuntu.com")
3962+
3963+
3964+class TestAptSrcDisablePockets(TestAptSrcAbs):
3965+ """TestAptSrcDisablePockets - tests disabling a suite in sources.list"""
3966+ conf_file = "examples/tests/apt_source_modify_disable_suite.yaml"
3967+
3968+ def test_disabled_suite(self):
3969+ """test_disabled_suite - Check if suites were disabled"""
3970+ # two not disabled
3971+ self.check_file_regex("sources.list",
3972+ r"deb.*us.archive.ubuntu.com")
3973+ self.check_file_regex("sources.list",
3974+ r"deb.*security.ubuntu.com")
3975+ # updates disabled
3976+ self.check_file_regex("sources.list",
3977+ r"# suite disabled by curtin:.*-updates")
3978+
3979+
3980+class TestAptSrcModifyArches(TestAptSrcModify):
3981+ """TestAptSrcModify - tests modifying sources.list with per arch mirror"""
3982+ # same test, just different yaml to specify the mirrors per arch
3983+ conf_file = "examples/tests/apt_source_modify_arches.yaml"
3984+
3985+
3986+class TestAptSrcSearch(TestAptSrcAbs):
3987+ """TestAptSrcSearch - tests checking a list of mirror options"""
3988+ conf_file = "examples/tests/apt_source_search.yaml"
3989+
3990+ def test_mirror_search(self):
3991+ """test_mirror_search
3992+ Check searching through a mirror list
3993+ This is checked in the test (late) intentionally.
3994+ No matter if resolution worked or failed it shouldn't fail
3995+ fatally (python error and trace).
3996+ We just can't rely on the content to be found in that case
3997+ so we skip the check then."""
3998+ res1 = util.is_resolvable_url("http://does.not.exist/ubuntu")
3999+ res2 = util.is_resolvable_url("http://does.also.not.exist/ubuntu")
4000+ res3 = util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu")
4001+ res4 = util.is_resolvable_url("http://security.ubuntu.com/ubuntu")
4002+ if res1 or res2 or not res3 or not res4:
4003+ raise SkipTest(("Name resolution not as required"
4004+ "(%s, %s, %s, %s)" % (res1, res2, res3, res4)))
4005+
4006+ self.check_file_regex("sources.list",
4007+ r"us.archive.ubuntu.com")
4008+ self.check_file_regex("sources.list",
4009+ r"security.ubuntu.com")
4010+
4011+
4012+class XenialTestAptSrcCustom(relbase.xenial, TestAptSrcCustom):
4013+ """ XenialTestAptSrcCustom
4014+ apt feature Test for Xenial with a custom template
4015+ """
4016+ __test__ = True
4017+
4018+
4019+class XenialTestAptSrcPreserve(relbase.xenial, TestAptSrcPreserve):
4020+ """ XenialTestAptSrcPreserve
4021+ apt feature Test for Xenial with apt_preserve_sources_list enabled
4022+ """
4023+ __test__ = True
4024+
4025+
4026+class XenialTestAptSrcModify(relbase.xenial, TestAptSrcModify):
4027+ """ XenialTestAptSrcModify
4028+ apt feature Test for Xenial modifying the sources.list of the image
4029+ """
4030+ __test__ = True
4031+
4032+
4033+class XenialTestAptSrcSearch(relbase.xenial, TestAptSrcSearch):
4034+ """ XenialTestAptSrcModify
4035+ apt feature Test for Xenial searching for mirrors
4036+ """
4037+ __test__ = True
4038+
4039+
4040+class XenialTestAptSrcModifyArches(relbase.xenial, TestAptSrcModifyArches):
4041+ """ XenialTestAptSrcModifyArches
4042+ apt feature Test for Xenial checking per arch mirror specification
4043+ """
4044+ __test__ = True
4045+
4046+
4047+class XenialTestAptSrcDisablePockets(relbase.xenial, TestAptSrcDisablePockets):
4048+ """ XenialTestAptSrcDisablePockets
4049+ apt feature Test for Xenial disabling a suite
4050+ """
4051+ __test__ = True
4052
4053=== added file 'tests/vmtests/test_old_apt_features.py'
4054--- tests/vmtests/test_old_apt_features.py 1970-01-01 00:00:00 +0000
4055+++ tests/vmtests/test_old_apt_features.py 2016-08-02 09:15:49 +0000
4056@@ -0,0 +1,80 @@
4057+""" testold_apt_features
4058+ Testing the former minimal apt features of curtin
4059+"""
4060+import re
4061+import textwrap
4062+
4063+from . import VMBaseClass
4064+from .releases import base_vm_classes as relbase
4065+
4066+from curtin import util
4067+
4068+
4069+class TestOldAptAbs(VMBaseClass):
4070+ """TestOldAptAbs - Basic tests for old apt features of curtin"""
4071+ interactive = False
4072+ extra_disks = []
4073+ fstab_expected = {}
4074+ disk_to_check = []
4075+ collect_scripts = [textwrap.dedent("""
4076+ cd OUTPUT_COLLECT_D
4077+ cat /etc/fstab > fstab
4078+ ls /dev/disk/by-dname > ls_dname
4079+ find /etc/network/interfaces.d > find_interfacesd
4080+ grep -A 3 "Name: debconf/priority" /var/cache/debconf/config.dat > debc
4081+ apt-config dump > aptconf
4082+ cp /etc/apt/apt.conf.d/90curtin-aptproxy .
4083+ cp /etc/apt/sources.list .
4084+ cp /etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg .
4085+ """)]
4086+ arch = util.get_architecture()
4087+ if arch in ['amd64', 'i386']:
4088+ conf_file = "examples/tests/test_old_apt_features.yaml"
4089+ exp_mirror = "http://us.archive.ubuntu.com/ubuntu"
4090+ exp_secmirror = "http://archive.ubuntu.com/ubuntu"
4091+ if arch in ['s390x', 'arm64', 'armhf', 'powerpc', 'ppc64el']:
4092+ conf_file = "examples/tests/test_old_apt_features_ports.yaml"
4093+ exp_mirror = "http://ports.ubuntu.com/ubuntu-ports"
4094+ exp_secmirror = "http://ports.ubuntu.com/ubuntu-ports"
4095+
4096+ def test_output_files_exist(self):
4097+ """test_output_files_exist - Check if all output files exist"""
4098+ self.output_files_exist(
4099+ ["debc", "aptconf", "sources.list", "90curtin-aptproxy",
4100+ "curtin-preserve-sources.cfg"])
4101+
4102+ def test_preserve_source(self):
4103+ """test_preserve_source - no clobbering sources.list by cloud-init"""
4104+ self.check_file_regex("curtin-preserve-sources.cfg",
4105+ "apt_preserve_sources_list.*true")
4106+
4107+ def test_debconf(self):
4108+ """test_debconf - Check if debconf is in place"""
4109+ self.check_file_strippedline("debc", "Value: low")
4110+
4111+ def test_aptconf(self):
4112+ """test_aptconf - Check if apt conf for proxy is in place"""
4113+ # this gets configured by tools/launch and get_apt_proxy in
4114+ # tests/vmtests/__init__.py, so compare with those
4115+ rproxy = r"Acquire::http::Proxy \"" + re.escape(self.proxy) + r"\";"
4116+ self.check_file_regex("aptconf", rproxy)
4117+ self.check_file_regex("90curtin-aptproxy", rproxy)
4118+
4119+ def test_mirrors(self):
4120+ """test_mirrors - Check for mirrors placed in source.list"""
4121+
4122+ self.check_file_strippedline("sources.list",
4123+ "deb %s %s" %
4124+ (self.exp_mirror, self.release) +
4125+ " main restricted universe multiverse")
4126+ self.check_file_strippedline("sources.list",
4127+ "deb %s %s-security" %
4128+ (self.exp_secmirror, self.release) +
4129+ " main restricted universe multiverse")
4130+
4131+
4132+class XenialTestOldApt(relbase.xenial, TestOldAptAbs):
4133+ """ XenialTestOldApt
4134+ Old apt features for Xenial
4135+ """
4136+ __test__ = True

Subscribers

People subscribed via source and target branches