summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/config/cc_apt_configure.py (renamed from cloudinit/config/cc_apt_update_upgrade.py)68
-rw-r--r--cloudinit/config/cc_apt_pipelining.py12
-rw-r--r--cloudinit/config/cc_ca_certs.py24
-rw-r--r--cloudinit/config/cc_chef.py30
-rw-r--r--cloudinit/config/cc_landscape.py22
-rw-r--r--cloudinit/config/cc_mcollective.py22
-rw-r--r--cloudinit/config/cc_migrator.py83
-rw-r--r--cloudinit/config/cc_mounts.py9
-rw-r--r--cloudinit/config/cc_package_update_upgrade_install.py99
-rw-r--r--cloudinit/config/cc_phone_home.py4
-rw-r--r--cloudinit/config/cc_puppet.py70
-rw-r--r--cloudinit/config/cc_resizefs.py5
-rw-r--r--cloudinit/config/cc_rsyslog.py3
-rw-r--r--cloudinit/config/cc_runcmd.py2
-rw-r--r--cloudinit/config/cc_salt_minion.py6
-rw-r--r--cloudinit/config/cc_set_hostname.py10
-rw-r--r--cloudinit/config/cc_set_passwords.py6
-rw-r--r--cloudinit/config/cc_ssh.py16
-rw-r--r--cloudinit/config/cc_ssh_authkey_fingerprints.py7
-rw-r--r--cloudinit/config/cc_update_etc_hosts.py3
-rw-r--r--cloudinit/config/cc_update_hostname.py8
-rw-r--r--cloudinit/config/cc_yum_add_repo.py106
-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
-rw-r--r--cloudinit/ec2_utils.py67
-rw-r--r--cloudinit/handlers/__init__.py15
-rw-r--r--cloudinit/helpers.py36
-rw-r--r--cloudinit/log.py12
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py21
-rw-r--r--cloudinit/sources/DataSourceCloudStack.py9
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py27
-rw-r--r--cloudinit/sources/DataSourceEc2.py25
-rw-r--r--cloudinit/sources/DataSourceMAAS.py5
-rw-r--r--cloudinit/sources/DataSourceOVF.py5
-rw-r--r--cloudinit/sources/__init__.py58
-rw-r--r--cloudinit/ssh_util.py26
-rw-r--r--cloudinit/stages.py35
-rw-r--r--cloudinit/user_data.py2
-rw-r--r--cloudinit/util.py13
42 files changed, 773 insertions, 411 deletions
diff --git a/cloudinit/config/cc_apt_update_upgrade.py b/cloudinit/config/cc_apt_configure.py
index 356bb98d..f8664160 100644
--- a/cloudinit/config/cc_apt_update_upgrade.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -20,7 +20,6 @@
import glob
import os
-import time
from cloudinit import templater
from cloudinit import util
@@ -47,9 +46,6 @@ EXPORT_GPG_KEYID = """
def handle(name, cfg, cloud, log, _args):
- update = util.get_cfg_option_bool(cfg, 'apt_update', False)
- upgrade = util.get_cfg_option_bool(cfg, 'apt_upgrade', False)
-
release = get_release()
mirrors = find_apt_mirror_info(cloud, cfg)
if not mirrors or "primary" not in mirrors:
@@ -61,7 +57,7 @@ def handle(name, cfg, cloud, log, _args):
mirror = mirrors["primary"]
mirrors["mirror"] = mirror
- log.debug("mirror info: %s" % mirrors)
+ log.debug("Mirror info: %s" % mirrors)
if not util.get_cfg_option_bool(cfg,
'apt_preserve_sources_list', False):
@@ -78,8 +74,7 @@ def handle(name, cfg, cloud, log, _args):
try:
# See man 'apt.conf'
contents = PROXY_TPL % (proxy)
- util.write_file(cloud.paths.join(False, proxy_filename),
- contents)
+ util.write_file(proxy_filename, contents)
except Exception as e:
util.logexc(log, "Failed to write proxy to %s", proxy_filename)
elif os.path.isfile(proxy_filename):
@@ -90,61 +85,18 @@ def handle(name, cfg, cloud, log, _args):
params = mirrors
params['RELEASE'] = release
params['MIRROR'] = mirror
- errors = add_sources(cloud, cfg['apt_sources'], params)
+ errors = add_sources(cfg['apt_sources'], params)
for e in errors:
- log.warn("Source Error: %s", ':'.join(e))
+ log.warn("Add source error: %s", ':'.join(e))
dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False)
if dconf_sel:
- log.debug("setting debconf selections per cloud config")
+ log.debug("Setting debconf selections per cloud config")
try:
util.subp(('debconf-set-selections', '-'), dconf_sel)
- except:
+ except Exception:
util.logexc(log, "Failed to run debconf-set-selections")
- pkglist = util.get_cfg_option_list(cfg, 'packages', [])
-
- errors = []
- if update or len(pkglist) or upgrade:
- try:
- cloud.distro.update_package_sources()
- except Exception as e:
- util.logexc(log, "Package update failed")
- errors.append(e)
-
- if upgrade:
- try:
- cloud.distro.package_command("upgrade")
- except Exception as e:
- util.logexc(log, "Package upgrade failed")
- errors.append(e)
-
- if len(pkglist):
- try:
- cloud.distro.install_packages(pkglist)
- except Exception as e:
- util.logexc(log, "Failed to install packages: %s ", pkglist)
- errors.append(e)
-
- # kernel and openssl (possibly some other packages)
- # write a file /var/run/reboot-required after upgrading.
- # if that file exists and configured, then just stop right now and reboot
- # TODO(smoser): handle this less voilently
- reboot_file = "/var/run/reboot-required"
- if ((upgrade or pkglist) and cfg.get("apt_reboot_if_required", False) and
- os.path.isfile(reboot_file)):
- log.warn("rebooting after upgrade or install per %s" % reboot_file)
- time.sleep(1) # give the warning time to get out
- util.subp(["/sbin/reboot"])
- time.sleep(60)
- log.warn("requested reboot did not happen!")
- errors.append(Exception("requested reboot did not happen!"))
-
- if len(errors):
- log.warn("%s failed with exceptions, re-raising the last one",
- len(errors))
- raise errors[-1]
-
# get gpg keyid from keyserver
def getkeybyid(keyid, keyserver):
@@ -196,11 +148,10 @@ def generate_sources_list(codename, mirrors, cloud, log):
params = {'codename': codename}
for k in mirrors:
params[k] = mirrors[k]
- out_fn = cloud.paths.join(False, '/etc/apt/sources.list')
- templater.render_to_file(template_fn, out_fn, params)
+ templater.render_to_file(template_fn, '/etc/apt/sources.list', params)
-def add_sources(cloud, srclist, template_params=None):
+def add_sources(srclist, template_params=None):
"""
add entries in /etc/apt/sources.list.d for each abbreviated
sources.list entry in 'srclist'. When rendering template, also
@@ -250,8 +201,7 @@ def add_sources(cloud, srclist, template_params=None):
try:
contents = "%s\n" % (source)
- util.write_file(cloud.paths.join(False, ent['filename']),
- contents, omode="ab")
+ util.write_file(ent['filename'], contents, omode="ab")
except:
errorlist.append([source,
"failed write to file %s" % ent['filename']])
diff --git a/cloudinit/config/cc_apt_pipelining.py b/cloudinit/config/cc_apt_pipelining.py
index 02056ee0..e5629175 100644
--- a/cloudinit/config/cc_apt_pipelining.py
+++ b/cloudinit/config/cc_apt_pipelining.py
@@ -34,26 +34,24 @@ APT_PIPE_TPL = ("//Written by cloud-init per 'apt_pipelining'\n"
# on TCP connections - otherwise data corruption will occur.
-def handle(_name, cfg, cloud, log, _args):
+def handle(_name, cfg, _cloud, log, _args):
apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", False)
apt_pipe_value_s = str(apt_pipe_value).lower().strip()
if apt_pipe_value_s == "false":
- write_apt_snippet(cloud, "0", log, DEFAULT_FILE)
+ write_apt_snippet("0", log, DEFAULT_FILE)
elif apt_pipe_value_s in ("none", "unchanged", "os"):
return
elif apt_pipe_value_s in [str(b) for b in xrange(0, 6)]:
- write_apt_snippet(cloud, apt_pipe_value_s, log, DEFAULT_FILE)
+ write_apt_snippet(apt_pipe_value_s, log, DEFAULT_FILE)
else:
log.warn("Invalid option for apt_pipeling: %s", apt_pipe_value)
-def write_apt_snippet(cloud, setting, log, f_name):
+def write_apt_snippet(setting, log, f_name):
"""Writes f_name with apt pipeline depth 'setting'."""
file_contents = APT_PIPE_TPL % (setting)
-
- util.write_file(cloud.paths.join(False, f_name), file_contents)
-
+ util.write_file(f_name, file_contents)
log.debug("Wrote %s with apt pipeline depth setting %s", f_name, setting)
diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py
index dc046bda..20f24357 100644
--- a/cloudinit/config/cc_ca_certs.py
+++ b/cloudinit/config/cc_ca_certs.py
@@ -22,6 +22,7 @@ CA_CERT_PATH = "/usr/share/ca-certificates/"
CA_CERT_FILENAME = "cloud-init-ca-certs.crt"
CA_CERT_CONFIG = "/etc/ca-certificates.conf"
CA_CERT_SYSTEM_PATH = "/etc/ssl/certs/"
+CA_CERT_FULL_PATH = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME)
distros = ['ubuntu', 'debian']
@@ -33,7 +34,7 @@ def update_ca_certs():
util.subp(["update-ca-certificates"], capture=False)
-def add_ca_certs(paths, certs):
+def add_ca_certs(certs):
"""
Adds certificates to the system. To actually apply the new certificates
you must also call L{update_ca_certs}.
@@ -43,27 +44,24 @@ def add_ca_certs(paths, certs):
if certs:
# First ensure they are strings...
cert_file_contents = "\n".join([str(c) for c in certs])
- cert_file_fullpath = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME)
- cert_file_fullpath = paths.join(False, cert_file_fullpath)
- util.write_file(cert_file_fullpath, cert_file_contents, mode=0644)
+ util.write_file(CA_CERT_FULL_PATH, cert_file_contents, mode=0644)
# Append cert filename to CA_CERT_CONFIG file.
- util.write_file(paths.join(False, CA_CERT_CONFIG),
- "\n%s" % CA_CERT_FILENAME, omode="ab")
+ util.write_file(CA_CERT_CONFIG, "\n%s" % CA_CERT_FILENAME, omode="ab")
-def remove_default_ca_certs(paths):
+def remove_default_ca_certs():
"""
Removes all default trusted CA certificates from the system. To actually
apply the change you must also call L{update_ca_certs}.
"""
- util.delete_dir_contents(paths.join(False, CA_CERT_PATH))
- util.delete_dir_contents(paths.join(False, CA_CERT_SYSTEM_PATH))
- util.write_file(paths.join(False, CA_CERT_CONFIG), "", mode=0644)
+ util.delete_dir_contents(CA_CERT_PATH)
+ util.delete_dir_contents(CA_CERT_SYSTEM_PATH)
+ util.write_file(CA_CERT_CONFIG, "", mode=0644)
debconf_sel = "ca-certificates ca-certificates/trust_new_crts select no"
util.subp(('debconf-set-selections', '-'), debconf_sel)
-def handle(name, cfg, cloud, log, _args):
+def handle(name, cfg, _cloud, log, _args):
"""
Call to handle ca-cert sections in cloud-config file.
@@ -85,14 +83,14 @@ def handle(name, cfg, cloud, log, _args):
# default trusted CA certs first.
if ca_cert_cfg.get("remove-defaults", False):
log.debug("Removing default certificates")
- remove_default_ca_certs(cloud.paths)
+ remove_default_ca_certs()
# If we are given any new trusted CA certs to add, add them.
if "trusted" in ca_cert_cfg:
trusted_certs = util.get_cfg_option_list(ca_cert_cfg, "trusted")
if trusted_certs:
log.debug("Adding %d certificates" % len(trusted_certs))
- add_ca_certs(cloud.paths, trusted_certs)
+ add_ca_certs(trusted_certs)
# Update the system with the new cert configuration.
log.debug("Updating certificates")
diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py
index 6f568261..7a3d6a31 100644
--- a/cloudinit/config/cc_chef.py
+++ b/cloudinit/config/cc_chef.py
@@ -26,6 +26,15 @@ from cloudinit import util
RUBY_VERSION_DEFAULT = "1.8"
+CHEF_DIRS = [
+ '/etc/chef',
+ '/var/log/chef',
+ '/var/lib/chef',
+ '/var/cache/chef',
+ '/var/backups/chef',
+ '/var/run/chef',
+]
+
def handle(name, cfg, cloud, log, _args):
@@ -37,24 +46,15 @@ def handle(name, cfg, cloud, log, _args):
chef_cfg = cfg['chef']
# Ensure the chef directories we use exist
- c_dirs = [
- '/etc/chef',
- '/var/log/chef',
- '/var/lib/chef',
- '/var/cache/chef',
- '/var/backups/chef',
- '/var/run/chef',
- ]
- for d in c_dirs:
- util.ensure_dir(cloud.paths.join(False, d))
+ for d in CHEF_DIRS:
+ util.ensure_dir(d)
# Set the validation key based on the presence of either 'validation_key'
# or 'validation_cert'. In the case where both exist, 'validation_key'
# takes precedence
for key in ('validation_key', 'validation_cert'):
if key in chef_cfg and chef_cfg[key]:
- v_fn = cloud.paths.join(False, '/etc/chef/validation.pem')
- util.write_file(v_fn, chef_cfg[key])
+ util.write_file('/etc/chef/validation.pem', chef_cfg[key])
break
# Create the chef config from template
@@ -68,8 +68,7 @@ def handle(name, cfg, cloud, log, _args):
'_default'),
'validation_name': chef_cfg['validation_name']
}
- out_fn = cloud.paths.join(False, '/etc/chef/client.rb')
- templater.render_to_file(template_fn, out_fn, params)
+ templater.render_to_file(template_fn, '/etc/chef/client.rb', params)
else:
log.warn("No template found, not rendering to /etc/chef/client.rb")
@@ -81,8 +80,7 @@ def handle(name, cfg, cloud, log, _args):
initial_attributes = chef_cfg['initial_attributes']
for k in list(initial_attributes.keys()):
initial_json[k] = initial_attributes[k]
- firstboot_fn = cloud.paths.join(False, '/etc/chef/firstboot.json')
- util.write_file(firstboot_fn, json.dumps(initial_json))
+ util.write_file('/etc/chef/firstboot.json', json.dumps(initial_json))
# If chef is not installed, we install chef based on 'install_type'
if not os.path.isfile('/usr/bin/chef-client'):
diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py
index 7cfb8296..02610dd0 100644
--- a/cloudinit/config/cc_landscape.py
+++ b/cloudinit/config/cc_landscape.py
@@ -59,28 +59,26 @@ def handle(_name, cfg, cloud, log, _args):
raise RuntimeError(("'landscape' key existed in config,"
" but not a dictionary type,"
" is a %s instead"), util.obj_name(ls_cloudcfg))
+ if not ls_cloudcfg:
+ return
+
+ cloud.distro.install_packages(["landscape-client"])
merge_data = [
LSC_BUILTIN_CFG,
- cloud.paths.join(True, LSC_CLIENT_CFG_FILE),
+ LSC_CLIENT_CFG_FILE,
ls_cloudcfg,
]
merged = merge_together(merge_data)
-
- lsc_client_fn = cloud.paths.join(False, LSC_CLIENT_CFG_FILE)
- lsc_dir = cloud.paths.join(False, os.path.dirname(lsc_client_fn))
- if not os.path.isdir(lsc_dir):
- util.ensure_dir(lsc_dir)
-
contents = StringIO()
merged.write(contents)
- contents.flush()
- util.write_file(lsc_client_fn, contents.getvalue())
- log.debug("Wrote landscape config file to %s", lsc_client_fn)
+ util.ensure_dir(os.path.dirname(LSC_CLIENT_CFG_FILE))
+ util.write_file(LSC_CLIENT_CFG_FILE, contents.getvalue())
+ log.debug("Wrote landscape config file to %s", LSC_CLIENT_CFG_FILE)
- if ls_cloudcfg:
- util.write_file(LS_DEFAULT_FILE, "RUN=1\n")
+ util.write_file(LS_DEFAULT_FILE, "RUN=1\n")
+ util.subp(["service", "landscape-client", "restart"])
def merge_together(objs):
diff --git a/cloudinit/config/cc_mcollective.py b/cloudinit/config/cc_mcollective.py
index 2acdbc6f..b670390d 100644
--- a/cloudinit/config/cc_mcollective.py
+++ b/cloudinit/config/cc_mcollective.py
@@ -29,6 +29,7 @@ from cloudinit import util
PUBCERT_FILE = "/etc/mcollective/ssl/server-public.pem"
PRICERT_FILE = "/etc/mcollective/ssl/server-private.pem"
+SERVER_CFG = '/etc/mcollective/server.cfg'
def handle(name, cfg, cloud, log, _args):
@@ -48,26 +49,23 @@ def handle(name, cfg, cloud, log, _args):
if 'conf' in mcollective_cfg:
# Read server.cfg values from the
# original file in order to be able to mix the rest up
- server_cfg_fn = cloud.paths.join(True, '/etc/mcollective/server.cfg')
- mcollective_config = ConfigObj(server_cfg_fn)
+ mcollective_config = ConfigObj(SERVER_CFG)
# See: http://tiny.cc/jh9agw
for (cfg_name, cfg) in mcollective_cfg['conf'].iteritems():
if cfg_name == 'public-cert':
- pubcert_fn = cloud.paths.join(True, PUBCERT_FILE)
- util.write_file(pubcert_fn, cfg, mode=0644)
- mcollective_config['plugin.ssl_server_public'] = pubcert_fn
+ util.write_file(PUBCERT_FILE, cfg, mode=0644)
+ mcollective_config['plugin.ssl_server_public'] = PUBCERT_FILE
mcollective_config['securityprovider'] = 'ssl'
elif cfg_name == 'private-cert':
- pricert_fn = cloud.paths.join(True, PRICERT_FILE)
- util.write_file(pricert_fn, cfg, mode=0600)
- mcollective_config['plugin.ssl_server_private'] = pricert_fn
+ util.write_file(PRICERT_FILE, cfg, mode=0600)
+ mcollective_config['plugin.ssl_server_private'] = PRICERT_FILE
mcollective_config['securityprovider'] = 'ssl'
else:
if isinstance(cfg, (basestring, str)):
# Just set it in the 'main' section
mcollective_config[cfg_name] = cfg
elif isinstance(cfg, (dict)):
- # Iterate throug the config items, create a section
+ # Iterate through the config items, create a section
# if it is needed and then add/or create items as needed
if cfg_name not in mcollective_config.sections:
mcollective_config[cfg_name] = {}
@@ -78,14 +76,12 @@ def handle(name, cfg, cloud, log, _args):
mcollective_config[cfg_name] = str(cfg)
# We got all our config as wanted we'll rename
# the previous server.cfg and create our new one
- old_fn = cloud.paths.join(False, '/etc/mcollective/server.cfg.old')
- util.rename(server_cfg_fn, old_fn)
+ util.rename(SERVER_CFG, "%s.old" % (SERVER_CFG))
# Now we got the whole file, write to disk...
contents = StringIO()
mcollective_config.write(contents)
contents = contents.getvalue()
- server_cfg_rw = cloud.paths.join(False, '/etc/mcollective/server.cfg')
- util.write_file(server_cfg_rw, contents, mode=0644)
+ util.write_file(SERVER_CFG, contents, mode=0644)
# Start mcollective
util.subp(['service', 'mcollective', 'start'], capture=False)
diff --git a/cloudinit/config/cc_migrator.py b/cloudinit/config/cc_migrator.py
new file mode 100644
index 00000000..58232fc9
--- /dev/null
+++ b/cloudinit/config/cc_migrator.py
@@ -0,0 +1,83 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Yahoo! Inc.
+#
+# Author: Joshua Harlow <harlowja@yahoo-inc.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/>.
+
+import os
+import shutil
+
+from cloudinit import helpers
+from cloudinit import util
+
+from cloudinit.settings import PER_ALWAYS
+
+frequency = PER_ALWAYS
+
+
+def _migrate_canon_sems(cloud):
+ sem_path = cloud.paths.get_ipath('sem')
+ if not sem_path or not os.path.exists(sem_path):
+ return 0
+ am_adjusted = 0
+ for p in os.listdir(sem_path):
+ full_path = os.path.join(sem_path, p)
+ if os.path.isfile(full_path):
+ (name, ext) = os.path.splitext(p)
+ canon_name = helpers.canon_sem_name(name)
+ if canon_name != name:
+ new_path = os.path.join(sem_path, canon_name + ext)
+ shutil.move(full_path, new_path)
+ am_adjusted += 1
+ return am_adjusted
+
+
+def _migrate_legacy_sems(cloud, log):
+ sem_path = cloud.paths.get_ipath('sem')
+ if not sem_path or not os.path.exists(sem_path):
+ return
+ legacy_adjust = {
+ 'apt-update-upgrade': [
+ 'apt-configure',
+ 'package-update-upgrade-install',
+ ],
+ }
+ sem_helper = helpers.FileSemaphores(sem_path)
+ for (mod_name, migrate_to) in legacy_adjust.items():
+ possibles = [mod_name, helpers.canon_sem_name(mod_name)]
+ old_exists = []
+ for p in os.listdir(sem_path):
+ (name, _ext) = os.path.splitext(p)
+ if name in possibles and os.path.isfile(p):
+ old_exists.append(p)
+ for p in old_exists:
+ util.del_file(os.path.join(sem_path, p))
+ (_name, freq) = os.path.splitext(p)
+ for m in migrate_to:
+ log.debug("Migrating %s => %s with the same frequency",
+ p, m)
+ with sem_helper.lock(m, freq):
+ pass
+
+
+def handle(name, cfg, cloud, log, _args):
+ do_migrate = util.get_cfg_option_str(cfg, "migrate", True)
+ if not util.translate_bool(do_migrate):
+ log.debug("Skipping module named %s, migration disabled", name)
+ return
+ sems_moved = _migrate_canon_sems(cloud)
+ log.debug("Migrated %s semaphore files to there canonicalized names",
+ sems_moved)
+ _migrate_legacy_sems(cloud, log)
diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py
index 14c965bb..cb772c86 100644
--- a/cloudinit/config/cc_mounts.py
+++ b/cloudinit/config/cc_mounts.py
@@ -28,6 +28,7 @@ from cloudinit import util
SHORTNAME_FILTER = r"^[x]{0,1}[shv]d[a-z][0-9]*$"
SHORTNAME = re.compile(SHORTNAME_FILTER)
WS = re.compile("[%s]+" % (whitespace))
+FSTAB_PATH = "/etc/fstab"
def is_mdname(name):
@@ -167,8 +168,7 @@ def handle(_name, cfg, cloud, log, _args):
cc_lines.append('\t'.join(line))
fstab_lines = []
- fstab = util.load_file(cloud.paths.join(True, "/etc/fstab"))
- for line in fstab.splitlines():
+ for line in util.load_file(FSTAB_PATH).splitlines():
try:
toks = WS.split(line)
if toks[3].find(comment) != -1:
@@ -179,7 +179,7 @@ def handle(_name, cfg, cloud, log, _args):
fstab_lines.extend(cc_lines)
contents = "%s\n" % ('\n'.join(fstab_lines))
- util.write_file(cloud.paths.join(False, "/etc/fstab"), contents)
+ util.write_file(FSTAB_PATH, contents)
if needswap:
try:
@@ -188,9 +188,8 @@ def handle(_name, cfg, cloud, log, _args):
util.logexc(log, "Activating swap via 'swapon -a' failed")
for d in dirs:
- real_dir = cloud.paths.join(False, d)
try:
- util.ensure_dir(real_dir)
+ util.ensure_dir(d)
except:
util.logexc(log, "Failed to make '%s' config-mount", d)
diff --git a/cloudinit/config/cc_package_update_upgrade_install.py b/cloudinit/config/cc_package_update_upgrade_install.py
new file mode 100644
index 00000000..73b0e30d
--- /dev/null
+++ b/cloudinit/config/cc_package_update_upgrade_install.py
@@ -0,0 +1,99 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Yahoo! Inc.
+#
+# Author: Joshua Harlow <harlowja@yahoo-inc.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/>.
+
+import os
+import time
+
+from cloudinit import log as logging
+from cloudinit import util
+
+REBOOT_FILE = "/var/run/reboot-required"
+REBOOT_CMD = ["/sbin/reboot"]
+
+
+def _multi_cfg_bool_get(cfg, *keys):
+ for k in keys:
+ if util.get_cfg_option_bool(cfg, k, False):
+ return True
+ return False
+
+
+def _fire_reboot(log, wait_attempts=6, initial_sleep=1, backoff=2):
+ util.subp(REBOOT_CMD)
+ start = time.time()
+ wait_time = initial_sleep
+ for _i in range(0, wait_attempts):
+ time.sleep(wait_time)
+ wait_time *= backoff
+ elapsed = time.time() - start
+ log.debug("Rebooted, but still running after %s seconds", int(elapsed))
+ # If we got here, not good
+ elapsed = time.time() - start
+ raise RuntimeError(("Reboot did not happen"
+ " after %s seconds!") % (int(elapsed)))
+
+
+def handle(_name, cfg, cloud, log, _args):
+ # Handle the old style + new config names
+ update = _multi_cfg_bool_get(cfg, 'apt_update', 'package_update')
+ upgrade = _multi_cfg_bool_get(cfg, 'package_upgrade', 'apt_upgrade')
+ reboot_if_required = _multi_cfg_bool_get(cfg, 'apt_reboot_if_required',
+ 'package_reboot_if_required')
+ pkglist = util.get_cfg_option_list(cfg, 'packages', [])
+
+ errors = []
+ if update or len(pkglist) or upgrade:
+ try:
+ cloud.distro.update_package_sources()
+ except Exception as e:
+ util.logexc(log, "Package update failed")
+ errors.append(e)
+
+ if upgrade:
+ try:
+ cloud.distro.package_command("upgrade")
+ except Exception as e:
+ util.logexc(log, "Package upgrade failed")
+ errors.append(e)
+
+ if len(pkglist):
+ try:
+ cloud.distro.install_packages(pkglist)
+ except Exception as e:
+ util.logexc(log, "Failed to install packages: %s", pkglist)
+ errors.append(e)
+
+ # TODO(smoser): handle this less violently
+ # kernel and openssl (possibly some other packages)
+ # write a file /var/run/reboot-required after upgrading.
+ # if that file exists and configured, then just stop right now and reboot
+ reboot_fn_exists = os.path.isfile(REBOOT_FILE)
+ if (upgrade or pkglist) and reboot_if_required and reboot_fn_exists:
+ try:
+ log.warn("Rebooting after upgrade or install per %s", REBOOT_FILE)
+ # Flush the above warning + anything else out...
+ logging.flushLoggers(log)
+ _fire_reboot(log)
+ except Exception as e:
+ util.logexc(log, "Requested reboot did not happen!")
+ errors.append(e)
+
+ if len(errors):
+ log.warn("%s failed with exceptions, re-raising the last one",
+ len(errors))
+ raise errors[-1]
diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py
index ae1349eb..886487f8 100644
--- a/cloudinit/config/cc_phone_home.py
+++ b/cloudinit/config/cc_phone_home.py
@@ -84,10 +84,10 @@ def handle(name, cfg, cloud, log, args):
for (n, path) in pubkeys.iteritems():
try:
- all_keys[n] = util.load_file(cloud.paths.join(True, path))
+ all_keys[n] = util.load_file(path)
except:
util.logexc(log, ("%s: failed to open, can not"
- " phone home that data"), path)
+ " phone home that data!"), path)
submit_keys = {}
for k in post_list:
diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py
index 74ee18e1..8fe3af57 100644
--- a/cloudinit/config/cc_puppet.py
+++ b/cloudinit/config/cc_puppet.py
@@ -21,12 +21,32 @@
from StringIO import StringIO
import os
-import pwd
import socket
from cloudinit import helpers
from cloudinit import util
+PUPPET_CONF_PATH = '/etc/puppet/puppet.conf'
+PUPPET_SSL_CERT_DIR = '/var/lib/puppet/ssl/certs/'
+PUPPET_SSL_DIR = '/var/lib/puppet/ssl'
+PUPPET_SSL_CERT_PATH = '/var/lib/puppet/ssl/certs/ca.pem'
+
+
+def _autostart_puppet(log):
+ # Set puppet to automatically start
+ if os.path.exists('/etc/default/puppet'):
+ util.subp(['sed', '-i',
+ '-e', 's/^START=.*/START=yes/',
+ '/etc/default/puppet'], capture=False)
+ elif os.path.exists('/bin/systemctl'):
+ util.subp(['/bin/systemctl', 'enable', 'puppet.service'],
+ capture=False)
+ elif os.path.exists('/sbin/chkconfig'):
+ util.subp(['/sbin/chkconfig', 'puppet', 'on'], capture=False)
+ else:
+ log.warn(("Sorry we do not know how to enable"
+ " puppet services on this system"))
+
def handle(name, cfg, cloud, log, _args):
# If there isn't a puppet key in the configuration don't do anything
@@ -43,8 +63,7 @@ def handle(name, cfg, cloud, log, _args):
# ... and then update the puppet configuration
if 'conf' in puppet_cfg:
# Add all sections from the conf object to puppet.conf
- puppet_conf_fn = cloud.paths.join(True, '/etc/puppet/puppet.conf')
- contents = util.load_file(puppet_conf_fn)
+ contents = util.load_file(PUPPET_CONF_PATH)
# Create object for reading puppet.conf values
puppet_config = helpers.DefaultingConfigParser()
# Read puppet.conf values from original file in order to be able to
@@ -53,28 +72,19 @@ def handle(name, cfg, cloud, log, _args):
cleaned_lines = [i.lstrip() for i in contents.splitlines()]
cleaned_contents = '\n'.join(cleaned_lines)
puppet_config.readfp(StringIO(cleaned_contents),
- filename=puppet_conf_fn)
+ filename=PUPPET_CONF_PATH)
for (cfg_name, cfg) in puppet_cfg['conf'].iteritems():
# Cert configuration is a special case
# Dump the puppet master ca certificate in the correct place
if cfg_name == 'ca_cert':
# Puppet ssl sub-directory isn't created yet
# Create it with the proper permissions and ownership
- pp_ssl_dir = cloud.paths.join(False, '/var/lib/puppet/ssl')
- util.ensure_dir(pp_ssl_dir, 0771)
- util.chownbyid(pp_ssl_dir,
- pwd.getpwnam('puppet').pw_uid, 0)
- pp_ssl_certs = cloud.paths.join(False,
- '/var/lib/puppet/ssl/certs/')
- util.ensure_dir(pp_ssl_certs)
- util.chownbyid(pp_ssl_certs,
- pwd.getpwnam('puppet').pw_uid, 0)
- pp_ssl_ca_certs = cloud.paths.join(False,
- ('/var/lib/puppet/'
- 'ssl/certs/ca.pem'))
- util.write_file(pp_ssl_ca_certs, cfg)
- util.chownbyid(pp_ssl_ca_certs,
- pwd.getpwnam('puppet').pw_uid, 0)
+ util.ensure_dir(PUPPET_SSL_DIR, 0771)
+ util.chownbyname(PUPPET_SSL_DIR, 'puppet', 'root')
+ util.ensure_dir(PUPPET_SSL_CERT_DIR)
+ util.chownbyname(PUPPET_SSL_CERT_DIR, 'puppet', 'root')
+ util.write_file(PUPPET_SSL_CERT_PATH, str(cfg))
+ util.chownbyname(PUPPET_SSL_CERT_PATH, 'puppet', 'root')
else:
# Iterate throug the config items, we'll use ConfigParser.set
# to overwrite or create new items as needed
@@ -90,25 +100,11 @@ def handle(name, cfg, cloud, log, _args):
puppet_config.set(cfg_name, o, v)
# We got all our config as wanted we'll rename
# the previous puppet.conf and create our new one
- conf_old_fn = cloud.paths.join(False,
- '/etc/puppet/puppet.conf.old')
- util.rename(puppet_conf_fn, conf_old_fn)
- puppet_conf_rw = cloud.paths.join(False, '/etc/puppet/puppet.conf')
- util.write_file(puppet_conf_rw, puppet_config.stringify())
+ util.rename(PUPPET_CONF_PATH, "%s.old" % (PUPPET_CONF_PATH))
+ util.write_file(PUPPET_CONF_PATH, puppet_config.stringify())
- # Set puppet to automatically start
- if os.path.exists('/etc/default/puppet'):
- util.subp(['sed', '-i',
- '-e', 's/^START=.*/START=yes/',
- '/etc/default/puppet'], capture=False)
- elif os.path.exists('/bin/systemctl'):
- util.subp(['/bin/systemctl', 'enable', 'puppet.service'],
- capture=False)
- elif os.path.exists('/sbin/chkconfig'):
- util.subp(['/sbin/chkconfig', 'puppet', 'on'], capture=False)
- else:
- log.warn(("Sorry we do not know how to enable"
- " puppet services on this system"))
+ # Set it up so it autostarts
+ _autostart_puppet(log)
# Start puppetd
util.subp(['service', 'puppet', 'start'], capture=False)
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index e7f27944..b958f332 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -62,7 +62,7 @@ def get_fs_type(st_dev, path, log):
raise
-def handle(name, cfg, cloud, log, args):
+def handle(name, cfg, _cloud, log, args):
if len(args) != 0:
resize_root = args[0]
else:
@@ -74,11 +74,10 @@ def handle(name, cfg, cloud, log, args):
# TODO(harlowja) is the directory ok to be used??
resize_root_d = util.get_cfg_option_str(cfg, "resize_rootfs_tmp", "/run")
- resize_root_d = cloud.paths.join(False, resize_root_d)
util.ensure_dir(resize_root_d)
# TODO(harlowja): allow what is to be resized to be configurable??
- resize_what = cloud.paths.join(False, "/")
+ resize_what = "/"
with util.ExtendedTemporaryFile(prefix="cloudinit.resizefs.",
dir=resize_root_d, delete=True) as tfh:
devpth = tfh.name
diff --git a/cloudinit/config/cc_rsyslog.py b/cloudinit/config/cc_rsyslog.py
index 78327526..0c2c6880 100644
--- a/cloudinit/config/cc_rsyslog.py
+++ b/cloudinit/config/cc_rsyslog.py
@@ -71,8 +71,7 @@ def handle(name, cfg, cloud, log, _args):
try:
contents = "%s\n" % (content)
- util.write_file(cloud.paths.join(False, filename),
- contents, omode=omode)
+ util.write_file(filename, contents, omode=omode)
except Exception:
util.logexc(log, "Failed to write to %s", filename)
diff --git a/cloudinit/config/cc_runcmd.py b/cloudinit/config/cc_runcmd.py
index 65064cfb..598c3a3e 100644
--- a/cloudinit/config/cc_runcmd.py
+++ b/cloudinit/config/cc_runcmd.py
@@ -33,6 +33,6 @@ def handle(name, cfg, cloud, log, _args):
cmd = cfg["runcmd"]
try:
content = util.shellify(cmd)
- util.write_file(cloud.paths.join(False, out_fn), content, 0700)
+ util.write_file(out_fn, content, 0700)
except:
util.logexc(log, "Failed to shellify %s into file %s", cmd, out_fn)
diff --git a/cloudinit/config/cc_salt_minion.py b/cloudinit/config/cc_salt_minion.py
index 8a1440d9..f3eede18 100644
--- a/cloudinit/config/cc_salt_minion.py
+++ b/cloudinit/config/cc_salt_minion.py
@@ -34,8 +34,7 @@ def handle(name, cfg, cloud, log, _args):
cloud.distro.install_packages(["salt-minion"])
# Ensure we can configure files at the right dir
- config_dir = cloud.paths.join(False, salt_cfg.get("config_dir",
- '/etc/salt'))
+ config_dir = salt_cfg.get("config_dir", '/etc/salt')
util.ensure_dir(config_dir)
# ... and then update the salt configuration
@@ -47,8 +46,7 @@ def handle(name, cfg, cloud, log, _args):
# ... copy the key pair if specified
if 'public_key' in salt_cfg and 'private_key' in salt_cfg:
- pki_dir = cloud.paths.join(False, salt_cfg.get('pki_dir',
- '/etc/salt/pki'))
+ pki_dir = salt_cfg.get('pki_dir', '/etc/salt/pki')
with util.umask(077):
util.ensure_dir(pki_dir)
pub_name = os.path.join(pki_dir, 'minion.pub')
diff --git a/cloudinit/config/cc_set_hostname.py b/cloudinit/config/cc_set_hostname.py
index b0f27ebf..2b32fc94 100644
--- a/cloudinit/config/cc_set_hostname.py
+++ b/cloudinit/config/cc_set_hostname.py
@@ -27,9 +27,11 @@ def handle(name, cfg, cloud, log, _args):
" not setting the hostname in module %s"), name)
return
- (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
try:
- log.debug("Setting hostname to %s", hostname)
- cloud.distro.set_hostname(hostname)
+ log.debug("Setting the hostname to %s (%s)", fqdn, hostname)
+ cloud.distro.set_hostname(hostname, fqdn)
except Exception:
- util.logexc(log, "Failed to set hostname to %s", hostname)
+ util.logexc(log, "Failed to set the hostname to %s (%s)",
+ fqdn, hostname)
+ raise
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
index 26c558ad..c6bf62fd 100644
--- a/cloudinit/config/cc_set_passwords.py
+++ b/cloudinit/config/cc_set_passwords.py
@@ -114,8 +114,7 @@ def handle(_name, cfg, cloud, log, args):
replaced_auth = False
# See: man sshd_config
- conf_fn = cloud.paths.join(True, ssh_util.DEF_SSHD_CFG)
- old_lines = ssh_util.parse_ssh_config(conf_fn)
+ old_lines = ssh_util.parse_ssh_config(ssh_util.DEF_SSHD_CFG)
new_lines = []
i = 0
for (i, line) in enumerate(old_lines):
@@ -134,8 +133,7 @@ def handle(_name, cfg, cloud, log, args):
pw_auth))
lines = [str(e) for e in new_lines]
- ssh_rw_fn = cloud.paths.join(False, ssh_util.DEF_SSHD_CFG)
- util.write_file(ssh_rw_fn, "\n".join(lines))
+ util.write_file(ssh_util.DEF_SSHD_CFG, "\n".join(lines))
try:
cmd = ['service']
diff --git a/cloudinit/config/cc_ssh.py b/cloudinit/config/cc_ssh.py
index 32e48c30..b623d476 100644
--- a/cloudinit/config/cc_ssh.py
+++ b/cloudinit/config/cc_ssh.py
@@ -59,7 +59,7 @@ def handle(_name, cfg, cloud, log, _args):
# remove the static keys from the pristine image
if cfg.get("ssh_deletekeys", True):
- key_pth = cloud.paths.join(False, "/etc/ssh/", "ssh_host_*key*")
+ key_pth = os.path.join("/etc/ssh/", "ssh_host_*key*")
for f in glob.glob(key_pth):
try:
util.del_file(f)
@@ -72,8 +72,7 @@ def handle(_name, cfg, cloud, log, _args):
if key in KEY_2_FILE:
tgt_fn = KEY_2_FILE[key][0]
tgt_perms = KEY_2_FILE[key][1]
- util.write_file(cloud.paths.join(False, tgt_fn),
- val, tgt_perms)
+ util.write_file(tgt_fn, val, tgt_perms)
for (priv, pub) in PRIV_2_PUB.iteritems():
if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']:
@@ -94,7 +93,7 @@ def handle(_name, cfg, cloud, log, _args):
'ssh_genkeytypes',
GENERATE_KEY_NAMES)
for keytype in genkeys:
- keyfile = cloud.paths.join(False, KEY_FILE_TPL % (keytype))
+ keyfile = KEY_FILE_TPL % (keytype)
util.ensure_dir(os.path.dirname(keyfile))
if not os.path.exists(keyfile):
cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
@@ -118,17 +117,16 @@ def handle(_name, cfg, cloud, log, _args):
cfgkeys = cfg["ssh_authorized_keys"]
keys.extend(cfgkeys)
- apply_credentials(keys, user, cloud.paths,
- disable_root, disable_root_opts)
+ apply_credentials(keys, user, disable_root, disable_root_opts)
except:
util.logexc(log, "Applying ssh credentials failed!")
-def apply_credentials(keys, user, paths, disable_root, disable_root_opts):
+def apply_credentials(keys, user, disable_root, disable_root_opts):
keys = set(keys)
if user:
- ssh_util.setup_user_keys(keys, user, '', paths)
+ ssh_util.setup_user_keys(keys, user, '')
if disable_root:
if not user:
@@ -137,4 +135,4 @@ def apply_credentials(keys, user, paths, disable_root, disable_root_opts):
else:
key_prefix = ''
- ssh_util.setup_user_keys(keys, 'root', key_prefix, paths)
+ ssh_util.setup_user_keys(keys, 'root', key_prefix)
diff --git a/cloudinit/config/cc_ssh_authkey_fingerprints.py b/cloudinit/config/cc_ssh_authkey_fingerprints.py
index 8c9a8806..c38bcea2 100644
--- a/cloudinit/config/cc_ssh_authkey_fingerprints.py
+++ b/cloudinit/config/cc_ssh_authkey_fingerprints.py
@@ -97,9 +97,8 @@ def handle(name, cfg, cloud, log, _args):
"logging of ssh fingerprints disabled"), name)
hash_meth = util.get_cfg_option_str(cfg, "authkey_hash", "md5")
- extract_func = ssh_util.extract_authorized_keys
(users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
for (user_name, _cfg) in users.items():
- (auth_key_fn, auth_key_entries) = extract_func(user_name, cloud.paths)
- _pprint_key_entries(user_name, auth_key_fn,
- auth_key_entries, hash_meth)
+ (key_fn, key_entries) = ssh_util.extract_authorized_keys(user_name)
+ _pprint_key_entries(user_name, key_fn,
+ key_entries, hash_meth)
diff --git a/cloudinit/config/cc_update_etc_hosts.py b/cloudinit/config/cc_update_etc_hosts.py
index 4d75000f..96103615 100644
--- a/cloudinit/config/cc_update_etc_hosts.py
+++ b/cloudinit/config/cc_update_etc_hosts.py
@@ -42,8 +42,7 @@ def handle(name, cfg, cloud, log, _args):
raise RuntimeError(("No hosts template could be"
" found for distro %s") % (cloud.distro.name))
- out_fn = cloud.paths.join(False, '/etc/hosts')
- templater.render_to_file(tpl_fn_name, out_fn,
+ templater.render_to_file(tpl_fn_name, '/etc/hosts',
{'hostname': hostname, 'fqdn': fqdn})
elif manage_hosts == "localhost":
diff --git a/cloudinit/config/cc_update_hostname.py b/cloudinit/config/cc_update_hostname.py
index 1d6679ea..52225cd8 100644
--- a/cloudinit/config/cc_update_hostname.py
+++ b/cloudinit/config/cc_update_hostname.py
@@ -32,10 +32,12 @@ def handle(name, cfg, cloud, log, _args):
" not updating the hostname in module %s"), name)
return
- (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
try:
prev_fn = os.path.join(cloud.get_cpath('data'), "previous-hostname")
- cloud.distro.update_hostname(hostname, prev_fn)
+ log.debug("Updating hostname to %s (%s)", fqdn, hostname)
+ cloud.distro.update_hostname(hostname, fqdn, prev_fn)
except Exception:
- util.logexc(log, "Failed to set the hostname to %s", hostname)
+ util.logexc(log, "Failed to update the hostname to %s (%s)",
+ fqdn, hostname)
raise
diff --git a/cloudinit/config/cc_yum_add_repo.py b/cloudinit/config/cc_yum_add_repo.py
new file mode 100644
index 00000000..5c273825
--- /dev/null
+++ b/cloudinit/config/cc_yum_add_repo.py
@@ -0,0 +1,106 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Yahoo! Inc.
+#
+# Author: Joshua Harlow <harlowja@yahoo-inc.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/>.
+
+import os
+
+from cloudinit import util
+
+import configobj
+
+
+def _canonicalize_id(repo_id):
+ repo_id = repo_id.lower().replace("-", "_")
+ repo_id = repo_id.replace(" ", "_")
+ return repo_id
+
+
+def _format_repo_value(val):
+ if isinstance(val, (bool)):
+ # Seems like yum prefers 1/0
+ return str(int(val))
+ if isinstance(val, (list, tuple)):
+ # Can handle 'lists' in certain cases
+ # See: http://bit.ly/Qqrf1t
+ return "\n ".join([_format_repo_value(v) for v in val])
+ if not isinstance(val, (basestring, str)):
+ return str(val)
+ return val
+
+
+## TODO(harlowja): move to distro?
+# See man yum.conf
+def _format_repository_config(repo_id, repo_config):
+ to_be = configobj.ConfigObj()
+ to_be[repo_id] = {}
+ # Do basic translation of the items -> values
+ for (k, v) in repo_config.items():
+ # For now assume that people using this know
+ # the format of yum and don't verify keys/values further
+ to_be[repo_id][k] = _format_repo_value(v)
+ lines = to_be.write()
+ lines.insert(0, "# Created by cloud-init on %s" % (util.time_rfc2822()))
+ return "\n".join(lines)
+
+
+def handle(name, cfg, _cloud, log, _args):
+ repos = cfg.get('yum_repos')
+ if not repos:
+ log.debug(("Skipping module named %s,"
+ " no 'yum_repos' configuration found"), name)
+ return
+ repo_base_path = util.get_cfg_option_str(cfg, 'yum_repo_dir',
+ '/etc/yum.repos.d/')
+ repo_locations = {}
+ repo_configs = {}
+ for (repo_id, repo_config) in repos.items():
+ canon_repo_id = _canonicalize_id(repo_id)
+ repo_fn_pth = os.path.join(repo_base_path, "%s.repo" % (canon_repo_id))
+ if os.path.exists(repo_fn_pth):
+ log.info("Skipping repo %s, file %s already exists!",
+ repo_id, repo_fn_pth)
+ continue
+ elif canon_repo_id in repo_locations:
+ log.info("Skipping repo %s, file %s already pending!",
+ repo_id, repo_fn_pth)
+ continue
+ if not repo_config:
+ repo_config = {}
+ # Do some basic sanity checks/cleaning
+ n_repo_config = {}
+ for (k, v) in repo_config.items():
+ k = k.lower().strip().replace("-", "_")
+ if k:
+ n_repo_config[k] = v
+ repo_config = n_repo_config
+ missing_required = 0
+ for req_field in ['baseurl']:
+ if not req_field in repo_config:
+ log.warn(("Repository %s does not contain a %s"
+ " configuration 'required' entry"),
+ repo_id, req_field)
+ missing_required += 1
+ if not missing_required:
+ repo_configs[canon_repo_id] = repo_config
+ repo_locations[canon_repo_id] = repo_fn_pth
+ else:
+ log.warn("Repository %s is missing %s required fields, skipping!",
+ repo_id, missing_required)
+ for (c_repo_id, path) in repo_locations.items():
+ repo_blob = _format_repository_config(c_repo_id,
+ repo_configs.get(c_repo_id))
+ util.write_file(path, repo_blob)
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
diff --git a/cloudinit/ec2_utils.py b/cloudinit/ec2_utils.py
new file mode 100644
index 00000000..32bf3968
--- /dev/null
+++ b/cloudinit/ec2_utils.py
@@ -0,0 +1,67 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Yahoo! Inc.
+#
+# Author: Joshua Harlow <harlowja@yahoo-inc.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/>.
+
+import pkg_resources
+from pkg_resources import parse_version as pver
+
+import boto.utils as boto_utils
+
+# Versions of boto >= 2.6.0 try to lazily load
+# the metadata backing, which doesn't work so well
+# in cloud-init especially since the metadata is
+# serialized and actions are performed where the
+# metadata server may be blocked (thus the datasource
+# will start failing) resulting in url exceptions
+# when fields that do exist (or would have existed)
+# do not exist due to the blocking that occurred.
+
+BOTO_LAZY = False
+try:
+ _boto_lib = pkg_resources.get_distribution('boto')
+ if _boto_lib.parsed_version > pver("2.5.2"): # pylint: disable=E1103
+ BOTO_LAZY = True
+except pkg_resources.DistributionNotFound:
+ pass
+
+
+def _unlazy_dict(mp):
+ if not isinstance(mp, (dict)):
+ return mp
+ if not BOTO_LAZY:
+ return mp
+ for (_k, v) in mp.items():
+ _unlazy_dict(v)
+ return mp
+
+
+def get_instance_userdata(api_version, metadata_address):
+ # Note: boto.utils.get_instance_metadata returns '' for empty string
+ # so the change from non-true to '' is not specifically necessary, but
+ # this way cloud-init will get consistent behavior even if boto changed
+ # in the future to return a None on "no user-data provided".
+ ud = boto_utils.get_instance_userdata(api_version, None, metadata_address)
+ if not ud:
+ ud = ''
+ return ud
+
+
+def get_instance_metadata(api_version, metadata_address):
+ metadata = boto_utils.get_instance_metadata(api_version, metadata_address)
+ if not isinstance(metadata, (dict)):
+ metadata = {}
+ return _unlazy_dict(metadata)
diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py
index 99caed1f..8d6dcd4d 100644
--- a/cloudinit/handlers/__init__.py
+++ b/cloudinit/handlers/__init__.py
@@ -160,6 +160,19 @@ def _extract_first_or_bytes(blob, size):
return start
+def _escape_string(text):
+ try:
+ return text.encode("string-escape")
+ except TypeError:
+ try:
+ # Unicode doesn't support string-escape...
+ return text.encode('unicode-escape')
+ except TypeError:
+ # Give up...
+ pass
+ return text
+
+
def walker_callback(pdata, ctype, filename, payload):
if ctype in PART_CONTENT_TYPES:
walker_handle_handler(pdata, ctype, filename, payload)
@@ -171,7 +184,7 @@ def walker_callback(pdata, ctype, filename, payload):
elif payload:
# Extract the first line or 24 bytes for displaying in the log
start = _extract_first_or_bytes(payload, 24)
- details = "'%s...'" % (start.encode("string-escape"))
+ details = "'%s...'" % (_escape_string(start))
if ctype == NOT_MULTIPART_TYPE:
LOG.warning("Unhandled non-multipart (%s) userdata: %s",
ctype, details)
diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py
index a4b20208..794f00ec 100644
--- a/cloudinit/helpers.py
+++ b/cloudinit/helpers.py
@@ -71,12 +71,17 @@ class FileLock(object):
return "<%s using file %r>" % (util.obj_name(self), self.fn)
+def canon_sem_name(name):
+ return name.replace("-", "_")
+
+
class FileSemaphores(object):
def __init__(self, sem_path):
self.sem_path = sem_path
@contextlib.contextmanager
def lock(self, name, freq, clear_on_fail=False):
+ name = canon_sem_name(name)
try:
yield self._acquire(name, freq)
except:
@@ -85,6 +90,7 @@ class FileSemaphores(object):
raise
def clear(self, name, freq):
+ name = canon_sem_name(name)
sem_file = self._get_path(name, freq)
try:
util.del_file(sem_file)
@@ -119,6 +125,7 @@ class FileSemaphores(object):
def has_run(self, name, freq):
if not freq or freq == PER_ALWAYS:
return False
+ name = canon_sem_name(name)
sem_file = self._get_path(name, freq)
# This isn't really a good atomic check
# but it suffices for where and when cloudinit runs
@@ -302,14 +309,10 @@ class Paths(object):
def __init__(self, path_cfgs, ds=None):
self.cfgs = path_cfgs
# Populate all the initial paths
- self.cloud_dir = self.join(False,
- path_cfgs.get('cloud_dir',
- '/var/lib/cloud'))
+ self.cloud_dir = path_cfgs.get('cloud_dir', '/var/lib/cloud')
self.instance_link = os.path.join(self.cloud_dir, 'instance')
self.boot_finished = os.path.join(self.instance_link, "boot-finished")
self.upstart_conf_d = path_cfgs.get('upstart_dir')
- if self.upstart_conf_d:
- self.upstart_conf_d = self.join(False, self.upstart_conf_d)
self.seed_dir = os.path.join(self.cloud_dir, 'seed')
# This one isn't joined, since it should just be read-only
template_dir = path_cfgs.get('templates_dir', '/etc/cloud/templates/')
@@ -328,29 +331,6 @@ class Paths(object):
# Set when a datasource becomes active
self.datasource = ds
- # joins the paths but also appends a read
- # or write root if available
- def join(self, read_only, *paths):
- if read_only:
- root = self.cfgs.get('read_root')
- else:
- root = self.cfgs.get('write_root')
- if not paths:
- return root
- if len(paths) > 1:
- joined = os.path.join(*paths)
- else:
- joined = paths[0]
- if root:
- pre_joined = joined
- # Need to remove any starting '/' since this
- # will confuse os.path.join
- joined = joined.lstrip("/")
- joined = os.path.join(root, joined)
- LOG.debug("Translated %s to adjusted path %s (read-only=%s)",
- pre_joined, joined, read_only)
- return joined
-
# get_ipath_cur: get the current instance path for an item
def get_ipath_cur(self, name=None):
ipath = self.instance_link
diff --git a/cloudinit/log.py b/cloudinit/log.py
index 2333e5ee..da6c2851 100644
--- a/cloudinit/log.py
+++ b/cloudinit/log.py
@@ -53,6 +53,18 @@ def setupBasicLogging():
root.setLevel(DEBUG)
+def flushLoggers(root):
+ if not root:
+ return
+ for h in root.handlers:
+ if isinstance(h, (logging.StreamHandler)):
+ try:
+ h.flush()
+ except IOError:
+ pass
+ flushLoggers(root.parent)
+
+
def setupLogging(cfg=None):
# See if the config provides any logging conf...
if not cfg:
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index 69c376a5..9812bdcb 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -47,7 +47,7 @@ META_DATA_NOT_SUPPORTED = {
'instance-id': 455,
'local-hostname': 'localhost',
'placement': {},
- }
+}
def read_user_data_callback(mount_dir):
@@ -73,13 +73,11 @@ def read_user_data_callback(mount_dir):
# First try deltacloud_user_data_file. On failure try user_data_file.
try:
- with open(deltacloud_user_data_file, 'r') as user_data_f:
- user_data = user_data_f.read().strip()
- except:
+ user_data = util.load_file(deltacloud_user_data_file).strip()
+ except IOError:
try:
- with open(user_data_file, 'r') as user_data_f:
- user_data = user_data_f.read().strip()
- except:
+ user_data = util.load_file(user_data_file).strip()
+ except IOError:
util.logexc(LOG, ('Failed accessing user data file.'))
return None
@@ -157,11 +155,10 @@ class DataSourceAltCloud(sources.DataSource):
if os.path.exists(CLOUD_INFO_FILE):
try:
- cloud_info = open(CLOUD_INFO_FILE)
- cloud_type = cloud_info.read().strip().upper()
- cloud_info.close()
- except:
- util.logexc(LOG, 'Unable to access cloud info file.')
+ cloud_type = util.load_file(CLOUD_INFO_FILE).strip().upper()
+ except IOError:
+ util.logexc(LOG, 'Unable to access cloud info file at %s.',
+ CLOUD_INFO_FILE)
return False
else:
cloud_type = self.get_cloud_type()
diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py
index f7ffa7cb..076dba5a 100644
--- a/cloudinit/sources/DataSourceCloudStack.py
+++ b/cloudinit/sources/DataSourceCloudStack.py
@@ -26,8 +26,7 @@ from struct import pack
import os
import time
-import boto.utils as boto_utils
-
+from cloudinit import ec2_utils as ec2
from cloudinit import log as logging
from cloudinit import sources
from cloudinit import url_helper as uhelp
@@ -116,10 +115,10 @@ class DataSourceCloudStack(sources.DataSource):
if not self.wait_for_metadata_service():
return False
start_time = time.time()
- self.userdata_raw = boto_utils.get_instance_userdata(self.api_ver,
- None, self.metadata_address)
- self.metadata = boto_utils.get_instance_metadata(self.api_ver,
+ self.userdata_raw = ec2.get_instance_userdata(self.api_ver,
self.metadata_address)
+ self.metadata = ec2.get_instance_metadata(self.api_ver,
+ self.metadata_address)
LOG.debug("Crawl of metadata service took %s seconds",
int(time.time() - start_time))
return True
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index 4af2e5ae..c7826851 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -219,9 +219,10 @@ class DataSourceConfigDrive(sources.DataSource):
return True
def get_public_ssh_keys(self):
- if not 'public-keys' in self.metadata:
- return []
- return self.metadata['public-keys']
+ name = "public_keys"
+ if self.version == 1:
+ name = "public-keys"
+ return sources.normalize_pubkey_data(self.metadata.get(name))
class DataSourceConfigDriveNet(DataSourceConfigDrive):
@@ -307,19 +308,19 @@ def read_config_drive_dir_v2(source_dir, version="2012-08-10"):
found = False
if os.path.isfile(fpath):
try:
- with open(fpath) as fp:
- data = fp.read()
- except Exception as exc:
- raise BrokenConfigDriveDir("failed to read: %s" % fpath)
+ data = util.load_file(fpath)
+ except IOError:
+ raise BrokenConfigDriveDir("Failed to read: %s" % fpath)
found = True
elif required:
- raise NonConfigDriveDir("missing mandatory %s" % fpath)
+ raise NonConfigDriveDir("Missing mandatory path: %s" % fpath)
if found and process:
try:
data = process(data)
except Exception as exc:
- raise BrokenConfigDriveDir("failed to process: %s" % fpath)
+ raise BrokenConfigDriveDir(("Failed to process "
+ "path: %s") % fpath)
if found:
results[name] = data
@@ -335,8 +336,7 @@ def read_config_drive_dir_v2(source_dir, version="2012-08-10"):
# do not use os.path.join here, as content_path starts with /
cpath = os.path.sep.join((source_dir, "openstack",
"./%s" % item['content_path']))
- with open(cpath) as fp:
- return(fp.read())
+ return util.load_file(cpath)
files = {}
try:
@@ -350,7 +350,7 @@ def read_config_drive_dir_v2(source_dir, version="2012-08-10"):
if item:
results['network_config'] = read_content_path(item)
except Exception as exc:
- raise BrokenConfigDriveDir("failed to read file %s: %s" % (item, exc))
+ raise BrokenConfigDriveDir("Failed to read file %s: %s" % (item, exc))
# to openstack, user can specify meta ('nova boot --meta=key=value') and
# those will appear under metadata['meta'].
@@ -465,8 +465,7 @@ def get_previous_iid(paths):
# hasn't declared itself found.
fname = os.path.join(paths.get_cpath('data'), 'instance-id')
try:
- with open(fname) as fp:
- return fp.read()
+ return util.load_file(fname)
except IOError:
return None
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 3686fa10..2db53446 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -23,8 +23,7 @@
import os
import time
-import boto.utils as boto_utils
-
+from cloudinit import ec2_utils as ec2
from cloudinit import log as logging
from cloudinit import sources
from cloudinit import url_helper as uhelp
@@ -65,10 +64,10 @@ class DataSourceEc2(sources.DataSource):
if not self.wait_for_metadata_service():
return False
start_time = time.time()
- self.userdata_raw = boto_utils.get_instance_userdata(self.api_ver,
- None, self.metadata_address)
- self.metadata = boto_utils.get_instance_metadata(self.api_ver,
+ self.userdata_raw = ec2.get_instance_userdata(self.api_ver,
self.metadata_address)
+ self.metadata = ec2.get_instance_metadata(self.api_ver,
+ self.metadata_address)
LOG.debug("Crawl of metadata service took %s seconds",
int(time.time() - start_time))
return True
@@ -86,9 +85,6 @@ class DataSourceEc2(sources.DataSource):
def get_instance_id(self):
return self.metadata['instance-id']
- def get_availability_zone(self):
- return self.metadata['placement']['availability-zone']
-
def _get_url_settings(self):
mcfg = self.ds_cfg
if not mcfg:
@@ -198,19 +194,6 @@ class DataSourceEc2(sources.DataSource):
return None
return ofound
- def is_vpc(self):
- # See: https://bugs.launchpad.net/ubuntu/+source/cloud-init/+bug/615545
- # Detect that the machine was launched in a VPC.
- # But I did notice that when in a VPC, meta-data
- # does not have public-ipv4 and public-hostname
- # listed as a possibility.
- ph = "public-hostname"
- p4 = "public-ipv4"
- if ((ph not in self.metadata or self.metadata[ph] == "") and
- (p4 not in self.metadata or self.metadata[p4] == "")):
- return True
- return False
-
@property
def availability_zone(self):
try:
diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py
index e187aec9..b55d8a21 100644
--- a/cloudinit/sources/DataSourceMAAS.py
+++ b/cloudinit/sources/DataSourceMAAS.py
@@ -336,8 +336,7 @@ if __name__ == "__main__":
'token_secret': args.tsec, 'consumer_secret': args.csec}
if args.config:
- with open(args.config) as fp:
- cfg = util.load_yaml(fp.read())
+ cfg = util.read_conf(args.config)
if 'datasource' in cfg:
cfg = cfg['datasource']['MAAS']
for key in creds.keys():
@@ -346,7 +345,7 @@ if __name__ == "__main__":
def geturl(url, headers_cb):
req = urllib2.Request(url, data=None, headers=headers_cb(url))
- return(urllib2.urlopen(req).read())
+ return (urllib2.urlopen(req).read())
def printurl(url, headers_cb):
print "== %s ==\n%s\n" % (url, geturl(url, headers_cb))
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index 771e64eb..e90150c6 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -204,9 +204,8 @@ def transport_iso9660(require_iso=True):
try:
# See if we can read anything at all...??
- with open(fullp, 'rb') as fp:
- fp.read(512)
- except:
+ util.peek_file(fullp, 512)
+ except IOError:
continue
try:
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index b22369a8..96baff90 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -20,8 +20,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from email.mime.multipart import MIMEMultipart
-
import abc
import os
@@ -102,32 +100,7 @@ class DataSource(object):
return {}
def get_public_ssh_keys(self):
- keys = []
-
- if not self.metadata or 'public-keys' not in self.metadata:
- return keys
-
- if isinstance(self.metadata['public-keys'], (basestring, str)):
- return str(self.metadata['public-keys']).splitlines()
-
- if isinstance(self.metadata['public-keys'], (list, set)):
- return list(self.metadata['public-keys'])
-
- if isinstance(self.metadata['public-keys'], (dict)):
- for (_keyname, klist) in self.metadata['public-keys'].iteritems():
- # lp:506332 uec metadata service responds with
- # data that makes boto populate a string for 'klist' rather
- # than a list.
- if isinstance(klist, (str, basestring)):
- klist = [klist]
- if isinstance(klist, (list, set)):
- for pkey in klist:
- # There is an empty string at
- # the end of the keylist, trim it
- if pkey:
- keys.append(pkey)
-
- return keys
+ return normalize_pubkey_data(self.metadata.get('public-keys'))
def _remap_device(self, short_name):
# LP: #611137
@@ -210,6 +183,35 @@ class DataSource(object):
availability_zone=self.availability_zone)
+def normalize_pubkey_data(pubkey_data):
+ keys = []
+
+ if not pubkey_data:
+ return keys
+
+ if isinstance(pubkey_data, (basestring, str)):
+ return str(pubkey_data).splitlines()
+
+ if isinstance(pubkey_data, (list, set)):
+ return list(pubkey_data)
+
+ if isinstance(pubkey_data, (dict)):
+ for (_keyname, klist) in pubkey_data.iteritems():
+ # lp:506332 uec metadata service responds with
+ # data that makes boto populate a string for 'klist' rather
+ # than a list.
+ if isinstance(klist, (str, basestring)):
+ klist = [klist]
+ if isinstance(klist, (list, set)):
+ for pkey in klist:
+ # There is an empty string at
+ # the end of the keylist, trim it
+ if pkey:
+ keys.append(pkey)
+
+ return keys
+
+
def find_source(sys_cfg, distro, paths, ds_deps, cfg_list, pkg_list):
ds_list = list_sources(cfg_list, ds_deps, pkg_list)
ds_names = [util.obj_name(f) for f in ds_list]
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index 88a11a1a..dd6b742f 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -212,17 +212,15 @@ def update_authorized_keys(old_entries, keys):
return '\n'.join(lines)
-def users_ssh_info(username, paths):
+def users_ssh_info(username):
pw_ent = pwd.getpwnam(username)
- if not pw_ent:
+ if not pw_ent or not pw_ent.pw_dir:
raise RuntimeError("Unable to get ssh info for user %r" % (username))
- ssh_dir = paths.join(False, os.path.join(pw_ent.pw_dir, '.ssh'))
- return (ssh_dir, pw_ent)
+ return (os.path.join(pw_ent.pw_dir, '.ssh'), pw_ent)
-def extract_authorized_keys(username, paths):
- (ssh_dir, pw_ent) = users_ssh_info(username, paths)
- sshd_conf_fn = paths.join(True, DEF_SSHD_CFG)
+def extract_authorized_keys(username):
+ (ssh_dir, pw_ent) = users_ssh_info(username)
auth_key_fn = None
with util.SeLinuxGuard(ssh_dir, recursive=True):
try:
@@ -231,7 +229,7 @@ def extract_authorized_keys(username, paths):
# The following tokens are defined: %% is replaced by a literal
# '%', %h is replaced by the home directory of the user being
# authenticated and %u is replaced by the username of that user.
- ssh_cfg = parse_ssh_config_map(sshd_conf_fn)
+ ssh_cfg = parse_ssh_config_map(DEF_SSHD_CFG)
auth_key_fn = ssh_cfg.get("authorizedkeysfile", '').strip()
if not auth_key_fn:
auth_key_fn = "%h/.ssh/authorized_keys"
@@ -240,7 +238,6 @@ def extract_authorized_keys(username, paths):
auth_key_fn = auth_key_fn.replace("%%", '%')
if not auth_key_fn.startswith('/'):
auth_key_fn = os.path.join(pw_ent.pw_dir, auth_key_fn)
- auth_key_fn = paths.join(False, auth_key_fn)
except (IOError, OSError):
# Give up and use a default key filename
auth_key_fn = os.path.join(ssh_dir, 'authorized_keys')
@@ -248,14 +245,13 @@ def extract_authorized_keys(username, paths):
" in ssh config"
" from %r, using 'AuthorizedKeysFile' file"
" %r instead"),
- sshd_conf_fn, auth_key_fn)
- auth_key_entries = parse_authorized_keys(auth_key_fn)
- return (auth_key_fn, auth_key_entries)
+ DEF_SSHD_CFG, auth_key_fn)
+ return (auth_key_fn, parse_authorized_keys(auth_key_fn))
-def setup_user_keys(keys, username, key_prefix, paths):
+def setup_user_keys(keys, username, key_prefix):
# Make sure the users .ssh dir is setup accordingly
- (ssh_dir, pwent) = users_ssh_info(username, paths)
+ (ssh_dir, pwent) = users_ssh_info(username)
if not os.path.isdir(ssh_dir):
util.ensure_dir(ssh_dir, mode=0700)
util.chownbyid(ssh_dir, pwent.pw_uid, pwent.pw_gid)
@@ -267,7 +263,7 @@ def setup_user_keys(keys, username, key_prefix, paths):
key_entries.append(parser.parse(str(k), def_opt=key_prefix))
# Extract the old and make the new
- (auth_key_fn, auth_key_entries) = extract_authorized_keys(username, paths)
+ (auth_key_fn, auth_key_entries) = extract_authorized_keys(username)
with util.SeLinuxGuard(ssh_dir, recursive=True):
content = update_authorized_keys(auth_key_entries, key_entries)
util.ensure_dir(os.path.dirname(auth_key_fn), mode=0700)
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index 4ed1a750..94a267df 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -47,6 +47,8 @@ from cloudinit import util
LOG = logging.getLogger(__name__)
+NULL_DATA_SOURCE = None
+
class Init(object):
def __init__(self, ds_deps=None):
@@ -58,8 +60,16 @@ class Init(object):
self._cfg = None
self._paths = None
self._distro = None
- # Created only when a fetch occurs
- self.datasource = None
+ # Changed only when a fetch occurs
+ self.datasource = NULL_DATA_SOURCE
+
+ def _reset(self, ds=False):
+ # Recreated on access
+ self._cfg = None
+ self._paths = None
+ self._distro = None
+ if ds:
+ self.datasource = NULL_DATA_SOURCE
@property
def distro(self):
@@ -191,7 +201,7 @@ class Init(object):
return None
def _write_to_cache(self):
- if not self.datasource:
+ if self.datasource is NULL_DATA_SOURCE:
return False
pickled_fn = self.paths.get_ipath_cur("obj_pkl")
try:
@@ -217,7 +227,7 @@ class Init(object):
return (cfg_list, pkg_list)
def _get_data_source(self):
- if self.datasource:
+ if self.datasource is not NULL_DATA_SOURCE:
return self.datasource
ds = self._restore_from_cache()
if ds:
@@ -236,7 +246,7 @@ class Init(object):
self.datasource = ds
# Ensure we adjust our path members datasource
# now that we have one (thus allowing ipath to be used)
- self.paths.datasource = ds
+ self._reset()
return ds
def _get_instance_subdirs(self):
@@ -296,6 +306,10 @@ class Init(object):
util.write_file(iid_fn, "%s\n" % iid)
util.write_file(os.path.join(dp, 'previous-instance-id'),
"%s\n" % (previous_iid))
+ # Ensure needed components are regenerated
+ # after change of instance which may cause
+ # change of configuration
+ self._reset()
return iid
def fetch(self):
@@ -409,6 +423,17 @@ class Init(object):
handlers.call_end(mod, data, frequency)
called.append(mod)
+ # Perform post-consumption adjustments so that
+ # modules that run during the init stage reflect
+ # this consumed set.
+ #
+ # They will be recreated on future access...
+ self._reset()
+ # Note(harlowja): the 'active' datasource will have
+ # references to the previous config, distro, paths
+ # objects before the load of the userdata happened,
+ # this is expected.
+
class Modules(object):
def __init__(self, init, cfg_files=None):
diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py
index 803ffc3a..58827e3d 100644
--- a/cloudinit/user_data.py
+++ b/cloudinit/user_data.py
@@ -224,7 +224,7 @@ class UserDataProcessor(object):
for header in list(ent.keys()):
if header in ('content', 'filename', 'type', 'launch-index'):
continue
- msg.add_header(header, ent['header'])
+ msg.add_header(header, ent[header])
self._attach_part(append_msg, msg)
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 918deba2..ab918433 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -980,6 +980,12 @@ def find_devs_with(criteria=None, oformat='device',
return entries
+def peek_file(fname, max_bytes):
+ LOG.debug("Peeking at %s (max_bytes=%s)", fname, max_bytes)
+ with open(fname, 'rb') as ifh:
+ return ifh.read(max_bytes)
+
+
def uniq_list(in_list):
out_list = []
for i in in_list:
@@ -1194,8 +1200,7 @@ def yaml_dumps(obj):
indent=4,
explicit_start=True,
explicit_end=True,
- default_flow_style=False,
- )
+ default_flow_style=False)
return formatted
@@ -1335,6 +1340,10 @@ def uptime():
return uptime_str
+def append_file(path, content):
+ write_file(path, content, omode="ab", mode=None)
+
+
def ensure_file(path, mode=0644):
write_file(path, content='', omode="ab", mode=mode)