~smoser/curtin/trunk.bzr-dead

« back to all changes in this revision

Viewing changes to curtin/commands/apt_config.py

  • Committer: Scott Moser
  • Date: 2017-12-20 17:33:03 UTC
  • Revision ID: smoser@ubuntu.com-20171220173303-29gha5qb8wpqrd40
README: Mention move of revision control to git.

curtin development has moved its revision control to git.
It is available at
  https://code.launchpad.net/curtin

Clone with
  git clone https://git.launchpad.net/curtin
or
  git clone git+ssh://git.launchpad.net/curtin

For more information see
  http://curtin.readthedocs.io/en/latest/topics/development.html

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#   Copyright (C) 2016 Canonical Ltd.
2
 
#
3
 
#   Author: Christian Ehrhardt <christian.ehrhardt@canonical.com>
4
 
#
5
 
#   Curtin is free software: you can redistribute it and/or modify it under
6
 
#   the terms of the GNU Affero General Public License as published by the
7
 
#   Free Software Foundation, either version 3 of the License, or (at your
8
 
#   option) any later version.
9
 
#
10
 
#   Curtin is distributed in the hope that it will be useful, but WITHOUT ANY
11
 
#   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
 
#   FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
13
 
#   more details.
14
 
#
15
 
#   You should have received a copy of the GNU Affero General Public License
16
 
#   along with Curtin.  If not, see <http://www.gnu.org/licenses/>.
17
 
"""
18
 
apt.py
19
 
Handle the setup of apt related tasks like proxies, mirrors, repositories.
20
 
"""
21
 
 
22
 
import argparse
23
 
import glob
24
 
import os
25
 
import re
26
 
import sys
27
 
import yaml
28
 
 
29
 
from curtin.log import LOG
30
 
from curtin import (config, util, gpg)
31
 
 
32
 
from . import populate_one_subcmd
33
 
 
34
 
# this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar')
35
 
ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
36
 
 
37
 
# place where apt stores cached repository data
38
 
APT_LISTS = "/var/lib/apt/lists"
39
 
 
40
 
# Files to store proxy information
41
 
APT_CONFIG_FN = "/etc/apt/apt.conf.d/94curtin-config"
42
 
APT_PROXY_FN = "/etc/apt/apt.conf.d/90curtin-aptproxy"
43
 
 
44
 
# Default keyserver to use
45
 
DEFAULT_KEYSERVER = "keyserver.ubuntu.com"
46
 
 
47
 
# Default archive mirrors
48
 
PRIMARY_ARCH_MIRRORS = {"PRIMARY": "http://archive.ubuntu.com/ubuntu/",
49
 
                        "SECURITY": "http://security.ubuntu.com/ubuntu/"}
50
 
PORTS_MIRRORS = {"PRIMARY": "http://ports.ubuntu.com/ubuntu-ports",
51
 
                 "SECURITY": "http://ports.ubuntu.com/ubuntu-ports"}
52
 
PRIMARY_ARCHES = ['amd64', 'i386']
53
 
PORTS_ARCHES = ['s390x', 'arm64', 'armhf', 'powerpc', 'ppc64el']
54
 
 
55
 
 
56
 
def get_default_mirrors(arch=None):
57
 
    """returns the default mirrors for the target. These depend on the
58
 
       architecture, for more see:
59
 
       https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports"""
60
 
    if arch is None:
61
 
        arch = util.get_architecture()
62
 
    if arch in PRIMARY_ARCHES:
63
 
        return PRIMARY_ARCH_MIRRORS.copy()
64
 
    if arch in PORTS_ARCHES:
65
 
        return PORTS_MIRRORS.copy()
66
 
    raise ValueError("No default mirror known for arch %s" % arch)
67
 
 
68
 
 
69
 
def handle_apt(cfg, target=None):
70
 
    """ handle_apt
71
 
        process the config for apt_config. This can be called from
72
 
        curthooks if a global apt config was provided or via the "apt"
73
 
        standalone command.
74
 
    """
75
 
    release = util.lsb_release(target=target)['codename']
76
 
    arch = util.get_architecture(target)
77
 
    mirrors = find_apt_mirror_info(cfg, arch)
78
 
    LOG.debug("Apt Mirror info: %s", mirrors)
79
 
 
80
 
    apply_debconf_selections(cfg, target)
81
 
 
82
 
    if not config.value_as_boolean(cfg.get('preserve_sources_list',
83
 
                                           True)):
84
 
        generate_sources_list(cfg, release, mirrors, target)
85
 
        rename_apt_lists(mirrors, target)
86
 
 
87
 
    try:
88
 
        apply_apt_proxy_config(cfg, target + APT_PROXY_FN,
89
 
                               target + APT_CONFIG_FN)
90
 
    except (IOError, OSError):
91
 
        LOG.exception("Failed to apply proxy or apt config info:")
92
 
 
93
 
    # Process 'apt_source -> sources {dict}'
94
 
    if 'sources' in cfg:
95
 
        params = mirrors
96
 
        params['RELEASE'] = release
97
 
        params['MIRROR'] = mirrors["MIRROR"]
98
 
 
99
 
        matcher = None
100
 
        matchcfg = cfg.get('add_apt_repo_match', ADD_APT_REPO_MATCH)
101
 
        if matchcfg:
102
 
            matcher = re.compile(matchcfg).search
103
 
 
104
 
        add_apt_sources(cfg['sources'], target,
105
 
                        template_params=params, aa_repo_match=matcher)
106
 
 
107
 
 
108
 
def debconf_set_selections(selections, target=None):
109
 
    util.subp(['debconf-set-selections'], data=selections, target=target,
110
 
              capture=True)
111
 
 
112
 
 
113
 
def dpkg_reconfigure(packages, target=None):
114
 
    # For any packages that are already installed, but have preseed data
115
 
    # we populate the debconf database, but the filesystem configuration
116
 
    # would be preferred on a subsequent dpkg-reconfigure.
117
 
    # so, what we have to do is "know" information about certain packages
118
 
    # to unconfigure them.
119
 
    unhandled = []
120
 
    to_config = []
121
 
    for pkg in packages:
122
 
        if pkg in CONFIG_CLEANERS:
123
 
            LOG.debug("unconfiguring %s", pkg)
124
 
            CONFIG_CLEANERS[pkg](target)
125
 
            to_config.append(pkg)
126
 
        else:
127
 
            unhandled.append(pkg)
128
 
 
129
 
    if len(unhandled):
130
 
        LOG.warn("The following packages were installed and preseeded, "
131
 
                 "but cannot be unconfigured: %s", unhandled)
132
 
 
133
 
    if len(to_config):
134
 
        util.subp(['dpkg-reconfigure', '--frontend=noninteractive'] +
135
 
                  list(to_config), data=None, target=target, capture=True)
136
 
 
137
 
 
138
 
def apply_debconf_selections(cfg, target=None):
139
 
    """apply_debconf_selections - push content to debconf"""
140
 
    # debconf_selections:
141
 
    #  set1: |
142
 
    #   cloud-init cloud-init/datasources multiselect MAAS
143
 
    #  set2: pkg pkg/value string bar
144
 
    selsets = cfg.get('debconf_selections')
145
 
    if not selsets:
146
 
        LOG.debug("debconf_selections was not set in config")
147
 
        return
148
 
 
149
 
    selections = '\n'.join(
150
 
        [selsets[key] for key in sorted(selsets.keys())])
151
 
    debconf_set_selections(selections.encode() + b"\n", target=target)
152
 
 
153
 
    # get a complete list of packages listed in input
154
 
    pkgs_cfgd = set()
155
 
    for key, content in selsets.items():
156
 
        for line in content.splitlines():
157
 
            if line.startswith("#"):
158
 
                continue
159
 
            pkg = re.sub(r"[:\s].*", "", line)
160
 
            pkgs_cfgd.add(pkg)
161
 
 
162
 
    pkgs_installed = util.get_installed_packages(target)
163
 
 
164
 
    LOG.debug("pkgs_cfgd: %s", pkgs_cfgd)
165
 
    LOG.debug("pkgs_installed: %s", pkgs_installed)
166
 
    need_reconfig = pkgs_cfgd.intersection(pkgs_installed)
167
 
 
168
 
    if len(need_reconfig) == 0:
169
 
        LOG.debug("no need for reconfig")
170
 
        return
171
 
 
172
 
    dpkg_reconfigure(need_reconfig, target=target)
173
 
 
174
 
 
175
 
def clean_cloud_init(target):
176
 
    """clean out any local cloud-init config"""
177
 
    flist = glob.glob(
178
 
        util.target_path(target, "/etc/cloud/cloud.cfg.d/*dpkg*"))
179
 
 
180
 
    LOG.debug("cleaning cloud-init config from: %s", flist)
181
 
    for dpkg_cfg in flist:
182
 
        os.unlink(dpkg_cfg)
183
 
 
184
 
 
185
 
def mirrorurl_to_apt_fileprefix(mirror):
186
 
    """ mirrorurl_to_apt_fileprefix
187
 
        Convert a mirror url to the file prefix used by apt on disk to
188
 
        store cache information for that mirror.
189
 
        To do so do:
190
 
        - take off ???://
191
 
        - drop tailing /
192
 
        - convert in string / to _
193
 
    """
194
 
    string = mirror
195
 
    if string.endswith("/"):
196
 
        string = string[0:-1]
197
 
    pos = string.find("://")
198
 
    if pos >= 0:
199
 
        string = string[pos + 3:]
200
 
    string = string.replace("/", "_")
201
 
    return string
202
 
 
203
 
 
204
 
def rename_apt_lists(new_mirrors, target=None):
205
 
    """rename_apt_lists - rename apt lists to preserve old cache data"""
206
 
    default_mirrors = get_default_mirrors(util.get_architecture(target))
207
 
 
208
 
    pre = util.target_path(target, APT_LISTS)
209
 
    for (name, omirror) in default_mirrors.items():
210
 
        nmirror = new_mirrors.get(name)
211
 
        if not nmirror:
212
 
            continue
213
 
 
214
 
        oprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(omirror)
215
 
        nprefix = pre + os.path.sep + mirrorurl_to_apt_fileprefix(nmirror)
216
 
        if oprefix == nprefix:
217
 
            continue
218
 
        olen = len(oprefix)
219
 
        for filename in glob.glob("%s_*" % oprefix):
220
 
            newname = "%s%s" % (nprefix, filename[olen:])
221
 
            LOG.debug("Renaming apt list %s to %s", filename, newname)
222
 
            try:
223
 
                os.rename(filename, newname)
224
 
            except OSError:
225
 
                # since this is a best effort task, warn with but don't fail
226
 
                LOG.warn("Failed to rename apt list:", exc_info=True)
227
 
 
228
 
 
229
 
def mirror_to_placeholder(tmpl, mirror, placeholder):
230
 
    """ mirror_to_placeholder
231
 
        replace the specified mirror in a template with a placeholder string
232
 
        Checks for existance of the expected mirror and warns if not found
233
 
    """
234
 
    if mirror not in tmpl:
235
 
        LOG.warn("Expected mirror '%s' not found in: %s", mirror, tmpl)
236
 
    return tmpl.replace(mirror, placeholder)
237
 
 
238
 
 
239
 
def map_known_suites(suite):
240
 
    """there are a few default names which will be auto-extended.
241
 
       This comes at the inability to use those names literally as suites,
242
 
       but on the other hand increases readability of the cfg quite a lot"""
243
 
    mapping = {'updates': '$RELEASE-updates',
244
 
               'backports': '$RELEASE-backports',
245
 
               'security': '$RELEASE-security',
246
 
               'proposed': '$RELEASE-proposed',
247
 
               'release': '$RELEASE'}
248
 
    try:
249
 
        retsuite = mapping[suite]
250
 
    except KeyError:
251
 
        retsuite = suite
252
 
    return retsuite
253
 
 
254
 
 
255
 
def disable_suites(disabled, src, release):
256
 
    """reads the config for suites to be disabled and removes those
257
 
       from the template"""
258
 
    if not disabled:
259
 
        return src
260
 
 
261
 
    retsrc = src
262
 
    for suite in disabled:
263
 
        suite = map_known_suites(suite)
264
 
        releasesuite = util.render_string(suite, {'RELEASE': release})
265
 
        LOG.debug("Disabling suite %s as %s", suite, releasesuite)
266
 
 
267
 
        newsrc = ""
268
 
        for line in retsrc.splitlines(True):
269
 
            if line.startswith("#"):
270
 
                newsrc += line
271
 
                continue
272
 
 
273
 
            # sources.list allow options in cols[1] which can have spaces
274
 
            # so the actual suite can be [2] or later. example:
275
 
            # deb [ arch=amd64,armel k=v ] http://example.com/debian
276
 
            cols = line.split()
277
 
            if len(cols) > 1:
278
 
                pcol = 2
279
 
                if cols[1].startswith("["):
280
 
                    for col in cols[1:]:
281
 
                        pcol += 1
282
 
                        if col.endswith("]"):
283
 
                            break
284
 
 
285
 
                if cols[pcol] == releasesuite:
286
 
                    line = '# suite disabled by curtin: %s' % line
287
 
            newsrc += line
288
 
        retsrc = newsrc
289
 
 
290
 
    return retsrc
291
 
 
292
 
 
293
 
def generate_sources_list(cfg, release, mirrors, target=None):
294
 
    """ generate_sources_list
295
 
        create a source.list file based on a custom or default template
296
 
        by replacing mirrors and release in the template
297
 
    """
298
 
    default_mirrors = get_default_mirrors(util.get_architecture(target))
299
 
    aptsrc = "/etc/apt/sources.list"
300
 
    params = {'RELEASE': release}
301
 
    for k in mirrors:
302
 
        params[k] = mirrors[k]
303
 
 
304
 
    tmpl = cfg.get('sources_list', None)
305
 
    if tmpl is None:
306
 
        LOG.info("No custom template provided, fall back to modify"
307
 
                 "mirrors in %s on the target system", aptsrc)
308
 
        tmpl = util.load_file(util.target_path(target, aptsrc))
309
 
        # Strategy if no custom template was provided:
310
 
        # - Only replacing mirrors
311
 
        # - no reason to replace "release" as it is from target anyway
312
 
        # - The less we depend upon, the more stable this is against changes
313
 
        # - warn if expected original content wasn't found
314
 
        tmpl = mirror_to_placeholder(tmpl, default_mirrors['PRIMARY'],
315
 
                                     "$MIRROR")
316
 
        tmpl = mirror_to_placeholder(tmpl, default_mirrors['SECURITY'],
317
 
                                     "$SECURITY")
318
 
 
319
 
    orig = util.target_path(target, aptsrc)
320
 
    if os.path.exists(orig):
321
 
        os.rename(orig, orig + ".curtin.old")
322
 
 
323
 
    rendered = util.render_string(tmpl, params)
324
 
    disabled = disable_suites(cfg.get('disable_suites'), rendered, release)
325
 
    util.write_file(util.target_path(target, aptsrc), disabled, mode=0o644)
326
 
 
327
 
    # protect the just generated sources.list from cloud-init
328
 
    cloudfile = "/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg"
329
 
    # this has to work with older cloud-init as well, so use old key
330
 
    cloudconf = yaml.dump({'apt_preserve_sources_list': True}, indent=1)
331
 
    try:
332
 
        util.write_file(util.target_path(target, cloudfile),
333
 
                        cloudconf, mode=0o644)
334
 
    except IOError:
335
 
        LOG.exception("Failed to protect source.list from cloud-init in (%s)",
336
 
                      util.target_path(target, cloudfile))
337
 
        raise
338
 
 
339
 
 
340
 
def add_apt_key_raw(key, target=None):
341
 
    """
342
 
    actual adding of a key as defined in key argument
343
 
    to the system
344
 
    """
345
 
    LOG.debug("Adding key:\n'%s'", key)
346
 
    try:
347
 
        util.subp(['apt-key', 'add', '-'], data=key.encode(), target=target)
348
 
    except util.ProcessExecutionError:
349
 
        LOG.exception("failed to add apt GPG Key to apt keyring")
350
 
        raise
351
 
 
352
 
 
353
 
def add_apt_key(ent, target=None):
354
 
    """
355
 
    Add key to the system as defined in ent (if any).
356
 
    Supports raw keys or keyid's
357
 
    The latter will as a first step fetched to get the raw key
358
 
    """
359
 
    if 'keyid' in ent and 'key' not in ent:
360
 
        keyserver = DEFAULT_KEYSERVER
361
 
        if 'keyserver' in ent:
362
 
            keyserver = ent['keyserver']
363
 
 
364
 
        ent['key'] = gpg.getkeybyid(ent['keyid'], keyserver,
365
 
                                    retries=(1, 2, 5, 10))
366
 
 
367
 
    if 'key' in ent:
368
 
        add_apt_key_raw(ent['key'], target)
369
 
 
370
 
 
371
 
def add_apt_sources(srcdict, target=None, template_params=None,
372
 
                    aa_repo_match=None):
373
 
    """
374
 
    add entries in /etc/apt/sources.list.d for each abbreviated
375
 
    sources.list entry in 'srcdict'.  When rendering template, also
376
 
    include the values in dictionary searchList
377
 
    """
378
 
    if template_params is None:
379
 
        template_params = {}
380
 
 
381
 
    if aa_repo_match is None:
382
 
        raise ValueError('did not get a valid repo matcher')
383
 
 
384
 
    if not isinstance(srcdict, dict):
385
 
        raise TypeError('unknown apt format: %s' % (srcdict))
386
 
 
387
 
    for filename in srcdict:
388
 
        ent = srcdict[filename]
389
 
        if 'filename' not in ent:
390
 
            ent['filename'] = filename
391
 
 
392
 
        add_apt_key(ent, target)
393
 
 
394
 
        if 'source' not in ent:
395
 
            continue
396
 
        source = ent['source']
397
 
        source = util.render_string(source, template_params)
398
 
 
399
 
        if not ent['filename'].startswith("/"):
400
 
            ent['filename'] = os.path.join("/etc/apt/sources.list.d/",
401
 
                                           ent['filename'])
402
 
        if not ent['filename'].endswith(".list"):
403
 
            ent['filename'] += ".list"
404
 
 
405
 
        if aa_repo_match(source):
406
 
            with util.ChrootableTarget(
407
 
                    target, sys_resolvconf=True) as in_chroot:
408
 
                try:
409
 
                    in_chroot.subp(["add-apt-repository", source],
410
 
                                   retries=(1, 2, 5, 10))
411
 
                except util.ProcessExecutionError:
412
 
                    LOG.exception("add-apt-repository failed.")
413
 
                    raise
414
 
            continue
415
 
 
416
 
        sourcefn = util.target_path(target, ent['filename'])
417
 
        try:
418
 
            contents = "%s\n" % (source)
419
 
            util.write_file(sourcefn, contents, omode="a")
420
 
        except IOError as detail:
421
 
            LOG.exception("failed write to file %s: %s", sourcefn, detail)
422
 
            raise
423
 
 
424
 
    util.apt_update(target=target, force=True,
425
 
                    comment="apt-source changed config")
426
 
 
427
 
    return
428
 
 
429
 
 
430
 
def search_for_mirror(candidates):
431
 
    """
432
 
    Search through a list of mirror urls for one that works
433
 
    This needs to return quickly.
434
 
    """
435
 
    if candidates is None:
436
 
        return None
437
 
 
438
 
    LOG.debug("search for mirror in candidates: '%s'", candidates)
439
 
    for cand in candidates:
440
 
        try:
441
 
            if util.is_resolvable_url(cand):
442
 
                LOG.debug("found working mirror: '%s'", cand)
443
 
                return cand
444
 
        except Exception:
445
 
            pass
446
 
    return None
447
 
 
448
 
 
449
 
def update_mirror_info(pmirror, smirror, arch):
450
 
    """sets security mirror to primary if not defined.
451
 
       returns defaults if no mirrors are defined"""
452
 
    if pmirror is not None:
453
 
        if smirror is None:
454
 
            smirror = pmirror
455
 
        return {'PRIMARY': pmirror,
456
 
                'SECURITY': smirror}
457
 
    return get_default_mirrors(arch)
458
 
 
459
 
 
460
 
def get_arch_mirrorconfig(cfg, mirrortype, arch):
461
 
    """out of a list of potential mirror configurations select
462
 
       and return the one matching the architecture (or default)"""
463
 
    # select the mirror specification (if-any)
464
 
    mirror_cfg_list = cfg.get(mirrortype, None)
465
 
    if mirror_cfg_list is None:
466
 
        return None
467
 
 
468
 
    # select the specification matching the target arch
469
 
    default = None
470
 
    for mirror_cfg_elem in mirror_cfg_list:
471
 
        arches = mirror_cfg_elem.get("arches")
472
 
        if arch in arches:
473
 
            return mirror_cfg_elem
474
 
        if "default" in arches:
475
 
            default = mirror_cfg_elem
476
 
    return default
477
 
 
478
 
 
479
 
def get_mirror(cfg, mirrortype, arch):
480
 
    """pass the three potential stages of mirror specification
481
 
       returns None is neither of them found anything otherwise the first
482
 
       hit is returned"""
483
 
    mcfg = get_arch_mirrorconfig(cfg, mirrortype, arch)
484
 
    if mcfg is None:
485
 
        return None
486
 
 
487
 
    # directly specified
488
 
    mirror = mcfg.get("uri", None)
489
 
 
490
 
    # fallback to search if specified
491
 
    if mirror is None:
492
 
        # list of mirrors to try to resolve
493
 
        mirror = search_for_mirror(mcfg.get("search", None))
494
 
 
495
 
    return mirror
496
 
 
497
 
 
498
 
def find_apt_mirror_info(cfg, arch=None):
499
 
    """find_apt_mirror_info
500
 
       find an apt_mirror given the cfg provided.
501
 
       It can check for separate config of primary and security mirrors
502
 
       If only primary is given security is assumed to be equal to primary
503
 
       If the generic apt_mirror is given that is defining for both
504
 
    """
505
 
 
506
 
    if arch is None:
507
 
        arch = util.get_architecture()
508
 
        LOG.debug("got arch for mirror selection: %s", arch)
509
 
    pmirror = get_mirror(cfg, "primary", arch)
510
 
    LOG.debug("got primary mirror: %s", pmirror)
511
 
    smirror = get_mirror(cfg, "security", arch)
512
 
    LOG.debug("got security mirror: %s", smirror)
513
 
 
514
 
    # Note: curtin has no cloud-datasource fallback
515
 
 
516
 
    mirror_info = update_mirror_info(pmirror, smirror, arch)
517
 
 
518
 
    # less complex replacements use only MIRROR, derive from primary
519
 
    mirror_info["MIRROR"] = mirror_info["PRIMARY"]
520
 
 
521
 
    return mirror_info
522
 
 
523
 
 
524
 
def apply_apt_proxy_config(cfg, proxy_fname, config_fname):
525
 
    """apply_apt_proxy_config
526
 
       Applies any apt*proxy config from if specified
527
 
    """
528
 
    # Set up any apt proxy
529
 
    cfgs = (('proxy', 'Acquire::http::Proxy "%s";'),
530
 
            ('http_proxy', 'Acquire::http::Proxy "%s";'),
531
 
            ('ftp_proxy', 'Acquire::ftp::Proxy "%s";'),
532
 
            ('https_proxy', 'Acquire::https::Proxy "%s";'))
533
 
 
534
 
    proxies = [fmt % cfg.get(name) for (name, fmt) in cfgs if cfg.get(name)]
535
 
    if len(proxies):
536
 
        LOG.debug("write apt proxy info to %s", proxy_fname)
537
 
        util.write_file(proxy_fname, '\n'.join(proxies) + '\n')
538
 
    elif os.path.isfile(proxy_fname):
539
 
        util.del_file(proxy_fname)
540
 
        LOG.debug("no apt proxy configured, removed %s", proxy_fname)
541
 
 
542
 
    if cfg.get('conf', None):
543
 
        LOG.debug("write apt config info to %s", config_fname)
544
 
        util.write_file(config_fname, cfg.get('conf'))
545
 
    elif os.path.isfile(config_fname):
546
 
        util.del_file(config_fname)
547
 
        LOG.debug("no apt config configured, removed %s", config_fname)
548
 
 
549
 
 
550
 
def apt_command(args):
551
 
    """ Main entry point for curtin apt-config standalone command
552
 
        This does not read the global config as handled by curthooks, but
553
 
        instead one can specify a different "target" and a new cfg via --config
554
 
        """
555
 
    cfg = config.load_command_config(args, {})
556
 
 
557
 
    if args.target is not None:
558
 
        target = args.target
559
 
    else:
560
 
        state = util.load_command_environment()
561
 
        target = state['target']
562
 
 
563
 
    if target is None:
564
 
        sys.stderr.write("Unable to find target.  "
565
 
                         "Use --target or set TARGET_MOUNT_POINT\n")
566
 
        sys.exit(2)
567
 
 
568
 
    apt_cfg = cfg.get("apt")
569
 
    # if no apt config section is available, do nothing
570
 
    if apt_cfg is not None:
571
 
        LOG.debug("Handling apt to target %s with config %s",
572
 
                  target, apt_cfg)
573
 
        try:
574
 
            with util.ChrootableTarget(target, sys_resolvconf=True):
575
 
                handle_apt(apt_cfg, target)
576
 
        except (RuntimeError, TypeError, ValueError, IOError):
577
 
            LOG.exception("Failed to configure apt features '%s'", apt_cfg)
578
 
            sys.exit(1)
579
 
    else:
580
 
        LOG.info("No apt config provided, skipping")
581
 
 
582
 
    sys.exit(0)
583
 
 
584
 
 
585
 
def translate_old_apt_features(cfg):
586
 
    """translate the few old apt related features into the new config format"""
587
 
    predef_apt_cfg = cfg.get("apt")
588
 
    if predef_apt_cfg is None:
589
 
        cfg['apt'] = {}
590
 
        predef_apt_cfg = cfg.get("apt")
591
 
 
592
 
    if cfg.get('apt_proxy') is not None:
593
 
        if predef_apt_cfg.get('proxy') is not None:
594
 
            msg = ("Error in apt_proxy configuration: "
595
 
                   "old and new format of apt features "
596
 
                   "are mutually exclusive")
597
 
            LOG.error(msg)
598
 
            raise ValueError(msg)
599
 
 
600
 
        cfg['apt']['proxy'] = cfg.get('apt_proxy')
601
 
        LOG.debug("Transferred %s into new format: %s", cfg.get('apt_proxy'),
602
 
                  cfg.get('apte'))
603
 
        del cfg['apt_proxy']
604
 
 
605
 
    if cfg.get('apt_mirrors') is not None:
606
 
        if predef_apt_cfg.get('mirrors') is not None:
607
 
            msg = ("Error in apt_mirror configuration: "
608
 
                   "old and new format of apt features "
609
 
                   "are mutually exclusive")
610
 
            LOG.error(msg)
611
 
            raise ValueError(msg)
612
 
 
613
 
        old = cfg.get('apt_mirrors')
614
 
        cfg['apt']['primary'] = [{"arches": ["default"],
615
 
                                  "uri": old.get('ubuntu_archive')}]
616
 
        cfg['apt']['security'] = [{"arches": ["default"],
617
 
                                   "uri": old.get('ubuntu_security')}]
618
 
        LOG.debug("Transferred %s into new format: %s", cfg.get('apt_mirror'),
619
 
                  cfg.get('apt'))
620
 
        del cfg['apt_mirrors']
621
 
        # to work this also needs to disable the default protection
622
 
        psl = predef_apt_cfg.get('preserve_sources_list')
623
 
        if psl is not None:
624
 
            if config.value_as_boolean(psl) is True:
625
 
                msg = ("Error in apt_mirror configuration: "
626
 
                       "apt_mirrors and preserve_sources_list: True "
627
 
                       "are mutually exclusive")
628
 
                LOG.error(msg)
629
 
                raise ValueError(msg)
630
 
        cfg['apt']['preserve_sources_list'] = False
631
 
 
632
 
    if cfg.get('debconf_selections') is not None:
633
 
        if predef_apt_cfg.get('debconf_selections') is not None:
634
 
            msg = ("Error in debconf_selections configuration: "
635
 
                   "old and new format of apt features "
636
 
                   "are mutually exclusive")
637
 
            LOG.error(msg)
638
 
            raise ValueError(msg)
639
 
 
640
 
        selsets = cfg.get('debconf_selections')
641
 
        cfg['apt']['debconf_selections'] = selsets
642
 
        LOG.info("Transferred %s into new format: %s",
643
 
                 cfg.get('debconf_selections'),
644
 
                 cfg.get('apt'))
645
 
        del cfg['debconf_selections']
646
 
 
647
 
    return cfg
648
 
 
649
 
 
650
 
CMD_ARGUMENTS = (
651
 
    ((('-c', '--config'),
652
 
      {'help': 'read configuration from cfg', 'action': util.MergedCmdAppend,
653
 
       'metavar': 'FILE', 'type': argparse.FileType("rb"),
654
 
       'dest': 'cfgopts', 'default': []}),
655
 
     (('-t', '--target'),
656
 
      {'help': 'chroot to target. default is env[TARGET_MOUNT_POINT]',
657
 
       'action': 'store', 'metavar': 'TARGET',
658
 
       'default': os.environ.get('TARGET_MOUNT_POINT')}),)
659
 
)
660
 
 
661
 
 
662
 
def POPULATE_SUBCMD(parser):
663
 
    """Populate subcommand option parsing for apt-config"""
664
 
    populate_one_subcmd(parser, CMD_ARGUMENTS, apt_command)
665
 
 
666
 
 
667
 
CONFIG_CLEANERS = {
668
 
    'cloud-init': clean_cloud_init,
669
 
}
670
 
 
671
 
# vi: ts=4 expandtab syntax=python