summaryrefslogtreecommitdiff
path: root/cloudinit/distros
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/distros')
-rw-r--r--cloudinit/distros/__init__.py181
-rw-r--r--cloudinit/distros/debian.py13
-rw-r--r--cloudinit/distros/fedora.py2
-rw-r--r--cloudinit/distros/rhel.py12
-rw-r--r--cloudinit/distros/ubuntu.py5
5 files changed, 140 insertions, 73 deletions
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 4bde2393..464ae550 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -24,6 +24,7 @@
from StringIO import StringIO
import abc
+import collections
import itertools
import os
import re
@@ -39,12 +40,12 @@ LOG = logging.getLogger(__name__)
class Distro(object):
-
__metaclass__ = abc.ABCMeta
default_user = None
default_user_groups = None
hosts_fn = "/etc/hosts"
ci_sudoers_fn = "/etc/sudoers.d/90-cloud-init-users"
+ hostname_conf_fn = "/etc/hostname"
def __init__(self, name, cfg, paths):
self._paths = paths
@@ -64,9 +65,10 @@ class Distro(object):
def get_option(self, opt_name, default=None):
return self._cfg.get(opt_name, default)
- @abc.abstractmethod
- def set_hostname(self, hostname):
- raise NotImplementedError()
+ def set_hostname(self, hostname, fqdn=None):
+ writeable_hostname = self._select_hostname(hostname, fqdn)
+ self._write_hostname(writeable_hostname, self.hostname_conf_fn)
+ self._apply_hostname(hostname)
@abc.abstractmethod
def package_command(self, cmd, args=None):
@@ -84,13 +86,13 @@ class Distro(object):
def _get_arch_package_mirror_info(self, arch=None):
mirror_info = self.get_option("package_mirrors", [])
- if arch == None:
+ if not arch:
arch = self.get_primary_arch()
return _get_arch_package_mirror_info(mirror_info, arch)
def get_package_mirror_info(self, arch=None,
availability_zone=None):
- # this resolves the package_mirrors config option
+ # This resolves the package_mirrors config option
# down to a single dict of {mirror_name: mirror_url}
arch_info = self._get_arch_package_mirror_info(arch)
return _get_package_mirror_info(availability_zone=availability_zone,
@@ -140,10 +142,14 @@ class Distro(object):
util.logexc(LOG, ("Failed to non-persistently adjust"
" the system hostname to %s"), hostname)
- def update_hostname(self, hostname, prev_hostname_fn):
- if not hostname:
- return
+ @abc.abstractmethod
+ def _select_hostname(self, hostname, fqdn):
+ raise NotImplementedError()
+ def update_hostname(self, hostname, fqdn,
+ previous_hostname_filename):
+ applying_hostname = hostname
+ hostname = self._select_hostname(hostname, fqdn)
prev_hostname = self._read_hostname(prev_hostname_fn)
(sys_fn, sys_hostname) = self._read_system_hostname()
update_files = []
@@ -171,7 +177,7 @@ class Distro(object):
prev_hostname_fn, sys_fn)
if sys_fn in update_files:
- self._apply_hostname(hostname)
+ self._apply_hostname(applying_hostname)
def update_etc_hosts(self, hostname, fqdn):
header = ''
@@ -239,22 +245,7 @@ class Distro(object):
return False
def get_default_user(self):
- if not self.default_user:
- return None
- user_cfg = {
- 'name': self.default_user,
- 'plain_text_passwd': self.default_user,
- 'home': "/home/%s" % (self.default_user),
- 'shell': "/bin/bash",
- 'lock_passwd': True,
- 'gecos': "%s" % (self.default_user.title()),
- 'sudo': "ALL=(ALL) NOPASSWD:ALL",
- }
- def_groups = self.default_user_groups
- if not def_groups:
- def_groups = []
- user_cfg['groups'] = util.uniq_merge_sorted(def_groups)
- return user_cfg
+ return self.get_option('default_user')
def create_user(self, name, **kwargs):
"""
@@ -270,23 +261,23 @@ class Distro(object):
# inputs. If something goes wrong, we can end up with a system
# that nobody can login to.
adduser_opts = {
- "gecos": '--comment',
- "homedir": '--home',
- "primary_group": '--gid',
- "groups": '--groups',
- "passwd": '--password',
- "shell": '--shell',
- "expiredate": '--expiredate',
- "inactive": '--inactive',
- "selinux_user": '--selinux-user',
- }
+ "gecos": '--comment',
+ "homedir": '--home',
+ "primary_group": '--gid',
+ "groups": '--groups',
+ "passwd": '--password',
+ "shell": '--shell',
+ "expiredate": '--expiredate',
+ "inactive": '--inactive',
+ "selinux_user": '--selinux-user',
+ }
adduser_opts_flags = {
- "no_user_group": '--no-user-group',
- "system": '--system',
- "no_log_init": '--no-log-init',
- "no_create_home": "-M",
- }
+ "no_user_group": '--no-user-group',
+ "system": '--system',
+ "no_log_init": '--no-log-init',
+ "no_create_home": "-M",
+ }
# Now check the value and create the command
for option in kwargs:
@@ -314,7 +305,7 @@ class Distro(object):
if util.is_user(name):
LOG.warn("User %s already exists, skipping." % name)
else:
- LOG.debug("Creating name %s" % name)
+ LOG.debug("Adding user named %s", name)
try:
util.subp(adduser_cmd, logstring=x_adduser_cmd)
except Exception as e:
@@ -343,7 +334,7 @@ class Distro(object):
# Import SSH keys
if 'ssh_authorized_keys' in kwargs:
keys = set(kwargs['ssh_authorized_keys']) or []
- ssh_util.setup_user_keys(keys, name, None, self._paths)
+ ssh_util.setup_user_keys(keys, name, key_prefix=None)
return True
@@ -362,32 +353,79 @@ class Distro(object):
return True
+ def ensure_sudo_dir(self, path, sudo_base='/etc/sudoers'):
+ # Ensure the dir is included and that
+ # it actually exists as a directory
+ sudoers_contents = ''
+ base_exists = False
+ if os.path.exists(sudo_base):
+ sudoers_contents = util.load_file(sudo_base)
+ base_exists = True
+ found_include = False
+ for line in sudoers_contents.splitlines():
+ line = line.strip()
+ include_match = re.search(r"^#includedir\s+(.*)$", line)
+ if not include_match:
+ continue
+ included_dir = include_match.group(1).strip()
+ if not included_dir:
+ continue
+ included_dir = os.path.abspath(included_dir)
+ if included_dir == path:
+ found_include = True
+ break
+ if not found_include:
+ try:
+ if not base_exists:
+ lines = [('# See sudoers(5) for more information'
+ ' on "#include" directives:'), '',
+ util.make_header(base="added"),
+ "#includedir %s" % (path), '']
+ sudoers_contents = "\n".join(lines)
+ util.write_file(sudo_base, sudoers_contents, 0440)
+ else:
+ lines = ['', util.make_header(base="added"),
+ "#includedir %s" % (path), '']
+ sudoers_contents = "\n".join(lines)
+ util.append_file(sudo_base, sudoers_contents)
+ LOG.debug("Added '#includedir %s' to %s" % (path, sudo_base))
+ except IOError as e:
+ util.logexc(LOG, "Failed to write %s" % sudo_base, e)
+ raise e
+ util.ensure_dir(path, 0750)
+
def write_sudo_rules(self, user, rules, sudo_file=None):
if not sudo_file:
sudo_file = self.ci_sudoers_fn
- content_header = "# User rules for %s" % user
- content = "%s\n%s %s\n\n" % (content_header, user, rules)
-
- if isinstance(rules, (list, tuple, set)):
- content = "%s\n" % content_header
+ lines = [
+ '',
+ "# User rules for %s" % user,
+ ]
+ if isinstance(rules, collections.Iterable):
for rule in rules:
- content += "%s %s\n" % (user, rule)
- content += "\n"
+ lines.append("%s %s" % (user, rule))
+ else:
+ lines.append("%s %s" % (user, rules))
+ content = "\n".join(lines)
+ self.ensure_sudo_dir(os.path.dirname(sudo_file))
if not os.path.exists(sudo_file):
contents = [
util.make_header(),
content,
]
- util.write_file(sudo_file, "\n".join(contents), 0440)
- else:
try:
- with open(sudo_file, 'a') as f:
- f.write(content)
+ util.write_file(sudo_file, "\n".join(contents), 0440)
except IOError as e:
util.logexc(LOG, "Failed to write sudoers file %s", sudo_file)
raise e
+ else:
+ try:
+ util.append_file(sudo_file, content)
+ except IOError as e:
+ util.logexc(LOG, "Failed to append sudoers file %s", sudo_file)
+ raise e
def create_group(self, name, members):
group_add_cmd = ['groupadd', name]
@@ -476,12 +514,36 @@ def _get_arch_package_mirror_info(package_mirrors, arch):
# is the standard form used in the rest
# of cloud-init
def _normalize_groups(grp_cfg):
- if isinstance(grp_cfg, (str, basestring, list)):
+ if isinstance(grp_cfg, (str, basestring)):
+ grp_cfg = grp_cfg.strip().split(",")
+ if isinstance(grp_cfg, (list)):
c_grp_cfg = {}
- for i in util.uniq_merge(grp_cfg):
- c_grp_cfg[i] = []
+ for i in grp_cfg:
+ if isinstance(i, (dict)):
+ for k, v in i.items():
+ if k not in c_grp_cfg:
+ if isinstance(v, (list)):
+ c_grp_cfg[k] = list(v)
+ elif isinstance(v, (basestring, str)):
+ c_grp_cfg[k] = [v]
+ else:
+ raise TypeError("Bad group member type %s" %
+ util.obj_name(v))
+ else:
+ if isinstance(v, (list)):
+ c_grp_cfg[k].extend(v)
+ elif isinstance(v, (basestring, str)):
+ c_grp_cfg[k].append(v)
+ else:
+ raise TypeError("Bad group member type %s" %
+ util.obj_name(v))
+ elif isinstance(i, (str, basestring)):
+ if i not in c_grp_cfg:
+ c_grp_cfg[i] = []
+ else:
+ raise TypeError("Unknown group name type %s" %
+ util.obj_name(i))
grp_cfg = c_grp_cfg
-
groups = {}
if isinstance(grp_cfg, (dict)):
for (grp_name, grp_members) in grp_cfg.items():
@@ -503,7 +565,7 @@ def _normalize_groups(grp_cfg):
# configuration.
#
# The output is a dictionary of user
-# names => user config which is the standard
+# names => user config which is the standard
# form used in the rest of cloud-init. Note
# the default user will have a special config
# entry 'default' which will be marked as true
@@ -568,6 +630,7 @@ def _normalize_users(u_cfg, def_user_cfg=None):
# Pickup what the default 'real name' is
# and any groups that are provided by the
# default config
+ def_user_cfg = def_user_cfg.copy()
def_user = def_user_cfg.pop('name')
def_groups = def_user_cfg.pop('groups', [])
# Pickup any config + groups for that user name
diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py
index 0d5cbac7..b6e7654f 100644
--- a/cloudinit/distros/debian.py
+++ b/cloudinit/distros/debian.py
@@ -80,9 +80,12 @@ class Distro(distros.Distro):
else:
return distros.Distro._bring_up_interfaces(self, device_names)
- def set_hostname(self, hostname):
- self._write_hostname(hostname, self.hostname_conf_fn)
- self._apply_hostname(hostname)
+ 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 _write_hostname(self, your_hostname, out_fn):
conf = self._read_hostname_conf(out_fn)
@@ -128,10 +131,10 @@ class Distro(distros.Distro):
if not os.path.isfile(tz_file):
raise RuntimeError(("Invalid timezone %s,"
" no file found at %s") % (tz, tz_file))
- # "" provides trailing newline during join
+ # Note: "" provides trailing newline during join
tz_lines = [
util.make_header(),
- str(tz),
+ str(tz),
"",
]
util.write_file(self.tz_conf_fn, "\n".join(tz_lines))
diff --git a/cloudinit/distros/fedora.py b/cloudinit/distros/fedora.py
index f65a820d..c777845d 100644
--- a/cloudinit/distros/fedora.py
+++ b/cloudinit/distros/fedora.py
@@ -28,4 +28,4 @@ LOG = logging.getLogger(__name__)
class Distro(rhel.Distro):
- default_user = 'ec2-user'
+ pass
diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py
index 039215c8..7df01c62 100644
--- a/cloudinit/distros/rhel.py
+++ b/cloudinit/distros/rhel.py
@@ -48,6 +48,7 @@ class Distro(distros.Distro):
clock_conf_fn = "/etc/sysconfig/clock"
locale_conf_fn = '/etc/sysconfig/i18n'
network_conf_fn = "/etc/sysconfig/network"
+ hostname_conf_fn = "/etc/sysconfig/network"
network_script_tpl = '/etc/sysconfig/network-scripts/ifcfg-%s'
resolve_conf_fn = "/etc/resolv.conf"
tz_local_fn = "/etc/localtime"
@@ -143,10 +144,6 @@ class Distro(distros.Distro):
lines.insert(0, util.make_header())
util.write_file(fn, "\n".join(lines), 0644)
- def set_hostname(self, hostname):
- self._write_hostname(hostname, self.network_conf_fn)
- self._apply_hostname(hostname)
-
def apply_locale(self, locale, out_fn=None):
if not out_fn:
out_fn = self.locale_conf_fn
@@ -161,6 +158,13 @@ class Distro(distros.Distro):
}
self._update_sysconfig_file(out_fn, host_cfg)
+ def _select_hostname(self, hostname, fqdn):
+ # See: http://bit.ly/TwitgL
+ # Should be fqdn if we can use it
+ if fqdn:
+ return fqdn
+ return hostname
+
def _read_system_hostname(self):
return (self.network_conf_fn,
self._read_hostname(self.network_conf_fn))
diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py
index 4e697f82..c527f248 100644
--- a/cloudinit/distros/ubuntu.py
+++ b/cloudinit/distros/ubuntu.py
@@ -28,7 +28,4 @@ LOG = logging.getLogger(__name__)
class Distro(debian.Distro):
-
- default_user = 'ubuntu'
- default_user_groups = ("adm,audio,cdrom,dialout,floppy,video,"
- "plugdev,dip,netdev,sudo")
+ pass