summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/config/cc_resolv_conf.py107
-rw-r--r--cloudinit/distros/__init__.py85
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py2
-rw-r--r--cloudinit/util.py7
4 files changed, 171 insertions, 30 deletions
diff --git a/cloudinit/config/cc_resolv_conf.py b/cloudinit/config/cc_resolv_conf.py
new file mode 100644
index 00000000..8a460f7e
--- /dev/null
+++ b/cloudinit/config/cc_resolv_conf.py
@@ -0,0 +1,107 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2013 Craig Tracey
+#
+# Author: Craig Tracey <craigtracey@gmail.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/>.
+
+# Note:
+# This module is intended to manage resolv.conf in environments where
+# early configuration of resolv.conf is necessary for further
+# bootstrapping and/or where configuration management such as puppet or
+# chef own dns configuration. As Debian/Ubuntu will, by default, utilize
+# resovlconf, and similarly RedHat will use sysconfig, this module is
+# likely to be of little use unless those are configured correctly.
+#
+# For RedHat with sysconfig, be sure to set PEERDNS=no for all DHCP
+# enabled NICs. And, in Ubuntu/Debian it is recommended that DNS
+# be configured via the standard /etc/network/interfaces configuration
+# file.
+#
+#
+# Usage Example:
+#
+# #cloud-config
+# manage_resolv_conf: true
+#
+# resolv_conf:
+# nameservers: ['8.8.4.4', '8.8.8.8']
+# searchdomains:
+# - foo.example.com
+# - bar.example.com
+# domain: example.com
+# options:
+# rotate: true
+# timeout: 1
+#
+
+
+from cloudinit.settings import PER_INSTANCE
+from cloudinit import templater
+from cloudinit import util
+
+frequency = PER_INSTANCE
+
+distros = ['fedora', 'rhel']
+
+
+def generate_resolv_conf(cloud, log, params):
+ template_fn = cloud.get_template_filename('resolv.conf')
+ if not template_fn:
+ log.warn("No template found, not rendering /etc/resolv.conf")
+ return
+
+ flags = []
+ false_flags = []
+ if 'options' in params:
+ for key, val in params['options'].iteritems():
+ if type(val) == bool:
+ if val:
+ flags.append(key)
+ else:
+ false_flags.append(key)
+
+ for flag in flags + false_flags:
+ del params['options'][flag]
+
+ params['flags'] = flags
+ log.debug("Writing resolv.conf from template %s" % template_fn)
+ templater.render_to_file(template_fn, '/etc/resolv.conf', params)
+
+
+def handle(name, cfg, _cloud, log, _args):
+ """
+ Handler for resolv.conf
+
+ @param name: The module name "resolv-conf" from cloud.cfg
+ @param cfg: A nested dict containing the entire cloud config contents.
+ @param cloud: The L{CloudInit} object in use.
+ @param log: Pre-initialized Python logger object to use for logging.
+ @param args: Any module arguments from cloud.cfg
+ """
+ if "manage_resolv_conf" not in cfg:
+ log.debug(("Skipping module named %s,"
+ " no 'manage_resolv_conf' key in configuration"), name)
+ return
+
+ if not util.get_cfg_option_bool(cfg, "manage_resolv_conf", False):
+ log.debug(("Skipping module named %s,"
+ " 'manage_resolv_conf' present but set to False"), name)
+ return
+
+ if not "resolv_conf" in cfg:
+ log.warn("manage_resolv_conf True but no parameters provided!")
+
+ generate_resolv_conf(_cloud, log, cfg["resolv_conf"])
+ return
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 5a2092c0..0db4aac7 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -720,41 +720,68 @@ def _normalize_users(u_cfg, def_user_cfg=None):
def normalize_users_groups(cfg, distro):
if not cfg:
cfg = {}
+
users = {}
groups = {}
if 'groups' in cfg:
groups = _normalize_groups(cfg['groups'])
- # Handle the previous style of doing this...
- old_user = None
+ # Handle the previous style of doing this where the first user
+ # overrides the concept of the default user if provided in the user: XYZ
+ # format.
+ old_user = {}
if 'user' in cfg and cfg['user']:
- old_user = str(cfg['user'])
- if not 'users' in cfg:
- cfg['users'] = old_user
- old_user = None
- if 'users' in cfg:
- default_user_config = None
- try:
- default_user_config = distro.get_default_user()
- except NotImplementedError:
- LOG.warn(("Distro has not implemented default user "
- "access. No default user will be normalized."))
- base_users = cfg['users']
- if old_user:
- if isinstance(base_users, (list)):
- if len(base_users):
- # The old user replaces user[0]
- base_users[0] = {'name': old_user}
- else:
- # Just add it on at the end...
- base_users.append({'name': old_user})
- elif isinstance(base_users, (dict)):
- if old_user not in base_users:
- base_users[old_user] = True
- elif isinstance(base_users, (str, basestring)):
- # Just append it on to be re-parsed later
- base_users += ",%s" % (old_user)
- users = _normalize_users(base_users, default_user_config)
+ old_user = cfg['user']
+ # Translate it into the format that is more useful
+ # going forward
+ if isinstance(old_user, (basestring, str)):
+ old_user = {
+ 'name': old_user,
+ }
+ if not isinstance(old_user, (dict)):
+ LOG.warn(("Format for 'user' key must be a string or "
+ "dictionary and not %s"), util.obj_name(old_user))
+ old_user = {}
+
+ # If no old user format, then assume the distro
+ # provides what the 'default' user maps to, but notice
+ # that if this is provided, we won't automatically inject
+ # a 'default' user into the users list, while if a old user
+ # format is provided we will.
+ distro_user_config = {}
+ try:
+ distro_user_config = distro.get_default_user()
+ except NotImplementedError:
+ LOG.warn(("Distro has not implemented default user "
+ "access. No distribution provided default user"
+ " will be normalized."))
+
+ # Merge the old user (which may just be an empty dict when not
+ # present with the distro provided default user configuration so
+ # that the old user style picks up all the distribution specific
+ # attributes (if any)
+ default_user_config = util.mergemanydict([old_user, distro_user_config])
+
+ base_users = cfg.get('users', [])
+ if not isinstance(base_users, (list, dict, str, basestring)):
+ LOG.warn(("Format for 'users' key must be a comma separated string"
+ " or a dictionary or a list and not %s"),
+ util.obj_name(base_users))
+ base_users = []
+
+ if old_user:
+ # Ensure that when user: is provided that this user
+ # always gets added (as the default user)
+ if isinstance(base_users, (list)):
+ # Just add it on at the end...
+ base_users.append({'name': 'default'})
+ elif isinstance(base_users, (dict)):
+ base_users['default'] = base_users.get('default', True)
+ elif isinstance(base_users, (str, basestring)):
+ # Just append it on to be re-parsed later
+ base_users += ",default"
+
+ users = _normalize_users(base_users, default_user_config)
return (users, groups)
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index c7826851..ec016a1d 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -270,7 +270,7 @@ def find_candidate_devs():
combined = (by_label + [d for d in by_fstype if d not in by_label])
# We are looking for block device (sda, not sda1), ignore partitions
- combined = [d for d in combined if d[-1] not in "0123456789"]
+ combined = [d for d in combined if not util.is_partition(d)]
return combined
diff --git a/cloudinit/util.py b/cloudinit/util.py
index ab918433..c0ea8d91 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -1553,3 +1553,10 @@ def keyval_str_to_dict(kvstring):
val = True
ret[key] = val
return ret
+
+
+def is_partition(device):
+ if device.startswith("/dev/"):
+ device = device[5:]
+
+ return os.path.isfile("/sys/class/block/%s/partition" % device)