25
25
from cloudinit import distros
27
from cloudinit.distros.parsers.resolv_conf import ResolvConf
28
from cloudinit.distros.parsers.sys_conf import SysConf
26
30
from cloudinit import helpers
27
31
from cloudinit import log as logging
28
32
from cloudinit import util
29
from cloudinit import version
31
34
from cloudinit.settings import PER_INSTANCE
33
36
LOG = logging.getLogger(__name__)
35
NETWORK_FN_TPL = '/etc/sysconfig/network-scripts/ifcfg-%s'
37
# See: http://tiny.cc/6r99fw
38
# For what alot of these files that are being written
39
# are and the format of them
41
# This library is used to parse/write
42
# out the various sysconfig files edited
44
# It has to be slightly modified though
45
# to ensure that all values are quoted
46
# since these configs are usually sourced into
48
from configobj import ConfigObj
50
# See: http://tiny.cc/oezbgw
60
39
def _make_sysconfig_bool(val):
68
ci_ver = version.version_string()
69
return '# Created by cloud-init v. %s' % (ci_ver)
72
46
class Distro(distros.Distro):
47
# See: http://tiny.cc/6r99fw
48
clock_conf_fn = "/etc/sysconfig/clock"
49
locale_conf_fn = '/etc/sysconfig/i18n'
50
network_conf_fn = "/etc/sysconfig/network"
51
hostname_conf_fn = "/etc/sysconfig/network"
52
network_script_tpl = '/etc/sysconfig/network-scripts/ifcfg-%s'
53
resolve_conf_fn = "/etc/resolv.conf"
54
tz_local_fn = "/etc/localtime"
55
tz_zone_dir = "/usr/share/zoneinfo"
74
57
def __init__(self, name, cfg, paths):
75
58
distros.Distro.__init__(self, name, cfg, paths)
81
64
def install_packages(self, pkglist):
82
65
self.package_command('install', pkglist)
84
def _write_resolve(self, dns_servers, search_servers):
67
def _adjust_resolve(self, dns_servers, search_servers):
69
r_conf = ResolvConf(util.load_file(self.resolve_conf_fn))
73
"Failed at parsing %s reverting to an empty instance",
75
r_conf = ResolvConf('')
87
78
for s in dns_servers:
88
contents.append("nameserver %s" % (s))
80
r_conf.add_nameserver(s)
82
util.logexc(LOG, "Failed at adding nameserver %s", s)
90
contents.append("search %s" % (" ".join(search_servers)))
92
contents.insert(0, _make_header())
93
util.write_file("/etc/resolv.conf", "\n".join(contents), 0644)
84
for s in search_servers:
86
r_conf.add_search_domain(s)
88
util.logexc(LOG, "Failed at adding search domain %s", s)
89
util.write_file(self.resolve_conf_fn, str(r_conf), 0644)
95
91
def _write_network(self, settings):
96
92
# TODO(harlowja) fix this... since this is the ubuntu format
102
98
searchservers = []
103
99
dev_names = entries.keys()
104
100
for (dev, info) in entries.iteritems():
105
net_fn = NETWORK_FN_TPL % (dev)
101
net_fn = self.network_script_tpl % (dev)
108
104
'NETMASK': info.get('netmask'),
119
115
if 'dns-search' in info:
120
116
searchservers.extend(info['dns-search'])
121
117
if nameservers or searchservers:
122
self._write_resolve(nameservers, searchservers)
118
self._adjust_resolve(nameservers, searchservers)
125
121
'NETWORKING': _make_sysconfig_bool(True),
127
self._update_sysconfig_file("/etc/sysconfig/network", net_cfg)
123
self._update_sysconfig_file(self.network_conf_fn, net_cfg)
130
126
def _update_sysconfig_file(self, fn, adjustments, allow_empty=False):
144
lines = contents.write()
146
lines.insert(0, _make_header())
144
lines.insert(0, util.make_header())
147
145
util.write_file(fn, "\n".join(lines), 0644)
149
def set_hostname(self, hostname, fqdn=None):
150
# See: http://bit.ly/TwitgL
151
# Should be fqdn if we can use it
152
sysconfig_hostname = fqdn
153
if not sysconfig_hostname:
154
sysconfig_hostname = hostname
155
self._write_hostname(sysconfig_hostname, '/etc/sysconfig/network')
156
LOG.debug("Setting hostname to %s", hostname)
157
util.subp(['hostname', hostname])
159
147
def apply_locale(self, locale, out_fn=None):
161
out_fn = '/etc/sysconfig/i18n'
149
out_fn = self.locale_conf_fn
171
159
self._update_sysconfig_file(out_fn, host_cfg)
173
def update_hostname(self, hostname, fqdn, prev_file):
161
def _select_hostname(self, hostname, fqdn):
174
162
# See: http://bit.ly/TwitgL
175
163
# Should be fqdn if we can use it
176
sysconfig_hostname = fqdn
177
if not sysconfig_hostname:
178
sysconfig_hostname = hostname
179
hostname_prev = self._read_hostname(prev_file)
180
hostname_in_sys = self._read_hostname("/etc/sysconfig/network")
182
if not hostname_prev or hostname_prev != sysconfig_hostname:
183
update_files.append(prev_file)
184
if (not hostname_in_sys or
185
(hostname_in_sys == hostname_prev
186
and hostname_in_sys != sysconfig_hostname)):
187
update_files.append("/etc/sysconfig/network")
188
for fn in update_files:
190
self._write_hostname(sysconfig_hostname, fn)
192
util.logexc(LOG, "Failed to write hostname %s to %s",
193
sysconfig_hostname, fn)
194
if (hostname_in_sys and hostname_prev and
195
hostname_in_sys != hostname_prev):
196
LOG.debug(("%s differs from /etc/sysconfig/network."
197
" Assuming user maintained hostname."), prev_file)
198
if "/etc/sysconfig/network" in update_files:
199
LOG.debug("Setting hostname to %s", hostname)
200
util.subp(['hostname', hostname])
168
def _read_system_hostname(self):
169
return (self.network_conf_fn,
170
self._read_hostname(self.network_conf_fn))
202
172
def _read_hostname(self, filename, default=None):
203
173
(_exists, contents) = self._read_conf(filename)
209
179
def _read_conf(self, fn):
211
if os.path.isfile(fn):
212
182
contents = util.load_file(fn).splitlines()
216
return (exists, QuotingConfigObj(contents))
218
189
def _bring_up_interfaces(self, device_names):
219
190
if device_names and 'all' in device_names:
222
193
return distros.Distro._bring_up_interfaces(self, device_names)
224
195
def set_timezone(self, tz):
225
tz_file = os.path.join("/usr/share/zoneinfo", tz)
196
# TODO(harlowja): move this code into
197
# the parent distro...
198
tz_file = os.path.join(self.tz_zone_dir, str(tz))
226
199
if not os.path.isfile(tz_file):
227
200
raise RuntimeError(("Invalid timezone %s,"
228
201
" no file found at %s") % (tz, tz_file))
229
202
# Adjust the sysconfig clock zone setting
233
self._update_sysconfig_file("/etc/sysconfig/clock", clock_cfg)
206
self._update_sysconfig_file(self.clock_conf_fn, clock_cfg)
234
207
# This ensures that the correct tz will be used for the system
235
util.copy(tz_file, "/etc/localtime")
208
util.copy(tz_file, self.tz_local_fn)
237
210
def package_command(self, command, args=None):
256
229
["makecache"], freq=PER_INSTANCE)
259
# This class helps adjust the configobj
260
# writing to ensure that when writing a k/v
261
# on a line, that they are properly quoted
262
# and have no spaces between the '=' sign.
263
# - This is mainly due to the fact that
264
# the sysconfig scripts are often sourced
265
# directly into bash/shell scripts so ensure
266
# that it works for those types of use cases.
267
class QuotingConfigObj(ConfigObj):
268
def __init__(self, lines):
269
ConfigObj.__init__(self, lines,
271
write_empty_values=True)
273
def _quote_posix(self, text):
276
for (k, v) in D_QUOTE_CHARS.iteritems():
277
text = text.replace(k, v)
278
return '"%s"' % (text)
280
def _quote_special(self, text):
281
if text.lower() in ['yes', 'no', 'true', 'false']:
284
return self._quote_posix(text)
286
def _write_line(self, indent_string, entry, this_entry, comment):
287
# Ensure it is formatted fine for
288
# how these sysconfig scripts are used
289
val = self._decode_element(self._quote(this_entry))
290
# Single quoted strings should
292
if not val.startswith("'"):
293
# Perform any special quoting
294
val = self._quote_special(val)
295
key = self._decode_element(self._quote(entry, multiline=False))
296
cmnt = self._decode_element(comment)
297
return '%s%s%s%s%s' % (indent_string,
304
232
# This is a util function to translate a ubuntu /etc/network/interfaces 'blob'
305
233
# to a rhel equiv. that can then be written to /etc/sysconfig/network-scripts/
306
234
# TODO(harlowja) remove when we have python-netcf active...