Merge ~ubuntu-server/cloud-init:derived-repo-v3-new-apt-spec into cloud-init:master
- Git
- lp:~ubuntu-server/cloud-init
- derived-repo-v3-new-apt-spec
- Merge into master
Proposed by
Scott Moser
Status: | Merged |
---|---|
Merged at revision: | d861415ff9ab816b1183b8c58ec35348be4fd458 |
Proposed branch: | ~ubuntu-server/cloud-init:derived-repo-v3-new-apt-spec |
Merge into: | cloud-init:master |
Diff against target: |
4996 lines (+3325/-1029) 17 files modified
cloudinit/config/cc_apt_configure.py (+547/-156) cloudinit/gpg.py (+4/-4) cloudinit/util.py (+130/-17) dev/null (+0/-516) doc/examples/cloud-config-add-apt-repos.txt (+28/-14) doc/examples/cloud-config-apt.txt (+328/-0) doc/examples/cloud-config-chef-oneiric.txt (+34/-33) doc/examples/cloud-config-chef.txt (+34/-33) doc/examples/cloud-config.txt (+1/-250) tests/configs/sample1.yaml (+0/-3) tests/unittests/test_distros/test_generic.py (+0/-3) tests/unittests/test_handler/test_handler_apt_conf_v1.py (+109/-0) tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py (+200/-0) tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py (+187/-0) tests/unittests/test_handler/test_handler_apt_source_v1.py (+551/-0) tests/unittests/test_handler/test_handler_apt_source_v3.py (+1103/-0) tests/unittests/test_util.py (+69/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+303190@code.launchpad.net |
Commit message
Apt: add new apt configuration format
This adds an improved apt configuration format that is fully backwards
compatible with previous behavior. This is mostly copied from curtin's
implementation.
It does:
* clean up and centralizes many of the top level 'apt_*' values that
previously existed into a single top level 'apt'key.
* support a 'source' in apt/sources/entry that has only a key
* documents new features and adds tests.
See the added doc/examples/
Description of the change
To post a comment you must log in.
- b7d9a53... by Scott Moser
-
fix error in disable_suites if empty lines in sources.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py |
2 | index 05ad4b0..609dbb5 100644 |
3 | --- a/cloudinit/config/cc_apt_configure.py |
4 | +++ b/cloudinit/config/cc_apt_configure.py |
5 | @@ -23,80 +23,182 @@ import os |
6 | import re |
7 | |
8 | from cloudinit import gpg |
9 | +from cloudinit import log as logging |
10 | from cloudinit import templater |
11 | from cloudinit import util |
12 | |
13 | -distros = ['ubuntu', 'debian'] |
14 | - |
15 | -PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n" |
16 | -APT_CONFIG_FN = "/etc/apt/apt.conf.d/94cloud-init-config" |
17 | -APT_PROXY_FN = "/etc/apt/apt.conf.d/95cloud-init-proxy" |
18 | +LOG = logging.getLogger(__name__) |
19 | |
20 | # this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar') |
21 | ADD_APT_REPO_MATCH = r"^[\w-]+:\w" |
22 | |
23 | +# place where apt stores cached repository data |
24 | +APT_LISTS = "/var/lib/apt/lists" |
25 | |
26 | -def handle(name, cfg, cloud, log, _args): |
27 | - if util.is_false(cfg.get('apt_configure_enabled', True)): |
28 | - log.debug("Skipping module named %s, disabled by config.", name) |
29 | - return |
30 | - |
31 | - release = get_release() |
32 | - mirrors = find_apt_mirror_info(cloud, cfg) |
33 | - if not mirrors or "primary" not in mirrors: |
34 | - log.debug(("Skipping module named %s," |
35 | - " no package 'mirror' located"), name) |
36 | - return |
37 | - |
38 | - # backwards compatibility |
39 | - mirror = mirrors["primary"] |
40 | - mirrors["mirror"] = mirror |
41 | - |
42 | - log.debug("Mirror info: %s" % mirrors) |
43 | - |
44 | - if not util.get_cfg_option_bool(cfg, |
45 | - 'apt_preserve_sources_list', False): |
46 | - generate_sources_list(cfg, release, mirrors, cloud, log) |
47 | - old_mirrors = cfg.get('apt_old_mirrors', |
48 | - {"primary": "archive.ubuntu.com/ubuntu", |
49 | - "security": "security.ubuntu.com/ubuntu"}) |
50 | - rename_apt_lists(old_mirrors, mirrors) |
51 | +# Files to store proxy information |
52 | +APT_CONFIG_FN = "/etc/apt/apt.conf.d/94cloud-init-config" |
53 | +APT_PROXY_FN = "/etc/apt/apt.conf.d/90cloud-init-aptproxy" |
54 | + |
55 | +# Default keyserver to use |
56 | +DEFAULT_KEYSERVER = "keyserver.ubuntu.com" |
57 | + |
58 | +# Default archive mirrors |
59 | +PRIMARY_ARCH_MIRRORS = {"PRIMARY": "http://archive.ubuntu.com/ubuntu/", |
60 | + "SECURITY": "http://security.ubuntu.com/ubuntu/"} |
61 | +PORTS_MIRRORS = {"PRIMARY": "http://ports.ubuntu.com/ubuntu-ports", |
62 | + "SECURITY": "http://ports.ubuntu.com/ubuntu-ports"} |
63 | +PRIMARY_ARCHES = ['amd64', 'i386'] |
64 | +PORTS_ARCHES = ['s390x', 'arm64', 'armhf', 'powerpc', 'ppc64el'] |
65 | + |
66 | + |
67 | +def get_default_mirrors(arch=None, target=None): |
68 | + """returns the default mirrors for the target. These depend on the |
69 | + architecture, for more see: |
70 | + https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports""" |
71 | + if arch is None: |
72 | + arch = util.get_architecture(target) |
73 | + if arch in PRIMARY_ARCHES: |
74 | + return PRIMARY_ARCH_MIRRORS.copy() |
75 | + if arch in PORTS_ARCHES: |
76 | + return PORTS_MIRRORS.copy() |
77 | + raise ValueError("No default mirror known for arch %s" % arch) |
78 | + |
79 | + |
80 | +def handle(name, ocfg, cloud, log, _): |
81 | + """process the config for apt_config. This can be called from |
82 | + curthooks if a global apt config was provided or via the "apt" |
83 | + standalone command.""" |
84 | + # keeping code close to curtin codebase via entry handler |
85 | + target = None |
86 | + if log is not None: |
87 | + global LOG |
88 | + LOG = log |
89 | + # feed back converted config, but only work on the subset under 'apt' |
90 | + ocfg = convert_to_v3_apt_format(ocfg) |
91 | + cfg = ocfg.get('apt', {}) |
92 | + |
93 | + if not isinstance(cfg, dict): |
94 | + raise ValueError("Expected dictionary for 'apt' config, found %s", |
95 | + type(cfg)) |
96 | + |
97 | + LOG.debug("handling apt (module %s) with apt config '%s'", name, cfg) |
98 | + |
99 | + release = util.lsb_release(target=target)['codename'] |
100 | + arch = util.get_architecture(target) |
101 | + mirrors = find_apt_mirror_info(cfg, cloud, arch=arch) |
102 | + LOG.debug("Apt Mirror info: %s", mirrors) |
103 | + |
104 | + apply_debconf_selections(cfg, target) |
105 | + |
106 | + if util.is_false(cfg.get('preserve_sources_list', False)): |
107 | + generate_sources_list(cfg, release, mirrors, cloud) |
108 | + rename_apt_lists(mirrors, target) |
109 | |
110 | try: |
111 | apply_apt_config(cfg, APT_PROXY_FN, APT_CONFIG_FN) |
112 | - except Exception as e: |
113 | - log.warn("failed to proxy or apt config info: %s", e) |
114 | + except (IOError, OSError): |
115 | + LOG.exception("Failed to apply proxy or apt config info:") |
116 | |
117 | - # Process 'apt_sources' |
118 | - if 'apt_sources' in cfg: |
119 | + # Process 'apt_source -> sources {dict}' |
120 | + if 'sources' in cfg: |
121 | params = mirrors |
122 | params['RELEASE'] = release |
123 | - params['MIRROR'] = mirror |
124 | + params['MIRROR'] = mirrors["MIRROR"] |
125 | |
126 | + matcher = None |
127 | matchcfg = cfg.get('add_apt_repo_match', ADD_APT_REPO_MATCH) |
128 | if matchcfg: |
129 | matcher = re.compile(matchcfg).search |
130 | + |
131 | + add_apt_sources(cfg['sources'], cloud, target=target, |
132 | + template_params=params, aa_repo_match=matcher) |
133 | + |
134 | + |
135 | +def debconf_set_selections(selections, target=None): |
136 | + util.subp(['debconf-set-selections'], data=selections, target=target, |
137 | + capture=True) |
138 | + |
139 | + |
140 | +def dpkg_reconfigure(packages, target=None): |
141 | + # For any packages that are already installed, but have preseed data |
142 | + # we populate the debconf database, but the filesystem configuration |
143 | + # would be preferred on a subsequent dpkg-reconfigure. |
144 | + # so, what we have to do is "know" information about certain packages |
145 | + # to unconfigure them. |
146 | + unhandled = [] |
147 | + to_config = [] |
148 | + for pkg in packages: |
149 | + if pkg in CONFIG_CLEANERS: |
150 | + LOG.debug("unconfiguring %s", pkg) |
151 | + CONFIG_CLEANERS[pkg](target) |
152 | + to_config.append(pkg) |
153 | else: |
154 | - def matcher(x): |
155 | - return False |
156 | + unhandled.append(pkg) |
157 | + |
158 | + if len(unhandled): |
159 | + LOG.warn("The following packages were installed and preseeded, " |
160 | + "but cannot be unconfigured: %s", unhandled) |
161 | + |
162 | + if len(to_config): |
163 | + util.subp(['dpkg-reconfigure', '--frontend=noninteractive'] + |
164 | + list(to_config), data=None, target=target, capture=True) |
165 | + |
166 | + |
167 | +def apply_debconf_selections(cfg, target=None): |
168 | + """apply_debconf_selections - push content to debconf""" |
169 | + # debconf_selections: |
170 | + # set1: | |
171 | + # cloud-init cloud-init/datasources multiselect MAAS |
172 | + # set2: pkg pkg/value string bar |
173 | + selsets = cfg.get('debconf_selections') |
174 | + if not selsets: |
175 | + LOG.debug("debconf_selections was not set in config") |
176 | + return |
177 | |
178 | - errors = add_apt_sources(cfg['apt_sources'], params, |
179 | - aa_repo_match=matcher) |
180 | - for e in errors: |
181 | - log.warn("Add source error: %s", ':'.join(e)) |
182 | + selections = '\n'.join( |
183 | + [selsets[key] for key in sorted(selsets.keys())]) |
184 | + debconf_set_selections(selections.encode() + b"\n", target=target) |
185 | |
186 | - dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False) |
187 | - if dconf_sel: |
188 | - log.debug("Setting debconf selections per cloud config") |
189 | - try: |
190 | - util.subp(('debconf-set-selections', '-'), dconf_sel) |
191 | - except Exception: |
192 | - util.logexc(log, "Failed to run debconf-set-selections") |
193 | + # get a complete list of packages listed in input |
194 | + pkgs_cfgd = set() |
195 | + for key, content in selsets.items(): |
196 | + for line in content.splitlines(): |
197 | + if line.startswith("#"): |
198 | + continue |
199 | + pkg = re.sub(r"[:\s].*", "", line) |
200 | + pkgs_cfgd.add(pkg) |
201 | + |
202 | + pkgs_installed = util.get_installed_packages(target) |
203 | + |
204 | + LOG.debug("pkgs_cfgd: %s", pkgs_cfgd) |
205 | + need_reconfig = pkgs_cfgd.intersection(pkgs_installed) |
206 | + |
207 | + if len(need_reconfig) == 0: |
208 | + LOG.debug("no need for reconfig") |
209 | + return |
210 | + |
211 | + dpkg_reconfigure(need_reconfig, target=target) |
212 | + |
213 | + |
214 | +def clean_cloud_init(target): |
215 | + """clean out any local cloud-init config""" |
216 | + flist = glob.glob( |
217 | + util.target_path(target, "/etc/cloud/cloud.cfg.d/*dpkg*")) |
218 | + |
219 | + LOG.debug("cleaning cloud-init config from: %s", flist) |
220 | + for dpkg_cfg in flist: |
221 | + os.unlink(dpkg_cfg) |
222 | |
223 | |
224 | def mirrorurl_to_apt_fileprefix(mirror): |
225 | + """mirrorurl_to_apt_fileprefix |
226 | + Convert a mirror url to the file prefix used by apt on disk to |
227 | + store cache information for that mirror. |
228 | + To do so do: |
229 | + - take off ???:// |
230 | + - drop tailing / |
231 | + - convert in string / to _""" |
232 | string = mirror |
233 | - # take off http:// or ftp:// |
234 | if string.endswith("/"): |
235 | string = string[0:-1] |
236 | pos = string.find("://") |
237 | @@ -106,174 +208,365 @@ def mirrorurl_to_apt_fileprefix(mirror): |
238 | return string |
239 | |
240 | |
241 | -def rename_apt_lists(old_mirrors, new_mirrors, lists_d="/var/lib/apt/lists"): |
242 | - for (name, omirror) in old_mirrors.items(): |
243 | +def rename_apt_lists(new_mirrors, target=None): |
244 | + """rename_apt_lists - rename apt lists to preserve old cache data""" |
245 | + default_mirrors = get_default_mirrors(util.get_architecture(target)) |
246 | + |
247 | + pre = util.target_path(target, APT_LISTS) |
248 | + for (name, omirror) in default_mirrors.items(): |
249 | nmirror = new_mirrors.get(name) |
250 | if not nmirror: |
251 | continue |
252 | - oprefix = os.path.join(lists_d, mirrorurl_to_apt_fileprefix(omirror)) |
253 | - nprefix = os.path.join(lists_d, mirrorurl_to_apt_fileprefix(nmirror)) |
254 | + |
255 | + oprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(omirror) |
256 | + nprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(nmirror) |
257 | if oprefix == nprefix: |
258 | continue |
259 | olen = len(oprefix) |
260 | for filename in glob.glob("%s_*" % oprefix): |
261 | - util.rename(filename, "%s%s" % (nprefix, filename[olen:])) |
262 | - |
263 | - |
264 | -def get_release(): |
265 | - (stdout, _stderr) = util.subp(['lsb_release', '-cs']) |
266 | - return stdout.strip() |
267 | - |
268 | - |
269 | -def generate_sources_list(cfg, codename, mirrors, cloud, log): |
270 | - params = {'codename': codename} |
271 | + newname = "%s%s" % (nprefix, filename[olen:]) |
272 | + LOG.debug("Renaming apt list %s to %s", filename, newname) |
273 | + try: |
274 | + os.rename(filename, newname) |
275 | + except OSError: |
276 | + # since this is a best effort task, warn with but don't fail |
277 | + LOG.warn("Failed to rename apt list:", exc_info=True) |
278 | + |
279 | + |
280 | +def mirror_to_placeholder(tmpl, mirror, placeholder): |
281 | + """mirror_to_placeholder |
282 | + replace the specified mirror in a template with a placeholder string |
283 | + Checks for existance of the expected mirror and warns if not found""" |
284 | + if mirror not in tmpl: |
285 | + LOG.warn("Expected mirror '%s' not found in: %s", mirror, tmpl) |
286 | + return tmpl.replace(mirror, placeholder) |
287 | + |
288 | + |
289 | +def map_known_suites(suite): |
290 | + """there are a few default names which will be auto-extended. |
291 | + This comes at the inability to use those names literally as suites, |
292 | + but on the other hand increases readability of the cfg quite a lot""" |
293 | + mapping = {'updates': '$RELEASE-updates', |
294 | + 'backports': '$RELEASE-backports', |
295 | + 'security': '$RELEASE-security', |
296 | + 'proposed': '$RELEASE-proposed', |
297 | + 'release': '$RELEASE'} |
298 | + try: |
299 | + retsuite = mapping[suite] |
300 | + except KeyError: |
301 | + retsuite = suite |
302 | + return retsuite |
303 | + |
304 | + |
305 | +def disable_suites(disabled, src, release): |
306 | + """reads the config for suites to be disabled and removes those |
307 | + from the template""" |
308 | + if not disabled: |
309 | + return src |
310 | + |
311 | + retsrc = src |
312 | + for suite in disabled: |
313 | + suite = map_known_suites(suite) |
314 | + releasesuite = templater.render_string(suite, {'RELEASE': release}) |
315 | + LOG.debug("Disabling suite %s as %s", suite, releasesuite) |
316 | + |
317 | + newsrc = "" |
318 | + for line in retsrc.splitlines(True): |
319 | + if line.startswith("#"): |
320 | + newsrc += line |
321 | + continue |
322 | + |
323 | + # sources.list allow options in cols[1] which can have spaces |
324 | + # so the actual suite can be [2] or later. example: |
325 | + # deb [ arch=amd64,armel k=v ] http://example.com/debian |
326 | + cols = line.split() |
327 | + if len(cols) > 1: |
328 | + pcol = 2 |
329 | + if cols[1].startswith("["): |
330 | + for col in cols[1:]: |
331 | + pcol += 1 |
332 | + if col.endswith("]"): |
333 | + break |
334 | + |
335 | + if cols[pcol] == releasesuite: |
336 | + line = '# suite disabled by cloud-init: %s' % line |
337 | + newsrc += line |
338 | + retsrc = newsrc |
339 | + |
340 | + return retsrc |
341 | + |
342 | + |
343 | +def generate_sources_list(cfg, release, mirrors, cloud): |
344 | + """generate_sources_list |
345 | + create a source.list file based on a custom or default template |
346 | + by replacing mirrors and release in the template""" |
347 | + aptsrc = "/etc/apt/sources.list" |
348 | + params = {'RELEASE': release, 'codename': release} |
349 | for k in mirrors: |
350 | params[k] = mirrors[k] |
351 | + params[k.lower()] = mirrors[k] |
352 | |
353 | - custtmpl = cfg.get('apt_custom_sources_list', None) |
354 | - if custtmpl is not None: |
355 | - templater.render_string_to_file(custtmpl, |
356 | - '/etc/apt/sources.list', params) |
357 | - return |
358 | - |
359 | - template_fn = cloud.get_template_filename('sources.list.%s' % |
360 | - (cloud.distro.name)) |
361 | - if not template_fn: |
362 | - template_fn = cloud.get_template_filename('sources.list') |
363 | + tmpl = cfg.get('sources_list', None) |
364 | + if tmpl is None: |
365 | + LOG.info("No custom template provided, fall back to builtin") |
366 | + template_fn = cloud.get_template_filename('sources.list.%s' % |
367 | + (cloud.distro.name)) |
368 | if not template_fn: |
369 | - log.warn("No template found, not rendering /etc/apt/sources.list") |
370 | + template_fn = cloud.get_template_filename('sources.list') |
371 | + if not template_fn: |
372 | + LOG.warn("No template found, not rendering /etc/apt/sources.list") |
373 | return |
374 | + tmpl = util.load_file(template_fn) |
375 | |
376 | - templater.render_to_file(template_fn, '/etc/apt/sources.list', params) |
377 | + rendered = templater.render_string(tmpl, params) |
378 | + disabled = disable_suites(cfg.get('disable_suites'), rendered, release) |
379 | + util.write_file(aptsrc, disabled, mode=0o644) |
380 | |
381 | |
382 | -def add_apt_key_raw(key): |
383 | +def add_apt_key_raw(key, target=None): |
384 | """ |
385 | actual adding of a key as defined in key argument |
386 | to the system |
387 | """ |
388 | + LOG.debug("Adding key:\n'%s'", key) |
389 | try: |
390 | - util.subp(('apt-key', 'add', '-'), key) |
391 | + util.subp(['apt-key', 'add', '-'], data=key.encode(), target=target) |
392 | except util.ProcessExecutionError: |
393 | - raise ValueError('failed to add apt GPG Key to apt keyring') |
394 | + LOG.exception("failed to add apt GPG Key to apt keyring") |
395 | + raise |
396 | |
397 | |
398 | -def add_apt_key(ent): |
399 | +def add_apt_key(ent, target=None): |
400 | """ |
401 | - add key to the system as defined in ent (if any) |
402 | - supports raw keys or keyid's |
403 | - The latter will as a first step fetch the raw key from a keyserver |
404 | + Add key to the system as defined in ent (if any). |
405 | + Supports raw keys or keyid's |
406 | + The latter will as a first step fetched to get the raw key |
407 | """ |
408 | if 'keyid' in ent and 'key' not in ent: |
409 | - keyserver = "keyserver.ubuntu.com" |
410 | + keyserver = DEFAULT_KEYSERVER |
411 | if 'keyserver' in ent: |
412 | keyserver = ent['keyserver'] |
413 | - ent['key'] = gpg.get_key_by_id(ent['keyid'], keyserver) |
414 | + |
415 | + ent['key'] = gpg.getkeybyid(ent['keyid'], keyserver) |
416 | |
417 | if 'key' in ent: |
418 | - add_apt_key_raw(ent['key']) |
419 | + add_apt_key_raw(ent['key'], target) |
420 | |
421 | |
422 | -def convert_to_new_format(srclist): |
423 | - """convert_to_new_format |
424 | - convert the old list based format to the new dict based one |
425 | - """ |
426 | - srcdict = {} |
427 | - if isinstance(srclist, list): |
428 | - for srcent in srclist: |
429 | - if 'filename' not in srcent: |
430 | - # file collides for multiple !filename cases for compatibility |
431 | - # yet we need them all processed, so not same dictionary key |
432 | - srcent['filename'] = "cloud_config_sources.list" |
433 | - key = util.rand_dict_key(srcdict, "cloud_config_sources.list") |
434 | - else: |
435 | - # all with filename use that as key (matching new format) |
436 | - key = srcent['filename'] |
437 | - srcdict[key] = srcent |
438 | - elif isinstance(srclist, dict): |
439 | - srcdict = srclist |
440 | - else: |
441 | - raise ValueError("unknown apt_sources format") |
442 | - |
443 | - return srcdict |
444 | +def update_packages(cloud): |
445 | + cloud.distro.update_package_sources() |
446 | |
447 | |
448 | -def add_apt_sources(srclist, template_params=None, aa_repo_match=None): |
449 | +def add_apt_sources(srcdict, cloud, target=None, template_params=None, |
450 | + aa_repo_match=None): |
451 | """ |
452 | add entries in /etc/apt/sources.list.d for each abbreviated |
453 | - sources.list entry in 'srclist'. When rendering template, also |
454 | + sources.list entry in 'srcdict'. When rendering template, also |
455 | include the values in dictionary searchList |
456 | """ |
457 | if template_params is None: |
458 | template_params = {} |
459 | |
460 | if aa_repo_match is None: |
461 | - def _aa_repo_match(x): |
462 | - return False |
463 | - aa_repo_match = _aa_repo_match |
464 | + raise ValueError('did not get a valid repo matcher') |
465 | |
466 | - errorlist = [] |
467 | - srcdict = convert_to_new_format(srclist) |
468 | + if not isinstance(srcdict, dict): |
469 | + raise TypeError('unknown apt format: %s' % (srcdict)) |
470 | |
471 | for filename in srcdict: |
472 | ent = srcdict[filename] |
473 | + LOG.debug("adding source/key '%s'", ent) |
474 | if 'filename' not in ent: |
475 | ent['filename'] = filename |
476 | |
477 | - # keys can be added without specifying a source |
478 | - try: |
479 | - add_apt_key(ent) |
480 | - except ValueError as detail: |
481 | - errorlist.append([ent, detail]) |
482 | + add_apt_key(ent, target) |
483 | |
484 | if 'source' not in ent: |
485 | - errorlist.append(["", "missing source"]) |
486 | continue |
487 | source = ent['source'] |
488 | source = templater.render_string(source, template_params) |
489 | |
490 | - if not ent['filename'].startswith(os.path.sep): |
491 | + if not ent['filename'].startswith("/"): |
492 | ent['filename'] = os.path.join("/etc/apt/sources.list.d/", |
493 | ent['filename']) |
494 | + if not ent['filename'].endswith(".list"): |
495 | + ent['filename'] += ".list" |
496 | |
497 | if aa_repo_match(source): |
498 | try: |
499 | - util.subp(["add-apt-repository", source]) |
500 | - except util.ProcessExecutionError as e: |
501 | - errorlist.append([source, |
502 | - ("add-apt-repository failed. " + str(e))]) |
503 | + util.subp(["add-apt-repository", source], target=target) |
504 | + except util.ProcessExecutionError: |
505 | + LOG.exception("add-apt-repository failed.") |
506 | + raise |
507 | continue |
508 | |
509 | + sourcefn = util.target_path(target, ent['filename']) |
510 | try: |
511 | contents = "%s\n" % (source) |
512 | - util.write_file(ent['filename'], contents, omode="ab") |
513 | - except Exception: |
514 | - errorlist.append([source, |
515 | - "failed write to file %s" % ent['filename']]) |
516 | + util.write_file(sourcefn, contents, omode="a") |
517 | + except IOError as detail: |
518 | + LOG.exception("failed write to file %s: %s", sourcefn, detail) |
519 | + raise |
520 | |
521 | - return errorlist |
522 | + update_packages(cloud) |
523 | |
524 | + return |
525 | |
526 | -def find_apt_mirror_info(cloud, cfg): |
527 | - """find an apt_mirror given the cloud and cfg provided.""" |
528 | |
529 | - mirror = None |
530 | +def convert_v1_to_v2_apt_format(srclist): |
531 | + """convert v1 apt format to v2 (dict in apt_sources)""" |
532 | + srcdict = {} |
533 | + if isinstance(srclist, list): |
534 | + LOG.debug("apt config: convert V1 to V2 format (source list to dict)") |
535 | + for srcent in srclist: |
536 | + if 'filename' not in srcent: |
537 | + # file collides for multiple !filename cases for compatibility |
538 | + # yet we need them all processed, so not same dictionary key |
539 | + srcent['filename'] = "cloud_config_sources.list" |
540 | + key = util.rand_dict_key(srcdict, "cloud_config_sources.list") |
541 | + else: |
542 | + # all with filename use that as key (matching new format) |
543 | + key = srcent['filename'] |
544 | + srcdict[key] = srcent |
545 | + elif isinstance(srclist, dict): |
546 | + srcdict = srclist |
547 | + else: |
548 | + raise ValueError("unknown apt_sources format") |
549 | + |
550 | + return srcdict |
551 | + |
552 | + |
553 | +def convert_key(oldcfg, aptcfg, oldkey, newkey): |
554 | + """convert an old key to the new one if the old one exists |
555 | + returns true if a key was found and converted""" |
556 | + if oldcfg.get(oldkey, None) is not None: |
557 | + aptcfg[newkey] = oldcfg.get(oldkey) |
558 | + del oldcfg[oldkey] |
559 | + return True |
560 | + return False |
561 | + |
562 | + |
563 | +def convert_mirror(oldcfg, aptcfg): |
564 | + """convert old apt_mirror keys into the new more advanced mirror spec""" |
565 | + keymap = [('apt_mirror', 'uri'), |
566 | + ('apt_mirror_search', 'search'), |
567 | + ('apt_mirror_search_dns', 'search_dns')] |
568 | + converted = False |
569 | + newmcfg = {'arches': ['default']} |
570 | + for oldkey, newkey in keymap: |
571 | + if convert_key(oldcfg, newmcfg, oldkey, newkey): |
572 | + converted = True |
573 | + |
574 | + # only insert new style config if anything was converted |
575 | + if converted: |
576 | + aptcfg['primary'] = [newmcfg] |
577 | + |
578 | + |
579 | +def convert_v2_to_v3_apt_format(oldcfg): |
580 | + """convert old to new keys and adapt restructured mirror spec""" |
581 | + oldkeys = ['apt_sources', 'apt_mirror', 'apt_mirror_search', |
582 | + 'apt_mirror_search_dns', 'apt_proxy', 'apt_http_proxy', |
583 | + 'apt_ftp_proxy', 'apt_https_proxy', |
584 | + 'apt_preserve_sources_list', 'apt_custom_sources_list', |
585 | + 'add_apt_repo_match'] |
586 | + needtoconvert = [] |
587 | + for oldkey in oldkeys: |
588 | + if oldcfg.get(oldkey, None) is not None: |
589 | + needtoconvert.append(oldkey) |
590 | + |
591 | + # no old config, so no new one to be created |
592 | + if not needtoconvert: |
593 | + return oldcfg |
594 | + LOG.debug("apt config: convert V2 to V3 format for keys '%s'", |
595 | + ", ".join(needtoconvert)) |
596 | + |
597 | + if oldcfg.get('apt', None) is not None: |
598 | + msg = ("Error in apt configuration: " |
599 | + "old and new format of apt features are mutually exclusive " |
600 | + "('apt':'%s' vs '%s' key)" % (oldcfg.get('apt', None), |
601 | + ", ".join(needtoconvert))) |
602 | + LOG.error(msg) |
603 | + raise ValueError(msg) |
604 | + |
605 | + # create new format from old keys |
606 | + aptcfg = {} |
607 | + |
608 | + # renames / moves under the apt key |
609 | + convert_key(oldcfg, aptcfg, 'add_apt_repo_match', 'add_apt_repo_match') |
610 | + convert_key(oldcfg, aptcfg, 'apt_proxy', 'proxy') |
611 | + convert_key(oldcfg, aptcfg, 'apt_http_proxy', 'http_proxy') |
612 | + convert_key(oldcfg, aptcfg, 'apt_https_proxy', 'https_proxy') |
613 | + convert_key(oldcfg, aptcfg, 'apt_ftp_proxy', 'ftp_proxy') |
614 | + convert_key(oldcfg, aptcfg, 'apt_custom_sources_list', 'sources_list') |
615 | + convert_key(oldcfg, aptcfg, 'apt_preserve_sources_list', |
616 | + 'preserve_sources_list') |
617 | + # dict format not changed since v2, just renamed and moved |
618 | + convert_key(oldcfg, aptcfg, 'apt_sources', 'sources') |
619 | + |
620 | + convert_mirror(oldcfg, aptcfg) |
621 | + |
622 | + for oldkey in oldkeys: |
623 | + if oldcfg.get(oldkey, None) is not None: |
624 | + raise ValueError("old apt key '%s' left after conversion" % oldkey) |
625 | + |
626 | + # insert new format into config and return full cfg with only v3 content |
627 | + oldcfg['apt'] = aptcfg |
628 | + return oldcfg |
629 | + |
630 | + |
631 | +def convert_to_v3_apt_format(cfg): |
632 | + """convert the old list based format to the new dict based one. After that |
633 | + convert the old dict keys/format to v3 a.k.a 'new apt config'""" |
634 | + # V1 -> V2, the apt_sources entry from list to dict |
635 | + apt_sources = cfg.get('apt_sources', None) |
636 | + if apt_sources is not None: |
637 | + cfg['apt_sources'] = convert_v1_to_v2_apt_format(apt_sources) |
638 | + |
639 | + # V2 -> V3, move all former globals under the "apt" key |
640 | + # Restructure into new key names and mirror hierarchy |
641 | + cfg = convert_v2_to_v3_apt_format(cfg) |
642 | + |
643 | + return cfg |
644 | + |
645 | + |
646 | +def search_for_mirror(candidates): |
647 | + """ |
648 | + Search through a list of mirror urls for one that works |
649 | + This needs to return quickly. |
650 | + """ |
651 | + if candidates is None: |
652 | + return None |
653 | + |
654 | + LOG.debug("search for mirror in candidates: '%s'", candidates) |
655 | + for cand in candidates: |
656 | + try: |
657 | + if util.is_resolvable_url(cand): |
658 | + LOG.debug("found working mirror: '%s'", cand) |
659 | + return cand |
660 | + except Exception: |
661 | + pass |
662 | + return None |
663 | |
664 | - # this is less preferred way of specifying mirror preferred would be to |
665 | - # use the distro's search or package_mirror. |
666 | - mirror = cfg.get("apt_mirror", None) |
667 | |
668 | - search = cfg.get("apt_mirror_search", None) |
669 | - if not mirror and search: |
670 | - mirror = util.search_for_mirror(search) |
671 | +def search_for_mirror_dns(configured, mirrortype, cfg, cloud): |
672 | + """ |
673 | + Try to resolve a list of predefines DNS names to pick mirrors |
674 | + """ |
675 | + mirror = None |
676 | |
677 | - if (not mirror and |
678 | - util.get_cfg_option_bool(cfg, "apt_mirror_search_dns", False)): |
679 | + if configured: |
680 | mydom = "" |
681 | doms = [] |
682 | |
683 | + if mirrortype == "primary": |
684 | + mirrordns = "mirror" |
685 | + elif mirrortype == "security": |
686 | + mirrordns = "security-mirror" |
687 | + else: |
688 | + raise ValueError("unknown mirror type") |
689 | + |
690 | # if we have a fqdn, then search its domain portion first |
691 | - (_hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) |
692 | + (_, fqdn) = util.get_hostname_fqdn(cfg, cloud) |
693 | mydom = ".".join(fqdn.split(".")[1:]) |
694 | if mydom: |
695 | doms.append(".%s" % mydom) |
696 | @@ -282,38 +575,136 @@ def find_apt_mirror_info(cloud, cfg): |
697 | |
698 | mirror_list = [] |
699 | distro = cloud.distro.name |
700 | - mirrorfmt = "http://%s-mirror%s/%s" % (distro, "%s", distro) |
701 | + mirrorfmt = "http://%s-%s%s/%s" % (distro, mirrordns, "%s", distro) |
702 | for post in doms: |
703 | mirror_list.append(mirrorfmt % (post)) |
704 | |
705 | - mirror = util.search_for_mirror(mirror_list) |
706 | + mirror = search_for_mirror(mirror_list) |
707 | + |
708 | + return mirror |
709 | |
710 | + |
711 | +def update_mirror_info(pmirror, smirror, arch, cloud): |
712 | + """sets security mirror to primary if not defined. |
713 | + returns defaults if no mirrors are defined""" |
714 | + if pmirror is not None: |
715 | + if smirror is None: |
716 | + smirror = pmirror |
717 | + return {'PRIMARY': pmirror, |
718 | + 'SECURITY': smirror} |
719 | + |
720 | + # None specified at all, get default mirrors from cloud |
721 | mirror_info = cloud.datasource.get_package_mirror_info() |
722 | + if mirror_info: |
723 | + # get_package_mirror_info() returns a dictionary with |
724 | + # arbitrary key/value pairs including 'primary' and 'security' keys. |
725 | + # caller expects dict with PRIMARY and SECURITY. |
726 | + m = mirror_info.copy() |
727 | + m['PRIMARY'] = m['primary'] |
728 | + m['SECURITY'] = m['security'] |
729 | + |
730 | + return m |
731 | + |
732 | + # if neither apt nor cloud configured mirrors fall back to |
733 | + return get_default_mirrors(arch) |
734 | + |
735 | + |
736 | +def get_arch_mirrorconfig(cfg, mirrortype, arch): |
737 | + """out of a list of potential mirror configurations select |
738 | + and return the one matching the architecture (or default)""" |
739 | + # select the mirror specification (if-any) |
740 | + mirror_cfg_list = cfg.get(mirrortype, None) |
741 | + if mirror_cfg_list is None: |
742 | + return None |
743 | + |
744 | + # select the specification matching the target arch |
745 | + default = None |
746 | + for mirror_cfg_elem in mirror_cfg_list: |
747 | + arches = mirror_cfg_elem.get("arches") |
748 | + if arch in arches: |
749 | + return mirror_cfg_elem |
750 | + if "default" in arches: |
751 | + default = mirror_cfg_elem |
752 | + return default |
753 | + |
754 | + |
755 | +def get_mirror(cfg, mirrortype, arch, cloud): |
756 | + """pass the three potential stages of mirror specification |
757 | + returns None is neither of them found anything otherwise the first |
758 | + hit is returned""" |
759 | + mcfg = get_arch_mirrorconfig(cfg, mirrortype, arch) |
760 | + if mcfg is None: |
761 | + return None |
762 | + |
763 | + # directly specified |
764 | + mirror = mcfg.get("uri", None) |
765 | + |
766 | + # fallback to search if specified |
767 | + if mirror is None: |
768 | + # list of mirrors to try to resolve |
769 | + mirror = search_for_mirror(mcfg.get("search", None)) |
770 | + |
771 | + # fallback to search_dns if specified |
772 | + if mirror is None: |
773 | + # list of mirrors to try to resolve |
774 | + mirror = search_for_mirror_dns(mcfg.get("search_dns", None), |
775 | + mirrortype, cfg, cloud) |
776 | + |
777 | + return mirror |
778 | + |
779 | + |
780 | +def find_apt_mirror_info(cfg, cloud, arch=None): |
781 | + """find_apt_mirror_info |
782 | + find an apt_mirror given the cfg provided. |
783 | + It can check for separate config of primary and security mirrors |
784 | + If only primary is given security is assumed to be equal to primary |
785 | + If the generic apt_mirror is given that is defining for both |
786 | + """ |
787 | |
788 | - # this is a bit strange. |
789 | - # if mirror is set, then one of the legacy options above set it |
790 | - # but they do not cover security. so we need to get that from |
791 | - # get_package_mirror_info |
792 | - if mirror: |
793 | - mirror_info.update({'primary': mirror}) |
794 | + if arch is None: |
795 | + arch = util.get_architecture() |
796 | + LOG.debug("got arch for mirror selection: %s", arch) |
797 | + pmirror = get_mirror(cfg, "primary", arch, cloud) |
798 | + LOG.debug("got primary mirror: %s", pmirror) |
799 | + smirror = get_mirror(cfg, "security", arch, cloud) |
800 | + LOG.debug("got security mirror: %s", smirror) |
801 | + |
802 | + mirror_info = update_mirror_info(pmirror, smirror, arch, cloud) |
803 | + |
804 | + # less complex replacements use only MIRROR, derive from primary |
805 | + mirror_info["MIRROR"] = mirror_info["PRIMARY"] |
806 | |
807 | return mirror_info |
808 | |
809 | |
810 | def apply_apt_config(cfg, proxy_fname, config_fname): |
811 | + """apply_apt_config |
812 | + Applies any apt*proxy config from if specified |
813 | + """ |
814 | # Set up any apt proxy |
815 | - cfgs = (('apt_proxy', 'Acquire::HTTP::Proxy "%s";'), |
816 | - ('apt_http_proxy', 'Acquire::HTTP::Proxy "%s";'), |
817 | - ('apt_ftp_proxy', 'Acquire::FTP::Proxy "%s";'), |
818 | - ('apt_https_proxy', 'Acquire::HTTPS::Proxy "%s";')) |
819 | + cfgs = (('proxy', 'Acquire::http::Proxy "%s";'), |
820 | + ('http_proxy', 'Acquire::http::Proxy "%s";'), |
821 | + ('ftp_proxy', 'Acquire::ftp::Proxy "%s";'), |
822 | + ('https_proxy', 'Acquire::https::Proxy "%s";')) |
823 | |
824 | proxies = [fmt % cfg.get(name) for (name, fmt) in cfgs if cfg.get(name)] |
825 | if len(proxies): |
826 | + LOG.debug("write apt proxy info to %s", proxy_fname) |
827 | util.write_file(proxy_fname, '\n'.join(proxies) + '\n') |
828 | elif os.path.isfile(proxy_fname): |
829 | util.del_file(proxy_fname) |
830 | + LOG.debug("no apt proxy configured, removed %s", proxy_fname) |
831 | |
832 | - if cfg.get('apt_config', None): |
833 | - util.write_file(config_fname, cfg.get('apt_config')) |
834 | + if cfg.get('conf', None): |
835 | + LOG.debug("write apt config info to %s", config_fname) |
836 | + util.write_file(config_fname, cfg.get('conf')) |
837 | elif os.path.isfile(config_fname): |
838 | util.del_file(config_fname) |
839 | + LOG.debug("no apt config configured, removed %s", config_fname) |
840 | + |
841 | + |
842 | +CONFIG_CLEANERS = { |
843 | + 'cloud-init': clean_cloud_init, |
844 | +} |
845 | + |
846 | +# vi: ts=4 expandtab syntax=python |
847 | diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py |
848 | index 6a76d78..5bbff51 100644 |
849 | --- a/cloudinit/gpg.py |
850 | +++ b/cloudinit/gpg.py |
851 | @@ -36,11 +36,11 @@ def export_armour(key): |
852 | return armour |
853 | |
854 | |
855 | -def receive_key(key, keyserver): |
856 | +def recv_key(key, keyserver): |
857 | """Receive gpg key from the specified keyserver""" |
858 | LOG.debug('Receive gpg key "%s"', key) |
859 | try: |
860 | - util.subp(["gpg", "--keyserver", keyserver, "--recv-keys", key], |
861 | + util.subp(["gpg", "--keyserver", keyserver, "--recv", key], |
862 | capture=True) |
863 | except util.ProcessExecutionError as error: |
864 | raise ValueError(('Failed to import key "%s" ' |
865 | @@ -57,12 +57,12 @@ def delete_key(key): |
866 | LOG.warn('Failed delete key "%s": %s', key, error) |
867 | |
868 | |
869 | -def get_key_by_id(keyid, keyserver="keyserver.ubuntu.com"): |
870 | +def getkeybyid(keyid, keyserver='keyserver.ubuntu.com'): |
871 | """get gpg keyid from keyserver""" |
872 | armour = export_armour(keyid) |
873 | if not armour: |
874 | try: |
875 | - receive_key(keyid, keyserver=keyserver) |
876 | + recv_key(keyid, keyserver=keyserver) |
877 | armour = export_armour(keyid) |
878 | except ValueError: |
879 | LOG.exception('Failed to obtain gpg key %s', keyid) |
880 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
881 | index 226628c..db80ca9 100644 |
882 | --- a/cloudinit/util.py |
883 | +++ b/cloudinit/util.py |
884 | @@ -61,6 +61,10 @@ from cloudinit import version |
885 | |
886 | from cloudinit.settings import (CFG_BUILTIN) |
887 | |
888 | +try: |
889 | + string_types = (basestring,) |
890 | +except NameError: |
891 | + string_types = (str,) |
892 | |
893 | _DNS_REDIRECT_IP = None |
894 | LOG = logging.getLogger(__name__) |
895 | @@ -82,6 +86,71 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], |
896 | |
897 | PROC_CMDLINE = None |
898 | |
899 | +_LSB_RELEASE = {} |
900 | + |
901 | + |
902 | +def get_architecture(target=None): |
903 | + out, _ = subp(['dpkg', '--print-architecture'], capture=True, |
904 | + target=target) |
905 | + return out.strip() |
906 | + |
907 | + |
908 | +def _lsb_release(target=None): |
909 | + fmap = {'Codename': 'codename', 'Description': 'description', |
910 | + 'Distributor ID': 'id', 'Release': 'release'} |
911 | + |
912 | + data = {} |
913 | + try: |
914 | + out, _ = subp(['lsb_release', '--all'], capture=True, target=target) |
915 | + for line in out.splitlines(): |
916 | + fname, _, val = line.partition(":") |
917 | + if fname in fmap: |
918 | + data[fmap[fname]] = val.strip() |
919 | + missing = [k for k in fmap.values() if k not in data] |
920 | + if len(missing): |
921 | + LOG.warn("Missing fields in lsb_release --all output: %s", |
922 | + ','.join(missing)) |
923 | + |
924 | + except ProcessExecutionError as err: |
925 | + LOG.warn("Unable to get lsb_release --all: %s", err) |
926 | + data = {v: "UNAVAILABLE" for v in fmap.values()} |
927 | + |
928 | + return data |
929 | + |
930 | + |
931 | +def lsb_release(target=None): |
932 | + if target_path(target) != "/": |
933 | + # do not use or update cache if target is provided |
934 | + return _lsb_release(target) |
935 | + |
936 | + global _LSB_RELEASE |
937 | + if not _LSB_RELEASE: |
938 | + data = _lsb_release() |
939 | + _LSB_RELEASE.update(data) |
940 | + return _LSB_RELEASE |
941 | + |
942 | + |
943 | +def target_path(target, path=None): |
944 | + # return 'path' inside target, accepting target as None |
945 | + if target in (None, ""): |
946 | + target = "/" |
947 | + elif not isinstance(target, string_types): |
948 | + raise ValueError("Unexpected input for target: %s" % target) |
949 | + else: |
950 | + target = os.path.abspath(target) |
951 | + # abspath("//") returns "//" specifically for 2 slashes. |
952 | + if target.startswith("//"): |
953 | + target = target[1:] |
954 | + |
955 | + if not path: |
956 | + return target |
957 | + |
958 | + # os.path.join("/etc", "/foo") returns "/foo". Chomp all leading /. |
959 | + while len(path) and path[0] == "/": |
960 | + path = path[1:] |
961 | + |
962 | + return os.path.join(target, path) |
963 | + |
964 | |
965 | def decode_binary(blob, encoding='utf-8'): |
966 | # Converts a binary type into a text type using given encoding. |
967 | @@ -1688,10 +1757,20 @@ def delete_dir_contents(dirname): |
968 | |
969 | |
970 | def subp(args, data=None, rcs=None, env=None, capture=True, shell=False, |
971 | - logstring=False): |
972 | + logstring=False, decode="replace", target=None): |
973 | + |
974 | + # not supported in cloud-init (yet), for now kept in the call signature |
975 | + # to ease maintaining code shared between cloud-init and curtin |
976 | + if target is not None: |
977 | + raise ValueError("target arg not supported by cloud-init") |
978 | + |
979 | if rcs is None: |
980 | rcs = [0] |
981 | + |
982 | + devnull_fp = None |
983 | try: |
984 | + if target_path(target) != "/": |
985 | + args = ['chroot', target] + list(args) |
986 | |
987 | if not logstring: |
988 | LOG.debug(("Running command %s with allowed return codes %s" |
989 | @@ -1700,33 +1779,52 @@ def subp(args, data=None, rcs=None, env=None, capture=True, shell=False, |
990 | LOG.debug(("Running hidden command to protect sensitive " |
991 | "input/output logstring: %s"), logstring) |
992 | |
993 | - if not capture: |
994 | - stdout = None |
995 | - stderr = None |
996 | - else: |
997 | + stdin = None |
998 | + stdout = None |
999 | + stderr = None |
1000 | + if capture: |
1001 | stdout = subprocess.PIPE |
1002 | stderr = subprocess.PIPE |
1003 | - stdin = subprocess.PIPE |
1004 | - kws = dict(stdout=stdout, stderr=stderr, stdin=stdin, |
1005 | - env=env, shell=shell) |
1006 | - if six.PY3: |
1007 | - # Use this so subprocess output will be (Python 3) str, not bytes. |
1008 | - kws['universal_newlines'] = True |
1009 | - sp = subprocess.Popen(args, **kws) |
1010 | + if data is None: |
1011 | + # using devnull assures any reads get null, rather |
1012 | + # than possibly waiting on input. |
1013 | + devnull_fp = open(os.devnull) |
1014 | + stdin = devnull_fp |
1015 | + else: |
1016 | + stdin = subprocess.PIPE |
1017 | + if not isinstance(data, bytes): |
1018 | + data = data.encode() |
1019 | + |
1020 | + sp = subprocess.Popen(args, stdout=stdout, |
1021 | + stderr=stderr, stdin=stdin, |
1022 | + env=env, shell=shell) |
1023 | (out, err) = sp.communicate(data) |
1024 | + |
1025 | + # Just ensure blank instead of none. |
1026 | + if not out and capture: |
1027 | + out = b'' |
1028 | + if not err and capture: |
1029 | + err = b'' |
1030 | + if decode: |
1031 | + def ldecode(data, m='utf-8'): |
1032 | + if not isinstance(data, bytes): |
1033 | + return data |
1034 | + return data.decode(m, errors=decode) |
1035 | + |
1036 | + out = ldecode(out) |
1037 | + err = ldecode(err) |
1038 | except OSError as e: |
1039 | raise ProcessExecutionError(cmd=args, reason=e, |
1040 | errno=e.errno) |
1041 | + finally: |
1042 | + if devnull_fp: |
1043 | + devnull_fp.close() |
1044 | + |
1045 | rc = sp.returncode |
1046 | if rc not in rcs: |
1047 | raise ProcessExecutionError(stdout=out, stderr=err, |
1048 | exit_code=rc, |
1049 | cmd=args) |
1050 | - # Just ensure blank instead of none?? (iff capturing) |
1051 | - if not out and capture: |
1052 | - out = '' |
1053 | - if not err and capture: |
1054 | - err = '' |
1055 | return (out, err) |
1056 | |
1057 | |
1058 | @@ -2251,3 +2349,18 @@ def message_from_string(string): |
1059 | if sys.version_info[:2] < (2, 7): |
1060 | return email.message_from_file(six.StringIO(string)) |
1061 | return email.message_from_string(string) |
1062 | + |
1063 | + |
1064 | +def get_installed_packages(target=None): |
1065 | + (out, _) = subp(['dpkg-query', '--list'], target=target, capture=True) |
1066 | + |
1067 | + pkgs_inst = set() |
1068 | + for line in out.splitlines(): |
1069 | + try: |
1070 | + (state, pkg, _) = line.split(None, 2) |
1071 | + except ValueError: |
1072 | + continue |
1073 | + if state.startswith("hi") or state.startswith("ii"): |
1074 | + pkgs_inst.add(re.sub(":.*", "", pkg)) |
1075 | + |
1076 | + return pkgs_inst |
1077 | diff --git a/doc/examples/cloud-config-add-apt-repos.txt b/doc/examples/cloud-config-add-apt-repos.txt |
1078 | index be9d547..22ef761 100644 |
1079 | --- a/doc/examples/cloud-config-add-apt-repos.txt |
1080 | +++ b/doc/examples/cloud-config-add-apt-repos.txt |
1081 | @@ -4,18 +4,21 @@ |
1082 | # |
1083 | # Default: auto select based on cloud metadata |
1084 | # in ec2, the default is <region>.archive.ubuntu.com |
1085 | -# apt_mirror: |
1086 | -# use the provided mirror |
1087 | -# apt_mirror_search: |
1088 | -# search the list for the first mirror. |
1089 | -# this is currently very limited, only verifying that |
1090 | -# the mirror is dns resolvable or an IP address |
1091 | +# apt: |
1092 | +# primary: |
1093 | +# - arches [default] |
1094 | +# uri: |
1095 | +# use the provided mirror |
1096 | +# search: |
1097 | +# search the list for the first mirror. |
1098 | +# this is currently very limited, only verifying that |
1099 | +# the mirror is dns resolvable or an IP address |
1100 | # |
1101 | -# if neither apt_mirror nor apt_mirror search is set (the default) |
1102 | +# if neither mirror is set (the default) |
1103 | # then use the mirror provided by the DataSource found. |
1104 | # In EC2, that means using <region>.ec2.archive.ubuntu.com |
1105 | -# |
1106 | -# if no mirror is provided by the DataSource, and 'apt_mirror_search_dns' is |
1107 | +# |
1108 | +# if no mirror is provided by the DataSource, but 'search_dns' is |
1109 | # true, then search for dns names '<distro>-mirror' in each of |
1110 | # - fqdn of this host per cloud metadata |
1111 | # - localdomain |
1112 | @@ -27,8 +30,19 @@ |
1113 | # up and expose them only by creating dns entries. |
1114 | # |
1115 | # if none of that is found, then the default distro mirror is used |
1116 | -apt_mirror: http://us.archive.ubuntu.com/ubuntu/ |
1117 | -apt_mirror_search: |
1118 | - - http://local-mirror.mydomain |
1119 | - - http://archive.ubuntu.com |
1120 | -apt_mirror_search_dns: False |
1121 | +apt: |
1122 | + primary: |
1123 | + - arches: [default] |
1124 | + uri: http://us.archive.ubuntu.com/ubuntu/ |
1125 | +# or |
1126 | +apt: |
1127 | + primary: |
1128 | + - arches: [default] |
1129 | + search: |
1130 | + - http://local-mirror.mydomain |
1131 | + - http://archive.ubuntu.com |
1132 | +# or |
1133 | +apt: |
1134 | + primary: |
1135 | + - arches: [default] |
1136 | + search_dns: True |
1137 | diff --git a/doc/examples/cloud-config-apt.txt b/doc/examples/cloud-config-apt.txt |
1138 | new file mode 100644 |
1139 | index 0000000..1a0fc6f |
1140 | --- /dev/null |
1141 | +++ b/doc/examples/cloud-config-apt.txt |
1142 | @@ -0,0 +1,328 @@ |
1143 | +# apt_pipelining (configure Acquire::http::Pipeline-Depth) |
1144 | +# Default: disables HTTP pipelining. Certain web servers, such |
1145 | +# as S3 do not pipeline properly (LP: #948461). |
1146 | +# Valid options: |
1147 | +# False/default: Disables pipelining for APT |
1148 | +# None/Unchanged: Use OS default |
1149 | +# Number: Set pipelining to some number (not recommended) |
1150 | +apt_pipelining: False |
1151 | + |
1152 | +## apt config via system_info: |
1153 | +# under the 'system_info', you can customize cloud-init's interaction |
1154 | +# with apt. |
1155 | +# system_info: |
1156 | +# apt_get_command: [command, argument, argument] |
1157 | +# apt_get_upgrade_subcommand: dist-upgrade |
1158 | +# |
1159 | +# apt_get_command: |
1160 | +# To specify a different 'apt-get' command, set 'apt_get_command'. |
1161 | +# This must be a list, and the subcommand (update, upgrade) is appended to it. |
1162 | +# default is: |
1163 | +# ['apt-get', '--option=Dpkg::Options::=--force-confold', |
1164 | +# '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet'] |
1165 | +# |
1166 | +# apt_get_upgrade_subcommand: "dist-upgrade" |
1167 | +# Specify a different subcommand for 'upgrade. The default is 'dist-upgrade'. |
1168 | +# This is the subcommand that is invoked for package_upgrade. |
1169 | +# |
1170 | +# apt_get_wrapper: |
1171 | +# command: eatmydata |
1172 | +# enabled: [True, False, "auto"] |
1173 | +# |
1174 | + |
1175 | +# Install additional packages on first boot |
1176 | +# |
1177 | +# Default: none |
1178 | +# |
1179 | +# if packages are specified, this apt_update will be set to true |
1180 | + |
1181 | +packages: ['pastebinit'] |
1182 | + |
1183 | +apt: |
1184 | + # The apt config consists of two major "areas". |
1185 | + # |
1186 | + # On one hand there is the global configuration for the apt feature. |
1187 | + # |
1188 | + # On one hand (down in this file) there is the source dictionary which allows |
1189 | + # to define various entries to be considered by apt. |
1190 | + |
1191 | + ############################################################################## |
1192 | + # Section 1: global apt configuration |
1193 | + # |
1194 | + # The following examples number the top keys to ease identification in |
1195 | + # discussions. |
1196 | + |
1197 | + # 1.1 preserve_sources_list |
1198 | + # |
1199 | + # Preserves the existing /etc/apt/sources.list |
1200 | + # Default: false - do overwrite sources_list. If set to true then any |
1201 | + # "mirrors" configuration will have no effect. |
1202 | + # Set to true to avoid affecting sources.list. In that case only |
1203 | + # "extra" source specifications will be written into |
1204 | + # /etc/apt/sources.list.d/* |
1205 | + preserve_sources_list: true |
1206 | + |
1207 | + # 1.2 disable_suites |
1208 | + # |
1209 | + # This is an empty list by default, so nothing is disabled. |
1210 | + # |
1211 | + # If given, those suites are removed from sources.list after all other |
1212 | + # modifications have been made. |
1213 | + # Suites are even disabled if no other modification was made, |
1214 | + # but not if is preserve_sources_list is active. |
1215 | + # There is a special alias “$RELEASE” as in the sources that will be replace |
1216 | + # by the matching release. |
1217 | + # |
1218 | + # To ease configuration and improve readability the following common ubuntu |
1219 | + # suites will be automatically mapped to their full definition. |
1220 | + # updates => $RELEASE-updates |
1221 | + # backports => $RELEASE-backports |
1222 | + # security => $RELEASE-security |
1223 | + # proposed => $RELEASE-proposed |
1224 | + # release => $RELEASE |
1225 | + # |
1226 | + # There is no harm in specifying a suite to be disabled that is not found in |
1227 | + # the source.list file (just a no-op then) |
1228 | + # |
1229 | + # Note: Lines don’t get deleted, but disabled by being converted to a comment. |
1230 | + # The following example disables all usual defaults except $RELEASE-security. |
1231 | + # On top it disables a custom suite called "mysuite" |
1232 | + disable_suites: [$RELEASE-updates, backports, $RELEASE, mysuite] |
1233 | + |
1234 | + # 1.3 primary/security archives |
1235 | + # |
1236 | + # Default: none - instead it is auto select based on cloud metadata |
1237 | + # so if neither "uri" nor "search", nor "search_dns" is set (the default) |
1238 | + # then use the mirror provided by the DataSource found. |
1239 | + # In EC2, that means using <region>.ec2.archive.ubuntu.com |
1240 | + # |
1241 | + # define a custom (e.g. localized) mirror that will be used in sources.list |
1242 | + # and any custom sources entries for deb / deb-src lines. |
1243 | + # |
1244 | + # One can set primary and security mirror to different uri's |
1245 | + # the child elements to the keys primary and secondary are equivalent |
1246 | + primary: |
1247 | + # arches is list of architectures the following config applies to |
1248 | + # the special keyword "default" applies to any architecture not explicitly |
1249 | + # listed. |
1250 | + - arches: [amd64, i386, default] |
1251 | + # uri is just defining the target as-is |
1252 | + uri: http://us.archive.ubuntu.com/ubuntu |
1253 | + # |
1254 | + # via search one can define lists that are tried one by one. |
1255 | + # The first with a working DNS resolution (or if it is an IP) will be |
1256 | + # picked. That way one can keep one configuration for multiple |
1257 | + # subenvironments that select the working one. |
1258 | + search: |
1259 | + - http://cool.but-sometimes-unreachable.com/ubuntu |
1260 | + - http://us.archive.ubuntu.com/ubuntu |
1261 | + # if no mirror is provided by uri or search but 'search_dns' is |
1262 | + # true, then search for dns names '<distro>-mirror' in each of |
1263 | + # - fqdn of this host per cloud metadata |
1264 | + # - localdomain |
1265 | + # - no domain (which would search domains listed in /etc/resolv.conf) |
1266 | + # If there is a dns entry for <distro>-mirror, then it is assumed that |
1267 | + # there is a distro mirror at http://<distro>-mirror.<domain>/<distro> |
1268 | + # |
1269 | + # That gives the cloud provider the opportunity to set mirrors of a distro |
1270 | + # up and expose them only by creating dns entries. |
1271 | + # |
1272 | + # if none of that is found, then the default distro mirror is used |
1273 | + search_dns: true |
1274 | + # |
1275 | + # If multiple of a category are given |
1276 | + # 1. uri |
1277 | + # 2. search |
1278 | + # 3. search_dns |
1279 | + # the first defining a valid mirror wins (in the order as defined here, |
1280 | + # not the order as listed in the config). |
1281 | + # |
1282 | + - arches: [s390x, arm64] |
1283 | + # as above, allowing to have one config for different per arch mirrors |
1284 | + # security is optional, if not defined it is set to the same value as primary |
1285 | + security: |
1286 | + uri: http://security.ubuntu.com/ubuntu |
1287 | + # If search_dns is set for security the searched pattern is: |
1288 | + # <distro>-security-mirror |
1289 | + |
1290 | + # if no mirrors are specified at all, or all lookups fail it will try |
1291 | + # to get them from the cloud datasource and if those neither provide one fall |
1292 | + # back to: |
1293 | + # primary: http://archive.ubuntu.com/ubuntu |
1294 | + # security: http://security.ubuntu.com/ubuntu |
1295 | + |
1296 | + # 1.4 sources_list |
1297 | + # |
1298 | + # Provide a custom template for rendering sources.list |
1299 | + # without one provided cloud-init uses builtin templates for |
1300 | + # ubuntu and debian. |
1301 | + # Within these sources.list templates you can use the following replacement |
1302 | + # variables (all have sane Ubuntu defaults, but mirrors can be overwritten |
1303 | + # as needed (see above)): |
1304 | + # => $RELEASE, $MIRROR, $PRIMARY, $SECURITY |
1305 | + sources_list: | # written by cloud-init custom template |
1306 | + deb $MIRROR $RELEASE main restricted |
1307 | + deb-src $MIRROR $RELEASE main restricted |
1308 | + deb $PRIMARY $RELEASE universe restricted |
1309 | + deb $SECURITY $RELEASE-security multiverse |
1310 | + |
1311 | + # 1.5 conf |
1312 | + # |
1313 | + # Any apt config string that will be made available to apt |
1314 | + # see the APT.CONF(5) man page for details what can be specified |
1315 | + conf: | # APT config |
1316 | + APT { |
1317 | + Get { |
1318 | + Assume-Yes "true"; |
1319 | + Fix-Broken "true"; |
1320 | + }; |
1321 | + }; |
1322 | + |
1323 | + # 1.6 (http_|ftp_|https_)proxy |
1324 | + # |
1325 | + # Proxies are the most common apt.conf option, so that for simplified use |
1326 | + # there is a shortcut for those. Those get automatically translated into the |
1327 | + # correct Acquire::*::Proxy statements. |
1328 | + # |
1329 | + # note: proxy actually being a short synonym to http_proxy |
1330 | + proxy: http://[[user][:pass]@]host[:port]/ |
1331 | + http_proxy: http://[[user][:pass]@]host[:port]/ |
1332 | + ftp_proxy: ftp://[[user][:pass]@]host[:port]/ |
1333 | + https_proxy: https://[[user][:pass]@]host[:port]/ |
1334 | + |
1335 | + # 1.7 add_apt_repo_match |
1336 | + # |
1337 | + # 'source' entries in apt-sources that match this python regex |
1338 | + # expression will be passed to add-apt-repository |
1339 | + # The following example is also the builtin default if nothing is specified |
1340 | + add_apt_repo_match: '^[\w-]+:\w' |
1341 | + |
1342 | + |
1343 | + ############################################################################## |
1344 | + # Section 2: source list entries |
1345 | + # |
1346 | + # This is a dictionary (unlike most block/net which are lists) |
1347 | + # |
1348 | + # The key of each source entry is the filename and will be prepended by |
1349 | + # /etc/apt/sources.list.d/ if it doesn't start with a '/'. |
1350 | + # If it doesn't end with .list it will be appended so that apt picks up it's |
1351 | + # configuration. |
1352 | + # |
1353 | + # Whenever there is no content to be written into such a file, the key is |
1354 | + # not used as filename - yet it can still be used as index for merging |
1355 | + # configuration. |
1356 | + # |
1357 | + # The values inside the entries consost of the following optional entries: |
1358 | + # 'source': a sources.list entry (some variable replacements apply) |
1359 | + # 'keyid': providing a key to import via shortid or fingerprint |
1360 | + # 'key': providing a raw PGP key |
1361 | + # 'keyserver': specify an alternate keyserver to pull keys from that |
1362 | + # were specified by keyid |
1363 | + |
1364 | + # This allows merging between multiple input files than a list like: |
1365 | + # cloud-config1 |
1366 | + # sources: |
1367 | + # s1: {'key': 'key1', 'source': 'source1'} |
1368 | + # cloud-config2 |
1369 | + # sources: |
1370 | + # s2: {'key': 'key2'} |
1371 | + # s1: {'keyserver': 'foo'} |
1372 | + # This would be merged to |
1373 | + # sources: |
1374 | + # s1: |
1375 | + # keyserver: foo |
1376 | + # key: key1 |
1377 | + # source: source1 |
1378 | + # s2: |
1379 | + # key: key2 |
1380 | + # |
1381 | + # The following examples number the subfeatures per sources entry to ease |
1382 | + # identification in discussions. |
1383 | + |
1384 | + |
1385 | + sources: |
1386 | + curtin-dev-ppa.list: |
1387 | + # 2.1 source |
1388 | + # |
1389 | + # Creates a file in /etc/apt/sources.list.d/ for the sources list entry |
1390 | + # based on the key: "/etc/apt/sources.list.d/curtin-dev-ppa.list" |
1391 | + source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main" |
1392 | + |
1393 | + # 2.2 keyid |
1394 | + # |
1395 | + # Importing a gpg key for a given key id. Used keyserver defaults to |
1396 | + # keyserver.ubuntu.com |
1397 | + keyid: F430BBA5 # GPG key ID published on a key server |
1398 | + |
1399 | + ignored1: |
1400 | + # 2.3 PPA shortcut |
1401 | + # |
1402 | + # Setup correct apt sources.list line and Auto-Import the signing key |
1403 | + # from LP |
1404 | + # |
1405 | + # See https://help.launchpad.net/Packaging/PPA for more information |
1406 | + # this requires 'add-apt-repository'. This will create a file in |
1407 | + # /etc/apt/sources.list.d automatically, therefore the key here is |
1408 | + # ignored as filename in those cases. |
1409 | + source: "ppa:curtin-dev/test-archive" # Quote the string |
1410 | + |
1411 | + my-repo2.list: |
1412 | + # 2.4 replacement variables |
1413 | + # |
1414 | + # sources can use $MIRROR, $PRIMARY, $SECURITY and $RELEASE replacement |
1415 | + # variables. |
1416 | + # They will be replaced with the default or specified mirrors and the |
1417 | + # running release. |
1418 | + # The entry below would be possibly turned into: |
1419 | + # source: deb http://archive.ubuntu.com/ubuntu xenial multiverse |
1420 | + source: deb $MIRROR $RELEASE multiverse |
1421 | + |
1422 | + my-repo3.list: |
1423 | + # this would have the same end effect as 'ppa:curtin-dev/test-archive' |
1424 | + source: "deb http://ppa.launchpad.net/curtin-dev/test-archive/ubuntu xenial main" |
1425 | + keyid: F430BBA5 # GPG key ID published on the key server |
1426 | + filename: curtin-dev-ppa.list |
1427 | + |
1428 | + ignored2: |
1429 | + # 2.5 key only |
1430 | + # |
1431 | + # this would only import the key without adding a ppa or other source spec |
1432 | + # since this doesn't generate a source.list file the filename key is ignored |
1433 | + keyid: F430BBA5 # GPG key ID published on a key server |
1434 | + |
1435 | + ignored3: |
1436 | + # 2.6 key id alternatives |
1437 | + # |
1438 | + # Keyid's can also be specified via their long fingerprints |
1439 | + keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77 |
1440 | + |
1441 | + ignored4: |
1442 | + # 2.7 alternative keyservers |
1443 | + # |
1444 | + # One can also specify alternative keyservers to fetch keys from. |
1445 | + keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77 |
1446 | + keyserver: pgp.mit.edu |
1447 | + |
1448 | + |
1449 | + my-repo4.list: |
1450 | + # 2.8 raw key |
1451 | + # |
1452 | + # The apt signing key can also be specified by providing a pgp public key |
1453 | + # block. Providing the PGP key this way is the most robust method for |
1454 | + # specifying a key, as it removes dependency on a remote key server. |
1455 | + # |
1456 | + # As with keyid's this can be specified with or without some actual source |
1457 | + # content. |
1458 | + key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK----- |
1459 | + -----BEGIN PGP PUBLIC KEY BLOCK----- |
1460 | + Version: SKS 1.0.10 |
1461 | + |
1462 | + mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6 |
1463 | + qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj |
1464 | + 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y |
1465 | + IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H |
1466 | + 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP |
1467 | + t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o |
1468 | + uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey |
1469 | + =Y2oI |
1470 | + -----END PGP PUBLIC KEY BLOCK----- |
1471 | diff --git a/doc/examples/cloud-config-chef-oneiric.txt b/doc/examples/cloud-config-chef-oneiric.txt |
1472 | index 2e5f4b1..75c9aee 100644 |
1473 | --- a/doc/examples/cloud-config-chef-oneiric.txt |
1474 | +++ b/doc/examples/cloud-config-chef-oneiric.txt |
1475 | @@ -11,39 +11,40 @@ |
1476 | # The default is to install from packages. |
1477 | |
1478 | # Key from http://apt.opscode.com/packages@opscode.com.gpg.key |
1479 | -apt_sources: |
1480 | - - source: "deb http://apt.opscode.com/ $RELEASE-0.10 main" |
1481 | - key: | |
1482 | - -----BEGIN PGP PUBLIC KEY BLOCK----- |
1483 | - Version: GnuPG v1.4.9 (GNU/Linux) |
1484 | - |
1485 | - mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu |
1486 | - twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99 |
1487 | - dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC |
1488 | - JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W |
1489 | - ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I |
1490 | - XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe |
1491 | - DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm |
1492 | - sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO |
1493 | - Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ |
1494 | - YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG |
1495 | - CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K |
1496 | - +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu5Ag0ESmkLtBAIAIO2SwlR |
1497 | - lU5i6gTOp42RHWW7/pmW78CwUqJnYqnXROrt3h9F9xrsGkH0Fh1FRtsnncgzIhvh |
1498 | - DLQnRHnkXm0ws0jV0PF74ttoUT6BLAUsFi2SPP1zYNJ9H9fhhK/pjijtAcQwdgxu |
1499 | - wwNJ5xCEscBZCjhSRXm0d30bK1o49Cow8ZIbHtnXVP41c9QWOzX/LaGZsKQZnaMx |
1500 | - EzDk8dyyctR2f03vRSVyTFGgdpUcpbr9eTFVgikCa6ODEBv+0BnCH6yGTXwBid9g |
1501 | - w0o1e/2DviKUWCC+AlAUOubLmOIGFBuI4UR+rux9affbHcLIOTiKQXv79lW3P7W8 |
1502 | - AAfniSQKfPWXrrcAAwUH/2XBqD4Uxhbs25HDUUiM/m6Gnlj6EsStg8n0nMggLhuN |
1503 | - QmPfoNByMPUqvA7sULyfr6xCYzbzRNxABHSpf85FzGQ29RF4xsA4vOOU8RDIYQ9X |
1504 | - Q8NqqR6pydprRFqWe47hsAN7BoYuhWqTtOLSBmnAnzTR5pURoqcquWYiiEavZixJ |
1505 | - 3ZRAq/HMGioJEtMFrvsZjGXuzef7f0ytfR1zYeLVWnL9Bd32CueBlI7dhYwkFe+V |
1506 | - Ep5jWOCj02C1wHcwt+uIRDJV6TdtbIiBYAdOMPk15+VBdweBXwMuYXr76+A7VeDL |
1507 | - zIhi7tKFo6WiwjKZq0dzctsJJjtIfr4K4vbiD9Ojg1iISQQYEQIACQUCSmkLtAIb |
1508 | - DAAKCRApQKupg++CauISAJ9CxYPOKhOxalBnVTLeNUkAHGg2gACeIsbobtaD4ZHG |
1509 | - 0GLl8EkfA8uhluM= |
1510 | - =zKAm |
1511 | - -----END PGP PUBLIC KEY BLOCK----- |
1512 | +apt: |
1513 | + sources: |
1514 | + - source: "deb http://apt.opscode.com/ $RELEASE-0.10 main" |
1515 | + key: | |
1516 | + -----BEGIN PGP PUBLIC KEY BLOCK----- |
1517 | + Version: GnuPG v1.4.9 (GNU/Linux) |
1518 | + |
1519 | + mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu |
1520 | + twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99 |
1521 | + dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC |
1522 | + JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W |
1523 | + ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I |
1524 | + XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe |
1525 | + DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm |
1526 | + sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO |
1527 | + Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ |
1528 | + YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG |
1529 | + CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K |
1530 | + +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu5Ag0ESmkLtBAIAIO2SwlR |
1531 | + lU5i6gTOp42RHWW7/pmW78CwUqJnYqnXROrt3h9F9xrsGkH0Fh1FRtsnncgzIhvh |
1532 | + DLQnRHnkXm0ws0jV0PF74ttoUT6BLAUsFi2SPP1zYNJ9H9fhhK/pjijtAcQwdgxu |
1533 | + wwNJ5xCEscBZCjhSRXm0d30bK1o49Cow8ZIbHtnXVP41c9QWOzX/LaGZsKQZnaMx |
1534 | + EzDk8dyyctR2f03vRSVyTFGgdpUcpbr9eTFVgikCa6ODEBv+0BnCH6yGTXwBid9g |
1535 | + w0o1e/2DviKUWCC+AlAUOubLmOIGFBuI4UR+rux9affbHcLIOTiKQXv79lW3P7W8 |
1536 | + AAfniSQKfPWXrrcAAwUH/2XBqD4Uxhbs25HDUUiM/m6Gnlj6EsStg8n0nMggLhuN |
1537 | + QmPfoNByMPUqvA7sULyfr6xCYzbzRNxABHSpf85FzGQ29RF4xsA4vOOU8RDIYQ9X |
1538 | + Q8NqqR6pydprRFqWe47hsAN7BoYuhWqTtOLSBmnAnzTR5pURoqcquWYiiEavZixJ |
1539 | + 3ZRAq/HMGioJEtMFrvsZjGXuzef7f0ytfR1zYeLVWnL9Bd32CueBlI7dhYwkFe+V |
1540 | + Ep5jWOCj02C1wHcwt+uIRDJV6TdtbIiBYAdOMPk15+VBdweBXwMuYXr76+A7VeDL |
1541 | + zIhi7tKFo6WiwjKZq0dzctsJJjtIfr4K4vbiD9Ojg1iISQQYEQIACQUCSmkLtAIb |
1542 | + DAAKCRApQKupg++CauISAJ9CxYPOKhOxalBnVTLeNUkAHGg2gACeIsbobtaD4ZHG |
1543 | + 0GLl8EkfA8uhluM= |
1544 | + =zKAm |
1545 | + -----END PGP PUBLIC KEY BLOCK----- |
1546 | |
1547 | chef: |
1548 | |
1549 | diff --git a/doc/examples/cloud-config-chef.txt b/doc/examples/cloud-config-chef.txt |
1550 | index b886cba..75d78a1 100644 |
1551 | --- a/doc/examples/cloud-config-chef.txt |
1552 | +++ b/doc/examples/cloud-config-chef.txt |
1553 | @@ -11,39 +11,40 @@ |
1554 | # The default is to install from packages. |
1555 | |
1556 | # Key from http://apt.opscode.com/packages@opscode.com.gpg.key |
1557 | -apt_sources: |
1558 | - - source: "deb http://apt.opscode.com/ $RELEASE-0.10 main" |
1559 | - key: | |
1560 | - -----BEGIN PGP PUBLIC KEY BLOCK----- |
1561 | - Version: GnuPG v1.4.9 (GNU/Linux) |
1562 | - |
1563 | - mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu |
1564 | - twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99 |
1565 | - dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC |
1566 | - JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W |
1567 | - ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I |
1568 | - XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe |
1569 | - DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm |
1570 | - sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO |
1571 | - Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ |
1572 | - YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG |
1573 | - CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K |
1574 | - +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu5Ag0ESmkLtBAIAIO2SwlR |
1575 | - lU5i6gTOp42RHWW7/pmW78CwUqJnYqnXROrt3h9F9xrsGkH0Fh1FRtsnncgzIhvh |
1576 | - DLQnRHnkXm0ws0jV0PF74ttoUT6BLAUsFi2SPP1zYNJ9H9fhhK/pjijtAcQwdgxu |
1577 | - wwNJ5xCEscBZCjhSRXm0d30bK1o49Cow8ZIbHtnXVP41c9QWOzX/LaGZsKQZnaMx |
1578 | - EzDk8dyyctR2f03vRSVyTFGgdpUcpbr9eTFVgikCa6ODEBv+0BnCH6yGTXwBid9g |
1579 | - w0o1e/2DviKUWCC+AlAUOubLmOIGFBuI4UR+rux9affbHcLIOTiKQXv79lW3P7W8 |
1580 | - AAfniSQKfPWXrrcAAwUH/2XBqD4Uxhbs25HDUUiM/m6Gnlj6EsStg8n0nMggLhuN |
1581 | - QmPfoNByMPUqvA7sULyfr6xCYzbzRNxABHSpf85FzGQ29RF4xsA4vOOU8RDIYQ9X |
1582 | - Q8NqqR6pydprRFqWe47hsAN7BoYuhWqTtOLSBmnAnzTR5pURoqcquWYiiEavZixJ |
1583 | - 3ZRAq/HMGioJEtMFrvsZjGXuzef7f0ytfR1zYeLVWnL9Bd32CueBlI7dhYwkFe+V |
1584 | - Ep5jWOCj02C1wHcwt+uIRDJV6TdtbIiBYAdOMPk15+VBdweBXwMuYXr76+A7VeDL |
1585 | - zIhi7tKFo6WiwjKZq0dzctsJJjtIfr4K4vbiD9Ojg1iISQQYEQIACQUCSmkLtAIb |
1586 | - DAAKCRApQKupg++CauISAJ9CxYPOKhOxalBnVTLeNUkAHGg2gACeIsbobtaD4ZHG |
1587 | - 0GLl8EkfA8uhluM= |
1588 | - =zKAm |
1589 | - -----END PGP PUBLIC KEY BLOCK----- |
1590 | +apt: |
1591 | + sources: |
1592 | + - source: "deb http://apt.opscode.com/ $RELEASE-0.10 main" |
1593 | + key: | |
1594 | + -----BEGIN PGP PUBLIC KEY BLOCK----- |
1595 | + Version: GnuPG v1.4.9 (GNU/Linux) |
1596 | + |
1597 | + mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu |
1598 | + twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99 |
1599 | + dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC |
1600 | + JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W |
1601 | + ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I |
1602 | + XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe |
1603 | + DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm |
1604 | + sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO |
1605 | + Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ |
1606 | + YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG |
1607 | + CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K |
1608 | + +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu5Ag0ESmkLtBAIAIO2SwlR |
1609 | + lU5i6gTOp42RHWW7/pmW78CwUqJnYqnXROrt3h9F9xrsGkH0Fh1FRtsnncgzIhvh |
1610 | + DLQnRHnkXm0ws0jV0PF74ttoUT6BLAUsFi2SPP1zYNJ9H9fhhK/pjijtAcQwdgxu |
1611 | + wwNJ5xCEscBZCjhSRXm0d30bK1o49Cow8ZIbHtnXVP41c9QWOzX/LaGZsKQZnaMx |
1612 | + EzDk8dyyctR2f03vRSVyTFGgdpUcpbr9eTFVgikCa6ODEBv+0BnCH6yGTXwBid9g |
1613 | + w0o1e/2DviKUWCC+AlAUOubLmOIGFBuI4UR+rux9affbHcLIOTiKQXv79lW3P7W8 |
1614 | + AAfniSQKfPWXrrcAAwUH/2XBqD4Uxhbs25HDUUiM/m6Gnlj6EsStg8n0nMggLhuN |
1615 | + QmPfoNByMPUqvA7sULyfr6xCYzbzRNxABHSpf85FzGQ29RF4xsA4vOOU8RDIYQ9X |
1616 | + Q8NqqR6pydprRFqWe47hsAN7BoYuhWqTtOLSBmnAnzTR5pURoqcquWYiiEavZixJ |
1617 | + 3ZRAq/HMGioJEtMFrvsZjGXuzef7f0ytfR1zYeLVWnL9Bd32CueBlI7dhYwkFe+V |
1618 | + Ep5jWOCj02C1wHcwt+uIRDJV6TdtbIiBYAdOMPk15+VBdweBXwMuYXr76+A7VeDL |
1619 | + zIhi7tKFo6WiwjKZq0dzctsJJjtIfr4K4vbiD9Ojg1iISQQYEQIACQUCSmkLtAIb |
1620 | + DAAKCRApQKupg++CauISAJ9CxYPOKhOxalBnVTLeNUkAHGg2gACeIsbobtaD4ZHG |
1621 | + 0GLl8EkfA8uhluM= |
1622 | + =zKAm |
1623 | + -----END PGP PUBLIC KEY BLOCK----- |
1624 | |
1625 | chef: |
1626 | |
1627 | diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt |
1628 | index 3cc9c05..190029e 100644 |
1629 | --- a/doc/examples/cloud-config.txt |
1630 | +++ b/doc/examples/cloud-config.txt |
1631 | @@ -18,256 +18,7 @@ package_upgrade: true |
1632 | # Aliases: apt_reboot_if_required |
1633 | package_reboot_if_required: true |
1634 | |
1635 | -# Add apt repositories |
1636 | -# |
1637 | -# Default: auto select based on cloud metadata |
1638 | -# in ec2, the default is <region>.archive.ubuntu.com |
1639 | -# apt_mirror: |
1640 | -# use the provided mirror |
1641 | -# apt_mirror_search: |
1642 | -# search the list for the first mirror. |
1643 | -# this is currently very limited, only verifying that |
1644 | -# the mirror is dns resolvable or an IP address |
1645 | -# |
1646 | -# if neither apt_mirror nor apt_mirror search is set (the default) |
1647 | -# then use the mirror provided by the DataSource found. |
1648 | -# In EC2, that means using <region>.ec2.archive.ubuntu.com |
1649 | -# |
1650 | -# if no mirror is provided by the DataSource, and 'apt_mirror_search_dns' is |
1651 | -# true, then search for dns names '<distro>-mirror' in each of |
1652 | -# - fqdn of this host per cloud metadata |
1653 | -# - localdomain |
1654 | -# - no domain (which would search domains listed in /etc/resolv.conf) |
1655 | -# If there is a dns entry for <distro>-mirror, then it is assumed that there |
1656 | -# is a distro mirror at http://<distro>-mirror.<domain>/<distro> |
1657 | -# |
1658 | -# That gives the cloud provider the opportunity to set mirrors of a distro |
1659 | -# up and expose them only by creating dns entries. |
1660 | -# |
1661 | -# if none of that is found, then the default distro mirror is used |
1662 | -apt_mirror: http://us.archive.ubuntu.com/ubuntu/ |
1663 | -apt_mirror_search: |
1664 | - - http://local-mirror.mydomain |
1665 | - - http://archive.ubuntu.com |
1666 | - |
1667 | -apt_mirror_search_dns: False |
1668 | - |
1669 | -# apt_proxy (configure Acquire::HTTP::Proxy) |
1670 | -# 'apt_http_proxy' is an alias for 'apt_proxy'. |
1671 | -# Also, available are 'apt_ftp_proxy' and 'apt_https_proxy'. |
1672 | -# These affect Acquire::FTP::Proxy and Acquire::HTTPS::Proxy respectively |
1673 | -apt_proxy: http://my.apt.proxy:3128 |
1674 | - |
1675 | -# apt_pipelining (configure Acquire::http::Pipeline-Depth) |
1676 | -# Default: disables HTTP pipelining. Certain web servers, such |
1677 | -# as S3 do not pipeline properly (LP: #948461). |
1678 | -# Valid options: |
1679 | -# False/default: Disables pipelining for APT |
1680 | -# None/Unchanged: Use OS default |
1681 | -# Number: Set pipelining to some number (not recommended) |
1682 | -apt_pipelining: False |
1683 | - |
1684 | -# Preserve existing /etc/apt/sources.list |
1685 | -# Default: overwrite sources_list with mirror. If this is true |
1686 | -# then apt_mirror above will have no effect |
1687 | -apt_preserve_sources_list: true |
1688 | - |
1689 | -# Provide a custom template for rendering sources.list |
1690 | -# Default: a default template for Ubuntu/Debain will be used as packaged in |
1691 | -# Ubuntu: /etc/cloud/templates/sources.list.ubuntu.tmpl |
1692 | -# Debian: /etc/cloud/templates/sources.list.debian.tmpl |
1693 | -# Others: n/a |
1694 | -# This will follow the normal mirror/codename replacement rules before |
1695 | -# being written to disk. |
1696 | -apt_custom_sources_list: | |
1697 | - ## template:jinja |
1698 | - ## Note, this file is written by cloud-init on first boot of an instance |
1699 | - ## modifications made here will not survive a re-bundle. |
1700 | - ## if you wish to make changes you can: |
1701 | - ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
1702 | - ## or do the same in user-data |
1703 | - ## b.) add sources in /etc/apt/sources.list.d |
1704 | - ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
1705 | - deb {{mirror}} {{codename}} main restricted |
1706 | - deb-src {{mirror}} {{codename}} main restricted |
1707 | - |
1708 | - # could drop some of the usually used entries |
1709 | - |
1710 | - # could refer to other mirrors |
1711 | - deb http://ddebs.ubuntu.com {{codename}} main restricted universe multiverse |
1712 | - deb http://ddebs.ubuntu.com {{codename}}-updates main restricted universe multiverse |
1713 | - deb http://ddebs.ubuntu.com {{codename}}-proposed main restricted universe multiverse |
1714 | - |
1715 | - # or even more uncommon examples like local or NFS mounted repos, |
1716 | - # eventually whatever is compatible with sources.list syntax |
1717 | - deb file:/home/apt/debian unstable main contrib non-free |
1718 | - |
1719 | -# 'source' entries in apt-sources that match this python regex |
1720 | -# expression will be passed to add-apt-repository |
1721 | -add_apt_repo_match: '^[\w-]+:\w' |
1722 | - |
1723 | -# 'apt_sources' is a dictionary |
1724 | -# The key is the filename and will be prepended by /etc/apt/sources.list.d/ if |
1725 | -# it doesn't start with a '/'. |
1726 | -# There are certain cases - where no content is written into a source.list file |
1727 | -# where the filename will be ignored - yet it can still be used as index for |
1728 | -# merging. |
1729 | -# The value it maps to is a dictionary with the following optional entries: |
1730 | -# source: a sources.list entry (some variable replacements apply) |
1731 | -# keyid: providing a key to import via shortid or fingerprint |
1732 | -# key: providing a raw PGP key |
1733 | -# keyserver: keyserver to fetch keys from, default is keyserver.ubuntu.com |
1734 | -# filename: for compatibility with the older format (now the key to this |
1735 | -# dictionary is the filename). If specified this overwrites the |
1736 | -# filename given as key. |
1737 | - |
1738 | -# the new "filename: {specification-dictionary}, filename2: ..." format allows |
1739 | -# better merging between multiple input files than a list like: |
1740 | -# cloud-config1 |
1741 | -# sources: |
1742 | -# s1: {'key': 'key1', 'source': 'source1'} |
1743 | -# cloud-config2 |
1744 | -# sources: |
1745 | -# s2: {'key': 'key2'} |
1746 | -# s1: {filename: 'foo'} |
1747 | -# this would be merged to |
1748 | -#sources: |
1749 | -# s1: |
1750 | -# filename: foo |
1751 | -# key: key1 |
1752 | -# source: source1 |
1753 | -# s2: |
1754 | -# key: key2 |
1755 | -# Be aware that this style of merging is not the default (for backward |
1756 | -# compatibility reasons). You should specify the following merge_how to get |
1757 | -# this more complete and modern merging behaviour: |
1758 | -# merge_how: "list()+dict()+str()" |
1759 | -# This would then also be equivalent to the config merging used in curtin |
1760 | -# (https://launchpad.net/curtin). |
1761 | - |
1762 | -# for more details see below in the various examples |
1763 | - |
1764 | -apt_sources: |
1765 | - byobu-ppa.list: |
1766 | - source: "deb http://ppa.launchpad.net/byobu/ppa/ubuntu karmic main" |
1767 | - keyid: F430BBA5 # GPG key ID published on a key server |
1768 | - # adding a source.list line, importing a gpg key for a given key id and |
1769 | - # storing it in the file /etc/apt/sources.list.d/byobu-ppa.list |
1770 | - |
1771 | - # PPA shortcut: |
1772 | - # * Setup correct apt sources.list line |
1773 | - # * Import the signing key from LP |
1774 | - # |
1775 | - # See https://help.launchpad.net/Packaging/PPA for more information |
1776 | - # this requires 'add-apt-repository' |
1777 | - # due to that the filename key is ignored in this case |
1778 | - ignored1: |
1779 | - source: "ppa:smoser/ppa" # Quote the string |
1780 | - |
1781 | - # Custom apt repository: |
1782 | - # * all that is required is 'source' |
1783 | - # * Creates a file in /etc/apt/sources.list.d/ for the sources list entry |
1784 | - # * [optional] Import the apt signing key from the keyserver |
1785 | - # * Defaults: |
1786 | - # + keyserver: keyserver.ubuntu.com |
1787 | - # |
1788 | - # See sources.list man page for more information about the format |
1789 | - my-repo.list: |
1790 | - source: deb http://archive.ubuntu.com/ubuntu karmic-backports main universe multiverse restricted |
1791 | - |
1792 | - # sources can use $MIRROR and $RELEASE and they will be replaced |
1793 | - # with the local mirror for this cloud, and the running release |
1794 | - # the entry below would be possibly turned into: |
1795 | - # source: deb http://us-east-1.ec2.archive.ubuntu.com/ubuntu natty multiverse |
1796 | - my-repo.list: |
1797 | - source: deb $MIRROR $RELEASE multiverse |
1798 | - |
1799 | - # this would have the same end effect as 'ppa:byobu/ppa' |
1800 | - my-repo.list: |
1801 | - source: "deb http://ppa.launchpad.net/byobu/ppa/ubuntu karmic main" |
1802 | - keyid: F430BBA5 # GPG key ID published on a key server |
1803 | - filename: byobu-ppa.list |
1804 | - |
1805 | - # this would only import the key without adding a ppa or other source spec |
1806 | - # since this doesn't generate a source.list file the filename key is ignored |
1807 | - ignored2: |
1808 | - keyid: F430BBA5 # GPG key ID published on a key server |
1809 | - |
1810 | - # In general keyid's can also be specified via their long fingerprints |
1811 | - # since this doesn't generate a source.list file the filename key is ignored |
1812 | - ignored3: |
1813 | - keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77 |
1814 | - |
1815 | - # Custom apt repository: |
1816 | - # * The apt signing key can also be specified |
1817 | - # by providing a pgp public key block |
1818 | - # * Providing the PGP key here is the most robust method for |
1819 | - # specifying a key, as it removes dependency on a remote key server |
1820 | - my-repo.list: |
1821 | - source: deb http://ppa.launchpad.net/alestic/ppa/ubuntu karmic main |
1822 | - key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK----- |
1823 | - -----BEGIN PGP PUBLIC KEY BLOCK----- |
1824 | - Version: SKS 1.0.10 |
1825 | - |
1826 | - mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6 |
1827 | - qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj |
1828 | - 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y |
1829 | - IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H |
1830 | - 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP |
1831 | - t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o |
1832 | - uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey |
1833 | - =Y2oI |
1834 | - -----END PGP PUBLIC KEY BLOCK----- |
1835 | - |
1836 | - # Custom gpg key: |
1837 | - # * As with keyid, a key may also be specified without a related source. |
1838 | - # * all other facts mentioned above still apply |
1839 | - # since this doesn't generate a source.list file the filename key is ignored |
1840 | - ignored4: |
1841 | - key: | # The value needs to start with -----BEGIN PGP PUBLIC KEY BLOCK----- |
1842 | - -----BEGIN PGP PUBLIC KEY BLOCK----- |
1843 | - Version: SKS 1.0.10 |
1844 | - |
1845 | - mI0ESpA3UQEEALdZKVIMq0j6qWAXAyxSlF63SvPVIgxHPb9Nk0DZUixn+akqytxG4zKCONz6 |
1846 | - qLjoBBfHnynyVLfT4ihg9an1PqxRnTO+JKQxl8NgKGz6Pon569GtAOdWNKw15XKinJTDLjnj |
1847 | - 9y96ljJqRcpV9t/WsIcdJPcKFR5voHTEoABE2aEXABEBAAG0GUxhdW5jaHBhZCBQUEEgZm9y |
1848 | - IEFsZXN0aWOItgQTAQIAIAUCSpA3UQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEA7H |
1849 | - 5Qi+CcVxWZ8D/1MyYvfj3FJPZUm2Yo1zZsQ657vHI9+pPouqflWOayRR9jbiyUFIn0VdQBrP |
1850 | - t0FwvnOFArUovUWoKAEdqR8hPy3M3APUZjl5K4cMZR/xaMQeQRZ5CHpS4DBKURKAHC0ltS5o |
1851 | - uBJKQOZm5iltJp15cgyIkBkGe8Mx18VFyVglAZey |
1852 | - =Y2oI |
1853 | - -----END PGP PUBLIC KEY BLOCK----- |
1854 | - |
1855 | - |
1856 | -## apt config via system_info: |
1857 | -# under the 'system_info', you can further customize cloud-init's interaction |
1858 | -# with apt. |
1859 | -# system_info: |
1860 | -# apt_get_command: [command, argument, argument] |
1861 | -# apt_get_upgrade_subcommand: dist-upgrade |
1862 | -# |
1863 | -# apt_get_command: |
1864 | -# To specify a different 'apt-get' command, set 'apt_get_command'. |
1865 | -# This must be a list, and the subcommand (update, upgrade) is appended to it. |
1866 | -# default is: |
1867 | -# ['apt-get', '--option=Dpkg::Options::=--force-confold', |
1868 | -# '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet'] |
1869 | -# |
1870 | -# apt_get_upgrade_subcommand: |
1871 | -# Specify a different subcommand for 'upgrade. The default is 'dist-upgrade'. |
1872 | -# This is the subcommand that is invoked if package_upgrade is set to true above. |
1873 | -# |
1874 | -# apt_get_wrapper: |
1875 | -# command: eatmydata |
1876 | -# enabled: [True, False, "auto"] |
1877 | -# |
1878 | - |
1879 | -# Install additional packages on first boot |
1880 | -# |
1881 | -# Default: none |
1882 | -# |
1883 | -# if packages are specified, this apt_update will be set to true |
1884 | -# |
1885 | +# For 'apt' specific config, see cloud-config-apt.txt |
1886 | packages: |
1887 | - pwgen |
1888 | - pastebinit |
1889 | diff --git a/tests/configs/sample1.yaml b/tests/configs/sample1.yaml |
1890 | index 6231f29..ae935cc 100644 |
1891 | --- a/tests/configs/sample1.yaml |
1892 | +++ b/tests/configs/sample1.yaml |
1893 | @@ -3,9 +3,6 @@ |
1894 | #apt_upgrade: true |
1895 | packages: [ bzr, pastebinit, ubuntu-dev-tools, ccache, bzr-builddeb, vim-nox, git-core, lftp ] |
1896 | |
1897 | -#apt_sources: |
1898 | -# - source: ppa:smoser/ppa |
1899 | - |
1900 | #disable_root: False |
1901 | |
1902 | # mounts: |
1903 | diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py |
1904 | index 96fa081..24ad115 100644 |
1905 | --- a/tests/unittests/test_distros/test_generic.py |
1906 | +++ b/tests/unittests/test_distros/test_generic.py |
1907 | @@ -226,8 +226,5 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase): |
1908 | os.symlink('/', '/run/systemd/system') |
1909 | self.assertFalse(d.uses_systemd()) |
1910 | |
1911 | -# def _get_package_mirror_info(mirror_info, availability_zone=None, |
1912 | -# mirror_filter=util.search_for_mirror): |
1913 | - |
1914 | |
1915 | # vi: ts=4 expandtab |
1916 | diff --git a/tests/unittests/test_handler/test_handler_apt_conf_v1.py b/tests/unittests/test_handler/test_handler_apt_conf_v1.py |
1917 | new file mode 100644 |
1918 | index 0000000..95fd1da |
1919 | --- /dev/null |
1920 | +++ b/tests/unittests/test_handler/test_handler_apt_conf_v1.py |
1921 | @@ -0,0 +1,109 @@ |
1922 | +from cloudinit.config import cc_apt_configure |
1923 | +from cloudinit import util |
1924 | + |
1925 | +from ..helpers import TestCase |
1926 | + |
1927 | +import os |
1928 | +import re |
1929 | +import shutil |
1930 | +import tempfile |
1931 | + |
1932 | + |
1933 | +def load_tfile_or_url(*args, **kwargs): |
1934 | + return(util.decode_binary(util.read_file_or_url(*args, **kwargs).contents)) |
1935 | + |
1936 | + |
1937 | +class TestAptProxyConfig(TestCase): |
1938 | + def setUp(self): |
1939 | + super(TestAptProxyConfig, self).setUp() |
1940 | + self.tmp = tempfile.mkdtemp() |
1941 | + self.addCleanup(shutil.rmtree, self.tmp) |
1942 | + self.pfile = os.path.join(self.tmp, "proxy.cfg") |
1943 | + self.cfile = os.path.join(self.tmp, "config.cfg") |
1944 | + |
1945 | + def _search_apt_config(self, contents, ptype, value): |
1946 | + return re.search( |
1947 | + r"acquire::%s::proxy\s+[\"']%s[\"'];\n" % (ptype, value), |
1948 | + contents, flags=re.IGNORECASE) |
1949 | + |
1950 | + def test_apt_proxy_written(self): |
1951 | + cfg = {'proxy': 'myproxy'} |
1952 | + cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
1953 | + |
1954 | + self.assertTrue(os.path.isfile(self.pfile)) |
1955 | + self.assertFalse(os.path.isfile(self.cfile)) |
1956 | + |
1957 | + contents = load_tfile_or_url(self.pfile) |
1958 | + self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) |
1959 | + |
1960 | + def test_apt_http_proxy_written(self): |
1961 | + cfg = {'http_proxy': 'myproxy'} |
1962 | + cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
1963 | + |
1964 | + self.assertTrue(os.path.isfile(self.pfile)) |
1965 | + self.assertFalse(os.path.isfile(self.cfile)) |
1966 | + |
1967 | + contents = load_tfile_or_url(self.pfile) |
1968 | + self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) |
1969 | + |
1970 | + def test_apt_all_proxy_written(self): |
1971 | + cfg = {'http_proxy': 'myproxy_http_proxy', |
1972 | + 'https_proxy': 'myproxy_https_proxy', |
1973 | + 'ftp_proxy': 'myproxy_ftp_proxy'} |
1974 | + |
1975 | + values = {'http': cfg['http_proxy'], |
1976 | + 'https': cfg['https_proxy'], |
1977 | + 'ftp': cfg['ftp_proxy'], |
1978 | + } |
1979 | + |
1980 | + cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
1981 | + |
1982 | + self.assertTrue(os.path.isfile(self.pfile)) |
1983 | + self.assertFalse(os.path.isfile(self.cfile)) |
1984 | + |
1985 | + contents = load_tfile_or_url(self.pfile) |
1986 | + |
1987 | + for ptype, pval in values.items(): |
1988 | + self.assertTrue(self._search_apt_config(contents, ptype, pval)) |
1989 | + |
1990 | + def test_proxy_deleted(self): |
1991 | + util.write_file(self.cfile, "content doesnt matter") |
1992 | + cc_apt_configure.apply_apt_config({}, self.pfile, self.cfile) |
1993 | + self.assertFalse(os.path.isfile(self.pfile)) |
1994 | + self.assertFalse(os.path.isfile(self.cfile)) |
1995 | + |
1996 | + def test_proxy_replaced(self): |
1997 | + util.write_file(self.cfile, "content doesnt matter") |
1998 | + cc_apt_configure.apply_apt_config({'proxy': "foo"}, |
1999 | + self.pfile, self.cfile) |
2000 | + self.assertTrue(os.path.isfile(self.pfile)) |
2001 | + contents = load_tfile_or_url(self.pfile) |
2002 | + self.assertTrue(self._search_apt_config(contents, "http", "foo")) |
2003 | + |
2004 | + def test_config_written(self): |
2005 | + payload = 'this is my apt config' |
2006 | + cfg = {'conf': payload} |
2007 | + |
2008 | + cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
2009 | + |
2010 | + self.assertTrue(os.path.isfile(self.cfile)) |
2011 | + self.assertFalse(os.path.isfile(self.pfile)) |
2012 | + |
2013 | + self.assertEqual(load_tfile_or_url(self.cfile), payload) |
2014 | + |
2015 | + def test_config_replaced(self): |
2016 | + util.write_file(self.pfile, "content doesnt matter") |
2017 | + cc_apt_configure.apply_apt_config({'conf': "foo"}, |
2018 | + self.pfile, self.cfile) |
2019 | + self.assertTrue(os.path.isfile(self.cfile)) |
2020 | + self.assertEqual(load_tfile_or_url(self.cfile), "foo") |
2021 | + |
2022 | + def test_config_deleted(self): |
2023 | + # if no 'conf' is provided, delete any previously written file |
2024 | + util.write_file(self.pfile, "content doesnt matter") |
2025 | + cc_apt_configure.apply_apt_config({}, self.pfile, self.cfile) |
2026 | + self.assertFalse(os.path.isfile(self.pfile)) |
2027 | + self.assertFalse(os.path.isfile(self.cfile)) |
2028 | + |
2029 | + |
2030 | +# vi: ts=4 expandtab |
2031 | diff --git a/tests/unittests/test_handler/test_handler_apt_configure.py b/tests/unittests/test_handler/test_handler_apt_configure.py |
2032 | deleted file mode 100644 |
2033 | index d1dca2c..0000000 |
2034 | --- a/tests/unittests/test_handler/test_handler_apt_configure.py |
2035 | +++ /dev/null |
2036 | @@ -1,109 +0,0 @@ |
2037 | -from cloudinit.config import cc_apt_configure |
2038 | -from cloudinit import util |
2039 | - |
2040 | -from ..helpers import TestCase |
2041 | - |
2042 | -import os |
2043 | -import re |
2044 | -import shutil |
2045 | -import tempfile |
2046 | - |
2047 | - |
2048 | -def load_tfile_or_url(*args, **kwargs): |
2049 | - return(util.decode_binary(util.read_file_or_url(*args, **kwargs).contents)) |
2050 | - |
2051 | - |
2052 | -class TestAptProxyConfig(TestCase): |
2053 | - def setUp(self): |
2054 | - super(TestAptProxyConfig, self).setUp() |
2055 | - self.tmp = tempfile.mkdtemp() |
2056 | - self.addCleanup(shutil.rmtree, self.tmp) |
2057 | - self.pfile = os.path.join(self.tmp, "proxy.cfg") |
2058 | - self.cfile = os.path.join(self.tmp, "config.cfg") |
2059 | - |
2060 | - def _search_apt_config(self, contents, ptype, value): |
2061 | - return re.search( |
2062 | - r"acquire::%s::proxy\s+[\"']%s[\"'];\n" % (ptype, value), |
2063 | - contents, flags=re.IGNORECASE) |
2064 | - |
2065 | - def test_apt_proxy_written(self): |
2066 | - cfg = {'apt_proxy': 'myproxy'} |
2067 | - cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
2068 | - |
2069 | - self.assertTrue(os.path.isfile(self.pfile)) |
2070 | - self.assertFalse(os.path.isfile(self.cfile)) |
2071 | - |
2072 | - contents = load_tfile_or_url(self.pfile) |
2073 | - self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) |
2074 | - |
2075 | - def test_apt_http_proxy_written(self): |
2076 | - cfg = {'apt_http_proxy': 'myproxy'} |
2077 | - cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
2078 | - |
2079 | - self.assertTrue(os.path.isfile(self.pfile)) |
2080 | - self.assertFalse(os.path.isfile(self.cfile)) |
2081 | - |
2082 | - contents = load_tfile_or_url(self.pfile) |
2083 | - self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) |
2084 | - |
2085 | - def test_apt_all_proxy_written(self): |
2086 | - cfg = {'apt_http_proxy': 'myproxy_http_proxy', |
2087 | - 'apt_https_proxy': 'myproxy_https_proxy', |
2088 | - 'apt_ftp_proxy': 'myproxy_ftp_proxy'} |
2089 | - |
2090 | - values = {'http': cfg['apt_http_proxy'], |
2091 | - 'https': cfg['apt_https_proxy'], |
2092 | - 'ftp': cfg['apt_ftp_proxy'], |
2093 | - } |
2094 | - |
2095 | - cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
2096 | - |
2097 | - self.assertTrue(os.path.isfile(self.pfile)) |
2098 | - self.assertFalse(os.path.isfile(self.cfile)) |
2099 | - |
2100 | - contents = load_tfile_or_url(self.pfile) |
2101 | - |
2102 | - for ptype, pval in values.items(): |
2103 | - self.assertTrue(self._search_apt_config(contents, ptype, pval)) |
2104 | - |
2105 | - def test_proxy_deleted(self): |
2106 | - util.write_file(self.cfile, "content doesnt matter") |
2107 | - cc_apt_configure.apply_apt_config({}, self.pfile, self.cfile) |
2108 | - self.assertFalse(os.path.isfile(self.pfile)) |
2109 | - self.assertFalse(os.path.isfile(self.cfile)) |
2110 | - |
2111 | - def test_proxy_replaced(self): |
2112 | - util.write_file(self.cfile, "content doesnt matter") |
2113 | - cc_apt_configure.apply_apt_config({'apt_proxy': "foo"}, |
2114 | - self.pfile, self.cfile) |
2115 | - self.assertTrue(os.path.isfile(self.pfile)) |
2116 | - contents = load_tfile_or_url(self.pfile) |
2117 | - self.assertTrue(self._search_apt_config(contents, "http", "foo")) |
2118 | - |
2119 | - def test_config_written(self): |
2120 | - payload = 'this is my apt config' |
2121 | - cfg = {'apt_config': payload} |
2122 | - |
2123 | - cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) |
2124 | - |
2125 | - self.assertTrue(os.path.isfile(self.cfile)) |
2126 | - self.assertFalse(os.path.isfile(self.pfile)) |
2127 | - |
2128 | - self.assertEqual(load_tfile_or_url(self.cfile), payload) |
2129 | - |
2130 | - def test_config_replaced(self): |
2131 | - util.write_file(self.pfile, "content doesnt matter") |
2132 | - cc_apt_configure.apply_apt_config({'apt_config': "foo"}, |
2133 | - self.pfile, self.cfile) |
2134 | - self.assertTrue(os.path.isfile(self.cfile)) |
2135 | - self.assertEqual(load_tfile_or_url(self.cfile), "foo") |
2136 | - |
2137 | - def test_config_deleted(self): |
2138 | - # if no 'apt_config' is provided, delete any previously written file |
2139 | - util.write_file(self.pfile, "content doesnt matter") |
2140 | - cc_apt_configure.apply_apt_config({}, self.pfile, self.cfile) |
2141 | - self.assertFalse(os.path.isfile(self.pfile)) |
2142 | - self.assertFalse(os.path.isfile(self.cfile)) |
2143 | - |
2144 | - |
2145 | -# vi: ts=4 expandtab |
2146 | diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py |
2147 | deleted file mode 100644 |
2148 | index acde086..0000000 |
2149 | --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list.py |
2150 | +++ /dev/null |
2151 | @@ -1,180 +0,0 @@ |
2152 | -""" test_handler_apt_configure_sources_list |
2153 | -Test templating of sources list |
2154 | -""" |
2155 | -import logging |
2156 | -import os |
2157 | -import shutil |
2158 | -import tempfile |
2159 | - |
2160 | -try: |
2161 | - from unittest import mock |
2162 | -except ImportError: |
2163 | - import mock |
2164 | - |
2165 | -from cloudinit import cloud |
2166 | -from cloudinit import distros |
2167 | -from cloudinit import helpers |
2168 | -from cloudinit import templater |
2169 | -from cloudinit import util |
2170 | - |
2171 | -from cloudinit.config import cc_apt_configure |
2172 | -from cloudinit.sources import DataSourceNone |
2173 | - |
2174 | -from cloudinit.distros.debian import Distro |
2175 | - |
2176 | -from .. import helpers as t_help |
2177 | - |
2178 | -LOG = logging.getLogger(__name__) |
2179 | - |
2180 | -YAML_TEXT_CUSTOM_SL = """ |
2181 | -apt_mirror: http://archive.ubuntu.com/ubuntu/ |
2182 | -apt_custom_sources_list: | |
2183 | - ## template:jinja |
2184 | - ## Note, this file is written by cloud-init on first boot of an instance |
2185 | - ## modifications made here will not survive a re-bundle. |
2186 | - ## if you wish to make changes you can: |
2187 | - ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
2188 | - ## or do the same in user-data |
2189 | - ## b.) add sources in /etc/apt/sources.list.d |
2190 | - ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
2191 | - |
2192 | - # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
2193 | - # newer versions of the distribution. |
2194 | - deb {{mirror}} {{codename}} main restricted |
2195 | - deb-src {{mirror}} {{codename}} main restricted |
2196 | - # FIND_SOMETHING_SPECIAL |
2197 | -""" |
2198 | - |
2199 | -EXPECTED_CONVERTED_CONTENT = ( |
2200 | - """## Note, this file is written by cloud-init on first boot of an instance |
2201 | -## modifications made here will not survive a re-bundle. |
2202 | -## if you wish to make changes you can: |
2203 | -## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
2204 | -## or do the same in user-data |
2205 | -## b.) add sources in /etc/apt/sources.list.d |
2206 | -## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
2207 | - |
2208 | -# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
2209 | -# newer versions of the distribution. |
2210 | -deb http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
2211 | -deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
2212 | -# FIND_SOMETHING_SPECIAL |
2213 | -""") |
2214 | - |
2215 | - |
2216 | -def load_tfile_or_url(*args, **kwargs): |
2217 | - """load_tfile_or_url |
2218 | - load file and return content after decoding |
2219 | - """ |
2220 | - return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
2221 | - |
2222 | - |
2223 | -class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): |
2224 | - """TestAptSourceConfigSourceList |
2225 | - Main Class to test sources list rendering |
2226 | - """ |
2227 | - def setUp(self): |
2228 | - super(TestAptSourceConfigSourceList, self).setUp() |
2229 | - self.subp = util.subp |
2230 | - self.new_root = tempfile.mkdtemp() |
2231 | - self.addCleanup(shutil.rmtree, self.new_root) |
2232 | - |
2233 | - def _get_cloud(self, distro, metadata=None): |
2234 | - self.patchUtils(self.new_root) |
2235 | - paths = helpers.Paths({}) |
2236 | - cls = distros.fetch(distro) |
2237 | - mydist = cls(distro, {}, paths) |
2238 | - myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
2239 | - if metadata: |
2240 | - myds.metadata.update(metadata) |
2241 | - return cloud.Cloud(myds, paths, {}, mydist, None) |
2242 | - |
2243 | - def apt_source_list(self, distro, mirror, mirrorcheck=None): |
2244 | - """apt_source_list |
2245 | - Test rendering of a source.list from template for a given distro |
2246 | - """ |
2247 | - if mirrorcheck is None: |
2248 | - mirrorcheck = mirror |
2249 | - |
2250 | - if isinstance(mirror, list): |
2251 | - cfg = {'apt_mirror_search': mirror} |
2252 | - else: |
2253 | - cfg = {'apt_mirror': mirror} |
2254 | - mycloud = self._get_cloud(distro) |
2255 | - |
2256 | - with mock.patch.object(templater, 'render_to_file') as mocktmpl: |
2257 | - with mock.patch.object(os.path, 'isfile', |
2258 | - return_value=True) as mockisfile: |
2259 | - with mock.patch.object(util, 'rename'): |
2260 | - cc_apt_configure.handle("notimportant", cfg, mycloud, |
2261 | - LOG, None) |
2262 | - |
2263 | - mockisfile.assert_any_call( |
2264 | - ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) |
2265 | - mocktmpl.assert_called_once_with( |
2266 | - ('/etc/cloud/templates/sources.list.%s.tmpl' % distro), |
2267 | - '/etc/apt/sources.list', |
2268 | - {'codename': '', 'primary': mirrorcheck, 'mirror': mirrorcheck}) |
2269 | - |
2270 | - def test_apt_source_list_debian(self): |
2271 | - """Test rendering of a source.list from template for debian""" |
2272 | - self.apt_source_list('debian', 'http://httpredir.debian.org/debian') |
2273 | - |
2274 | - def test_apt_source_list_ubuntu(self): |
2275 | - """Test rendering of a source.list from template for ubuntu""" |
2276 | - self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') |
2277 | - |
2278 | - @staticmethod |
2279 | - def myresolve(name): |
2280 | - """Fake util.is_resolvable for mirrorfail tests""" |
2281 | - if name == "does.not.exist": |
2282 | - print("Faking FAIL for '%s'" % name) |
2283 | - return False |
2284 | - else: |
2285 | - print("Faking SUCCESS for '%s'" % name) |
2286 | - return True |
2287 | - |
2288 | - def test_apt_srcl_debian_mirrorfail(self): |
2289 | - """Test rendering of a source.list from template for debian""" |
2290 | - with mock.patch.object(util, 'is_resolvable', |
2291 | - side_effect=self.myresolve) as mockresolve: |
2292 | - self.apt_source_list('debian', |
2293 | - ['http://does.not.exist', |
2294 | - 'http://httpredir.debian.org/debian'], |
2295 | - 'http://httpredir.debian.org/debian') |
2296 | - mockresolve.assert_any_call("does.not.exist") |
2297 | - mockresolve.assert_any_call("httpredir.debian.org") |
2298 | - |
2299 | - def test_apt_srcl_ubuntu_mirrorfail(self): |
2300 | - """Test rendering of a source.list from template for ubuntu""" |
2301 | - with mock.patch.object(util, 'is_resolvable', |
2302 | - side_effect=self.myresolve) as mockresolve: |
2303 | - self.apt_source_list('ubuntu', |
2304 | - ['http://does.not.exist', |
2305 | - 'http://archive.ubuntu.com/ubuntu/'], |
2306 | - 'http://archive.ubuntu.com/ubuntu/') |
2307 | - mockresolve.assert_any_call("does.not.exist") |
2308 | - mockresolve.assert_any_call("archive.ubuntu.com") |
2309 | - |
2310 | - def test_apt_srcl_custom(self): |
2311 | - """Test rendering from a custom source.list template""" |
2312 | - cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) |
2313 | - mycloud = self._get_cloud('ubuntu') |
2314 | - |
2315 | - # the second mock restores the original subp |
2316 | - with mock.patch.object(util, 'write_file') as mockwrite: |
2317 | - with mock.patch.object(util, 'subp', self.subp): |
2318 | - with mock.patch.object(cc_apt_configure, 'get_release', |
2319 | - return_value='fakerelease'): |
2320 | - with mock.patch.object(Distro, 'get_primary_arch', |
2321 | - return_value='amd64'): |
2322 | - cc_apt_configure.handle("notimportant", cfg, mycloud, |
2323 | - LOG, None) |
2324 | - |
2325 | - mockwrite.assert_called_once_with( |
2326 | - '/etc/apt/sources.list', |
2327 | - EXPECTED_CONVERTED_CONTENT, |
2328 | - mode=420) |
2329 | - |
2330 | - |
2331 | -# vi: ts=4 expandtab |
2332 | diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py |
2333 | new file mode 100644 |
2334 | index 0000000..f441186 |
2335 | --- /dev/null |
2336 | +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py |
2337 | @@ -0,0 +1,200 @@ |
2338 | +""" test_handler_apt_configure_sources_list |
2339 | +Test templating of sources list |
2340 | +""" |
2341 | +import logging |
2342 | +import os |
2343 | +import shutil |
2344 | +import tempfile |
2345 | + |
2346 | +try: |
2347 | + from unittest import mock |
2348 | +except ImportError: |
2349 | + import mock |
2350 | + |
2351 | +from cloudinit import cloud |
2352 | +from cloudinit import distros |
2353 | +from cloudinit import helpers |
2354 | +from cloudinit import templater |
2355 | +from cloudinit import util |
2356 | + |
2357 | +from cloudinit.config import cc_apt_configure |
2358 | +from cloudinit.sources import DataSourceNone |
2359 | + |
2360 | +from cloudinit.distros.debian import Distro |
2361 | + |
2362 | +from .. import helpers as t_help |
2363 | + |
2364 | +LOG = logging.getLogger(__name__) |
2365 | + |
2366 | +YAML_TEXT_CUSTOM_SL = """ |
2367 | +apt_mirror: http://archive.ubuntu.com/ubuntu/ |
2368 | +apt_custom_sources_list: | |
2369 | + ## template:jinja |
2370 | + ## Note, this file is written by cloud-init on first boot of an instance |
2371 | + ## modifications made here will not survive a re-bundle. |
2372 | + ## if you wish to make changes you can: |
2373 | + ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
2374 | + ## or do the same in user-data |
2375 | + ## b.) add sources in /etc/apt/sources.list.d |
2376 | + ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
2377 | + |
2378 | + # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
2379 | + # newer versions of the distribution. |
2380 | + deb {{mirror}} {{codename}} main restricted |
2381 | + deb-src {{mirror}} {{codename}} main restricted |
2382 | + # FIND_SOMETHING_SPECIAL |
2383 | +""" |
2384 | + |
2385 | +EXPECTED_CONVERTED_CONTENT = ( |
2386 | + """## Note, this file is written by cloud-init on first boot of an instance |
2387 | +## modifications made here will not survive a re-bundle. |
2388 | +## if you wish to make changes you can: |
2389 | +## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg |
2390 | +## or do the same in user-data |
2391 | +## b.) add sources in /etc/apt/sources.list.d |
2392 | +## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl |
2393 | + |
2394 | +# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
2395 | +# newer versions of the distribution. |
2396 | +deb http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
2397 | +deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
2398 | +# FIND_SOMETHING_SPECIAL |
2399 | +""") |
2400 | + |
2401 | + |
2402 | +def load_tfile_or_url(*args, **kwargs): |
2403 | + """load_tfile_or_url |
2404 | + load file and return content after decoding |
2405 | + """ |
2406 | + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
2407 | + |
2408 | + |
2409 | +class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): |
2410 | + """TestAptSourceConfigSourceList |
2411 | + Main Class to test sources list rendering |
2412 | + """ |
2413 | + def setUp(self): |
2414 | + super(TestAptSourceConfigSourceList, self).setUp() |
2415 | + self.subp = util.subp |
2416 | + self.new_root = tempfile.mkdtemp() |
2417 | + self.addCleanup(shutil.rmtree, self.new_root) |
2418 | + |
2419 | + rpatcher = mock.patch("cloudinit.util.lsb_release") |
2420 | + get_rel = rpatcher.start() |
2421 | + get_rel.return_value = {'codename': "fakerelease"} |
2422 | + self.addCleanup(rpatcher.stop) |
2423 | + apatcher = mock.patch("cloudinit.util.get_architecture") |
2424 | + get_arch = apatcher.start() |
2425 | + get_arch.return_value = 'amd64' |
2426 | + self.addCleanup(apatcher.stop) |
2427 | + |
2428 | + def _get_cloud(self, distro, metadata=None): |
2429 | + self.patchUtils(self.new_root) |
2430 | + paths = helpers.Paths({}) |
2431 | + cls = distros.fetch(distro) |
2432 | + mydist = cls(distro, {}, paths) |
2433 | + myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
2434 | + if metadata: |
2435 | + myds.metadata.update(metadata) |
2436 | + return cloud.Cloud(myds, paths, {}, mydist, None) |
2437 | + |
2438 | + def apt_source_list(self, distro, mirror, mirrorcheck=None): |
2439 | + """apt_source_list |
2440 | + Test rendering of a source.list from template for a given distro |
2441 | + """ |
2442 | + if mirrorcheck is None: |
2443 | + mirrorcheck = mirror |
2444 | + |
2445 | + if isinstance(mirror, list): |
2446 | + cfg = {'apt_mirror_search': mirror} |
2447 | + else: |
2448 | + cfg = {'apt_mirror': mirror} |
2449 | + mycloud = self._get_cloud(distro) |
2450 | + |
2451 | + with mock.patch.object(util, 'write_file') as mockwf: |
2452 | + with mock.patch.object(util, 'load_file', |
2453 | + return_value="faketmpl") as mocklf: |
2454 | + with mock.patch.object(os.path, 'isfile', |
2455 | + return_value=True) as mockisfile: |
2456 | + with mock.patch.object(templater, 'render_string', |
2457 | + return_value="fake") as mockrnd: |
2458 | + with mock.patch.object(util, 'rename'): |
2459 | + cc_apt_configure.handle("test", cfg, mycloud, |
2460 | + LOG, None) |
2461 | + |
2462 | + mockisfile.assert_any_call( |
2463 | + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) |
2464 | + mocklf.assert_any_call( |
2465 | + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) |
2466 | + mockrnd.assert_called_once_with('faketmpl', |
2467 | + {'RELEASE': 'fakerelease', |
2468 | + 'PRIMARY': mirrorcheck, |
2469 | + 'MIRROR': mirrorcheck, |
2470 | + 'SECURITY': mirrorcheck, |
2471 | + 'codename': 'fakerelease', |
2472 | + 'primary': mirrorcheck, |
2473 | + 'mirror': mirrorcheck, |
2474 | + 'security': mirrorcheck}) |
2475 | + mockwf.assert_called_once_with('/etc/apt/sources.list', 'fake', |
2476 | + mode=0o644) |
2477 | + |
2478 | + def test_apt_v1_source_list_debian(self): |
2479 | + """Test rendering of a source.list from template for debian""" |
2480 | + self.apt_source_list('debian', 'http://httpredir.debian.org/debian') |
2481 | + |
2482 | + def test_apt_v1_source_list_ubuntu(self): |
2483 | + """Test rendering of a source.list from template for ubuntu""" |
2484 | + self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') |
2485 | + |
2486 | + @staticmethod |
2487 | + def myresolve(name): |
2488 | + """Fake util.is_resolvable for mirrorfail tests""" |
2489 | + if name == "does.not.exist": |
2490 | + print("Faking FAIL for '%s'" % name) |
2491 | + return False |
2492 | + else: |
2493 | + print("Faking SUCCESS for '%s'" % name) |
2494 | + return True |
2495 | + |
2496 | + def test_apt_v1_srcl_debian_mirrorfail(self): |
2497 | + """Test rendering of a source.list from template for debian""" |
2498 | + with mock.patch.object(util, 'is_resolvable', |
2499 | + side_effect=self.myresolve) as mockresolve: |
2500 | + self.apt_source_list('debian', |
2501 | + ['http://does.not.exist', |
2502 | + 'http://httpredir.debian.org/debian'], |
2503 | + 'http://httpredir.debian.org/debian') |
2504 | + mockresolve.assert_any_call("does.not.exist") |
2505 | + mockresolve.assert_any_call("httpredir.debian.org") |
2506 | + |
2507 | + def test_apt_v1_srcl_ubuntu_mirrorfail(self): |
2508 | + """Test rendering of a source.list from template for ubuntu""" |
2509 | + with mock.patch.object(util, 'is_resolvable', |
2510 | + side_effect=self.myresolve) as mockresolve: |
2511 | + self.apt_source_list('ubuntu', |
2512 | + ['http://does.not.exist', |
2513 | + 'http://archive.ubuntu.com/ubuntu/'], |
2514 | + 'http://archive.ubuntu.com/ubuntu/') |
2515 | + mockresolve.assert_any_call("does.not.exist") |
2516 | + mockresolve.assert_any_call("archive.ubuntu.com") |
2517 | + |
2518 | + def test_apt_v1_srcl_custom(self): |
2519 | + """Test rendering from a custom source.list template""" |
2520 | + cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) |
2521 | + mycloud = self._get_cloud('ubuntu') |
2522 | + |
2523 | + # the second mock restores the original subp |
2524 | + with mock.patch.object(util, 'write_file') as mockwrite: |
2525 | + with mock.patch.object(util, 'subp', self.subp): |
2526 | + with mock.patch.object(Distro, 'get_primary_arch', |
2527 | + return_value='amd64'): |
2528 | + cc_apt_configure.handle("notimportant", cfg, mycloud, |
2529 | + LOG, None) |
2530 | + |
2531 | + mockwrite.assert_called_once_with( |
2532 | + '/etc/apt/sources.list', |
2533 | + EXPECTED_CONVERTED_CONTENT, |
2534 | + mode=420) |
2535 | + |
2536 | + |
2537 | +# vi: ts=4 expandtab |
2538 | diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py |
2539 | new file mode 100644 |
2540 | index 0000000..e53b045 |
2541 | --- /dev/null |
2542 | +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py |
2543 | @@ -0,0 +1,187 @@ |
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 | +try: |
2553 | + from unittest import mock |
2554 | +except ImportError: |
2555 | + import mock |
2556 | +from mock import call |
2557 | + |
2558 | +from cloudinit import cloud |
2559 | +from cloudinit import distros |
2560 | +from cloudinit import helpers |
2561 | +from cloudinit import util |
2562 | + |
2563 | +from cloudinit.config import cc_apt_configure |
2564 | +from cloudinit.sources import DataSourceNone |
2565 | + |
2566 | +from cloudinit.distros.debian import Distro |
2567 | + |
2568 | +from .. import helpers as t_help |
2569 | + |
2570 | +LOG = logging.getLogger(__name__) |
2571 | + |
2572 | +TARGET = "/" |
2573 | + |
2574 | +# Input and expected output for the custom template |
2575 | +YAML_TEXT_CUSTOM_SL = """ |
2576 | +apt: |
2577 | + primary: |
2578 | + - arches: [default] |
2579 | + uri: http://test.ubuntu.com/ubuntu/ |
2580 | + security: |
2581 | + - arches: [default] |
2582 | + uri: http://testsec.ubuntu.com/ubuntu/ |
2583 | + sources_list: | |
2584 | + |
2585 | + # Note, this file is written by cloud-init at install time. It should not |
2586 | + # end up on the installed system itself. |
2587 | + # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
2588 | + # newer versions of the distribution. |
2589 | + deb $MIRROR $RELEASE main restricted |
2590 | + deb-src $MIRROR $RELEASE main restricted |
2591 | + deb $PRIMARY $RELEASE universe restricted |
2592 | + deb $SECURITY $RELEASE-security multiverse |
2593 | + # FIND_SOMETHING_SPECIAL |
2594 | +""" |
2595 | + |
2596 | +EXPECTED_CONVERTED_CONTENT = """ |
2597 | +# Note, this file is written by cloud-init at install time. It should not |
2598 | +# end up on the installed system itself. |
2599 | +# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to |
2600 | +# newer versions of the distribution. |
2601 | +deb http://test.ubuntu.com/ubuntu/ fakerel main restricted |
2602 | +deb-src http://test.ubuntu.com/ubuntu/ fakerel main restricted |
2603 | +deb http://test.ubuntu.com/ubuntu/ fakerel universe restricted |
2604 | +deb http://testsec.ubuntu.com/ubuntu/ fakerel-security multiverse |
2605 | +# FIND_SOMETHING_SPECIAL |
2606 | +""" |
2607 | + |
2608 | +# mocked to be independent to the unittest system |
2609 | +MOCKED_APT_SRC_LIST = """ |
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_BASE_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://testsec.ubuntu.com/ubuntu/ notouched-security main restricted |
2621 | +""") |
2622 | + |
2623 | +EXPECTED_MIRROR_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://test.ubuntu.com/ubuntu/ notouched-security main restricted |
2628 | +""") |
2629 | + |
2630 | +EXPECTED_PRIMSEC_CONTENT = (""" |
2631 | +deb http://test.ubuntu.com/ubuntu/ notouched main restricted |
2632 | +deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted |
2633 | +deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted |
2634 | +deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted |
2635 | +""") |
2636 | + |
2637 | + |
2638 | +class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): |
2639 | + """TestAptSourceConfigSourceList - Class to test sources list rendering""" |
2640 | + def setUp(self): |
2641 | + super(TestAptSourceConfigSourceList, self).setUp() |
2642 | + self.subp = util.subp |
2643 | + self.new_root = tempfile.mkdtemp() |
2644 | + self.addCleanup(shutil.rmtree, self.new_root) |
2645 | + |
2646 | + rpatcher = mock.patch("cloudinit.util.lsb_release") |
2647 | + get_rel = rpatcher.start() |
2648 | + get_rel.return_value = {'codename': "fakerel"} |
2649 | + self.addCleanup(rpatcher.stop) |
2650 | + apatcher = mock.patch("cloudinit.util.get_architecture") |
2651 | + get_arch = apatcher.start() |
2652 | + get_arch.return_value = 'amd64' |
2653 | + self.addCleanup(apatcher.stop) |
2654 | + |
2655 | + def _get_cloud(self, distro, metadata=None): |
2656 | + self.patchUtils(self.new_root) |
2657 | + paths = helpers.Paths({}) |
2658 | + cls = distros.fetch(distro) |
2659 | + mydist = cls(distro, {}, paths) |
2660 | + myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
2661 | + if metadata: |
2662 | + myds.metadata.update(metadata) |
2663 | + return cloud.Cloud(myds, paths, {}, mydist, None) |
2664 | + |
2665 | + def _apt_source_list(self, cfg, expected, distro): |
2666 | + "_apt_source_list - Test rendering from template (generic)" |
2667 | + |
2668 | + # entry at top level now, wrap in 'apt' key |
2669 | + cfg = {'apt': cfg} |
2670 | + mycloud = self._get_cloud(distro) |
2671 | + with mock.patch.object(util, 'write_file') as mockwf: |
2672 | + with mock.patch.object(util, 'load_file', |
2673 | + return_value=MOCKED_APT_SRC_LIST) as mocklf: |
2674 | + with mock.patch.object(os.path, 'isfile', |
2675 | + return_value=True) as mockisfile: |
2676 | + with mock.patch.object(util, 'rename'): |
2677 | + cc_apt_configure.handle("test", cfg, mycloud, |
2678 | + LOG, None) |
2679 | + |
2680 | + # check if it would have loaded the distro template |
2681 | + mockisfile.assert_any_call( |
2682 | + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) |
2683 | + mocklf.assert_any_call( |
2684 | + ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) |
2685 | + # check expected content in result |
2686 | + mockwf.assert_called_once_with('/etc/apt/sources.list', expected, |
2687 | + mode=0o644) |
2688 | + |
2689 | + def test_apt_v3_source_list_debian(self): |
2690 | + """test_apt_v3_source_list_debian - without custom sources or parms""" |
2691 | + cfg = {} |
2692 | + self._apt_source_list(cfg, EXPECTED_BASE_CONTENT, 'debian') |
2693 | + |
2694 | + def test_apt_v3_source_list_ubuntu(self): |
2695 | + """test_apt_v3_source_list_ubuntu - without custom sources or parms""" |
2696 | + cfg = {} |
2697 | + self._apt_source_list(cfg, EXPECTED_BASE_CONTENT, 'ubuntu') |
2698 | + |
2699 | + def test_apt_v3_source_list_psm(self): |
2700 | + """test_apt_v3_source_list_psm - Test specifying prim+sec mirrors""" |
2701 | + pm = 'http://test.ubuntu.com/ubuntu/' |
2702 | + sm = 'http://testsec.ubuntu.com/ubuntu/' |
2703 | + cfg = {'preserve_sources_list': False, |
2704 | + 'primary': [{'arches': ["default"], |
2705 | + 'uri': pm}], |
2706 | + 'security': [{'arches': ["default"], |
2707 | + 'uri': sm}]} |
2708 | + |
2709 | + self._apt_source_list(cfg, EXPECTED_PRIMSEC_CONTENT, 'ubuntu') |
2710 | + |
2711 | + def test_apt_v3_srcl_custom(self): |
2712 | + """test_apt_v3_srcl_custom - Test rendering a custom source template""" |
2713 | + cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL) |
2714 | + mycloud = self._get_cloud('ubuntu') |
2715 | + |
2716 | + # the second mock restores the original subp |
2717 | + with mock.patch.object(util, 'write_file') as mockwrite: |
2718 | + with mock.patch.object(util, 'subp', self.subp): |
2719 | + with mock.patch.object(Distro, 'get_primary_arch', |
2720 | + return_value='amd64'): |
2721 | + cc_apt_configure.handle("notimportant", cfg, mycloud, |
2722 | + LOG, None) |
2723 | + |
2724 | + calls = [call('/etc/apt/sources.list', |
2725 | + EXPECTED_CONVERTED_CONTENT, |
2726 | + mode=0o644)] |
2727 | + mockwrite.assert_has_calls(calls) |
2728 | + |
2729 | + |
2730 | +# vi: ts=4 expandtab |
2731 | diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py |
2732 | deleted file mode 100644 |
2733 | index 99a4d86..0000000 |
2734 | --- a/tests/unittests/test_handler/test_handler_apt_source.py |
2735 | +++ /dev/null |
2736 | @@ -1,516 +0,0 @@ |
2737 | -""" test_handler_apt_source |
2738 | -Testing various config variations of the apt_source config |
2739 | -""" |
2740 | -import os |
2741 | -import re |
2742 | -import shutil |
2743 | -import tempfile |
2744 | - |
2745 | -try: |
2746 | - from unittest import mock |
2747 | -except ImportError: |
2748 | - import mock |
2749 | -from mock import call |
2750 | - |
2751 | -from cloudinit.config import cc_apt_configure |
2752 | -from cloudinit import gpg |
2753 | -from cloudinit import util |
2754 | - |
2755 | -from ..helpers import TestCase |
2756 | - |
2757 | -EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- |
2758 | -Version: GnuPG v1 |
2759 | - |
2760 | -mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ |
2761 | -NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy |
2762 | -8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0 |
2763 | -HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG |
2764 | -CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29 |
2765 | -OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ |
2766 | -FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm |
2767 | -S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== |
2768 | -=ACB2 |
2769 | ------END PGP PUBLIC KEY BLOCK-----""" |
2770 | - |
2771 | - |
2772 | -def load_tfile_or_url(*args, **kwargs): |
2773 | - """load_tfile_or_url |
2774 | - load file and return content after decoding |
2775 | - """ |
2776 | - return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
2777 | - |
2778 | - |
2779 | -class TestAptSourceConfig(TestCase): |
2780 | - """TestAptSourceConfig |
2781 | - Main Class to test apt_source configs |
2782 | - """ |
2783 | - release = "fantastic" |
2784 | - |
2785 | - def setUp(self): |
2786 | - super(TestAptSourceConfig, self).setUp() |
2787 | - self.tmp = tempfile.mkdtemp() |
2788 | - self.addCleanup(shutil.rmtree, self.tmp) |
2789 | - self.aptlistfile = os.path.join(self.tmp, "single-deb.list") |
2790 | - self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") |
2791 | - self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") |
2792 | - self.join = os.path.join |
2793 | - # mock fallback filename into writable tmp dir |
2794 | - self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", |
2795 | - "cloud_config_sources.list") |
2796 | - |
2797 | - patcher = mock.patch("cloudinit.config.cc_apt_configure.get_release") |
2798 | - get_rel = patcher.start() |
2799 | - get_rel.return_value = self.release |
2800 | - self.addCleanup(patcher.stop) |
2801 | - |
2802 | - @staticmethod |
2803 | - def _get_default_params(): |
2804 | - """get_default_params |
2805 | - Get the most basic default mrror and release info to be used in tests |
2806 | - """ |
2807 | - params = {} |
2808 | - params['RELEASE'] = cc_apt_configure.get_release() |
2809 | - params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" |
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 | - cc_apt_configure.add_apt_sources(cfg, params) |
2828 | - |
2829 | - self.assertTrue(os.path.isfile(filename)) |
2830 | - |
2831 | - contents = load_tfile_or_url(filename) |
2832 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
2833 | - ("deb", "http://archive.ubuntu.com/ubuntu", |
2834 | - "karmic-backports", |
2835 | - "main universe multiverse restricted"), |
2836 | - contents, flags=re.IGNORECASE)) |
2837 | - |
2838 | - def test_apt_src_basic(self): |
2839 | - """Test deb source string, overwrite mirror and filename""" |
2840 | - cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
2841 | - ' karmic-backports' |
2842 | - ' main universe multiverse restricted'), |
2843 | - 'filename': self.aptlistfile} |
2844 | - self.apt_src_basic(self.aptlistfile, [cfg]) |
2845 | - |
2846 | - def test_apt_src_basic_dict(self): |
2847 | - """Test deb source string, overwrite mirror and filename (dict)""" |
2848 | - cfg = {self.aptlistfile: {'source': |
2849 | - ('deb http://archive.ubuntu.com/ubuntu' |
2850 | - ' karmic-backports' |
2851 | - ' main universe multiverse restricted')}} |
2852 | - self.apt_src_basic(self.aptlistfile, cfg) |
2853 | - |
2854 | - def apt_src_basic_tri(self, cfg): |
2855 | - """apt_src_basic_tri |
2856 | - Test Fix three deb source string, has to overwrite mirror conf in |
2857 | - params. Test with filenames provided in config. |
2858 | - generic part to check three files with different content |
2859 | - """ |
2860 | - self.apt_src_basic(self.aptlistfile, cfg) |
2861 | - |
2862 | - # extra verify on two extra files of this test |
2863 | - contents = load_tfile_or_url(self.aptlistfile2) |
2864 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
2865 | - ("deb", "http://archive.ubuntu.com/ubuntu", |
2866 | - "precise-backports", |
2867 | - "main universe multiverse restricted"), |
2868 | - contents, flags=re.IGNORECASE)) |
2869 | - contents = load_tfile_or_url(self.aptlistfile3) |
2870 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
2871 | - ("deb", "http://archive.ubuntu.com/ubuntu", |
2872 | - "lucid-backports", |
2873 | - "main universe multiverse restricted"), |
2874 | - contents, flags=re.IGNORECASE)) |
2875 | - |
2876 | - def test_apt_src_basic_tri(self): |
2877 | - """Test Fix three deb source string with filenames""" |
2878 | - cfg1 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
2879 | - ' karmic-backports' |
2880 | - ' main universe multiverse restricted'), |
2881 | - 'filename': self.aptlistfile} |
2882 | - cfg2 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
2883 | - ' precise-backports' |
2884 | - ' main universe multiverse restricted'), |
2885 | - 'filename': self.aptlistfile2} |
2886 | - cfg3 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
2887 | - ' lucid-backports' |
2888 | - ' main universe multiverse restricted'), |
2889 | - 'filename': self.aptlistfile3} |
2890 | - self.apt_src_basic_tri([cfg1, cfg2, cfg3]) |
2891 | - |
2892 | - def test_apt_src_basic_dict_tri(self): |
2893 | - """Test Fix three deb source string with filenames (dict)""" |
2894 | - cfg = {self.aptlistfile: {'source': |
2895 | - ('deb http://archive.ubuntu.com/ubuntu' |
2896 | - ' karmic-backports' |
2897 | - ' main universe multiverse restricted')}, |
2898 | - self.aptlistfile2: {'source': |
2899 | - ('deb http://archive.ubuntu.com/ubuntu' |
2900 | - ' precise-backports' |
2901 | - ' main universe multiverse restricted')}, |
2902 | - self.aptlistfile3: {'source': |
2903 | - ('deb http://archive.ubuntu.com/ubuntu' |
2904 | - ' lucid-backports' |
2905 | - ' main universe multiverse restricted')}} |
2906 | - self.apt_src_basic_tri(cfg) |
2907 | - |
2908 | - def test_apt_src_basic_nofn(self): |
2909 | - """Test Fix three deb source string without filenames (dict)""" |
2910 | - cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
2911 | - ' karmic-backports' |
2912 | - ' main universe multiverse restricted')} |
2913 | - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
2914 | - self.apt_src_basic(self.fallbackfn, [cfg]) |
2915 | - |
2916 | - def apt_src_replacement(self, filename, cfg): |
2917 | - """apt_src_replace |
2918 | - Test Autoreplacement of MIRROR and RELEASE in source specs |
2919 | - """ |
2920 | - params = self._get_default_params() |
2921 | - cc_apt_configure.add_apt_sources(cfg, params) |
2922 | - |
2923 | - self.assertTrue(os.path.isfile(filename)) |
2924 | - |
2925 | - contents = load_tfile_or_url(filename) |
2926 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
2927 | - ("deb", params['MIRROR'], params['RELEASE'], |
2928 | - "multiverse"), |
2929 | - contents, flags=re.IGNORECASE)) |
2930 | - |
2931 | - def test_apt_src_replace(self): |
2932 | - """Test Autoreplacement of MIRROR and RELEASE in source specs""" |
2933 | - cfg = {'source': 'deb $MIRROR $RELEASE multiverse', |
2934 | - 'filename': self.aptlistfile} |
2935 | - self.apt_src_replacement(self.aptlistfile, [cfg]) |
2936 | - |
2937 | - def apt_src_replace_tri(self, cfg): |
2938 | - """apt_src_replace_tri |
2939 | - Test three autoreplacements of MIRROR and RELEASE in source specs with |
2940 | - generic part |
2941 | - """ |
2942 | - self.apt_src_replacement(self.aptlistfile, cfg) |
2943 | - |
2944 | - # extra verify on two extra files of this test |
2945 | - params = self._get_default_params() |
2946 | - contents = load_tfile_or_url(self.aptlistfile2) |
2947 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
2948 | - ("deb", params['MIRROR'], params['RELEASE'], |
2949 | - "main"), |
2950 | - contents, flags=re.IGNORECASE)) |
2951 | - contents = load_tfile_or_url(self.aptlistfile3) |
2952 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
2953 | - ("deb", params['MIRROR'], params['RELEASE'], |
2954 | - "universe"), |
2955 | - contents, flags=re.IGNORECASE)) |
2956 | - |
2957 | - def test_apt_src_replace_tri(self): |
2958 | - """Test triple Autoreplacement of MIRROR and RELEASE in source specs""" |
2959 | - cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', |
2960 | - 'filename': self.aptlistfile} |
2961 | - cfg2 = {'source': 'deb $MIRROR $RELEASE main', |
2962 | - 'filename': self.aptlistfile2} |
2963 | - cfg3 = {'source': 'deb $MIRROR $RELEASE universe', |
2964 | - 'filename': self.aptlistfile3} |
2965 | - self.apt_src_replace_tri([cfg1, cfg2, cfg3]) |
2966 | - |
2967 | - def test_apt_src_replace_dict_tri(self): |
2968 | - """Test triple Autoreplacement in source specs (dict)""" |
2969 | - cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, |
2970 | - 'notused': {'source': 'deb $MIRROR $RELEASE main', |
2971 | - 'filename': self.aptlistfile2}, |
2972 | - self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} |
2973 | - self.apt_src_replace_tri(cfg) |
2974 | - |
2975 | - def test_apt_src_replace_nofn(self): |
2976 | - """Test Autoreplacement of MIRROR and RELEASE in source specs nofile""" |
2977 | - cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} |
2978 | - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
2979 | - self.apt_src_replacement(self.fallbackfn, [cfg]) |
2980 | - |
2981 | - def apt_src_keyid(self, filename, cfg, keynum): |
2982 | - """apt_src_keyid |
2983 | - Test specification of a source + keyid |
2984 | - """ |
2985 | - params = self._get_default_params() |
2986 | - |
2987 | - with mock.patch.object(util, 'subp', |
2988 | - return_value=('fakekey 1234', '')) as mockobj: |
2989 | - cc_apt_configure.add_apt_sources(cfg, params) |
2990 | - |
2991 | - # check if it added the right ammount of keys |
2992 | - calls = [] |
2993 | - for _ in range(keynum): |
2994 | - calls.append(call(('apt-key', 'add', '-'), 'fakekey 1234')) |
2995 | - mockobj.assert_has_calls(calls, any_order=True) |
2996 | - |
2997 | - self.assertTrue(os.path.isfile(filename)) |
2998 | - |
2999 | - contents = load_tfile_or_url(filename) |
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", "main"), |
3005 | - contents, flags=re.IGNORECASE)) |
3006 | - |
3007 | - def test_apt_src_keyid(self): |
3008 | - """Test specification of a source + keyid with filename being set""" |
3009 | - cfg = {'source': ('deb ' |
3010 | - 'http://ppa.launchpad.net/' |
3011 | - 'smoser/cloud-init-test/ubuntu' |
3012 | - ' xenial main'), |
3013 | - 'keyid': "03683F77", |
3014 | - 'filename': self.aptlistfile} |
3015 | - self.apt_src_keyid(self.aptlistfile, [cfg], 1) |
3016 | - |
3017 | - def test_apt_src_keyid_tri(self): |
3018 | - """Test 3x specification of a source + keyid with filename being set""" |
3019 | - cfg1 = {'source': ('deb ' |
3020 | - 'http://ppa.launchpad.net/' |
3021 | - 'smoser/cloud-init-test/ubuntu' |
3022 | - ' xenial main'), |
3023 | - 'keyid': "03683F77", |
3024 | - 'filename': self.aptlistfile} |
3025 | - cfg2 = {'source': ('deb ' |
3026 | - 'http://ppa.launchpad.net/' |
3027 | - 'smoser/cloud-init-test/ubuntu' |
3028 | - ' xenial universe'), |
3029 | - 'keyid': "03683F77", |
3030 | - 'filename': self.aptlistfile2} |
3031 | - cfg3 = {'source': ('deb ' |
3032 | - 'http://ppa.launchpad.net/' |
3033 | - 'smoser/cloud-init-test/ubuntu' |
3034 | - ' xenial multiverse'), |
3035 | - 'keyid': "03683F77", |
3036 | - 'filename': self.aptlistfile3} |
3037 | - |
3038 | - self.apt_src_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) |
3039 | - contents = load_tfile_or_url(self.aptlistfile2) |
3040 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
3041 | - ("deb", |
3042 | - ('http://ppa.launchpad.net/smoser/' |
3043 | - 'cloud-init-test/ubuntu'), |
3044 | - "xenial", "universe"), |
3045 | - contents, flags=re.IGNORECASE)) |
3046 | - contents = load_tfile_or_url(self.aptlistfile3) |
3047 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
3048 | - ("deb", |
3049 | - ('http://ppa.launchpad.net/smoser/' |
3050 | - 'cloud-init-test/ubuntu'), |
3051 | - "xenial", "multiverse"), |
3052 | - contents, flags=re.IGNORECASE)) |
3053 | - |
3054 | - def test_apt_src_keyid_nofn(self): |
3055 | - """Test specification of a source + keyid without filename being set""" |
3056 | - cfg = {'source': ('deb ' |
3057 | - 'http://ppa.launchpad.net/' |
3058 | - 'smoser/cloud-init-test/ubuntu' |
3059 | - ' xenial main'), |
3060 | - 'keyid': "03683F77"} |
3061 | - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
3062 | - self.apt_src_keyid(self.fallbackfn, [cfg], 1) |
3063 | - |
3064 | - def apt_src_key(self, filename, cfg): |
3065 | - """apt_src_key |
3066 | - Test specification of a source + key |
3067 | - """ |
3068 | - params = self._get_default_params() |
3069 | - |
3070 | - with mock.patch.object(util, 'subp') as mockobj: |
3071 | - cc_apt_configure.add_apt_sources([cfg], params) |
3072 | - |
3073 | - mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') |
3074 | - |
3075 | - self.assertTrue(os.path.isfile(filename)) |
3076 | - |
3077 | - contents = load_tfile_or_url(filename) |
3078 | - self.assertTrue(re.search(r"%s %s %s %s\n" % |
3079 | - ("deb", |
3080 | - ('http://ppa.launchpad.net/smoser/' |
3081 | - 'cloud-init-test/ubuntu'), |
3082 | - "xenial", "main"), |
3083 | - contents, flags=re.IGNORECASE)) |
3084 | - |
3085 | - def test_apt_src_key(self): |
3086 | - """Test specification of a source + key with filename being set""" |
3087 | - cfg = {'source': ('deb ' |
3088 | - 'http://ppa.launchpad.net/' |
3089 | - 'smoser/cloud-init-test/ubuntu' |
3090 | - ' xenial main'), |
3091 | - 'key': "fakekey 4321", |
3092 | - 'filename': self.aptlistfile} |
3093 | - self.apt_src_key(self.aptlistfile, cfg) |
3094 | - |
3095 | - def test_apt_src_key_nofn(self): |
3096 | - """Test specification of a source + key without filename being set""" |
3097 | - cfg = {'source': ('deb ' |
3098 | - 'http://ppa.launchpad.net/' |
3099 | - 'smoser/cloud-init-test/ubuntu' |
3100 | - ' xenial main'), |
3101 | - 'key': "fakekey 4321"} |
3102 | - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
3103 | - self.apt_src_key(self.fallbackfn, cfg) |
3104 | - |
3105 | - def test_apt_src_keyonly(self): |
3106 | - """Test specifying key without source""" |
3107 | - params = self._get_default_params() |
3108 | - cfg = {'key': "fakekey 4242", |
3109 | - 'filename': self.aptlistfile} |
3110 | - |
3111 | - with mock.patch.object(util, 'subp') as mockobj: |
3112 | - cc_apt_configure.add_apt_sources([cfg], params) |
3113 | - |
3114 | - mockobj.assert_called_once_with(('apt-key', 'add', '-'), |
3115 | - 'fakekey 4242') |
3116 | - |
3117 | - # filename should be ignored on key only |
3118 | - self.assertFalse(os.path.isfile(self.aptlistfile)) |
3119 | - |
3120 | - def test_apt_src_keyidonly(self): |
3121 | - """Test specification of a keyid without source""" |
3122 | - params = self._get_default_params() |
3123 | - cfg = {'keyid': "03683F77", |
3124 | - 'filename': self.aptlistfile} |
3125 | - |
3126 | - with mock.patch.object(util, 'subp', |
3127 | - return_value=('fakekey 1212', '')) as mockobj: |
3128 | - cc_apt_configure.add_apt_sources([cfg], params) |
3129 | - |
3130 | - mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1212') |
3131 | - |
3132 | - # filename should be ignored on key only |
3133 | - self.assertFalse(os.path.isfile(self.aptlistfile)) |
3134 | - |
3135 | - def apt_src_keyid_real(self, cfg, expectedkey): |
3136 | - """apt_src_keyid_real |
3137 | - Test specification of a keyid without source including |
3138 | - up to addition of the key (add_apt_key_raw mocked to keep the |
3139 | - environment as is) |
3140 | - """ |
3141 | - params = self._get_default_params() |
3142 | - |
3143 | - with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: |
3144 | - with mock.patch.object(gpg, 'get_key_by_id', |
3145 | - return_value=expectedkey) as mockgetkey: |
3146 | - cc_apt_configure.add_apt_sources([cfg], params) |
3147 | - |
3148 | - mockgetkey.assert_called_with(cfg['keyid'], |
3149 | - cfg.get('keyserver', |
3150 | - 'keyserver.ubuntu.com')) |
3151 | - mockkey.assert_called_with(expectedkey) |
3152 | - |
3153 | - # filename should be ignored on key only |
3154 | - self.assertFalse(os.path.isfile(self.aptlistfile)) |
3155 | - |
3156 | - def test_apt_src_keyid_real(self): |
3157 | - """test_apt_src_keyid_real - Test keyid including key add""" |
3158 | - keyid = "03683F77" |
3159 | - cfg = {'keyid': keyid, |
3160 | - 'filename': self.aptlistfile} |
3161 | - |
3162 | - self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
3163 | - |
3164 | - def test_apt_src_longkeyid_real(self): |
3165 | - """test_apt_src_longkeyid_real - Test long keyid including key add""" |
3166 | - keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
3167 | - cfg = {'keyid': keyid, |
3168 | - 'filename': self.aptlistfile} |
3169 | - |
3170 | - self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
3171 | - |
3172 | - def test_apt_src_longkeyid_ks_real(self): |
3173 | - """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" |
3174 | - keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
3175 | - cfg = {'keyid': keyid, |
3176 | - 'keyserver': 'keys.gnupg.net', |
3177 | - 'filename': self.aptlistfile} |
3178 | - |
3179 | - self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
3180 | - |
3181 | - def test_apt_src_ppa(self): |
3182 | - """Test adding a ppa""" |
3183 | - params = self._get_default_params() |
3184 | - cfg = {'source': 'ppa:smoser/cloud-init-test', |
3185 | - 'filename': self.aptlistfile} |
3186 | - |
3187 | - # default matcher needed for ppa |
3188 | - matcher = re.compile(r'^[\w-]+:\w').search |
3189 | - |
3190 | - with mock.patch.object(util, 'subp') as mockobj: |
3191 | - cc_apt_configure.add_apt_sources([cfg], params, |
3192 | - aa_repo_match=matcher) |
3193 | - mockobj.assert_called_once_with(['add-apt-repository', |
3194 | - 'ppa:smoser/cloud-init-test']) |
3195 | - |
3196 | - # adding ppa should ignore filename (uses add-apt-repository) |
3197 | - self.assertFalse(os.path.isfile(self.aptlistfile)) |
3198 | - |
3199 | - def test_apt_src_ppa_tri(self): |
3200 | - """Test adding three ppa's""" |
3201 | - params = self._get_default_params() |
3202 | - cfg1 = {'source': 'ppa:smoser/cloud-init-test', |
3203 | - 'filename': self.aptlistfile} |
3204 | - cfg2 = {'source': 'ppa:smoser/cloud-init-test2', |
3205 | - 'filename': self.aptlistfile2} |
3206 | - cfg3 = {'source': 'ppa:smoser/cloud-init-test3', |
3207 | - 'filename': self.aptlistfile3} |
3208 | - |
3209 | - # default matcher needed for ppa |
3210 | - matcher = re.compile(r'^[\w-]+:\w').search |
3211 | - |
3212 | - with mock.patch.object(util, 'subp') as mockobj: |
3213 | - cc_apt_configure.add_apt_sources([cfg1, cfg2, cfg3], params, |
3214 | - aa_repo_match=matcher) |
3215 | - calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test']), |
3216 | - call(['add-apt-repository', 'ppa:smoser/cloud-init-test2']), |
3217 | - call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'])] |
3218 | - mockobj.assert_has_calls(calls, any_order=True) |
3219 | - |
3220 | - # adding ppa should ignore all filenames (uses add-apt-repository) |
3221 | - self.assertFalse(os.path.isfile(self.aptlistfile)) |
3222 | - self.assertFalse(os.path.isfile(self.aptlistfile2)) |
3223 | - self.assertFalse(os.path.isfile(self.aptlistfile3)) |
3224 | - |
3225 | - def test_convert_to_new_format(self): |
3226 | - """Test the conversion of old to new format""" |
3227 | - cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', |
3228 | - 'filename': self.aptlistfile} |
3229 | - cfg2 = {'source': 'deb $MIRROR $RELEASE main', |
3230 | - 'filename': self.aptlistfile2} |
3231 | - cfg3 = {'source': 'deb $MIRROR $RELEASE universe', |
3232 | - 'filename': self.aptlistfile3} |
3233 | - checkcfg = {self.aptlistfile: {'filename': self.aptlistfile, |
3234 | - 'source': 'deb $MIRROR $RELEASE ' |
3235 | - 'multiverse'}, |
3236 | - self.aptlistfile2: {'filename': self.aptlistfile2, |
3237 | - 'source': 'deb $MIRROR $RELEASE main'}, |
3238 | - self.aptlistfile3: {'filename': self.aptlistfile3, |
3239 | - 'source': 'deb $MIRROR $RELEASE ' |
3240 | - 'universe'}} |
3241 | - |
3242 | - newcfg = cc_apt_configure.convert_to_new_format([cfg1, cfg2, cfg3]) |
3243 | - self.assertEqual(newcfg, checkcfg) |
3244 | - |
3245 | - newcfg2 = cc_apt_configure.convert_to_new_format(newcfg) |
3246 | - self.assertEqual(newcfg2, checkcfg) |
3247 | - |
3248 | - with self.assertRaises(ValueError): |
3249 | - cc_apt_configure.convert_to_new_format(5) |
3250 | - |
3251 | - |
3252 | -# vi: ts=4 expandtab |
3253 | diff --git a/tests/unittests/test_handler/test_handler_apt_source_v1.py b/tests/unittests/test_handler/test_handler_apt_source_v1.py |
3254 | new file mode 100644 |
3255 | index 0000000..d96779c |
3256 | --- /dev/null |
3257 | +++ b/tests/unittests/test_handler/test_handler_apt_source_v1.py |
3258 | @@ -0,0 +1,551 @@ |
3259 | +""" test_handler_apt_source_v1 |
3260 | +Testing various config variations of the apt_source config |
3261 | +This calls all things with v1 format to stress the conversion code on top of |
3262 | +the actually tested code. |
3263 | +""" |
3264 | +import os |
3265 | +import re |
3266 | +import shutil |
3267 | +import tempfile |
3268 | + |
3269 | +try: |
3270 | + from unittest import mock |
3271 | +except ImportError: |
3272 | + import mock |
3273 | +from mock import call |
3274 | + |
3275 | +from cloudinit.config import cc_apt_configure |
3276 | +from cloudinit import gpg |
3277 | +from cloudinit import util |
3278 | + |
3279 | +from ..helpers import TestCase |
3280 | + |
3281 | +EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- |
3282 | +Version: GnuPG v1 |
3283 | + |
3284 | +mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ |
3285 | +NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy |
3286 | +8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0 |
3287 | +HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG |
3288 | +CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29 |
3289 | +OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ |
3290 | +FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm |
3291 | +S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== |
3292 | +=ACB2 |
3293 | +-----END PGP PUBLIC KEY BLOCK-----""" |
3294 | + |
3295 | +ADD_APT_REPO_MATCH = r"^[\w-]+:\w" |
3296 | + |
3297 | + |
3298 | +def load_tfile_or_url(*args, **kwargs): |
3299 | + """load_tfile_or_url |
3300 | + load file and return content after decoding |
3301 | + """ |
3302 | + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
3303 | + |
3304 | + |
3305 | +class FakeDistro(object): |
3306 | + """Fake Distro helper object""" |
3307 | + def update_package_sources(self): |
3308 | + """Fake update_package_sources helper method""" |
3309 | + return |
3310 | + |
3311 | + |
3312 | +class FakeCloud(object): |
3313 | + """Fake Cloud helper object""" |
3314 | + def __init__(self): |
3315 | + self.distro = FakeDistro() |
3316 | + |
3317 | + |
3318 | +class TestAptSourceConfig(TestCase): |
3319 | + """TestAptSourceConfig |
3320 | + Main Class to test apt_source configs |
3321 | + """ |
3322 | + release = "fantastic" |
3323 | + |
3324 | + def setUp(self): |
3325 | + super(TestAptSourceConfig, self).setUp() |
3326 | + self.tmp = tempfile.mkdtemp() |
3327 | + self.addCleanup(shutil.rmtree, self.tmp) |
3328 | + self.aptlistfile = os.path.join(self.tmp, "single-deb.list") |
3329 | + self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") |
3330 | + self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") |
3331 | + self.join = os.path.join |
3332 | + self.matcher = re.compile(ADD_APT_REPO_MATCH).search |
3333 | + # mock fallback filename into writable tmp dir |
3334 | + self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", |
3335 | + "cloud_config_sources.list") |
3336 | + |
3337 | + self.fakecloud = FakeCloud() |
3338 | + |
3339 | + rpatcher = mock.patch("cloudinit.util.lsb_release") |
3340 | + get_rel = rpatcher.start() |
3341 | + get_rel.return_value = {'codename': self.release} |
3342 | + self.addCleanup(rpatcher.stop) |
3343 | + apatcher = mock.patch("cloudinit.util.get_architecture") |
3344 | + get_arch = apatcher.start() |
3345 | + get_arch.return_value = 'amd64' |
3346 | + self.addCleanup(apatcher.stop) |
3347 | + |
3348 | + def _get_default_params(self): |
3349 | + """get_default_params |
3350 | + Get the most basic default mrror and release info to be used in tests |
3351 | + """ |
3352 | + params = {} |
3353 | + params['RELEASE'] = self.release |
3354 | + params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" |
3355 | + return params |
3356 | + |
3357 | + def wrapv1conf(self, cfg): |
3358 | + params = self._get_default_params() |
3359 | + # old v1 list format under old keys, but callabe to main handler |
3360 | + # disable source.list rendering and set mirror to avoid other code |
3361 | + return {'apt_preserve_sources_list': True, |
3362 | + 'apt_mirror': params['MIRROR'], |
3363 | + 'apt_sources': cfg} |
3364 | + |
3365 | + def myjoin(self, *args, **kwargs): |
3366 | + """myjoin - redir into writable tmpdir""" |
3367 | + if (args[0] == "/etc/apt/sources.list.d/" and |
3368 | + args[1] == "cloud_config_sources.list" and |
3369 | + len(args) == 2): |
3370 | + return self.join(self.tmp, args[0].lstrip("/"), args[1]) |
3371 | + else: |
3372 | + return self.join(*args, **kwargs) |
3373 | + |
3374 | + def apt_src_basic(self, filename, cfg): |
3375 | + """apt_src_basic |
3376 | + Test Fix deb source string, has to overwrite mirror conf in params |
3377 | + """ |
3378 | + cfg = self.wrapv1conf(cfg) |
3379 | + |
3380 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3381 | + |
3382 | + self.assertTrue(os.path.isfile(filename)) |
3383 | + |
3384 | + contents = load_tfile_or_url(filename) |
3385 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3386 | + ("deb", "http://archive.ubuntu.com/ubuntu", |
3387 | + "karmic-backports", |
3388 | + "main universe multiverse restricted"), |
3389 | + contents, flags=re.IGNORECASE)) |
3390 | + |
3391 | + def test_apt_src_basic(self): |
3392 | + """Test deb source string, overwrite mirror and filename""" |
3393 | + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
3394 | + ' karmic-backports' |
3395 | + ' main universe multiverse restricted'), |
3396 | + 'filename': self.aptlistfile} |
3397 | + self.apt_src_basic(self.aptlistfile, [cfg]) |
3398 | + |
3399 | + def test_apt_src_basic_dict(self): |
3400 | + """Test deb source string, overwrite mirror and filename (dict)""" |
3401 | + cfg = {self.aptlistfile: {'source': |
3402 | + ('deb http://archive.ubuntu.com/ubuntu' |
3403 | + ' karmic-backports' |
3404 | + ' main universe multiverse restricted')}} |
3405 | + self.apt_src_basic(self.aptlistfile, cfg) |
3406 | + |
3407 | + def apt_src_basic_tri(self, cfg): |
3408 | + """apt_src_basic_tri |
3409 | + Test Fix three deb source string, has to overwrite mirror conf in |
3410 | + params. Test with filenames provided in config. |
3411 | + generic part to check three files with different content |
3412 | + """ |
3413 | + self.apt_src_basic(self.aptlistfile, cfg) |
3414 | + |
3415 | + # extra verify on two extra files of this test |
3416 | + contents = load_tfile_or_url(self.aptlistfile2) |
3417 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3418 | + ("deb", "http://archive.ubuntu.com/ubuntu", |
3419 | + "precise-backports", |
3420 | + "main universe multiverse restricted"), |
3421 | + contents, flags=re.IGNORECASE)) |
3422 | + contents = load_tfile_or_url(self.aptlistfile3) |
3423 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3424 | + ("deb", "http://archive.ubuntu.com/ubuntu", |
3425 | + "lucid-backports", |
3426 | + "main universe multiverse restricted"), |
3427 | + contents, flags=re.IGNORECASE)) |
3428 | + |
3429 | + def test_apt_src_basic_tri(self): |
3430 | + """Test Fix three deb source string with filenames""" |
3431 | + cfg1 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
3432 | + ' karmic-backports' |
3433 | + ' main universe multiverse restricted'), |
3434 | + 'filename': self.aptlistfile} |
3435 | + cfg2 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
3436 | + ' precise-backports' |
3437 | + ' main universe multiverse restricted'), |
3438 | + 'filename': self.aptlistfile2} |
3439 | + cfg3 = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
3440 | + ' lucid-backports' |
3441 | + ' main universe multiverse restricted'), |
3442 | + 'filename': self.aptlistfile3} |
3443 | + self.apt_src_basic_tri([cfg1, cfg2, cfg3]) |
3444 | + |
3445 | + def test_apt_src_basic_dict_tri(self): |
3446 | + """Test Fix three deb source string with filenames (dict)""" |
3447 | + cfg = {self.aptlistfile: {'source': |
3448 | + ('deb http://archive.ubuntu.com/ubuntu' |
3449 | + ' karmic-backports' |
3450 | + ' main universe multiverse restricted')}, |
3451 | + self.aptlistfile2: {'source': |
3452 | + ('deb http://archive.ubuntu.com/ubuntu' |
3453 | + ' precise-backports' |
3454 | + ' main universe multiverse restricted')}, |
3455 | + self.aptlistfile3: {'source': |
3456 | + ('deb http://archive.ubuntu.com/ubuntu' |
3457 | + ' lucid-backports' |
3458 | + ' main universe multiverse restricted')}} |
3459 | + self.apt_src_basic_tri(cfg) |
3460 | + |
3461 | + def test_apt_src_basic_nofn(self): |
3462 | + """Test Fix three deb source string without filenames (dict)""" |
3463 | + cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' |
3464 | + ' karmic-backports' |
3465 | + ' main universe multiverse restricted')} |
3466 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
3467 | + self.apt_src_basic(self.fallbackfn, [cfg]) |
3468 | + |
3469 | + def apt_src_replacement(self, filename, cfg): |
3470 | + """apt_src_replace |
3471 | + Test Autoreplacement of MIRROR and RELEASE in source specs |
3472 | + """ |
3473 | + cfg = self.wrapv1conf(cfg) |
3474 | + params = self._get_default_params() |
3475 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3476 | + |
3477 | + self.assertTrue(os.path.isfile(filename)) |
3478 | + |
3479 | + contents = load_tfile_or_url(filename) |
3480 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3481 | + ("deb", params['MIRROR'], params['RELEASE'], |
3482 | + "multiverse"), |
3483 | + contents, flags=re.IGNORECASE)) |
3484 | + |
3485 | + def test_apt_src_replace(self): |
3486 | + """Test Autoreplacement of MIRROR and RELEASE in source specs""" |
3487 | + cfg = {'source': 'deb $MIRROR $RELEASE multiverse', |
3488 | + 'filename': self.aptlistfile} |
3489 | + self.apt_src_replacement(self.aptlistfile, [cfg]) |
3490 | + |
3491 | + def apt_src_replace_tri(self, cfg): |
3492 | + """apt_src_replace_tri |
3493 | + Test three autoreplacements of MIRROR and RELEASE in source specs with |
3494 | + generic part |
3495 | + """ |
3496 | + self.apt_src_replacement(self.aptlistfile, cfg) |
3497 | + |
3498 | + # extra verify on two extra files of this test |
3499 | + params = self._get_default_params() |
3500 | + contents = load_tfile_or_url(self.aptlistfile2) |
3501 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3502 | + ("deb", params['MIRROR'], params['RELEASE'], |
3503 | + "main"), |
3504 | + contents, flags=re.IGNORECASE)) |
3505 | + contents = load_tfile_or_url(self.aptlistfile3) |
3506 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3507 | + ("deb", params['MIRROR'], params['RELEASE'], |
3508 | + "universe"), |
3509 | + contents, flags=re.IGNORECASE)) |
3510 | + |
3511 | + def test_apt_src_replace_tri(self): |
3512 | + """Test triple Autoreplacement of MIRROR and RELEASE in source specs""" |
3513 | + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', |
3514 | + 'filename': self.aptlistfile} |
3515 | + cfg2 = {'source': 'deb $MIRROR $RELEASE main', |
3516 | + 'filename': self.aptlistfile2} |
3517 | + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', |
3518 | + 'filename': self.aptlistfile3} |
3519 | + self.apt_src_replace_tri([cfg1, cfg2, cfg3]) |
3520 | + |
3521 | + def test_apt_src_replace_dict_tri(self): |
3522 | + """Test triple Autoreplacement in source specs (dict)""" |
3523 | + cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, |
3524 | + 'notused': {'source': 'deb $MIRROR $RELEASE main', |
3525 | + 'filename': self.aptlistfile2}, |
3526 | + self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} |
3527 | + self.apt_src_replace_tri(cfg) |
3528 | + |
3529 | + def test_apt_src_replace_nofn(self): |
3530 | + """Test Autoreplacement of MIRROR and RELEASE in source specs nofile""" |
3531 | + cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} |
3532 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
3533 | + self.apt_src_replacement(self.fallbackfn, [cfg]) |
3534 | + |
3535 | + def apt_src_keyid(self, filename, cfg, keynum): |
3536 | + """apt_src_keyid |
3537 | + Test specification of a source + keyid |
3538 | + """ |
3539 | + cfg = self.wrapv1conf(cfg) |
3540 | + |
3541 | + with mock.patch.object(util, 'subp', |
3542 | + return_value=('fakekey 1234', '')) as mockobj: |
3543 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3544 | + |
3545 | + # check if it added the right ammount of keys |
3546 | + calls = [] |
3547 | + for _ in range(keynum): |
3548 | + calls.append(call(['apt-key', 'add', '-'], |
3549 | + data=b'fakekey 1234', |
3550 | + target=None)) |
3551 | + mockobj.assert_has_calls(calls, any_order=True) |
3552 | + |
3553 | + self.assertTrue(os.path.isfile(filename)) |
3554 | + |
3555 | + contents = load_tfile_or_url(filename) |
3556 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3557 | + ("deb", |
3558 | + ('http://ppa.launchpad.net/smoser/' |
3559 | + 'cloud-init-test/ubuntu'), |
3560 | + "xenial", "main"), |
3561 | + contents, flags=re.IGNORECASE)) |
3562 | + |
3563 | + def test_apt_src_keyid(self): |
3564 | + """Test specification of a source + keyid with filename being set""" |
3565 | + cfg = {'source': ('deb ' |
3566 | + 'http://ppa.launchpad.net/' |
3567 | + 'smoser/cloud-init-test/ubuntu' |
3568 | + ' xenial main'), |
3569 | + 'keyid': "03683F77", |
3570 | + 'filename': self.aptlistfile} |
3571 | + self.apt_src_keyid(self.aptlistfile, [cfg], 1) |
3572 | + |
3573 | + def test_apt_src_keyid_tri(self): |
3574 | + """Test 3x specification of a source + keyid with filename being set""" |
3575 | + cfg1 = {'source': ('deb ' |
3576 | + 'http://ppa.launchpad.net/' |
3577 | + 'smoser/cloud-init-test/ubuntu' |
3578 | + ' xenial main'), |
3579 | + 'keyid': "03683F77", |
3580 | + 'filename': self.aptlistfile} |
3581 | + cfg2 = {'source': ('deb ' |
3582 | + 'http://ppa.launchpad.net/' |
3583 | + 'smoser/cloud-init-test/ubuntu' |
3584 | + ' xenial universe'), |
3585 | + 'keyid': "03683F77", |
3586 | + 'filename': self.aptlistfile2} |
3587 | + cfg3 = {'source': ('deb ' |
3588 | + 'http://ppa.launchpad.net/' |
3589 | + 'smoser/cloud-init-test/ubuntu' |
3590 | + ' xenial multiverse'), |
3591 | + 'keyid': "03683F77", |
3592 | + 'filename': self.aptlistfile3} |
3593 | + |
3594 | + self.apt_src_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) |
3595 | + contents = load_tfile_or_url(self.aptlistfile2) |
3596 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3597 | + ("deb", |
3598 | + ('http://ppa.launchpad.net/smoser/' |
3599 | + 'cloud-init-test/ubuntu'), |
3600 | + "xenial", "universe"), |
3601 | + contents, flags=re.IGNORECASE)) |
3602 | + contents = load_tfile_or_url(self.aptlistfile3) |
3603 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3604 | + ("deb", |
3605 | + ('http://ppa.launchpad.net/smoser/' |
3606 | + 'cloud-init-test/ubuntu'), |
3607 | + "xenial", "multiverse"), |
3608 | + contents, flags=re.IGNORECASE)) |
3609 | + |
3610 | + def test_apt_src_keyid_nofn(self): |
3611 | + """Test specification of a source + keyid without filename being set""" |
3612 | + cfg = {'source': ('deb ' |
3613 | + 'http://ppa.launchpad.net/' |
3614 | + 'smoser/cloud-init-test/ubuntu' |
3615 | + ' xenial main'), |
3616 | + 'keyid': "03683F77"} |
3617 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
3618 | + self.apt_src_keyid(self.fallbackfn, [cfg], 1) |
3619 | + |
3620 | + def apt_src_key(self, filename, cfg): |
3621 | + """apt_src_key |
3622 | + Test specification of a source + key |
3623 | + """ |
3624 | + cfg = self.wrapv1conf([cfg]) |
3625 | + |
3626 | + with mock.patch.object(util, 'subp') as mockobj: |
3627 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3628 | + |
3629 | + mockobj.assert_called_with(['apt-key', 'add', '-'], |
3630 | + data=b'fakekey 4321', target=None) |
3631 | + |
3632 | + self.assertTrue(os.path.isfile(filename)) |
3633 | + |
3634 | + contents = load_tfile_or_url(filename) |
3635 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3636 | + ("deb", |
3637 | + ('http://ppa.launchpad.net/smoser/' |
3638 | + 'cloud-init-test/ubuntu'), |
3639 | + "xenial", "main"), |
3640 | + contents, flags=re.IGNORECASE)) |
3641 | + |
3642 | + def test_apt_src_key(self): |
3643 | + """Test specification of a source + key with filename being set""" |
3644 | + cfg = {'source': ('deb ' |
3645 | + 'http://ppa.launchpad.net/' |
3646 | + 'smoser/cloud-init-test/ubuntu' |
3647 | + ' xenial main'), |
3648 | + 'key': "fakekey 4321", |
3649 | + 'filename': self.aptlistfile} |
3650 | + self.apt_src_key(self.aptlistfile, cfg) |
3651 | + |
3652 | + def test_apt_src_key_nofn(self): |
3653 | + """Test specification of a source + key without filename being set""" |
3654 | + cfg = {'source': ('deb ' |
3655 | + 'http://ppa.launchpad.net/' |
3656 | + 'smoser/cloud-init-test/ubuntu' |
3657 | + ' xenial main'), |
3658 | + 'key': "fakekey 4321"} |
3659 | + with mock.patch.object(os.path, 'join', side_effect=self.myjoin): |
3660 | + self.apt_src_key(self.fallbackfn, cfg) |
3661 | + |
3662 | + def test_apt_src_keyonly(self): |
3663 | + """Test specifying key without source""" |
3664 | + cfg = {'key': "fakekey 4242", |
3665 | + 'filename': self.aptlistfile} |
3666 | + cfg = self.wrapv1conf([cfg]) |
3667 | + |
3668 | + with mock.patch.object(util, 'subp') as mockobj: |
3669 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3670 | + |
3671 | + mockobj.assert_called_once_with(['apt-key', 'add', '-'], |
3672 | + data=b'fakekey 4242', target=None) |
3673 | + |
3674 | + # filename should be ignored on key only |
3675 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
3676 | + |
3677 | + def test_apt_src_keyidonly(self): |
3678 | + """Test specification of a keyid without source""" |
3679 | + cfg = {'keyid': "03683F77", |
3680 | + 'filename': self.aptlistfile} |
3681 | + cfg = self.wrapv1conf([cfg]) |
3682 | + |
3683 | + with mock.patch.object(util, 'subp', |
3684 | + return_value=('fakekey 1212', '')) as mockobj: |
3685 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3686 | + |
3687 | + mockobj.assert_called_with(['apt-key', 'add', '-'], |
3688 | + data=b'fakekey 1212', target=None) |
3689 | + |
3690 | + # filename should be ignored on key only |
3691 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
3692 | + |
3693 | + def apt_src_keyid_real(self, cfg, expectedkey): |
3694 | + """apt_src_keyid_real |
3695 | + Test specification of a keyid without source including |
3696 | + up to addition of the key (add_apt_key_raw mocked to keep the |
3697 | + environment as is) |
3698 | + """ |
3699 | + key = cfg['keyid'] |
3700 | + keyserver = cfg.get('keyserver', 'keyserver.ubuntu.com') |
3701 | + cfg = self.wrapv1conf([cfg]) |
3702 | + |
3703 | + with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: |
3704 | + with mock.patch.object(gpg, 'getkeybyid', |
3705 | + return_value=expectedkey) as mockgetkey: |
3706 | + cc_apt_configure.handle("test", cfg, self.fakecloud, |
3707 | + None, None) |
3708 | + |
3709 | + mockgetkey.assert_called_with(key, keyserver) |
3710 | + mockkey.assert_called_with(expectedkey, None) |
3711 | + |
3712 | + # filename should be ignored on key only |
3713 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
3714 | + |
3715 | + def test_apt_src_keyid_real(self): |
3716 | + """test_apt_src_keyid_real - Test keyid including key add""" |
3717 | + keyid = "03683F77" |
3718 | + cfg = {'keyid': keyid, |
3719 | + 'filename': self.aptlistfile} |
3720 | + |
3721 | + self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
3722 | + |
3723 | + def test_apt_src_longkeyid_real(self): |
3724 | + """test_apt_src_longkeyid_real - Test long keyid including key add""" |
3725 | + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
3726 | + cfg = {'keyid': keyid, |
3727 | + 'filename': self.aptlistfile} |
3728 | + |
3729 | + self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
3730 | + |
3731 | + def test_apt_src_longkeyid_ks_real(self): |
3732 | + """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" |
3733 | + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
3734 | + cfg = {'keyid': keyid, |
3735 | + 'keyserver': 'keys.gnupg.net', |
3736 | + 'filename': self.aptlistfile} |
3737 | + |
3738 | + self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
3739 | + |
3740 | + def test_apt_src_ppa(self): |
3741 | + """Test adding a ppa""" |
3742 | + cfg = {'source': 'ppa:smoser/cloud-init-test', |
3743 | + 'filename': self.aptlistfile} |
3744 | + cfg = self.wrapv1conf([cfg]) |
3745 | + |
3746 | + with mock.patch.object(util, 'subp') as mockobj: |
3747 | + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) |
3748 | + mockobj.assert_called_once_with(['add-apt-repository', |
3749 | + 'ppa:smoser/cloud-init-test'], |
3750 | + target=None) |
3751 | + |
3752 | + # adding ppa should ignore filename (uses add-apt-repository) |
3753 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
3754 | + |
3755 | + def test_apt_src_ppa_tri(self): |
3756 | + """Test adding three ppa's""" |
3757 | + cfg1 = {'source': 'ppa:smoser/cloud-init-test', |
3758 | + 'filename': self.aptlistfile} |
3759 | + cfg2 = {'source': 'ppa:smoser/cloud-init-test2', |
3760 | + 'filename': self.aptlistfile2} |
3761 | + cfg3 = {'source': 'ppa:smoser/cloud-init-test3', |
3762 | + 'filename': self.aptlistfile3} |
3763 | + cfg = self.wrapv1conf([cfg1, cfg2, cfg3]) |
3764 | + |
3765 | + with mock.patch.object(util, 'subp') as mockobj: |
3766 | + cc_apt_configure.handle("test", cfg, self.fakecloud, |
3767 | + None, None) |
3768 | + calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'], |
3769 | + target=None), |
3770 | + call(['add-apt-repository', 'ppa:smoser/cloud-init-test2'], |
3771 | + target=None), |
3772 | + call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'], |
3773 | + target=None)] |
3774 | + mockobj.assert_has_calls(calls, any_order=True) |
3775 | + |
3776 | + # adding ppa should ignore all filenames (uses add-apt-repository) |
3777 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
3778 | + self.assertFalse(os.path.isfile(self.aptlistfile2)) |
3779 | + self.assertFalse(os.path.isfile(self.aptlistfile3)) |
3780 | + |
3781 | + def test_convert_to_new_format(self): |
3782 | + """Test the conversion of old to new format""" |
3783 | + cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', |
3784 | + 'filename': self.aptlistfile} |
3785 | + cfg2 = {'source': 'deb $MIRROR $RELEASE main', |
3786 | + 'filename': self.aptlistfile2} |
3787 | + cfg3 = {'source': 'deb $MIRROR $RELEASE universe', |
3788 | + 'filename': self.aptlistfile3} |
3789 | + checkcfg = {self.aptlistfile: {'filename': self.aptlistfile, |
3790 | + 'source': 'deb $MIRROR $RELEASE ' |
3791 | + 'multiverse'}, |
3792 | + self.aptlistfile2: {'filename': self.aptlistfile2, |
3793 | + 'source': 'deb $MIRROR $RELEASE main'}, |
3794 | + self.aptlistfile3: {'filename': self.aptlistfile3, |
3795 | + 'source': 'deb $MIRROR $RELEASE ' |
3796 | + 'universe'}} |
3797 | + |
3798 | + newcfg = cc_apt_configure.convert_v1_to_v2_apt_format([cfg1, cfg2, |
3799 | + cfg3]) |
3800 | + self.assertEqual(newcfg, checkcfg) |
3801 | + |
3802 | + newcfg2 = cc_apt_configure.convert_v1_to_v2_apt_format(newcfg) |
3803 | + self.assertEqual(newcfg2, checkcfg) |
3804 | + |
3805 | + with self.assertRaises(ValueError): |
3806 | + cc_apt_configure.convert_v1_to_v2_apt_format(5) |
3807 | + |
3808 | + |
3809 | +# vi: ts=4 expandtab |
3810 | diff --git a/tests/unittests/test_handler/test_handler_apt_source_v3.py b/tests/unittests/test_handler/test_handler_apt_source_v3.py |
3811 | new file mode 100644 |
3812 | index 0000000..75556b6 |
3813 | --- /dev/null |
3814 | +++ b/tests/unittests/test_handler/test_handler_apt_source_v3.py |
3815 | @@ -0,0 +1,1103 @@ |
3816 | +"""test_handler_apt_source_v3 |
3817 | +Testing various config variations of the apt_source custom config |
3818 | +This tries to call all in the new v3 format and cares about new features |
3819 | +""" |
3820 | +import glob |
3821 | +import os |
3822 | +import re |
3823 | +import shutil |
3824 | +import socket |
3825 | +import tempfile |
3826 | + |
3827 | +from unittest import TestCase |
3828 | + |
3829 | +try: |
3830 | + from unittest import mock |
3831 | +except ImportError: |
3832 | + import mock |
3833 | +from mock import call |
3834 | + |
3835 | +from cloudinit import cloud |
3836 | +from cloudinit import distros |
3837 | +from cloudinit import gpg |
3838 | +from cloudinit import helpers |
3839 | +from cloudinit import util |
3840 | + |
3841 | +from cloudinit.config import cc_apt_configure |
3842 | +from cloudinit.sources import DataSourceNone |
3843 | + |
3844 | +from .. import helpers as t_help |
3845 | + |
3846 | +EXPECTEDKEY = u"""-----BEGIN PGP PUBLIC KEY BLOCK----- |
3847 | +Version: GnuPG v1 |
3848 | + |
3849 | +mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ |
3850 | +NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy |
3851 | +8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0 |
3852 | +HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG |
3853 | +CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29 |
3854 | +OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ |
3855 | +FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm |
3856 | +S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== |
3857 | +=ACB2 |
3858 | +-----END PGP PUBLIC KEY BLOCK-----""" |
3859 | + |
3860 | +ADD_APT_REPO_MATCH = r"^[\w-]+:\w" |
3861 | + |
3862 | +TARGET = None |
3863 | + |
3864 | + |
3865 | +def load_tfile(*args, **kwargs): |
3866 | + """load_tfile_or_url |
3867 | + load file and return content after decoding |
3868 | + """ |
3869 | + return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
3870 | + |
3871 | + |
3872 | +class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
3873 | + """TestAptSourceConfig |
3874 | + Main Class to test apt configs |
3875 | + """ |
3876 | + def setUp(self): |
3877 | + super(TestAptSourceConfig, self).setUp() |
3878 | + self.tmp = tempfile.mkdtemp() |
3879 | + self.new_root = tempfile.mkdtemp() |
3880 | + self.addCleanup(shutil.rmtree, self.tmp) |
3881 | + self.aptlistfile = os.path.join(self.tmp, "single-deb.list") |
3882 | + self.aptlistfile2 = os.path.join(self.tmp, "single-deb2.list") |
3883 | + self.aptlistfile3 = os.path.join(self.tmp, "single-deb3.list") |
3884 | + self.join = os.path.join |
3885 | + self.matcher = re.compile(ADD_APT_REPO_MATCH).search |
3886 | + |
3887 | + @staticmethod |
3888 | + def _add_apt_sources(*args, **kwargs): |
3889 | + with mock.patch.object(cc_apt_configure, 'update_packages'): |
3890 | + cc_apt_configure.add_apt_sources(*args, **kwargs) |
3891 | + |
3892 | + @staticmethod |
3893 | + def _get_default_params(): |
3894 | + """get_default_params |
3895 | + Get the most basic default mrror and release info to be used in tests |
3896 | + """ |
3897 | + params = {} |
3898 | + params['RELEASE'] = util.lsb_release()['codename'] |
3899 | + arch = 'amd64' |
3900 | + params['MIRROR'] = cc_apt_configure.\ |
3901 | + get_default_mirrors(arch)["PRIMARY"] |
3902 | + return params |
3903 | + |
3904 | + def _myjoin(self, *args, **kwargs): |
3905 | + """_myjoin - redir into writable tmpdir""" |
3906 | + if (args[0] == "/etc/apt/sources.list.d/" and |
3907 | + args[1] == "cloud_config_sources.list" and |
3908 | + len(args) == 2): |
3909 | + return self.join(self.tmp, args[0].lstrip("/"), args[1]) |
3910 | + else: |
3911 | + return self.join(*args, **kwargs) |
3912 | + |
3913 | + def _get_cloud(self, distro, metadata=None): |
3914 | + self.patchUtils(self.new_root) |
3915 | + paths = helpers.Paths({}) |
3916 | + cls = distros.fetch(distro) |
3917 | + mydist = cls(distro, {}, paths) |
3918 | + myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
3919 | + if metadata: |
3920 | + myds.metadata.update(metadata) |
3921 | + return cloud.Cloud(myds, paths, {}, mydist, None) |
3922 | + |
3923 | + def _apt_src_basic(self, filename, cfg): |
3924 | + """_apt_src_basic |
3925 | + Test Fix deb source string, has to overwrite mirror conf in params |
3926 | + """ |
3927 | + params = self._get_default_params() |
3928 | + |
3929 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
3930 | + aa_repo_match=self.matcher) |
3931 | + |
3932 | + self.assertTrue(os.path.isfile(filename)) |
3933 | + |
3934 | + contents = load_tfile(filename) |
3935 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3936 | + ("deb", "http://test.ubuntu.com/ubuntu", |
3937 | + "karmic-backports", |
3938 | + "main universe multiverse restricted"), |
3939 | + contents, flags=re.IGNORECASE)) |
3940 | + |
3941 | + def test_apt_v3_src_basic(self): |
3942 | + """test_apt_v3_src_basic - Test fix deb source string""" |
3943 | + cfg = {self.aptlistfile: {'source': |
3944 | + ('deb http://test.ubuntu.com/ubuntu' |
3945 | + ' karmic-backports' |
3946 | + ' main universe multiverse restricted')}} |
3947 | + self._apt_src_basic(self.aptlistfile, cfg) |
3948 | + |
3949 | + def test_apt_v3_src_basic_tri(self): |
3950 | + """test_apt_v3_src_basic_tri - Test multiple fix deb source strings""" |
3951 | + cfg = {self.aptlistfile: {'source': |
3952 | + ('deb http://test.ubuntu.com/ubuntu' |
3953 | + ' karmic-backports' |
3954 | + ' main universe multiverse restricted')}, |
3955 | + self.aptlistfile2: {'source': |
3956 | + ('deb http://test.ubuntu.com/ubuntu' |
3957 | + ' precise-backports' |
3958 | + ' main universe multiverse restricted')}, |
3959 | + self.aptlistfile3: {'source': |
3960 | + ('deb http://test.ubuntu.com/ubuntu' |
3961 | + ' lucid-backports' |
3962 | + ' main universe multiverse restricted')}} |
3963 | + self._apt_src_basic(self.aptlistfile, cfg) |
3964 | + |
3965 | + # extra verify on two extra files of this test |
3966 | + contents = load_tfile(self.aptlistfile2) |
3967 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3968 | + ("deb", "http://test.ubuntu.com/ubuntu", |
3969 | + "precise-backports", |
3970 | + "main universe multiverse restricted"), |
3971 | + contents, flags=re.IGNORECASE)) |
3972 | + contents = load_tfile(self.aptlistfile3) |
3973 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3974 | + ("deb", "http://test.ubuntu.com/ubuntu", |
3975 | + "lucid-backports", |
3976 | + "main universe multiverse restricted"), |
3977 | + contents, flags=re.IGNORECASE)) |
3978 | + |
3979 | + def _apt_src_replacement(self, filename, cfg): |
3980 | + """apt_src_replace |
3981 | + Test Autoreplacement of MIRROR and RELEASE in source specs |
3982 | + """ |
3983 | + params = self._get_default_params() |
3984 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
3985 | + aa_repo_match=self.matcher) |
3986 | + |
3987 | + self.assertTrue(os.path.isfile(filename)) |
3988 | + |
3989 | + contents = load_tfile(filename) |
3990 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
3991 | + ("deb", params['MIRROR'], params['RELEASE'], |
3992 | + "multiverse"), |
3993 | + contents, flags=re.IGNORECASE)) |
3994 | + |
3995 | + def test_apt_v3_src_replace(self): |
3996 | + """test_apt_v3_src_replace - Test replacement of MIRROR & RELEASE""" |
3997 | + cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}} |
3998 | + self._apt_src_replacement(self.aptlistfile, cfg) |
3999 | + |
4000 | + def test_apt_v3_src_replace_fn(self): |
4001 | + """test_apt_v3_src_replace_fn - Test filename overwritten in dict""" |
4002 | + cfg = {'ignored': {'source': 'deb $MIRROR $RELEASE multiverse', |
4003 | + 'filename': self.aptlistfile}} |
4004 | + # second file should overwrite the dict key |
4005 | + self._apt_src_replacement(self.aptlistfile, cfg) |
4006 | + |
4007 | + def _apt_src_replace_tri(self, cfg): |
4008 | + """_apt_src_replace_tri |
4009 | + Test three autoreplacements of MIRROR and RELEASE in source specs with |
4010 | + generic part |
4011 | + """ |
4012 | + self._apt_src_replacement(self.aptlistfile, cfg) |
4013 | + |
4014 | + # extra verify on two extra files of this test |
4015 | + params = self._get_default_params() |
4016 | + contents = load_tfile(self.aptlistfile2) |
4017 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
4018 | + ("deb", params['MIRROR'], params['RELEASE'], |
4019 | + "main"), |
4020 | + contents, flags=re.IGNORECASE)) |
4021 | + contents = load_tfile(self.aptlistfile3) |
4022 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
4023 | + ("deb", params['MIRROR'], params['RELEASE'], |
4024 | + "universe"), |
4025 | + contents, flags=re.IGNORECASE)) |
4026 | + |
4027 | + def test_apt_v3_src_replace_tri(self): |
4028 | + """test_apt_v3_src_replace_tri - Test multiple replace/overwrites""" |
4029 | + cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, |
4030 | + 'notused': {'source': 'deb $MIRROR $RELEASE main', |
4031 | + 'filename': self.aptlistfile2}, |
4032 | + self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} |
4033 | + self._apt_src_replace_tri(cfg) |
4034 | + |
4035 | + def _apt_src_keyid(self, filename, cfg, keynum): |
4036 | + """_apt_src_keyid |
4037 | + Test specification of a source + keyid |
4038 | + """ |
4039 | + params = self._get_default_params() |
4040 | + |
4041 | + with mock.patch("cloudinit.util.subp", |
4042 | + return_value=('fakekey 1234', '')) as mockobj: |
4043 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4044 | + aa_repo_match=self.matcher) |
4045 | + |
4046 | + # check if it added the right ammount of keys |
4047 | + calls = [] |
4048 | + for _ in range(keynum): |
4049 | + calls.append(call(['apt-key', 'add', '-'], data=b'fakekey 1234', |
4050 | + target=TARGET)) |
4051 | + mockobj.assert_has_calls(calls, any_order=True) |
4052 | + |
4053 | + self.assertTrue(os.path.isfile(filename)) |
4054 | + |
4055 | + contents = load_tfile(filename) |
4056 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
4057 | + ("deb", |
4058 | + ('http://ppa.launchpad.net/smoser/' |
4059 | + 'cloud-init-test/ubuntu'), |
4060 | + "xenial", "main"), |
4061 | + contents, flags=re.IGNORECASE)) |
4062 | + |
4063 | + def test_apt_v3_src_keyid(self): |
4064 | + """test_apt_v3_src_keyid - Test source + keyid with filename""" |
4065 | + cfg = {self.aptlistfile: {'source': ('deb ' |
4066 | + 'http://ppa.launchpad.net/' |
4067 | + 'smoser/cloud-init-test/ubuntu' |
4068 | + ' xenial main'), |
4069 | + 'keyid': "03683F77"}} |
4070 | + self._apt_src_keyid(self.aptlistfile, cfg, 1) |
4071 | + |
4072 | + def test_apt_v3_src_keyid_tri(self): |
4073 | + """test_apt_v3_src_keyid_tri - Test multiple src+key+filen writes""" |
4074 | + cfg = {self.aptlistfile: {'source': ('deb ' |
4075 | + 'http://ppa.launchpad.net/' |
4076 | + 'smoser/cloud-init-test/ubuntu' |
4077 | + ' xenial main'), |
4078 | + 'keyid': "03683F77"}, |
4079 | + 'ignored': {'source': ('deb ' |
4080 | + 'http://ppa.launchpad.net/' |
4081 | + 'smoser/cloud-init-test/ubuntu' |
4082 | + ' xenial universe'), |
4083 | + 'keyid': "03683F77", |
4084 | + 'filename': self.aptlistfile2}, |
4085 | + self.aptlistfile3: {'source': ('deb ' |
4086 | + 'http://ppa.launchpad.net/' |
4087 | + 'smoser/cloud-init-test/ubuntu' |
4088 | + ' xenial multiverse'), |
4089 | + 'keyid': "03683F77"}} |
4090 | + |
4091 | + self._apt_src_keyid(self.aptlistfile, cfg, 3) |
4092 | + contents = load_tfile(self.aptlistfile2) |
4093 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
4094 | + ("deb", |
4095 | + ('http://ppa.launchpad.net/smoser/' |
4096 | + 'cloud-init-test/ubuntu'), |
4097 | + "xenial", "universe"), |
4098 | + contents, flags=re.IGNORECASE)) |
4099 | + contents = load_tfile(self.aptlistfile3) |
4100 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
4101 | + ("deb", |
4102 | + ('http://ppa.launchpad.net/smoser/' |
4103 | + 'cloud-init-test/ubuntu'), |
4104 | + "xenial", "multiverse"), |
4105 | + contents, flags=re.IGNORECASE)) |
4106 | + |
4107 | + def test_apt_v3_src_key(self): |
4108 | + """test_apt_v3_src_key - Test source + key""" |
4109 | + params = self._get_default_params() |
4110 | + cfg = {self.aptlistfile: {'source': ('deb ' |
4111 | + 'http://ppa.launchpad.net/' |
4112 | + 'smoser/cloud-init-test/ubuntu' |
4113 | + ' xenial main'), |
4114 | + 'key': "fakekey 4321"}} |
4115 | + |
4116 | + with mock.patch.object(util, 'subp') as mockobj: |
4117 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4118 | + aa_repo_match=self.matcher) |
4119 | + |
4120 | + mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 4321', |
4121 | + target=TARGET) |
4122 | + |
4123 | + self.assertTrue(os.path.isfile(self.aptlistfile)) |
4124 | + |
4125 | + contents = load_tfile(self.aptlistfile) |
4126 | + self.assertTrue(re.search(r"%s %s %s %s\n" % |
4127 | + ("deb", |
4128 | + ('http://ppa.launchpad.net/smoser/' |
4129 | + 'cloud-init-test/ubuntu'), |
4130 | + "xenial", "main"), |
4131 | + contents, flags=re.IGNORECASE)) |
4132 | + |
4133 | + def test_apt_v3_src_keyonly(self): |
4134 | + """test_apt_v3_src_keyonly - Test key without source""" |
4135 | + params = self._get_default_params() |
4136 | + cfg = {self.aptlistfile: {'key': "fakekey 4242"}} |
4137 | + |
4138 | + with mock.patch.object(util, 'subp') as mockobj: |
4139 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4140 | + aa_repo_match=self.matcher) |
4141 | + |
4142 | + mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 4242', |
4143 | + target=TARGET) |
4144 | + |
4145 | + # filename should be ignored on key only |
4146 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
4147 | + |
4148 | + def test_apt_v3_src_keyidonly(self): |
4149 | + """test_apt_v3_src_keyidonly - Test keyid without source""" |
4150 | + params = self._get_default_params() |
4151 | + cfg = {self.aptlistfile: {'keyid': "03683F77"}} |
4152 | + |
4153 | + with mock.patch.object(util, 'subp', |
4154 | + return_value=('fakekey 1212', '')) as mockobj: |
4155 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4156 | + aa_repo_match=self.matcher) |
4157 | + |
4158 | + mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 1212', |
4159 | + target=TARGET) |
4160 | + |
4161 | + # filename should be ignored on key only |
4162 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
4163 | + |
4164 | + def apt_src_keyid_real(self, cfg, expectedkey): |
4165 | + """apt_src_keyid_real |
4166 | + Test specification of a keyid without source including |
4167 | + up to addition of the key (add_apt_key_raw mocked to keep the |
4168 | + environment as is) |
4169 | + """ |
4170 | + params = self._get_default_params() |
4171 | + |
4172 | + with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: |
4173 | + with mock.patch.object(gpg, 'getkeybyid', |
4174 | + return_value=expectedkey) as mockgetkey: |
4175 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4176 | + aa_repo_match=self.matcher) |
4177 | + |
4178 | + keycfg = cfg[self.aptlistfile] |
4179 | + mockgetkey.assert_called_with(keycfg['keyid'], |
4180 | + keycfg.get('keyserver', |
4181 | + 'keyserver.ubuntu.com')) |
4182 | + mockkey.assert_called_with(expectedkey, TARGET) |
4183 | + |
4184 | + # filename should be ignored on key only |
4185 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
4186 | + |
4187 | + def test_apt_v3_src_keyid_real(self): |
4188 | + """test_apt_v3_src_keyid_real - Test keyid including key add""" |
4189 | + keyid = "03683F77" |
4190 | + cfg = {self.aptlistfile: {'keyid': keyid}} |
4191 | + |
4192 | + self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
4193 | + |
4194 | + def test_apt_v3_src_longkeyid_real(self): |
4195 | + """test_apt_v3_src_longkeyid_real Test long keyid including key add""" |
4196 | + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
4197 | + cfg = {self.aptlistfile: {'keyid': keyid}} |
4198 | + |
4199 | + self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
4200 | + |
4201 | + def test_apt_v3_src_longkeyid_ks_real(self): |
4202 | + """test_apt_v3_src_longkeyid_ks_real Test long keyid from other ks""" |
4203 | + keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" |
4204 | + cfg = {self.aptlistfile: {'keyid': keyid, |
4205 | + 'keyserver': 'keys.gnupg.net'}} |
4206 | + |
4207 | + self.apt_src_keyid_real(cfg, EXPECTEDKEY) |
4208 | + |
4209 | + def test_apt_v3_src_keyid_keyserver(self): |
4210 | + """test_apt_v3_src_keyid_keyserver - Test custom keyserver""" |
4211 | + keyid = "03683F77" |
4212 | + params = self._get_default_params() |
4213 | + cfg = {self.aptlistfile: {'keyid': keyid, |
4214 | + 'keyserver': 'test.random.com'}} |
4215 | + |
4216 | + # in some test environments only *.ubuntu.com is reachable |
4217 | + # so mock the call and check if the config got there |
4218 | + with mock.patch.object(gpg, 'getkeybyid', |
4219 | + return_value="fakekey") as mockgetkey: |
4220 | + with mock.patch.object(cc_apt_configure, |
4221 | + 'add_apt_key_raw') as mockadd: |
4222 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4223 | + aa_repo_match=self.matcher) |
4224 | + |
4225 | + mockgetkey.assert_called_with('03683F77', 'test.random.com') |
4226 | + mockadd.assert_called_with('fakekey', TARGET) |
4227 | + |
4228 | + # filename should be ignored on key only |
4229 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
4230 | + |
4231 | + def test_apt_v3_src_ppa(self): |
4232 | + """test_apt_v3_src_ppa - Test specification of a ppa""" |
4233 | + params = self._get_default_params() |
4234 | + cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'}} |
4235 | + |
4236 | + with mock.patch("cloudinit.util.subp") as mockobj: |
4237 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4238 | + aa_repo_match=self.matcher) |
4239 | + mockobj.assert_any_call(['add-apt-repository', |
4240 | + 'ppa:smoser/cloud-init-test'], target=TARGET) |
4241 | + |
4242 | + # adding ppa should ignore filename (uses add-apt-repository) |
4243 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
4244 | + |
4245 | + def test_apt_v3_src_ppa_tri(self): |
4246 | + """test_apt_v3_src_ppa_tri - Test specification of multiple ppa's""" |
4247 | + params = self._get_default_params() |
4248 | + cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'}, |
4249 | + self.aptlistfile2: {'source': 'ppa:smoser/cloud-init-test2'}, |
4250 | + self.aptlistfile3: {'source': 'ppa:smoser/cloud-init-test3'}} |
4251 | + |
4252 | + with mock.patch("cloudinit.util.subp") as mockobj: |
4253 | + self._add_apt_sources(cfg, TARGET, template_params=params, |
4254 | + aa_repo_match=self.matcher) |
4255 | + calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'], |
4256 | + target=TARGET), |
4257 | + call(['add-apt-repository', 'ppa:smoser/cloud-init-test2'], |
4258 | + target=TARGET), |
4259 | + call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'], |
4260 | + target=TARGET)] |
4261 | + mockobj.assert_has_calls(calls, any_order=True) |
4262 | + |
4263 | + # adding ppa should ignore all filenames (uses add-apt-repository) |
4264 | + self.assertFalse(os.path.isfile(self.aptlistfile)) |
4265 | + self.assertFalse(os.path.isfile(self.aptlistfile2)) |
4266 | + self.assertFalse(os.path.isfile(self.aptlistfile3)) |
4267 | + |
4268 | + @mock.patch("cloudinit.config.cc_apt_configure.util.get_architecture") |
4269 | + def test_apt_v3_list_rename(self, m_get_architecture): |
4270 | + """test_apt_v3_list_rename - Test find mirror and apt list renaming""" |
4271 | + pre = "/var/lib/apt/lists" |
4272 | + # filenames are archive dependent |
4273 | + |
4274 | + arch = 's390x' |
4275 | + m_get_architecture.return_value = arch |
4276 | + component = "ubuntu-ports" |
4277 | + archive = "ports.ubuntu.com" |
4278 | + |
4279 | + cfg = {'primary': [{'arches': ["default"], |
4280 | + 'uri': |
4281 | + 'http://test.ubuntu.com/%s/' % component}], |
4282 | + 'security': [{'arches': ["default"], |
4283 | + 'uri': |
4284 | + 'http://testsec.ubuntu.com/%s/' % component}]} |
4285 | + post = ("%s_dists_%s-updates_InRelease" % |
4286 | + (component, util.lsb_release()['codename'])) |
4287 | + fromfn = ("%s/%s_%s" % (pre, archive, post)) |
4288 | + tofn = ("%s/test.ubuntu.com_%s" % (pre, post)) |
4289 | + |
4290 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, arch) |
4291 | + |
4292 | + self.assertEqual(mirrors['MIRROR'], |
4293 | + "http://test.ubuntu.com/%s/" % component) |
4294 | + self.assertEqual(mirrors['PRIMARY'], |
4295 | + "http://test.ubuntu.com/%s/" % component) |
4296 | + self.assertEqual(mirrors['SECURITY'], |
4297 | + "http://testsec.ubuntu.com/%s/" % component) |
4298 | + |
4299 | + with mock.patch.object(os, 'rename') as mockren: |
4300 | + with mock.patch.object(glob, 'glob', |
4301 | + return_value=[fromfn]): |
4302 | + cc_apt_configure.rename_apt_lists(mirrors, TARGET) |
4303 | + |
4304 | + mockren.assert_any_call(fromfn, tofn) |
4305 | + |
4306 | + @mock.patch("cloudinit.config.cc_apt_configure.util.get_architecture") |
4307 | + def test_apt_v3_list_rename_non_slash(self, m_get_architecture): |
4308 | + target = os.path.join(self.tmp, "rename_non_slash") |
4309 | + apt_lists_d = os.path.join(target, "./" + cc_apt_configure.APT_LISTS) |
4310 | + |
4311 | + m_get_architecture.return_value = 'amd64' |
4312 | + |
4313 | + mirror_path = "some/random/path/" |
4314 | + primary = "http://test.ubuntu.com/" + mirror_path |
4315 | + security = "http://test-security.ubuntu.com/" + mirror_path |
4316 | + mirrors = {'PRIMARY': primary, 'SECURITY': security} |
4317 | + |
4318 | + # these match default archive prefixes |
4319 | + opri_pre = "archive.ubuntu.com_ubuntu_dists_xenial" |
4320 | + osec_pre = "security.ubuntu.com_ubuntu_dists_xenial" |
4321 | + # this one won't match and should not be renamed defaults. |
4322 | + other_pre = "dl.google.com_linux_chrome_deb_dists_stable" |
4323 | + # these are our new expected prefixes |
4324 | + npri_pre = "test.ubuntu.com_some_random_path_dists_xenial" |
4325 | + nsec_pre = "test-security.ubuntu.com_some_random_path_dists_xenial" |
4326 | + |
4327 | + files = [ |
4328 | + # orig prefix, new prefix, suffix |
4329 | + (opri_pre, npri_pre, "_main_binary-amd64_Packages"), |
4330 | + (opri_pre, npri_pre, "_main_binary-amd64_InRelease"), |
4331 | + (opri_pre, npri_pre, "-updates_main_binary-amd64_Packages"), |
4332 | + (opri_pre, npri_pre, "-updates_main_binary-amd64_InRelease"), |
4333 | + (other_pre, other_pre, "_main_binary-amd64_Packages"), |
4334 | + (other_pre, other_pre, "_Release"), |
4335 | + (other_pre, other_pre, "_Release.gpg"), |
4336 | + (osec_pre, nsec_pre, "_InRelease"), |
4337 | + (osec_pre, nsec_pre, "_main_binary-amd64_Packages"), |
4338 | + (osec_pre, nsec_pre, "_universe_binary-amd64_Packages"), |
4339 | + ] |
4340 | + |
4341 | + expected = sorted([npre + suff for opre, npre, suff in files]) |
4342 | + # create files |
4343 | + for (opre, npre, suff) in files: |
4344 | + fpath = os.path.join(apt_lists_d, opre + suff) |
4345 | + util.write_file(fpath, content=fpath) |
4346 | + |
4347 | + cc_apt_configure.rename_apt_lists(mirrors, target) |
4348 | + found = sorted(os.listdir(apt_lists_d)) |
4349 | + self.assertEqual(expected, found) |
4350 | + |
4351 | + @staticmethod |
4352 | + def test_apt_v3_proxy(): |
4353 | + """test_apt_v3_proxy - Test apt_*proxy configuration""" |
4354 | + cfg = {"proxy": "foobar1", |
4355 | + "http_proxy": "foobar2", |
4356 | + "ftp_proxy": "foobar3", |
4357 | + "https_proxy": "foobar4"} |
4358 | + |
4359 | + with mock.patch.object(util, 'write_file') as mockobj: |
4360 | + cc_apt_configure.apply_apt_config(cfg, "proxyfn", "notused") |
4361 | + |
4362 | + mockobj.assert_called_with('proxyfn', |
4363 | + ('Acquire::http::Proxy "foobar1";\n' |
4364 | + 'Acquire::http::Proxy "foobar2";\n' |
4365 | + 'Acquire::ftp::Proxy "foobar3";\n' |
4366 | + 'Acquire::https::Proxy "foobar4";\n')) |
4367 | + |
4368 | + def test_apt_v3_mirror(self): |
4369 | + """test_apt_v3_mirror - Test defining a mirror""" |
4370 | + pmir = "http://us.archive.ubuntu.com/ubuntu/" |
4371 | + smir = "http://security.ubuntu.com/ubuntu/" |
4372 | + cfg = {"primary": [{'arches': ["default"], |
4373 | + "uri": pmir}], |
4374 | + "security": [{'arches': ["default"], |
4375 | + "uri": smir}]} |
4376 | + |
4377 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, 'amd64') |
4378 | + |
4379 | + self.assertEqual(mirrors['MIRROR'], |
4380 | + pmir) |
4381 | + self.assertEqual(mirrors['PRIMARY'], |
4382 | + pmir) |
4383 | + self.assertEqual(mirrors['SECURITY'], |
4384 | + smir) |
4385 | + |
4386 | + def test_apt_v3_mirror_default(self): |
4387 | + """test_apt_v3_mirror_default - Test without defining a mirror""" |
4388 | + arch = 'amd64' |
4389 | + default_mirrors = cc_apt_configure.get_default_mirrors(arch) |
4390 | + pmir = default_mirrors["PRIMARY"] |
4391 | + smir = default_mirrors["SECURITY"] |
4392 | + mycloud = self._get_cloud('ubuntu') |
4393 | + mirrors = cc_apt_configure.find_apt_mirror_info({}, mycloud, arch) |
4394 | + |
4395 | + self.assertEqual(mirrors['MIRROR'], |
4396 | + pmir) |
4397 | + self.assertEqual(mirrors['PRIMARY'], |
4398 | + pmir) |
4399 | + self.assertEqual(mirrors['SECURITY'], |
4400 | + smir) |
4401 | + |
4402 | + def test_apt_v3_mirror_arches(self): |
4403 | + """test_apt_v3_mirror_arches - Test arches selection of mirror""" |
4404 | + pmir = "http://my-primary.ubuntu.com/ubuntu/" |
4405 | + smir = "http://my-security.ubuntu.com/ubuntu/" |
4406 | + arch = 'ppc64el' |
4407 | + cfg = {"primary": [{'arches': ["default"], "uri": "notthis-primary"}, |
4408 | + {'arches': [arch], "uri": pmir}], |
4409 | + "security": [{'arches': ["default"], "uri": "nothis-security"}, |
4410 | + {'arches': [arch], "uri": smir}]} |
4411 | + |
4412 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, arch) |
4413 | + |
4414 | + self.assertEqual(mirrors['PRIMARY'], pmir) |
4415 | + self.assertEqual(mirrors['MIRROR'], pmir) |
4416 | + self.assertEqual(mirrors['SECURITY'], smir) |
4417 | + |
4418 | + def test_apt_v3_mirror_arches_default(self): |
4419 | + """test_apt_v3_mirror_arches - Test falling back to default arch""" |
4420 | + pmir = "http://us.archive.ubuntu.com/ubuntu/" |
4421 | + smir = "http://security.ubuntu.com/ubuntu/" |
4422 | + cfg = {"primary": [{'arches': ["default"], |
4423 | + "uri": pmir}, |
4424 | + {'arches': ["thisarchdoesntexist"], |
4425 | + "uri": "notthis"}], |
4426 | + "security": [{'arches': ["thisarchdoesntexist"], |
4427 | + "uri": "nothat"}, |
4428 | + {'arches': ["default"], |
4429 | + "uri": smir}]} |
4430 | + |
4431 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, 'amd64') |
4432 | + |
4433 | + self.assertEqual(mirrors['MIRROR'], |
4434 | + pmir) |
4435 | + self.assertEqual(mirrors['PRIMARY'], |
4436 | + pmir) |
4437 | + self.assertEqual(mirrors['SECURITY'], |
4438 | + smir) |
4439 | + |
4440 | + @mock.patch("cloudinit.config.cc_apt_configure.util.get_architecture") |
4441 | + def test_apt_v3_get_def_mir_non_intel_no_arch(self, m_get_architecture): |
4442 | + arch = 'ppc64el' |
4443 | + m_get_architecture.return_value = arch |
4444 | + expected = {'PRIMARY': 'http://ports.ubuntu.com/ubuntu-ports', |
4445 | + 'SECURITY': 'http://ports.ubuntu.com/ubuntu-ports'} |
4446 | + self.assertEqual(expected, cc_apt_configure.get_default_mirrors()) |
4447 | + |
4448 | + def test_apt_v3_get_default_mirrors_non_intel_with_arch(self): |
4449 | + found = cc_apt_configure.get_default_mirrors('ppc64el') |
4450 | + |
4451 | + expected = {'PRIMARY': 'http://ports.ubuntu.com/ubuntu-ports', |
4452 | + 'SECURITY': 'http://ports.ubuntu.com/ubuntu-ports'} |
4453 | + self.assertEqual(expected, found) |
4454 | + |
4455 | + def test_apt_v3_mirror_arches_sysdefault(self): |
4456 | + """test_apt_v3_mirror_arches - Test arches fallback to sys default""" |
4457 | + arch = 'amd64' |
4458 | + default_mirrors = cc_apt_configure.get_default_mirrors(arch) |
4459 | + pmir = default_mirrors["PRIMARY"] |
4460 | + smir = default_mirrors["SECURITY"] |
4461 | + mycloud = self._get_cloud('ubuntu') |
4462 | + cfg = {"primary": [{'arches': ["thisarchdoesntexist_64"], |
4463 | + "uri": "notthis"}, |
4464 | + {'arches': ["thisarchdoesntexist"], |
4465 | + "uri": "notthiseither"}], |
4466 | + "security": [{'arches': ["thisarchdoesntexist"], |
4467 | + "uri": "nothat"}, |
4468 | + {'arches': ["thisarchdoesntexist_64"], |
4469 | + "uri": "nothateither"}]} |
4470 | + |
4471 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) |
4472 | + |
4473 | + self.assertEqual(mirrors['MIRROR'], pmir) |
4474 | + self.assertEqual(mirrors['PRIMARY'], pmir) |
4475 | + self.assertEqual(mirrors['SECURITY'], smir) |
4476 | + |
4477 | + def test_apt_v3_mirror_search(self): |
4478 | + """test_apt_v3_mirror_search - Test searching mirrors in a list |
4479 | + mock checks to avoid relying on network connectivity""" |
4480 | + pmir = "http://us.archive.ubuntu.com/ubuntu/" |
4481 | + smir = "http://security.ubuntu.com/ubuntu/" |
4482 | + cfg = {"primary": [{'arches': ["default"], |
4483 | + "search": ["pfailme", pmir]}], |
4484 | + "security": [{'arches': ["default"], |
4485 | + "search": ["sfailme", smir]}]} |
4486 | + |
4487 | + with mock.patch.object(cc_apt_configure, 'search_for_mirror', |
4488 | + side_effect=[pmir, smir]) as mocksearch: |
4489 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, |
4490 | + 'amd64') |
4491 | + |
4492 | + calls = [call(["pfailme", pmir]), |
4493 | + call(["sfailme", smir])] |
4494 | + mocksearch.assert_has_calls(calls) |
4495 | + |
4496 | + self.assertEqual(mirrors['MIRROR'], |
4497 | + pmir) |
4498 | + self.assertEqual(mirrors['PRIMARY'], |
4499 | + pmir) |
4500 | + self.assertEqual(mirrors['SECURITY'], |
4501 | + smir) |
4502 | + |
4503 | + def test_apt_v3_mirror_search_many2(self): |
4504 | + """test_apt_v3_mirror_search_many3 - Test both mirrors specs at once""" |
4505 | + pmir = "http://us.archive.ubuntu.com/ubuntu/" |
4506 | + smir = "http://security.ubuntu.com/ubuntu/" |
4507 | + cfg = {"primary": [{'arches': ["default"], |
4508 | + "uri": pmir, |
4509 | + "search": ["pfailme", "foo"]}], |
4510 | + "security": [{'arches': ["default"], |
4511 | + "uri": smir, |
4512 | + "search": ["sfailme", "bar"]}]} |
4513 | + |
4514 | + arch = 'amd64' |
4515 | + |
4516 | + # should be called only once per type, despite two mirror configs |
4517 | + mycloud = None |
4518 | + with mock.patch.object(cc_apt_configure, 'get_mirror', |
4519 | + return_value="http://mocked/foo") as mockgm: |
4520 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) |
4521 | + calls = [call(cfg, 'primary', arch, mycloud), |
4522 | + call(cfg, 'security', arch, mycloud)] |
4523 | + mockgm.assert_has_calls(calls) |
4524 | + |
4525 | + # should not be called, since primary is specified |
4526 | + with mock.patch.object(cc_apt_configure, |
4527 | + 'search_for_mirror') as mockse: |
4528 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, None, arch) |
4529 | + mockse.assert_not_called() |
4530 | + |
4531 | + self.assertEqual(mirrors['MIRROR'], |
4532 | + pmir) |
4533 | + self.assertEqual(mirrors['PRIMARY'], |
4534 | + pmir) |
4535 | + self.assertEqual(mirrors['SECURITY'], |
4536 | + smir) |
4537 | + |
4538 | + def test_apt_v3_url_resolvable(self): |
4539 | + """test_apt_v3_url_resolvable - Test resolving urls""" |
4540 | + |
4541 | + with mock.patch.object(util, 'is_resolvable') as mockresolve: |
4542 | + util.is_resolvable_url("http://1.2.3.4/ubuntu") |
4543 | + mockresolve.assert_called_with("1.2.3.4") |
4544 | + |
4545 | + with mock.patch.object(util, 'is_resolvable') as mockresolve: |
4546 | + util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu") |
4547 | + mockresolve.assert_called_with("us.archive.ubuntu.com") |
4548 | + |
4549 | + # former tests can leave this set (or not if the test is ran directly) |
4550 | + # do a hard reset to ensure a stable result |
4551 | + util._DNS_REDIRECT_IP = None |
4552 | + bad = [(None, None, None, "badname", ["10.3.2.1"])] |
4553 | + good = [(None, None, None, "goodname", ["10.2.3.4"])] |
4554 | + with mock.patch.object(socket, 'getaddrinfo', |
4555 | + side_effect=[bad, bad, bad, good, |
4556 | + good]) as mocksock: |
4557 | + ret = util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu") |
4558 | + ret2 = util.is_resolvable_url("http://1.2.3.4/ubuntu") |
4559 | + mocksock.assert_any_call('does-not-exist.example.com.', None, |
4560 | + 0, 0, 1, 2) |
4561 | + mocksock.assert_any_call('example.invalid.', None, 0, 0, 1, 2) |
4562 | + mocksock.assert_any_call('us.archive.ubuntu.com', None) |
4563 | + mocksock.assert_any_call('1.2.3.4', None) |
4564 | + |
4565 | + self.assertTrue(ret) |
4566 | + self.assertTrue(ret2) |
4567 | + |
4568 | + # side effect need only bad ret after initial call |
4569 | + with mock.patch.object(socket, 'getaddrinfo', |
4570 | + side_effect=[bad]) as mocksock: |
4571 | + ret3 = util.is_resolvable_url("http://failme.com/ubuntu") |
4572 | + calls = [call('failme.com', None)] |
4573 | + mocksock.assert_has_calls(calls) |
4574 | + self.assertFalse(ret3) |
4575 | + |
4576 | + def test_apt_v3_disable_suites(self): |
4577 | + """test_disable_suites - disable_suites with many configurations""" |
4578 | + release = "xenial" |
4579 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4580 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4581 | +deb http://ubuntu.com//ubuntu xenial-security main |
4582 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4583 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4584 | + |
4585 | + # disable nothing |
4586 | + disabled = [] |
4587 | + expect = """deb http://ubuntu.com//ubuntu xenial main |
4588 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4589 | +deb http://ubuntu.com//ubuntu xenial-security main |
4590 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4591 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4592 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4593 | + self.assertEqual(expect, result) |
4594 | + |
4595 | + # single disable release suite |
4596 | + disabled = ["$RELEASE"] |
4597 | + expect = """\ |
4598 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu xenial main |
4599 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4600 | +deb http://ubuntu.com//ubuntu xenial-security main |
4601 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4602 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4603 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4604 | + self.assertEqual(expect, result) |
4605 | + |
4606 | + # single disable other suite |
4607 | + disabled = ["$RELEASE-updates"] |
4608 | + expect = ("""deb http://ubuntu.com//ubuntu xenial main |
4609 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu""" |
4610 | + """ xenial-updates main |
4611 | +deb http://ubuntu.com//ubuntu xenial-security main |
4612 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4613 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""") |
4614 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4615 | + self.assertEqual(expect, result) |
4616 | + |
4617 | + # multi disable |
4618 | + disabled = ["$RELEASE-updates", "$RELEASE-security"] |
4619 | + expect = ("""deb http://ubuntu.com//ubuntu xenial main |
4620 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4621 | + """xenial-updates main |
4622 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4623 | + """xenial-security main |
4624 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4625 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""") |
4626 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4627 | + self.assertEqual(expect, result) |
4628 | + |
4629 | + # multi line disable (same suite multiple times in input) |
4630 | + disabled = ["$RELEASE-updates", "$RELEASE-security"] |
4631 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4632 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4633 | +deb http://ubuntu.com//ubuntu xenial-security main |
4634 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4635 | +deb http://UBUNTU.com//ubuntu xenial-updates main |
4636 | +deb http://UBUNTU.COM//ubuntu xenial-updates main |
4637 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4638 | + expect = ("""deb http://ubuntu.com//ubuntu xenial main |
4639 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4640 | + """xenial-updates main |
4641 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4642 | + """xenial-security main |
4643 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4644 | +# suite disabled by cloud-init: deb http://UBUNTU.com//ubuntu """ |
4645 | + """xenial-updates main |
4646 | +# suite disabled by cloud-init: deb http://UBUNTU.COM//ubuntu """ |
4647 | + """xenial-updates main |
4648 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""") |
4649 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4650 | + self.assertEqual(expect, result) |
4651 | + |
4652 | + # comment in input |
4653 | + disabled = ["$RELEASE-updates", "$RELEASE-security"] |
4654 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4655 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4656 | +deb http://ubuntu.com//ubuntu xenial-security main |
4657 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4658 | +#foo |
4659 | +#deb http://UBUNTU.com//ubuntu xenial-updates main |
4660 | +deb http://UBUNTU.COM//ubuntu xenial-updates main |
4661 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4662 | + expect = ("""deb http://ubuntu.com//ubuntu xenial main |
4663 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4664 | + """xenial-updates main |
4665 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4666 | + """xenial-security main |
4667 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4668 | +#foo |
4669 | +#deb http://UBUNTU.com//ubuntu xenial-updates main |
4670 | +# suite disabled by cloud-init: deb http://UBUNTU.COM//ubuntu """ |
4671 | + """xenial-updates main |
4672 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""") |
4673 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4674 | + self.assertEqual(expect, result) |
4675 | + |
4676 | + # single disable custom suite |
4677 | + disabled = ["foobar"] |
4678 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4679 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4680 | +deb http://ubuntu.com//ubuntu xenial-security main |
4681 | +deb http://ubuntu.com/ubuntu/ foobar main""" |
4682 | + expect = """deb http://ubuntu.com//ubuntu xenial main |
4683 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4684 | +deb http://ubuntu.com//ubuntu xenial-security main |
4685 | +# suite disabled by cloud-init: deb http://ubuntu.com/ubuntu/ foobar main""" |
4686 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4687 | + self.assertEqual(expect, result) |
4688 | + |
4689 | + # single disable non existing suite |
4690 | + disabled = ["foobar"] |
4691 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4692 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4693 | +deb http://ubuntu.com//ubuntu xenial-security main |
4694 | +deb http://ubuntu.com/ubuntu/ notfoobar main""" |
4695 | + expect = """deb http://ubuntu.com//ubuntu xenial main |
4696 | +deb http://ubuntu.com//ubuntu xenial-updates main |
4697 | +deb http://ubuntu.com//ubuntu xenial-security main |
4698 | +deb http://ubuntu.com/ubuntu/ notfoobar main""" |
4699 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4700 | + self.assertEqual(expect, result) |
4701 | + |
4702 | + # single disable suite with option |
4703 | + disabled = ["$RELEASE-updates"] |
4704 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4705 | +deb [a=b] http://ubu.com//ubu xenial-updates main |
4706 | +deb http://ubuntu.com//ubuntu xenial-security main |
4707 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4708 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4709 | + expect = ("""deb http://ubuntu.com//ubuntu xenial main |
4710 | +# suite disabled by cloud-init: deb [a=b] http://ubu.com//ubu """ |
4711 | + """xenial-updates main |
4712 | +deb http://ubuntu.com//ubuntu xenial-security main |
4713 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4714 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""") |
4715 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4716 | + self.assertEqual(expect, result) |
4717 | + |
4718 | + # single disable suite with more options and auto $RELEASE expansion |
4719 | + disabled = ["updates"] |
4720 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4721 | +deb [a=b c=d] http://ubu.com//ubu xenial-updates main |
4722 | +deb http://ubuntu.com//ubuntu xenial-security main |
4723 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4724 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4725 | + expect = """deb http://ubuntu.com//ubuntu xenial main |
4726 | +# suite disabled by cloud-init: deb [a=b c=d] \ |
4727 | +http://ubu.com//ubu xenial-updates main |
4728 | +deb http://ubuntu.com//ubuntu xenial-security main |
4729 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4730 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4731 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4732 | + self.assertEqual(expect, result) |
4733 | + |
4734 | + # single disable suite while options at others |
4735 | + disabled = ["$RELEASE-security"] |
4736 | + orig = """deb http://ubuntu.com//ubuntu xenial main |
4737 | +deb [arch=foo] http://ubuntu.com//ubuntu xenial-updates main |
4738 | +deb http://ubuntu.com//ubuntu xenial-security main |
4739 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4740 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" |
4741 | + expect = ("""deb http://ubuntu.com//ubuntu xenial main |
4742 | +deb [arch=foo] http://ubuntu.com//ubuntu xenial-updates main |
4743 | +# suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ |
4744 | + """xenial-security main |
4745 | +deb-src http://ubuntu.com//ubuntu universe multiverse |
4746 | +deb http://ubuntu.com/ubuntu/ xenial-proposed main""") |
4747 | + result = cc_apt_configure.disable_suites(disabled, orig, release) |
4748 | + self.assertEqual(expect, result) |
4749 | + |
4750 | + def test_disable_suites_blank_lines(self): |
4751 | + """test_disable_suites_blank_lines - ensure blank lines allowed""" |
4752 | + lines = ["deb %(repo)s %(rel)s main universe", |
4753 | + "", |
4754 | + "deb %(repo)s %(rel)s-updates main universe", |
4755 | + " # random comment", |
4756 | + "#comment here", |
4757 | + ""] |
4758 | + rel = "trusty" |
4759 | + repo = 'http://example.com/mirrors/ubuntu' |
4760 | + orig = "\n".join(lines) % {'repo': repo, 'rel': rel} |
4761 | + self.assertEqual( |
4762 | + orig, cc_apt_configure.disable_suites(["proposed"], orig, rel)) |
4763 | + |
4764 | + def test_apt_v3_mirror_search_dns(self): |
4765 | + """test_apt_v3_mirror_search_dns - Test searching dns patterns""" |
4766 | + pmir = "phit" |
4767 | + smir = "shit" |
4768 | + arch = 'amd64' |
4769 | + mycloud = self._get_cloud('ubuntu') |
4770 | + cfg = {"primary": [{'arches': ["default"], |
4771 | + "search_dns": True}], |
4772 | + "security": [{'arches': ["default"], |
4773 | + "search_dns": True}]} |
4774 | + |
4775 | + with mock.patch.object(cc_apt_configure, 'get_mirror', |
4776 | + return_value="http://mocked/foo") as mockgm: |
4777 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) |
4778 | + calls = [call(cfg, 'primary', arch, mycloud), |
4779 | + call(cfg, 'security', arch, mycloud)] |
4780 | + mockgm.assert_has_calls(calls) |
4781 | + |
4782 | + with mock.patch.object(cc_apt_configure, 'search_for_mirror_dns', |
4783 | + return_value="http://mocked/foo") as mocksdns: |
4784 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) |
4785 | + calls = [call(True, 'primary', cfg, mycloud), |
4786 | + call(True, 'security', cfg, mycloud)] |
4787 | + mocksdns.assert_has_calls(calls) |
4788 | + |
4789 | + # first return is for the non-dns call before |
4790 | + with mock.patch.object(cc_apt_configure, 'search_for_mirror', |
4791 | + side_effect=[None, pmir, None, smir]) as mockse: |
4792 | + mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) |
4793 | + |
4794 | + calls = [call(None), |
4795 | + call(['http://ubuntu-mirror.localdomain/ubuntu', |
4796 | + 'http://ubuntu-mirror/ubuntu']), |
4797 | + call(None), |
4798 | + call(['http://ubuntu-security-mirror.localdomain/ubuntu', |
4799 | + 'http://ubuntu-security-mirror/ubuntu'])] |
4800 | + mockse.assert_has_calls(calls) |
4801 | + |
4802 | + self.assertEqual(mirrors['MIRROR'], |
4803 | + pmir) |
4804 | + self.assertEqual(mirrors['PRIMARY'], |
4805 | + pmir) |
4806 | + self.assertEqual(mirrors['SECURITY'], |
4807 | + smir) |
4808 | + |
4809 | + |
4810 | +class TestDebconfSelections(TestCase): |
4811 | + |
4812 | + @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") |
4813 | + def test_no_set_sel_if_none_to_set(self, m_set_sel): |
4814 | + cc_apt_configure.apply_debconf_selections({'foo': 'bar'}) |
4815 | + m_set_sel.assert_not_called() |
4816 | + |
4817 | + @mock.patch("cloudinit.config.cc_apt_configure." |
4818 | + "debconf_set_selections") |
4819 | + @mock.patch("cloudinit.config.cc_apt_configure." |
4820 | + "util.get_installed_packages") |
4821 | + def test_set_sel_call_has_expected_input(self, m_get_inst, m_set_sel): |
4822 | + data = { |
4823 | + 'set1': 'pkga pkga/q1 mybool false', |
4824 | + 'set2': ('pkgb\tpkgb/b1\tstr\tthis is a string\n' |
4825 | + 'pkgc\tpkgc/ip\tstring\t10.0.0.1')} |
4826 | + lines = '\n'.join(data.values()).split('\n') |
4827 | + |
4828 | + m_get_inst.return_value = ["adduser", "apparmor"] |
4829 | + m_set_sel.return_value = None |
4830 | + |
4831 | + cc_apt_configure.apply_debconf_selections({'debconf_selections': data}) |
4832 | + self.assertTrue(m_get_inst.called) |
4833 | + self.assertEqual(m_set_sel.call_count, 1) |
4834 | + |
4835 | + # assumes called with *args value. |
4836 | + selections = m_set_sel.call_args_list[0][0][0].decode() |
4837 | + |
4838 | + missing = [l for l in lines if l not in selections.splitlines()] |
4839 | + self.assertEqual([], missing) |
4840 | + |
4841 | + @mock.patch("cloudinit.config.cc_apt_configure.dpkg_reconfigure") |
4842 | + @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") |
4843 | + @mock.patch("cloudinit.config.cc_apt_configure." |
4844 | + "util.get_installed_packages") |
4845 | + def test_reconfigure_if_intersection(self, m_get_inst, m_set_sel, |
4846 | + m_dpkg_r): |
4847 | + data = { |
4848 | + 'set1': 'pkga pkga/q1 mybool false', |
4849 | + 'set2': ('pkgb\tpkgb/b1\tstr\tthis is a string\n' |
4850 | + 'pkgc\tpkgc/ip\tstring\t10.0.0.1'), |
4851 | + 'cloud-init': ('cloud-init cloud-init/datasources' |
4852 | + 'multiselect MAAS')} |
4853 | + |
4854 | + m_set_sel.return_value = None |
4855 | + m_get_inst.return_value = ["adduser", "apparmor", "pkgb", |
4856 | + "cloud-init", 'zdog'] |
4857 | + |
4858 | + cc_apt_configure.apply_debconf_selections({'debconf_selections': data}) |
4859 | + |
4860 | + # reconfigure should be called with the intersection |
4861 | + # of (packages in config, packages installed) |
4862 | + self.assertEqual(m_dpkg_r.call_count, 1) |
4863 | + # assumes called with *args (dpkg_reconfigure([a,b,c], target=)) |
4864 | + packages = m_dpkg_r.call_args_list[0][0][0] |
4865 | + self.assertEqual(set(['cloud-init', 'pkgb']), set(packages)) |
4866 | + |
4867 | + @mock.patch("cloudinit.config.cc_apt_configure.dpkg_reconfigure") |
4868 | + @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") |
4869 | + @mock.patch("cloudinit.config.cc_apt_configure." |
4870 | + "util.get_installed_packages") |
4871 | + def test_reconfigure_if_no_intersection(self, m_get_inst, m_set_sel, |
4872 | + m_dpkg_r): |
4873 | + data = {'set1': 'pkga pkga/q1 mybool false'} |
4874 | + |
4875 | + m_get_inst.return_value = ["adduser", "apparmor", "pkgb", |
4876 | + "cloud-init", 'zdog'] |
4877 | + m_set_sel.return_value = None |
4878 | + |
4879 | + cc_apt_configure.apply_debconf_selections({'debconf_selections': data}) |
4880 | + |
4881 | + self.assertTrue(m_get_inst.called) |
4882 | + self.assertEqual(m_dpkg_r.call_count, 0) |
4883 | + |
4884 | + @mock.patch("cloudinit.config.cc_apt_configure.util.subp") |
4885 | + def test_dpkg_reconfigure_does_reconfigure(self, m_subp): |
4886 | + target = "/foo-target" |
4887 | + |
4888 | + # due to the way the cleaners are called (via dictionary reference) |
4889 | + # mocking clean_cloud_init directly does not work. So we mock |
4890 | + # the CONFIG_CLEANERS dictionary and assert our cleaner is called. |
4891 | + ci_cleaner = mock.MagicMock() |
4892 | + with mock.patch.dict(("cloudinit.config.cc_apt_configure." |
4893 | + "CONFIG_CLEANERS"), |
4894 | + values={'cloud-init': ci_cleaner}, clear=True): |
4895 | + cc_apt_configure.dpkg_reconfigure(['pkga', 'cloud-init'], |
4896 | + target=target) |
4897 | + # cloud-init is actually the only package we have a cleaner for |
4898 | + # so for now, its the only one that should reconfigured |
4899 | + self.assertTrue(m_subp.called) |
4900 | + ci_cleaner.assert_called_with(target) |
4901 | + self.assertEqual(m_subp.call_count, 1) |
4902 | + found = m_subp.call_args_list[0][0][0] |
4903 | + expected = ['dpkg-reconfigure', '--frontend=noninteractive', |
4904 | + 'cloud-init'] |
4905 | + self.assertEqual(expected, found) |
4906 | + |
4907 | + @mock.patch("cloudinit.config.cc_apt_configure.util.subp") |
4908 | + def test_dpkg_reconfigure_not_done_on_no_data(self, m_subp): |
4909 | + cc_apt_configure.dpkg_reconfigure([]) |
4910 | + m_subp.assert_not_called() |
4911 | + |
4912 | + @mock.patch("cloudinit.config.cc_apt_configure.util.subp") |
4913 | + def test_dpkg_reconfigure_not_done_if_no_cleaners(self, m_subp): |
4914 | + cc_apt_configure.dpkg_reconfigure(['pkgfoo', 'pkgbar']) |
4915 | + m_subp.assert_not_called() |
4916 | + |
4917 | +# |
4918 | +# vi: ts=4 expandtab |
4919 | diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py |
4920 | index 73369cd..d2031f5 100644 |
4921 | --- a/tests/unittests/test_util.py |
4922 | +++ b/tests/unittests/test_util.py |
4923 | @@ -508,4 +508,73 @@ class TestReadSeeded(helpers.TestCase): |
4924 | self.assertEqual(found_md, {'key1': 'val1'}) |
4925 | self.assertEqual(found_ud, ud) |
4926 | |
4927 | + |
4928 | +class TestSubp(helpers.TestCase): |
4929 | + |
4930 | + stdin2err = ['bash', '-c', 'cat >&2'] |
4931 | + stdin2out = ['cat'] |
4932 | + utf8_invalid = b'ab\xaadef' |
4933 | + utf8_valid = b'start \xc3\xa9 end' |
4934 | + utf8_valid_2 = b'd\xc3\xa9j\xc8\xa7' |
4935 | + |
4936 | + def printf_cmd(self, *args): |
4937 | + # bash's printf supports \xaa. So does /usr/bin/printf |
4938 | + # but by using bash, we remove dependency on another program. |
4939 | + return(['bash', '-c', 'printf "$@"', 'printf'] + list(args)) |
4940 | + |
4941 | + def test_subp_handles_utf8(self): |
4942 | + # The given bytes contain utf-8 accented characters as seen in e.g. |
4943 | + # the "deja dup" package in Ubuntu. |
4944 | + cmd = self.printf_cmd(self.utf8_valid_2) |
4945 | + (out, _err) = util.subp(cmd, capture=True) |
4946 | + self.assertEqual(out, self.utf8_valid_2.decode('utf-8')) |
4947 | + |
4948 | + def test_subp_respects_decode_false(self): |
4949 | + (out, err) = util.subp(self.stdin2out, capture=True, decode=False, |
4950 | + data=self.utf8_valid) |
4951 | + self.assertTrue(isinstance(out, bytes)) |
4952 | + self.assertTrue(isinstance(err, bytes)) |
4953 | + self.assertEqual(out, self.utf8_valid) |
4954 | + |
4955 | + def test_subp_decode_ignore(self): |
4956 | + # this executes a string that writes invalid utf-8 to stdout |
4957 | + (out, _err) = util.subp(self.printf_cmd('abc\\xaadef'), |
4958 | + capture=True, decode='ignore') |
4959 | + self.assertEqual(out, 'abcdef') |
4960 | + |
4961 | + def test_subp_decode_strict_valid_utf8(self): |
4962 | + (out, _err) = util.subp(self.stdin2out, capture=True, |
4963 | + decode='strict', data=self.utf8_valid) |
4964 | + self.assertEqual(out, self.utf8_valid.decode('utf-8')) |
4965 | + |
4966 | + def test_subp_decode_invalid_utf8_replaces(self): |
4967 | + (out, _err) = util.subp(self.stdin2out, capture=True, |
4968 | + data=self.utf8_invalid) |
4969 | + expected = self.utf8_invalid.decode('utf-8', errors='replace') |
4970 | + self.assertEqual(out, expected) |
4971 | + |
4972 | + def test_subp_decode_strict_raises(self): |
4973 | + args = [] |
4974 | + kwargs = {'args': self.stdin2out, 'capture': True, |
4975 | + 'decode': 'strict', 'data': self.utf8_invalid} |
4976 | + self.assertRaises(UnicodeDecodeError, util.subp, *args, **kwargs) |
4977 | + |
4978 | + def test_subp_capture_stderr(self): |
4979 | + data = b'hello world' |
4980 | + (out, err) = util.subp(self.stdin2err, capture=True, |
4981 | + decode=False, data=data) |
4982 | + self.assertEqual(err, data) |
4983 | + self.assertEqual(out, b'') |
4984 | + |
4985 | + def test_returns_none_if_no_capture(self): |
4986 | + (out, err) = util.subp(self.stdin2out, data=b'', capture=False) |
4987 | + self.assertEqual(err, None) |
4988 | + self.assertEqual(out, None) |
4989 | + |
4990 | + def test_bunch_of_slashes_in_path(self): |
4991 | + self.assertEqual("/target/my/path/", |
4992 | + util.target_path("/target/", "//my/path/")) |
4993 | + self.assertEqual("/target/my/path/", |
4994 | + util.target_path("/target/", "///my/path/")) |
4995 | + |
4996 | # vi: ts=4 expandtab |