From e05e747a7293f991843ca4b7ba5e0736cb5f043f Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Tue, 25 Jun 2013 08:51:21 +0200 Subject: Add a SLES distro handler --- cloudinit/distros/sles.py | 226 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 cloudinit/distros/sles.py (limited to 'cloudinit/distros/sles.py') diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py new file mode 100644 index 00000000..e068b4bd --- /dev/null +++ b/cloudinit/distros/sles.py @@ -0,0 +1,226 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2013 SUSE LLC +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. +# +# Author: Robert Schweikert +# Author: Juerg Haefliger +# +# Leaning very heavily on the RHEL and Debian implementation +# +# 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 . + +import os + +from cloudinit import distros + +from cloudinit.distros.parsers.hostname import HostnameConf + +from cloudinit import helpers +from cloudinit import log as logging +from cloudinit import util + +from cloudinit.distros import rhel_util +from cloudinit.settings import PER_INSTANCE + +LOG = logging.getLogger(__name__) + + +class Distro(distros.Distro): + clock_conf_fn = '/etc/sysconfig/clock' + locale_conf_fn = '/etc/sysconfig/language' + network_conf_fn = '/etc/sysconfig/network' + hostname_conf_fn = '/etc/HOSTNAME' + network_script_tpl = '/etc/sysconfig/network/ifcfg-%s' + resolve_conf_fn = '/etc/resolv.conf' + tz_local_fn = '/etc/localtime' + tz_zone_dir = '/usr/share/zoneinfo' + + 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) + self.osfamily = 'suse' + + def install_packages(self, pkglist): + self.package_command('install', args='-l', pkgs=pkglist) + + def _write_network(self, settings): + # Convert debian settings to ifcfg format + entries = rhel_util.translate_network(settings) + LOG.debug("Translated ubuntu style network settings %s into %s", + settings, entries) + # Make the intermediate format as the suse format... + nameservers = [] + searchservers = [] + dev_names = entries.keys() + for (dev, info) in entries.iteritems(): + net_fn = self.network_script_tpl % (dev) + mode = info.get('auto') + if mode and mode.lower() == 'true': + mode = 'auto' + else: + mode = 'manual' + net_cfg = { + 'BOOTPROTO': info.get('bootproto'), + 'BROADCAST': info.get('broadcast'), + 'GATEWAY': info.get('gateway'), + 'IPADDR': info.get('address'), + 'LLADDR': info.get('hwaddress'), + 'NETMASK': info.get('netmask'), + 'STARTMODE': mode, + 'USERCONTROL': 'no' + } + if dev != 'lo': + net_cfg['ETHERDEVICE'] = dev + net_cfg['ETHTOOL_OPTIONS'] = '' + else: + net_cfg['FIREWALL'] = 'no' + rhel_util.update_sysconfig_file(net_fn, net_cfg, True) + if 'dns-nameservers' in info: + nameservers.extend(info['dns-nameservers']) + if 'dns-search' in info: + searchservers.extend(info['dns-search']) + if nameservers or searchservers: + rhel_util.update_resolve_conf_file(self.resolve_conf_fn, + nameservers, searchservers) + return dev_names + + def apply_locale(self, locale, out_fn=None): + if not out_fn: + out_fn = self.locale_conf_fn + locale_cfg = { + 'RC_LANG': locale, + } + rhel_util.update_sysconfig_file(out_fn, locale_cfg) + + def _write_hostname(self, hostname, out_fn): + 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 + if not conf: + conf = HostnameConf('') + conf.set_hostname(hostname) + util.write_file(out_fn, str(conf), 0644) + + def _select_hostname(self, hostname, fqdn): + # Prefer the short hostname over the long + # fully qualified domain name + if not hostname: + return fqdn + return hostname + + def _read_system_hostname(self): + host_fn = self.hostname_conf_fn + return (host_fn, self._read_hostname(host_fn)) + + def _read_hostname_conf(self, filename): + conf = HostnameConf(util.load_file(filename)) + conf.parse() + return conf + + def _read_hostname(self, filename, default=None): + hostname = None + try: + conf = self._read_hostname_conf(filename) + hostname = conf.hostname + except IOError: + pass + if not hostname: + return default + return hostname + + def _bring_up_interfaces(self, device_names): + if device_names and 'all' in device_names: + raise RuntimeError(('Distro %s can not translate ' + 'the device name "all"') % (self.name)) + return distros.Distro._bring_up_interfaces(self, device_names) + + def set_timezone(self, tz): + # TODO(harlowja): move this code into + # the parent distro... + tz_file = os.path.join(self.tz_zone_dir, str(tz)) + if not os.path.isfile(tz_file): + raise RuntimeError(("Invalid timezone %s," + " no file found at %s") % (tz, tz_file)) + # Adjust the sysconfig clock zone setting + clock_cfg = { + 'TIMEZONE': str(tz), + } + rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg) + # This ensures that the correct tz will be used for the system + util.copy(tz_file, self.tz_local_fn) + + def package_command(self, command, args=None, pkgs=None): + if pkgs is None: + pkgs = [] + + cmd = ['zypper'] + # No user interaction possible, enable non-interactive mode + cmd.append('-t') + # Do not check the keys, we assume that the initial repos configured + # in the image can be trusted + cmd.append('--no-gpg-checks') + + # Comand is the operation, such as install + cmd.append(command) + + # args are the arguments to the command, not global options + if args and isinstance(args, str): + cmd.append(args) + elif args and isinstance(args, list): + cmd.extend(args) + + pkglist = util.expand_package_list('%s-%s', pkgs) + cmd.extend(pkglist) + + # Allow the output of this to flow outwards (ie not be captured) + util.subp(cmd, capture=False) + + def update_package_sources(self): + self._runner.run("update-sources", self.package_command, + ['refresh'], freq=PER_INSTANCE) + + # Copied from parent class and modified to use short option names since + # the SLES command doesn't support long names (yet). This method can be + # removed when SLES finally catches up. + def lock_passwd(self, name): + """ + Lock the password of a user, i.e., disable password logins + """ + try: + util.subp(['passwd', '-l', name]) + except Exception as e: + util.logexc(LOG, 'Failed to disable password for user %s', name) + raise e + + # Copied from parent class and modified to use short option names since + # the SLES command doesn't support long names (yet). This method can be + # removed when SLES finally catches up. + def set_passwd(self, user, passwd, hashed=False): + pass_string = '%s:%s' % (user, passwd) + cmd = ['chpasswd'] + if hashed: + cmd.append('-e') + try: + util.subp(cmd, pass_string, logstring="chpasswd for %s" % user) + except Exception as e: + util.logexc(LOG, "Failed to set password for %s", user) + raise e + return True -- cgit v1.2.3 From c8eb622ae0c3f9fab2b25112aa87a2dbf39788db Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Wed, 26 Jun 2013 09:51:59 +0200 Subject: Use short option names for passwd utilities SLES 11 doesn't support long option names for the passwd utilities. Use the short option names in the parent distro class and remove the custom SLES methods. --- cloudinit/distros/__init__.py | 10 ++++++++-- cloudinit/distros/sles.py | 28 ---------------------------- 2 files changed, 8 insertions(+), 30 deletions(-) (limited to 'cloudinit/distros/sles.py') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index ef5db86b..f8727fc1 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -386,7 +386,10 @@ class Distro(object): Lock the password of a user, i.e., disable password logins """ try: - util.subp(['passwd', '--lock', name]) + # Need to use the short option name '-l' instead of '--lock' + # (which would be more descriptive) since SLES 11 doesn't know + # about long names. + util.subp(['passwd', '-l', name]) except Exception as e: util.logexc(LOG, 'Failed to disable password for user %s', name) raise e @@ -396,7 +399,10 @@ class Distro(object): cmd = ['chpasswd'] if hashed: - cmd.append('--encrypted') + # Need to use the short option name '-e' instead of '--encrypted' + # (which would be more descriptive) since SLES 11 doesn't know + # about long names. + cmd.append('-e') try: util.subp(cmd, pass_string, logstring="chpasswd for %s" % user) diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py index e068b4bd..95bc411a 100644 --- a/cloudinit/distros/sles.py +++ b/cloudinit/distros/sles.py @@ -196,31 +196,3 @@ class Distro(distros.Distro): def update_package_sources(self): self._runner.run("update-sources", self.package_command, ['refresh'], freq=PER_INSTANCE) - - # Copied from parent class and modified to use short option names since - # the SLES command doesn't support long names (yet). This method can be - # removed when SLES finally catches up. - def lock_passwd(self, name): - """ - Lock the password of a user, i.e., disable password logins - """ - try: - util.subp(['passwd', '-l', name]) - except Exception as e: - util.logexc(LOG, 'Failed to disable password for user %s', name) - raise e - - # Copied from parent class and modified to use short option names since - # the SLES command doesn't support long names (yet). This method can be - # removed when SLES finally catches up. - def set_passwd(self, user, passwd, hashed=False): - pass_string = '%s:%s' % (user, passwd) - cmd = ['chpasswd'] - if hashed: - cmd.append('-e') - try: - util.subp(cmd, pass_string, logstring="chpasswd for %s" % user) - except Exception as e: - util.logexc(LOG, "Failed to set password for %s", user) - raise e - return True -- cgit v1.2.3 From 467d45906c4575c1d231af268c47e812356657b8 Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Thu, 27 Jun 2013 13:50:33 +0200 Subject: Remove 'Copyright SUSE' from the headers Per discussion with Robert @ SUSE since he can't sign the CCA. --- cloudinit/config/cc_resolv_conf.py | 2 -- cloudinit/distros/__init__.py | 2 -- cloudinit/distros/rhel.py | 2 -- cloudinit/distros/rhel_util.py | 2 -- cloudinit/distros/sles.py | 2 -- 5 files changed, 10 deletions(-) (limited to 'cloudinit/distros/sles.py') diff --git a/cloudinit/config/cc_resolv_conf.py b/cloudinit/config/cc_resolv_conf.py index d4fead12..879b62b1 100644 --- a/cloudinit/config/cc_resolv_conf.py +++ b/cloudinit/config/cc_resolv_conf.py @@ -1,11 +1,9 @@ # vi: ts=4 expandtab # # Copyright (C) 2013 Craig Tracey -# Copyright (C) 2013 SUSE LLC # Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # # Author: Craig Tracey -# Author: Robert Schweikert # Author: Juerg Haefliger # # This program is free software: you can redistribute it and/or modify diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index f8727fc1..cda2c6af 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -3,13 +3,11 @@ # Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. # Copyright (C) 2012 Yahoo! Inc. -# Copyright (C) 2013 SUSE LLC # # Author: Scott Moser # Author: Juerg Haefliger # Author: Joshua Harlow # Author: Ben Howard -# Author: Robert Schweikert # # 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 diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index 4fd4239c..a022ca60 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -3,12 +3,10 @@ # Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. # Copyright (C) 2012 Yahoo! Inc. -# Copyright (C) 2013 SUSE LLC # # Author: Scott Moser # Author: Juerg Haefliger # Author: Joshua Harlow -# Author: Robert Schweikert # # 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 diff --git a/cloudinit/distros/rhel_util.py b/cloudinit/distros/rhel_util.py index 504b5d2c..1aba58b8 100644 --- a/cloudinit/distros/rhel_util.py +++ b/cloudinit/distros/rhel_util.py @@ -3,12 +3,10 @@ # Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. # Copyright (C) 2012 Yahoo! Inc. -# Copyright (C) 2013 SUSE LLC # # Author: Scott Moser # Author: Juerg Haefliger # Author: Joshua Harlow -# Author: Robert Schweikert # # 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 diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py index 95bc411a..d0c15feb 100644 --- a/cloudinit/distros/sles.py +++ b/cloudinit/distros/sles.py @@ -1,9 +1,7 @@ # vi: ts=4 expandtab # -# Copyright (C) 2013 SUSE LLC # Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # -# Author: Robert Schweikert # Author: Juerg Haefliger # # Leaning very heavily on the RHEL and Debian implementation -- cgit v1.2.3 From 0c7c5b999e09acf8795c6db2f1b50a801c0eae8f Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Thu, 27 Jun 2013 14:14:51 +0200 Subject: Fix SLES zypper command usage --- cloudinit/distros/sles.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'cloudinit/distros/sles.py') diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py index d0c15feb..904e931a 100644 --- a/cloudinit/distros/sles.py +++ b/cloudinit/distros/sles.py @@ -171,10 +171,7 @@ class Distro(distros.Distro): cmd = ['zypper'] # No user interaction possible, enable non-interactive mode - cmd.append('-t') - # Do not check the keys, we assume that the initial repos configured - # in the image can be trusted - cmd.append('--no-gpg-checks') + cmd.append('--non-interactive') # Comand is the operation, such as install cmd.append(command) -- cgit v1.2.3 From 696bcc1f0acc67646872cd6ce1b90375ca0ae068 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 24 Jul 2013 22:12:33 -0700 Subject: Remove duplicate timezone file finding and error raising. --- cloudinit/distros/__init__.py | 9 +++++++++ cloudinit/distros/debian.py | 8 +------- cloudinit/distros/rhel.py | 8 +------- cloudinit/distros/sles.py | 8 +------- 4 files changed, 12 insertions(+), 21 deletions(-) (limited to 'cloudinit/distros/sles.py') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 249e1b19..74e95797 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -47,9 +47,11 @@ LOG = logging.getLogger(__name__) class Distro(object): __metaclass__ = abc.ABCMeta + hosts_fn = "/etc/hosts" ci_sudoers_fn = "/etc/sudoers.d/90-cloud-init-users" hostname_conf_fn = "/etc/hostname" + tz_zone_dir = "/usr/share/zoneinfo" def __init__(self, name, cfg, paths): self._paths = paths @@ -66,6 +68,13 @@ class Distro(object): # to write this blob out in a distro format raise NotImplementedError() + def _find_tz_file(self, tz): + tz_file = os.path.join(self.tz_zone_dir, str(tz)) + if not os.path.isfile(tz_file): + raise IOError(("Invalid timezone %s," + " no file found at %s") % (tz, tz_file)) + return tz_file + def get_option(self, opt_name, default=None): return self._cfg.get(opt_name, default) diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index 0811eefd..8fe49cbe 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -44,7 +44,6 @@ class Distro(distros.Distro): network_conf_fn = "/etc/network/interfaces" tz_conf_fn = "/etc/timezone" tz_local_fn = "/etc/localtime" - tz_zone_dir = "/usr/share/zoneinfo" def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) @@ -130,12 +129,7 @@ class Distro(distros.Distro): return "127.0.1.1" def set_timezone(self, tz): - # TODO(harlowja): move this code into - # the parent distro... - tz_file = os.path.join(self.tz_zone_dir, str(tz)) - if not os.path.isfile(tz_file): - raise RuntimeError(("Invalid timezone %s," - " no file found at %s") % (tz, tz_file)) + tz_file = self._find_tz_file(tz) # Note: "" provides trailing newline during join tz_lines = [ util.make_header(), diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index a022ca60..ece1a5ff 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -51,7 +51,6 @@ class Distro(distros.Distro): network_script_tpl = '/etc/sysconfig/network-scripts/ifcfg-%s' resolve_conf_fn = "/etc/resolv.conf" tz_local_fn = "/etc/localtime" - tz_zone_dir = "/usr/share/zoneinfo" def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) @@ -164,12 +163,7 @@ class Distro(distros.Distro): return distros.Distro._bring_up_interfaces(self, device_names) def set_timezone(self, tz): - # TODO(harlowja): move this code into - # the parent distro... - tz_file = os.path.join(self.tz_zone_dir, str(tz)) - if not os.path.isfile(tz_file): - raise RuntimeError(("Invalid timezone %s," - " no file found at %s") % (tz, tz_file)) + tz_file = self._find_tz_file(tz) if self._dist_uses_systemd(): # Currently, timedatectl complains if invoked during startup # so for compatibility, create the link manually. diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py index 904e931a..92a1c307 100644 --- a/cloudinit/distros/sles.py +++ b/cloudinit/distros/sles.py @@ -42,7 +42,6 @@ class Distro(distros.Distro): network_script_tpl = '/etc/sysconfig/network/ifcfg-%s' resolve_conf_fn = '/etc/resolv.conf' tz_local_fn = '/etc/localtime' - tz_zone_dir = '/usr/share/zoneinfo' def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) @@ -151,12 +150,7 @@ class Distro(distros.Distro): return distros.Distro._bring_up_interfaces(self, device_names) def set_timezone(self, tz): - # TODO(harlowja): move this code into - # the parent distro... - tz_file = os.path.join(self.tz_zone_dir, str(tz)) - if not os.path.isfile(tz_file): - raise RuntimeError(("Invalid timezone %s," - " no file found at %s") % (tz, tz_file)) + tz_file = self._find_tz_file(tz) # Adjust the sysconfig clock zone setting clock_cfg = { 'TIMEZONE': str(tz), -- cgit v1.2.3 From 2e4d57b867b71831270655956d06a8e14793a8f3 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 6 Aug 2013 11:36:30 +0100 Subject: fix pep8 and pylint warnings --- cloudinit/distros/rhel.py | 2 -- cloudinit/distros/sles.py | 2 -- tests/unittests/test_datasource/test_azure.py | 1 - 3 files changed, 5 deletions(-) (limited to 'cloudinit/distros/sles.py') diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index ece1a5ff..30195384 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -20,8 +20,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os - from cloudinit import distros from cloudinit import helpers from cloudinit import log as logging diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py index 92a1c307..f2ac4efc 100644 --- a/cloudinit/distros/sles.py +++ b/cloudinit/distros/sles.py @@ -18,8 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os - from cloudinit import distros from cloudinit.distros.parsers.hostname import HostnameConf diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py index 4cd3f213..06f8a5d2 100644 --- a/tests/unittests/test_datasource/test_azure.py +++ b/tests/unittests/test_datasource/test_azure.py @@ -249,7 +249,6 @@ class TestAzureDataSource(MockerTestCase): def test_apply_bounce_call_1(self): # hostname needs to get through to apply_hostname_bounce - mydata = "FOOBAR" odata = {'HostName': 'my-random-hostname'} data = {'ovfcontent': construct_valid_ovf_env(data=odata)} -- cgit v1.2.3