debian.py 7.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# vi: ts=4 expandtab
#
#    Copyright (C) 2012 Canonical Ltd.
#    Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
#    Copyright (C) 2012 Yahoo! Inc.
#
#    Author: Scott Moser <scott.moser@canonical.com>
#    Author: Juerg Haefliger <juerg.haefliger@hp.com>
#    Author: Joshua Harlow <harlowja@yahoo-inc.com>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License version 3, as
#    published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os

from cloudinit import distros
from cloudinit import helpers
from cloudinit import log as logging
28
from cloudinit import net
29
from cloudinit import util
30

31
from cloudinit.distros.parsers.hostname import HostnameConf
Joshua Harlow's avatar
Joshua Harlow committed
32

33 34 35 36
from cloudinit.settings import PER_INSTANCE

LOG = logging.getLogger(__name__)

37 38 39
APT_GET_COMMAND = ('apt-get', '--option=Dpkg::Options::=--force-confold',
                   '--option=Dpkg::options::=--force-unsafe-io',
                   '--assume-yes', '--quiet')
40 41 42 43
APT_GET_WRAPPER = {
    'command': 'eatmydata',
    'enabled': 'auto',
}
44

45 46

class Distro(distros.Distro):
Joshua Harlow's avatar
Joshua Harlow committed
47 48
    hostname_conf_fn = "/etc/hostname"
    locale_conf_fn = "/etc/default/locale"
49 50
    network_conf_fn = "/etc/network/interfaces.d/50-cloud-init.cfg"
    links_prefix = "/etc/systemd/network/50-cloud-init-"
51 52 53 54 55 56 57

    def __init__(self, name, cfg, paths):
        distros.Distro.__init__(self, name, cfg, paths)
        # This will be used to restrict certain
        # calls from repeatly happening (when they
        # should only happen say once per instance...)
        self._runner = helpers.Runners(paths)
58
        self.osfamily = 'debian'
59 60 61

    def apply_locale(self, locale, out_fn=None):
        if not out_fn:
Joshua Harlow's avatar
Joshua Harlow committed
62
            out_fn = self.locale_conf_fn
63 64
        util.subp(['locale-gen', locale], capture=False)
        util.subp(['update-locale', locale], capture=False)
Joshua Harlow's avatar
Joshua Harlow committed
65 66 67 68 69 70
        # "" provides trailing newline during join
        lines = [
            util.make_header(),
            'LANG="%s"' % (locale),
            "",
        ]
71
        util.write_file(out_fn, "\n".join(lines))
72 73

    def install_packages(self, pkglist):
74
        self.update_package_sources()
75
        self.package_command('install', pkgs=pkglist)
76 77

    def _write_network(self, settings):
Joshua Harlow's avatar
Joshua Harlow committed
78
        util.write_file(self.network_conf_fn, settings)
79
        return ['all']
80

81
    def _write_network_config(self, netconfig):
82
        ns = net.parse_net_config_data(netconfig)
83 84
        net.render_network_state(target="/", network_state=ns,
                                 eni=self.network_conf_fn,
85 86
                                 links_prefix=self.links_prefix,
                                 netrules=None)
87 88
        _maybe_remove_legacy_eth0()

89
        return []
90

91
    def _bring_up_interfaces(self, device_names):
92 93 94 95 96 97 98 99
        use_all = False
        for d in device_names:
            if d == 'all':
                use_all = True
        if use_all:
            return distros.Distro._bring_up_interface(self, '--all')
        else:
            return distros.Distro._bring_up_interfaces(self, device_names)
100

101
    def _write_hostname(self, your_hostname, out_fn):
102 103 104 105 106 107 108
        conf = None
        try:
            # Try to update the previous one
            # so lets see if we can read it first.
            conf = self._read_hostname_conf(out_fn)
        except IOError:
            pass
109 110 111
        if not conf:
            conf = HostnameConf('')
        conf.set_hostname(your_hostname)
112
        util.write_file(out_fn, str(conf), 0o644)
Joshua Harlow's avatar
Joshua Harlow committed
113 114

    def _read_system_hostname(self):
115
        sys_hostname = self._read_hostname(self.hostname_conf_fn)
116 117 118
        return (self.hostname_conf_fn, sys_hostname)

    def _read_hostname_conf(self, filename):
119 120 121
        conf = HostnameConf(util.load_file(filename))
        conf.parse()
        return conf
122 123

    def _read_hostname(self, filename, default=None):
124 125 126 127 128 129 130
        hostname = None
        try:
            conf = self._read_hostname_conf(filename)
            hostname = conf.hostname
        except IOError:
            pass
        if not hostname:
131
            return default
132
        return hostname
133 134 135 136 137 138

    def _get_localhost_ip(self):
        # Note: http://www.leonardoborda.com/blog/127-0-1-1-ubuntu-debian/
        return "127.0.1.1"

    def set_timezone(self, tz):
Scott Moser's avatar
Scott Moser committed
139
        distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))
140

Scott Moser's avatar
Scott Moser committed
141 142 143 144
    def package_command(self, command, args=None, pkgs=None):
        if pkgs is None:
            pkgs = []

145 146 147 148
        e = os.environ.copy()
        # See: http://tiny.cc/kg91fw
        # Or: http://tiny.cc/mh91fw
        e['DEBIAN_FRONTEND'] = 'noninteractive'
149 150 151 152 153 154 155

        wcfg = self.get_option("apt_get_wrapper", APT_GET_WRAPPER)
        cmd = _get_wrapper_prefix(
            wcfg.get('command', APT_GET_WRAPPER['command']),
            wcfg.get('enabled', APT_GET_WRAPPER['enabled']))

        cmd.extend(list(self.get_option("apt_get_command", APT_GET_COMMAND)))
156 157 158 159

        if args and isinstance(args, str):
            cmd.append(args)
        elif args and isinstance(args, list):
160
            cmd.extend(args)
161

162 163 164 165 166 167
        subcmd = command
        if command == "upgrade":
            subcmd = self.get_option("apt_get_upgrade_subcommand",
                                     "dist-upgrade")

        cmd.append(subcmd)
168 169 170 171

        pkglist = util.expand_package_list('%s=%s', pkgs)
        cmd.extend(pkglist)

172
        # Allow the output of this to flow outwards (ie not be captured)
173
        util.log_time(logfunc=LOG.debug,
174 175 176
                      msg="apt-%s [%s]" % (command, ' '.join(cmd)),
                      func=util.subp,
                      args=(cmd,), kwargs={'env': e, 'capture': False})
177

178
    def update_package_sources(self):
179 180
        self._runner.run("update-sources", self.package_command,
                         ["update"], freq=PER_INSTANCE)
181 182 183 184

    def get_primary_arch(self):
        (arch, _err) = util.subp(['dpkg', '--print-architecture'])
        return str(arch).strip()
185 186 187 188 189 190 191 192 193 194 195 196


def _get_wrapper_prefix(cmd, mode):
    if isinstance(cmd, str):
        cmd = [str(cmd)]

    if (util.is_true(mode) or
        (str(mode).lower() == "auto" and cmd[0] and
         util.which(cmd[0]))):
        return cmd
    else:
        return []
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223


def _maybe_remove_legacy_eth0(path="/etc/network/interfaces.d/eth0.cfg"):
    """Ubuntu cloud images previously included a 'eth0.cfg' that had
       hard coded content.  That file would interfere with the rendered
       configuration if it was present.

       if the file does not exist do nothing.
       If the file exists:
         - with known content, remove it and warn
         - with unknown content, leave it and warn
    """

    if not os.path.exists(path):
        return

    bmsg = "Dynamic networking config may not apply."
    try:
        contents = util.load_file(path)
        known_contents = ["auto eth0", "iface eth0 inet dhcp"]
        lines = [f.strip() for f in contents.splitlines()
                 if not f.startswith("#")]
        if lines == known_contents:
            util.del_file(path)
            msg = "removed %s with known contents" % path
        else:
            msg = (bmsg + " '%s' exists with user configured content." % path)
224
    except Exception:
225 226 227
        msg = bmsg + " %s exists, but could not be read." % path

    LOG.warn(msg)