~ubuntu-branches/ubuntu/saucy/cloud-init/saucy

« back to all changes in this revision

Viewing changes to cloudinit/distros/rhel.py

  • Committer: Scott Moser
  • Date: 2012-07-06 21:24:18 UTC
  • mfrom: (1.1.26)
  • Revision ID: smoser@ubuntu.com-20120706212418-zwcpwxsdodi0pms3
New upstream snapshot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vi: ts=4 expandtab
 
2
#
 
3
#    Copyright (C) 2012 Canonical Ltd.
 
4
#    Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
 
5
#    Copyright (C) 2012 Yahoo! Inc.
 
6
#
 
7
#    Author: Scott Moser <scott.moser@canonical.com>
 
8
#    Author: Juerg Haefliger <juerg.haefliger@hp.com>
 
9
#    Author: Joshua Harlow <harlowja@yahoo-inc.com>
 
10
#
 
11
#    This program is free software: you can redistribute it and/or modify
 
12
#    it under the terms of the GNU General Public License version 3, as
 
13
#    published by the Free Software Foundation.
 
14
#
 
15
#    This program is distributed in the hope that it will be useful,
 
16
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
#    GNU General Public License for more details.
 
19
#
 
20
#    You should have received a copy of the GNU General Public License
 
21
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
22
 
 
23
import os
 
24
 
 
25
from cloudinit import distros
 
26
from cloudinit import helpers
 
27
from cloudinit import log as logging
 
28
from cloudinit import util
 
29
 
 
30
from cloudinit.settings import PER_INSTANCE
 
31
 
 
32
LOG = logging.getLogger(__name__)
 
33
 
 
34
NETWORK_FN_TPL = '/etc/sysconfig/network-scripts/ifcfg-%s'
 
35
 
 
36
# See: http://tiny.cc/6r99fw
 
37
# For what alot of these files that are being written
 
38
# are and the format of them
 
39
 
 
40
# This library is used to parse/write
 
41
# out the various sysconfig files edited
 
42
#
 
43
# It has to be slightly modified though
 
44
# to ensure that all values are quoted
 
45
# since these configs are usually sourced into
 
46
# bash scripts...
 
47
from configobj import ConfigObj
 
48
 
 
49
# See: http://tiny.cc/oezbgw
 
50
D_QUOTE_CHARS = {
 
51
    "\"": "\\\"",
 
52
    "(": "\\(",
 
53
    ")": "\\)",
 
54
    "$": '\$',
 
55
    '`': '\`',
 
56
}
 
57
 
 
58
 
 
59
class Distro(distros.Distro):
 
60
 
 
61
    def __init__(self, name, cfg, paths):
 
62
        distros.Distro.__init__(self, name, cfg, paths)
 
63
        # This will be used to restrict certain
 
64
        # calls from repeatly happening (when they
 
65
        # should only happen say once per instance...)
 
66
        self._runner = helpers.Runners(paths)
 
67
 
 
68
    def install_packages(self, pkglist):
 
69
        self.package_command('install', pkglist)
 
70
 
 
71
    def _write_network(self, settings):
 
72
        # TODO fix this... since this is the ubuntu format
 
73
        entries = translate_network(settings)
 
74
        LOG.debug("Translated ubuntu style network settings %s into %s",
 
75
                  settings, entries)
 
76
        # Make the intermediate format as the rhel format...
 
77
        for (dev, info) in entries.iteritems():
 
78
            net_fn = NETWORK_FN_TPL % (dev)
 
79
            net_ro_fn = self._paths.join(True, net_fn)
 
80
            (prev_exist, net_cfg) = self._read_conf(net_ro_fn)
 
81
            net_cfg['DEVICE'] = dev
 
82
            boot_proto = info.get('bootproto')
 
83
            if boot_proto:
 
84
                net_cfg['BOOTPROTO'] = boot_proto
 
85
            net_mask = info.get('netmask')
 
86
            if net_mask:
 
87
                net_cfg["NETMASK"] = net_mask
 
88
            addr = info.get('address')
 
89
            if addr:
 
90
                net_cfg["IPADDR"] = addr
 
91
            if info.get('auto'):
 
92
                net_cfg['ONBOOT'] = 'yes'
 
93
            else:
 
94
                net_cfg['ONBOOT'] = 'no'
 
95
            gtway = info.get('gateway')
 
96
            if gtway:
 
97
                net_cfg["GATEWAY"] = gtway
 
98
            bcast = info.get('broadcast')
 
99
            if bcast:
 
100
                net_cfg["BROADCAST"] = bcast
 
101
            mac_addr = info.get('hwaddress')
 
102
            if mac_addr:
 
103
                net_cfg["MACADDR"] = mac_addr
 
104
            lines = net_cfg.write()
 
105
            if not prev_exist:
 
106
                lines.insert(0, '# Created by cloud-init')
 
107
            w_contents = "\n".join(lines)
 
108
            net_rw_fn = self._paths.join(False, net_fn)
 
109
            util.write_file(net_rw_fn, w_contents, 0644)
 
110
 
 
111
    def set_hostname(self, hostname):
 
112
        out_fn = self._paths.join(False, '/etc/sysconfig/network')
 
113
        self._write_hostname(hostname, out_fn)
 
114
        if out_fn == '/etc/sysconfig/network':
 
115
            # Only do this if we are running in non-adjusted root mode
 
116
            LOG.debug("Setting hostname to %s", hostname)
 
117
            util.subp(['hostname', hostname])
 
118
 
 
119
    def apply_locale(self, locale, out_fn=None):
 
120
        if not out_fn:
 
121
            out_fn = self._paths.join(False, '/etc/sysconfig/i18n')
 
122
        ro_fn = self._paths.join(True, '/etc/sysconfig/i18n')
 
123
        (_exists, contents) = self._read_conf(ro_fn)
 
124
        contents['LANG'] = locale
 
125
        w_contents = "\n".join(contents.write())
 
126
        util.write_file(out_fn, w_contents, 0644)
 
127
 
 
128
    def _write_hostname(self, hostname, out_fn):
 
129
        (_exists, contents) = self._read_conf(out_fn)
 
130
        contents['HOSTNAME'] = hostname
 
131
        w_contents = "\n".join(contents.write())
 
132
        util.write_file(out_fn, w_contents, 0644)
 
133
 
 
134
    def update_hostname(self, hostname, prev_file):
 
135
        hostname_prev = self._read_hostname(prev_file)
 
136
        read_fn = self._paths.join(True, "/etc/sysconfig/network")
 
137
        hostname_in_sys = self._read_hostname(read_fn)
 
138
        update_files = []
 
139
        if not hostname_prev or hostname_prev != hostname:
 
140
            update_files.append(prev_file)
 
141
        if (not hostname_in_sys or
 
142
            (hostname_in_sys == hostname_prev
 
143
             and hostname_in_sys != hostname)):
 
144
            write_fn = self._paths.join(False, "/etc/sysconfig/network")
 
145
            update_files.append(write_fn)
 
146
        for fn in update_files:
 
147
            try:
 
148
                self._write_hostname(hostname, fn)
 
149
            except:
 
150
                util.logexc(LOG, "Failed to write hostname %s to %s",
 
151
                            hostname, fn)
 
152
        if (hostname_in_sys and hostname_prev and
 
153
            hostname_in_sys != hostname_prev):
 
154
            LOG.debug(("%s differs from /etc/sysconfig/network."
 
155
                        " Assuming user maintained hostname."), prev_file)
 
156
        if "/etc/sysconfig/network" in update_files:
 
157
            # Only do this if we are running in non-adjusted root mode
 
158
            LOG.debug("Setting hostname to %s", hostname)
 
159
            util.subp(['hostname', hostname])
 
160
 
 
161
    def _read_hostname(self, filename, default=None):
 
162
        (_exists, contents) = self._read_conf(filename)
 
163
        if 'HOSTNAME' in contents:
 
164
            return contents['HOSTNAME']
 
165
        else:
 
166
            return default
 
167
 
 
168
    def _read_conf(self, fn):
 
169
        exists = False
 
170
        if os.path.isfile(fn):
 
171
            contents = util.load_file(fn).splitlines()
 
172
            exists = True
 
173
        else:
 
174
            contents = []
 
175
        return (exists, QuotingConfigObj(contents))
 
176
 
 
177
    def set_timezone(self, tz):
 
178
        tz_file = os.path.join("/usr/share/zoneinfo", tz)
 
179
        if not os.path.isfile(tz_file):
 
180
            raise RuntimeError(("Invalid timezone %s,"
 
181
                                " no file found at %s") % (tz, tz_file))
 
182
        # Adjust the sysconfig clock zone setting
 
183
        read_fn = self._paths.join(True, "/etc/sysconfig/clock")
 
184
        (_exists, contents) = self._read_conf(read_fn)
 
185
        contents['ZONE'] = tz
 
186
        tz_contents = "\n".join(contents.write())
 
187
        write_fn = self._paths.join(False, "/etc/sysconfig/clock")
 
188
        util.write_file(write_fn, tz_contents)
 
189
        # This ensures that the correct tz will be used for the system
 
190
        util.copy(tz_file, self._paths.join(False, "/etc/localtime"))
 
191
 
 
192
    def package_command(self, command, args=None):
 
193
        cmd = ['yum']
 
194
        # If enabled, then yum will be tolerant of errors on the command line
 
195
        # with regard to packages.
 
196
        # For example: if you request to install foo, bar and baz and baz is
 
197
        # installed; yum won't error out complaining that baz is already
 
198
        # installed.
 
199
        cmd.append("-t")
 
200
        # Determines whether or not yum prompts for confirmation
 
201
        # of critical actions. We don't want to prompt...
 
202
        cmd.append("-y")
 
203
        cmd.append(command)
 
204
        if args:
 
205
            cmd.extend(args)
 
206
        # Allow the output of this to flow outwards (ie not be captured)
 
207
        util.subp(cmd, capture=False)
 
208
 
 
209
    def update_package_sources(self):
 
210
        self._runner.run("update-sources", self.package_command,
 
211
                         ["update"], freq=PER_INSTANCE)
 
212
 
 
213
 
 
214
# This class helps adjust the configobj
 
215
# writing to ensure that when writing a k/v
 
216
# on a line, that they are properly quoted
 
217
# and have no spaces between the '=' sign.
 
218
# - This is mainly due to the fact that
 
219
# the sysconfig scripts are often sourced
 
220
# directly into bash/shell scripts so ensure
 
221
# that it works for those types of use cases.
 
222
class QuotingConfigObj(ConfigObj):
 
223
    def __init__(self, lines):
 
224
        ConfigObj.__init__(self, lines,
 
225
                           interpolation=False,
 
226
                           write_empty_values=True)
 
227
 
 
228
    def _quote_posix(self, text):
 
229
        if not text:
 
230
            return ''
 
231
        for (k, v) in D_QUOTE_CHARS.iteritems():
 
232
            text = text.replace(k, v)
 
233
        return '"%s"' % (text)
 
234
 
 
235
    def _quote_special(self, text):
 
236
        if text.lower() in ['yes', 'no', 'true', 'false']:
 
237
            return text
 
238
        else:
 
239
            return self._quote_posix(text)
 
240
 
 
241
    def _write_line(self, indent_string, entry, this_entry, comment):
 
242
        # Ensure it is formatted fine for
 
243
        # how these sysconfig scripts are used
 
244
        val = self._decode_element(self._quote(this_entry))
 
245
        # Single quoted strings should
 
246
        # always work.
 
247
        if not val.startswith("'"):
 
248
            # Perform any special quoting
 
249
            val = self._quote_special(val)
 
250
        key = self._decode_element(self._quote(entry, multiline=False))
 
251
        cmnt = self._decode_element(comment)
 
252
        return '%s%s%s%s%s' % (indent_string,
 
253
                               key,
 
254
                               "=",
 
255
                               val,
 
256
                               cmnt)
 
257
 
 
258
 
 
259
# This is a util function to translate a ubuntu /etc/network/interfaces 'blob'
 
260
# to a rhel equiv. that can then be written to /etc/sysconfig/network-scripts/
 
261
# TODO remove when we have python-netcf active...
 
262
def translate_network(settings):
 
263
    # Get the standard cmd, args from the ubuntu format
 
264
    entries = []
 
265
    for line in settings.splitlines():
 
266
        line = line.strip()
 
267
        if not line or line.startswith("#"):
 
268
            continue
 
269
        split_up = line.split(None, 1)
 
270
        if len(split_up) <= 1:
 
271
            continue
 
272
        entries.append(split_up)
 
273
    # Figure out where each iface section is
 
274
    ifaces = []
 
275
    consume = {}
 
276
    for (cmd, args) in entries:
 
277
        if cmd == 'iface':
 
278
            if consume:
 
279
                ifaces.append(consume)
 
280
                consume = {}
 
281
            consume[cmd] = args
 
282
        else:
 
283
            consume[cmd] = args
 
284
    # Check if anything left over to consume
 
285
    absorb = False
 
286
    for (cmd, args) in consume.iteritems():
 
287
        if cmd == 'iface':
 
288
            absorb = True
 
289
    if absorb:
 
290
        ifaces.append(consume)
 
291
    # Now translate
 
292
    real_ifaces = {}
 
293
    for info in ifaces:
 
294
        if 'iface' not in info:
 
295
            continue
 
296
        iface_details = info['iface'].split(None)
 
297
        dev_name = None
 
298
        if len(iface_details) >= 1:
 
299
            dev = iface_details[0].strip().lower()
 
300
            if dev:
 
301
                dev_name = dev
 
302
        if not dev_name:
 
303
            continue
 
304
        iface_info = {}
 
305
        if len(iface_details) >= 3:
 
306
            proto_type = iface_details[2].strip().lower()
 
307
            # Seems like this can be 'loopback' which we don't
 
308
            # really care about
 
309
            if proto_type in ['dhcp', 'static']:
 
310
                iface_info['bootproto'] = proto_type
 
311
        # These can just be copied over
 
312
        for k in ['netmask', 'address', 'gateway', 'broadcast']:
 
313
            if k in info:
 
314
                val = info[k].strip().lower()
 
315
                if val:
 
316
                    iface_info[k] = val
 
317
        # Is any mac address spoofing going on??
 
318
        if 'hwaddress' in info:
 
319
            hw_info = info['hwaddress'].lower().strip()
 
320
            hw_split = hw_info.split(None, 1)
 
321
            if len(hw_split) == 2 and hw_split[0].startswith('ether'):
 
322
                hw_addr = hw_split[1]
 
323
                if hw_addr:
 
324
                    iface_info['hwaddress'] = hw_addr
 
325
        real_ifaces[dev_name] = iface_info
 
326
    # Check for those that should be started on boot via 'auto'
 
327
    for (cmd, args) in entries:
 
328
        if cmd == 'auto':
 
329
            # Seems like auto can be like 'auto eth0 eth0:1' so just get the
 
330
            # first part out as the device name
 
331
            args = args.split(None)
 
332
            if not args:
 
333
                continue
 
334
            dev_name = args[0].strip().lower()
 
335
            if dev_name in real_ifaces:
 
336
                real_ifaces[dev_name]['auto'] = True
 
337
    return real_ifaces