summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Harlow <harlowja@yahoo-inc.com>2012-11-07 20:53:34 -0800
committerJoshua Harlow <harlowja@yahoo-inc.com>2012-11-07 20:53:34 -0800
commitdbeb73f174ccc3b992a8488b7a72cf35c685f139 (patch)
treeeb857beba4c6b71cd1ab8ba4b947ab54907349e5
parent04f52eb593e4f5114626c74fd8f3c5a9a8d440bd (diff)
parent1e6fc277a1c8d695c37741cc31f5ddab3d5b5600 (diff)
downloadvyos-cloud-init-dbeb73f174ccc3b992a8488b7a72cf35c685f139.tar.gz
vyos-cloud-init-dbeb73f174ccc3b992a8488b7a72cf35c685f139.zip
1. Rebased with trunk
2. Added example cloud-config 3. Added functioning test for yum config
-rw-r--r--ChangeLog13
-rw-r--r--Makefile8
-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.py15
-rw-r--r--cloudinit/config/cc_mcollective.py22
-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_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/distros/__init__.py112
-rw-r--r--cloudinit/distros/debian.py26
-rw-r--r--cloudinit/distros/fedora.py2
-rw-r--r--cloudinit/distros/ubuntu.py5
-rw-r--r--cloudinit/handlers/__init__.py15
-rw-r--r--cloudinit/helpers.py29
-rw-r--r--cloudinit/log.py12
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py21
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py20
-rw-r--r--cloudinit/sources/DataSourceEc2.py16
-rw-r--r--cloudinit/sources/DataSourceMAAS.py5
-rw-r--r--cloudinit/sources/DataSourceOVF.py5
-rw-r--r--cloudinit/sources/__init__.py2
-rw-r--r--cloudinit/ssh_util.py26
-rw-r--r--cloudinit/user_data.py2
-rw-r--r--cloudinit/util.py13
-rw-r--r--config/cloud.cfg13
-rw-r--r--doc/examples/cloud-config-user-groups.txt11
-rw-r--r--doc/examples/cloud-config-yum-repo.txt20
-rw-r--r--doc/examples/cloud-config.txt13
-rw-r--r--pylintrc19
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/configs/sample1.yaml1
-rw-r--r--tests/data/roots/simple_ubuntu/etc/networks/interfaces3
-rw-r--r--tests/unittests/__init__.py0
-rw-r--r--tests/unittests/helpers.py104
-rw-r--r--tests/unittests/test_datasource/__init__.py0
-rw-r--r--tests/unittests/test_datasource/test_configdrive.py5
-rw-r--r--tests/unittests/test_distros/__init__.py0
-rw-r--r--tests/unittests/test_distros/test_user_data_normalize.py30
-rw-r--r--tests/unittests/test_filters/__init__.py0
-rw-r--r--tests/unittests/test_filters/test_launch_index.py4
-rw-r--r--tests/unittests/test_handler/__init__.py0
-rw-r--r--tests/unittests/test_handler/test_handler_ca_certs.py18
-rw-r--r--tests/unittests/test_handler/test_handler_yum_add_repo.py68
-rw-r--r--tests/unittests/test_runs/__init__.py0
-rw-r--r--tests/unittests/test_runs/test_simple_run.py80
-rwxr-xr-xtools/Z99-cloud-locale-test.sh4
-rwxr-xr-xtools/hacking.py4
-rwxr-xr-xtools/run-pep82
-rwxr-xr-xtools/run-pylint19
61 files changed, 740 insertions, 411 deletions
diff --git a/ChangeLog b/ChangeLog
index 4287d58f..8f304db3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,19 @@
- config-drive: map hostname to local-hostname (LP: #1061964)
- landscape: install landscape-client package if not installed.
only take action if cloud-config is present (LP: #1066115)
+ - cc_landscape: restart landscape after install or config (LP: #1070345)
+ - multipart/archive. do not fail on unknown headers in multipart
+ mime or cloud-archive config (LP: #1065116).
+ - tools/Z99-cloud-locale-test.sh: avoid warning when user's shell is
+ zsh (LP: #1073077)
+ - fix stack trace when unknown user-data input had unicode (LP: #1075756)
+ - split 'apt-update-upgrade' config module into 'apt-configure' and
+ 'package-update-upgrade-install'. The 'package-update-upgrade-install'
+ will be a cross distro module.
+ - Cleanups:
+ - Remove usage of paths.join, as all code should run through util helpers
+ - Fix pylint complaining about tests folder 'helpers.py' not being found
+ - Add a pylintrc file that is used instead options hidden in 'run_pylint'
0.7.0:
- add a 'exception_cb' argument to 'wait_for_url'. If provided, this
method will be called back with the exception received and the message.
diff --git a/Makefile b/Makefile
index 49324ca0..88c90b9b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,20 @@
CWD=$(shell pwd)
-PY_FILES=$(shell find cloudinit bin tests tools -name "*.py")
+PY_FILES=$(shell find cloudinit bin tests tools -name "*.py" -type f )
PY_FILES+="bin/cloud-init"
all: test
pep8:
- $(CWD)/tools/run-pep8 $(PY_FILES)
+ @$(CWD)/tools/run-pep8 $(PY_FILES)
pylint:
- $(CWD)/tools/run-pylint $(PY_FILES)
+ @$(CWD)/tools/run-pylint $(PY_FILES)
pyflakes:
pyflakes $(PY_FILES)
test:
- nosetests $(noseopts) tests/unittests/
+ @nosetests $(noseopts) tests/
2to3:
2to3 $(PY_FILES)
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 331559f4..02610dd0 100644
--- a/cloudinit/config/cc_landscape.py
+++ b/cloudinit/config/cc_landscape.py
@@ -66,24 +66,19 @@ def handle(_name, cfg, cloud, log, _args):
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)
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_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_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/distros/__init__.py b/cloudinit/distros/__init__.py
index 21efe8d9..31724e2c 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -37,10 +37,7 @@ LOG = logging.getLogger(__name__)
class Distro(object):
-
__metaclass__ = abc.ABCMeta
- default_user = None
- default_user_groups = None
def __init__(self, name, cfg, paths):
self._paths = paths
@@ -84,7 +81,7 @@ 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)
@@ -125,8 +122,7 @@ class Distro(object):
new_etchosts = StringIO()
need_write = False
need_change = True
- hosts_ro_fn = self._paths.join(True, "/etc/hosts")
- for line in util.load_file(hosts_ro_fn).splitlines():
+ for line in util.load_file("/etc/hosts").splitlines():
if line.strip().startswith(header):
continue
if not line.strip() or line.strip().startswith("#"):
@@ -150,8 +146,7 @@ class Distro(object):
need_write = True
if need_write:
contents = new_etchosts.getvalue()
- util.write_file(self._paths.join(False, "/etc/hosts"),
- contents, mode=0644)
+ util.write_file("/etc/hosts", contents, mode=0644)
def _bring_up_interface(self, device_name):
cmd = ['ifup', device_name]
@@ -176,22 +171,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):
"""
@@ -207,23 +187,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:
@@ -251,7 +231,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:
@@ -280,7 +260,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
@@ -299,11 +279,42 @@ class Distro(object):
return True
- def write_sudo_rules(self,
- user,
- rules,
- sudo_file="/etc/sudoers.d/90-cloud-init-users",
- ):
+ 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 = ''
+ if os.path.exists(sudo_base):
+ sudoers_contents = util.load_file(sudo_base)
+ 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:
+ sudoers_contents += "\n#includedir %s\n" % (path)
+ try:
+ if not os.path.exists(sudo_base):
+ util.write_file(sudo_base, sudoers_contents, 0440)
+ else:
+ with open(sudo_base, 'a') as f:
+ f.write(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, 0755)
+
+ def write_sudo_rules(self, user, rules, sudo_file=None):
+ if not sudo_file:
+ sudo_file = "/etc/sudoers.d/90-cloud-init-users"
content_header = "# user rules for %s" % user
content = "%s\n%s %s\n\n" % (content_header, user, rules)
@@ -314,13 +325,13 @@ class Distro(object):
content += "%s %s\n" % (user, rule)
content += "\n"
+ self.ensure_sudo_dir(os.path.dirname(sudo_file))
+
if not os.path.exists(sudo_file):
util.write_file(sudo_file, content, 0440)
-
else:
try:
- with open(sudo_file, 'a') as f:
- f.write(content)
+ util.append_file(sudo_file, content)
except IOError as e:
util.logexc(LOG, "Failed to write %s" % sudo_file, e)
raise e
@@ -439,7 +450,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
@@ -504,6 +515,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 88f4e978..cc7e53a0 100644
--- a/cloudinit/distros/debian.py
+++ b/cloudinit/distros/debian.py
@@ -43,7 +43,7 @@ class Distro(distros.Distro):
def apply_locale(self, locale, out_fn=None):
if not out_fn:
- out_fn = self._paths.join(False, '/etc/default/locale')
+ out_fn = '/etc/default/locale'
util.subp(['locale-gen', locale], capture=False)
util.subp(['update-locale', locale], capture=False)
lines = ["# Created by cloud-init", 'LANG="%s"' % (locale), ""]
@@ -54,8 +54,7 @@ class Distro(distros.Distro):
self.package_command('install', pkglist)
def _write_network(self, settings):
- net_fn = self._paths.join(False, "/etc/network/interfaces")
- util.write_file(net_fn, settings)
+ util.write_file("/etc/network/interfaces", settings)
return ['all']
def _bring_up_interfaces(self, device_names):
@@ -69,12 +68,9 @@ class Distro(distros.Distro):
return distros.Distro._bring_up_interfaces(self, device_names)
def set_hostname(self, hostname):
- out_fn = self._paths.join(False, "/etc/hostname")
- self._write_hostname(hostname, out_fn)
- if out_fn == '/etc/hostname':
- # Only do this if we are running in non-adjusted root mode
- LOG.debug("Setting hostname to %s", hostname)
- util.subp(['hostname', hostname])
+ self._write_hostname(hostname, "/etc/hostname")
+ LOG.debug("Setting hostname to %s", hostname)
+ util.subp(['hostname', hostname])
def _write_hostname(self, hostname, out_fn):
# "" gives trailing newline.
@@ -82,16 +78,14 @@ class Distro(distros.Distro):
def update_hostname(self, hostname, prev_fn):
hostname_prev = self._read_hostname(prev_fn)
- read_fn = self._paths.join(True, "/etc/hostname")
- hostname_in_etc = self._read_hostname(read_fn)
+ hostname_in_etc = self._read_hostname("/etc/hostname")
update_files = []
if not hostname_prev or hostname_prev != hostname:
update_files.append(prev_fn)
if (not hostname_in_etc or
(hostname_in_etc == hostname_prev and
hostname_in_etc != hostname)):
- write_fn = self._paths.join(False, "/etc/hostname")
- update_files.append(write_fn)
+ update_files.append("/etc/hostname")
for fn in update_files:
try:
self._write_hostname(hostname, fn)
@@ -103,7 +97,6 @@ class Distro(distros.Distro):
LOG.debug(("%s differs from /etc/hostname."
" Assuming user maintained hostname."), prev_fn)
if "/etc/hostname" in update_files:
- # Only do this if we are running in non-adjusted root mode
LOG.debug("Setting hostname to %s", hostname)
util.subp(['hostname', hostname])
@@ -130,9 +123,8 @@ class Distro(distros.Distro):
" no file found at %s") % (tz, tz_file))
# "" provides trailing newline during join
tz_lines = ["# Created by cloud-init", str(tz), ""]
- tz_fn = self._paths.join(False, "/etc/timezone")
- util.write_file(tz_fn, "\n".join(tz_lines))
- util.copy(tz_file, self._paths.join(False, "/etc/localtime"))
+ util.write_file("/etc/timezone", "\n".join(tz_lines))
+ util.copy(tz_file, "/etc/localtime")
def package_command(self, command, args=None):
e = os.environ.copy()
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/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/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..985ce3e5 100644
--- a/cloudinit/helpers.py
+++ b/cloudinit/helpers.py
@@ -302,14 +302,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 +324,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/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index 4af2e5ae..9729cfb9 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -307,19 +307,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 +335,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 +349,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 +464,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..cff50669 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -86,9 +86,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 +195,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..745627d0 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
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/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 f5a7ac12..4f5b15ee 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -983,6 +983,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 load_file(fname, read_cb=None, quiet=False):
LOG.debug("Reading from %s (quiet=%s)", fname, quiet)
ofh = StringIO()
@@ -1187,8 +1193,7 @@ def yaml_dumps(obj):
indent=4,
explicit_start=True,
explicit_end=True,
- default_flow_style=False,
- )
+ default_flow_style=False)
return formatted
@@ -1328,6 +1333,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)
diff --git a/config/cloud.cfg b/config/cloud.cfg
index b3411d11..05bb4eef 100644
--- a/config/cloud.cfg
+++ b/config/cloud.cfg
@@ -1,7 +1,9 @@
# The top level settings are used as module
# and system configuration.
-# Implement for Ubuntu only: create the default 'ubuntu' user
+# A set of users which may be applied and/or used by various modules
+# when a 'default' entry is found it will reference the 'default_user'
+# from the distro configuration specified below
users:
- default
@@ -43,7 +45,8 @@ cloud_config_modules:
- set-passwords
- grub-dpkg
- apt-pipelining
- - apt-update-upgrade
+ - apt-configure
+ - package-update-upgrade-install
- landscape
- timezone
- puppet
@@ -71,6 +74,12 @@ cloud_final_modules:
system_info:
# This will affect which distro class gets used
distro: ubuntu
+ # Default user name + that default users groups (if added/used)
+ default_user:
+ name: Ubuntu
+ lock_passwd: True
+ gecos: Ubuntu
+ groups: [adm, audio, cdrom, dialout, floppy, video, plugdev, dip, netdev]
# Other config here will be given to the distro class and/or path classes
paths:
cloud_dir: /var/lib/cloud/
diff --git a/doc/examples/cloud-config-user-groups.txt b/doc/examples/cloud-config-user-groups.txt
index 1a46c540..de5f321b 100644
--- a/doc/examples/cloud-config-user-groups.txt
+++ b/doc/examples/cloud-config-user-groups.txt
@@ -96,3 +96,14 @@ users:
# foobar: ...
#
# users[0] (the first user in users) overrides the user directive.
+#
+# The 'default' user above references the distro's config:
+# system_info:
+# default_user:
+# name: Ubuntu
+# plain_text_passwd: 'ubuntu'
+# home: /home/ubuntu
+# shell: /bin/bash
+# lock_passwd: True
+# gecos: Ubuntu
+# groups: [adm, audio, cdrom, dialout, floppy, video, plugdev, dip, netdev]
diff --git a/doc/examples/cloud-config-yum-repo.txt b/doc/examples/cloud-config-yum-repo.txt
new file mode 100644
index 00000000..ab2c031e
--- /dev/null
+++ b/doc/examples/cloud-config-yum-repo.txt
@@ -0,0 +1,20 @@
+#cloud-config
+# vim: syntax=yaml
+#
+# Add yum repository configuration to the system
+#
+# The following example adds the file /etc/yum.repos.d/epel_testing.repo
+# which can then subsequently be used by yum for later operations.
+yum_repos:
+ # The name of the repository
+ epel-testing:
+ # Any repository configuration options
+ # See: man yum.conf
+ #
+ # This one is required!
+ baseurl: http://download.fedoraproject.org/pub/epel/testing/5/$basearch
+ enabled: false
+ failovermethod: priority
+ gpgcheck: true
+ gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL
+ name: Extra Packages for Enterprise Linux 5 - Testing
diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt
index 56a6c35a..04bb5df1 100644
--- a/doc/examples/cloud-config.txt
+++ b/doc/examples/cloud-config.txt
@@ -3,15 +3,20 @@
# (ie run apt-get update)
#
# Default: true
-#
-apt_update: false
+# Aliases: apt_update
+package_update: false
# Upgrade the instance on first boot
# (ie run apt-get upgrade)
#
# Default: false
-#
-apt_upgrade: true
+# Aliases: apt_upgrade
+package_upgrade: true
+
+# Reboot after package install/update if necessary
+# Default: false
+# Aliases: apt_reboot_if_required
+package_reboot_if_required: true
# Add apt repositories
#
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 00000000..ee886510
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,19 @@
+[General]
+init-hook='import sys; sys.path.append("tests/")'
+
+[MESSAGES CONTROL]
+# See: http://pylint-messages.wikidot.com/all-codes
+# W0142: *args and **kwargs are fine.
+# W0511: TODOs in code comments are fine.
+# W0702: No exception type(s) specified
+# W0703: Catch "Exception"
+# C0103: Invalid name
+# C0111: Missing docstring
+disable=W0142,W0511,W0702,W0703,C0103,C0111
+
+[REPORTS]
+reports=no
+include-ids=yes
+
+[FORMAT]
+max-line-length=79
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/configs/sample1.yaml b/tests/configs/sample1.yaml
index 24e874ee..6231f293 100644
--- a/tests/configs/sample1.yaml
+++ b/tests/configs/sample1.yaml
@@ -50,4 +50,3 @@ runcmd:
byobu_by_default: user
-output: {all: '| tee -a /var/log/cloud-init-output.log'}
diff --git a/tests/data/roots/simple_ubuntu/etc/networks/interfaces b/tests/data/roots/simple_ubuntu/etc/networks/interfaces
new file mode 100644
index 00000000..77efa67d
--- /dev/null
+++ b/tests/data/roots/simple_ubuntu/etc/networks/interfaces
@@ -0,0 +1,3 @@
+auto lo
+iface lo inet loopback
+
diff --git a/tests/unittests/__init__.py b/tests/unittests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/unittests/__init__.py
diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
index d0f09e70..2c5dcad2 100644
--- a/tests/unittests/helpers.py
+++ b/tests/unittests/helpers.py
@@ -3,6 +3,38 @@ import os
from mocker import MockerTestCase
from cloudinit import helpers as ch
+from cloudinit import util
+
+import shutil
+
+
+# Makes the old path start
+# with new base instead of whatever
+# it previously had
+def rebase_path(old_path, new_base):
+ if old_path.startswith(new_base):
+ # Already handled...
+ return old_path
+ # Retarget the base of that path
+ # to the new base instead of the
+ # old one...
+ path = os.path.join(new_base, old_path.lstrip("/"))
+ path = os.path.abspath(path)
+ return path
+
+
+# Can work on anything that takes a path as arguments
+def retarget_many_wrapper(new_base, am, old_func):
+ def wrapper(*args, **kwds):
+ n_args = list(args)
+ nam = am
+ if am == -1:
+ nam = len(n_args)
+ for i in range(0, nam):
+ path = args[i]
+ n_args[i] = rebase_path(path, new_base)
+ return old_func(*n_args, **kwds)
+ return wrapper
class ResourceUsingTestCase(MockerTestCase):
@@ -40,3 +72,75 @@ class ResourceUsingTestCase(MockerTestCase):
'templates_dir': self.resourceLocation(),
})
return cp
+
+
+class FilesystemMockingTestCase(ResourceUsingTestCase):
+ def __init__(self, methodName="runTest"):
+ ResourceUsingTestCase.__init__(self, methodName)
+ self.patched_funcs = []
+
+ def replicateTestRoot(self, example_root, target_root):
+ real_root = self.resourceLocation()
+ real_root = os.path.join(real_root, 'roots', example_root)
+ for (dir_path, _dirnames, filenames) in os.walk(real_root):
+ real_path = dir_path
+ make_path = rebase_path(real_path[len(real_root):], target_root)
+ util.ensure_dir(make_path)
+ for f in filenames:
+ real_path = util.abs_join(real_path, f)
+ make_path = util.abs_join(make_path, f)
+ shutil.copy(real_path, make_path)
+
+ def tearDown(self):
+ self.restore()
+ ResourceUsingTestCase.tearDown(self)
+
+ def restore(self):
+ for (mod, f, func) in self.patched_funcs:
+ setattr(mod, f, func)
+ self.patched_funcs = []
+
+ def patchUtils(self, new_root):
+ patch_funcs = {
+ util: [('write_file', 1),
+ ('load_file', 1),
+ ('ensure_dir', 1),
+ ('chmod', 1),
+ ('delete_dir_contents', 1),
+ ('del_file', 1),
+ ('sym_link', -1)],
+ }
+ for (mod, funcs) in patch_funcs.items():
+ for (f, am) in funcs:
+ func = getattr(mod, f)
+ trap_func = retarget_many_wrapper(new_root, am, func)
+ setattr(mod, f, trap_func)
+ self.patched_funcs.append((mod, f, func))
+
+ # Handle subprocess calls
+ func = getattr(util, 'subp')
+
+ def nsubp(*_args, **_kwargs):
+ return ('', '')
+
+ setattr(util, 'subp', nsubp)
+ self.patched_funcs.append((util, 'subp', func))
+
+ def null_func(*_args, **_kwargs):
+ return None
+
+ for f in ['chownbyid', 'chownbyname']:
+ func = getattr(util, f)
+ setattr(util, f, null_func)
+ self.patched_funcs.append((util, f, func))
+
+ def patchOS(self, new_root):
+ patch_funcs = {
+ os.path: ['isfile', 'exists', 'islink', 'isdir'],
+ }
+ for (mod, funcs) in patch_funcs.items():
+ for f in funcs:
+ func = getattr(mod, f)
+ trap_func = retarget_many_wrapper(new_root, 1, func)
+ setattr(mod, f, trap_func)
+ self.patched_funcs.append((mod, f, func))
diff --git a/tests/unittests/test_datasource/__init__.py b/tests/unittests/test_datasource/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/unittests/test_datasource/__init__.py
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 4fa13db8..00379e03 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -6,11 +6,10 @@ import os.path
import mocker
from mocker import MockerTestCase
-from cloudinit.sources import DataSourceConfigDrive as ds
+from cloudinit import helpers
from cloudinit import settings
+from cloudinit.sources import DataSourceConfigDrive as ds
from cloudinit import util
-from cloudinit import helpers
-
PUBKEY = u'ssh-rsa AAAAB3NzaC1....sIkJhq8wdX+4I3A4cYbYP ubuntu@server-460\n'
diff --git a/tests/unittests/test_distros/__init__.py b/tests/unittests/test_distros/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/unittests/test_distros/__init__.py
diff --git a/tests/unittests/test_distros/test_user_data_normalize.py b/tests/unittests/test_distros/test_user_data_normalize.py
index 9d6fb996..8f0d8896 100644
--- a/tests/unittests/test_distros/test_user_data_normalize.py
+++ b/tests/unittests/test_distros/test_user_data_normalize.py
@@ -4,19 +4,27 @@ from cloudinit import distros
from cloudinit import helpers
from cloudinit import settings
+bcfg = {
+ 'name': 'bob',
+ 'plain_text_passwd': 'ubuntu',
+ 'home': "/home/ubuntu",
+ 'shell': "/bin/bash",
+ 'lock_passwd': True,
+ 'gecos': "Ubuntu",
+ 'groups': ["foo"]
+}
+
class TestUGNormalize(MockerTestCase):
- def _make_distro(self, dtype, def_user=None, def_groups=None):
+ def _make_distro(self, dtype, def_user=None):
cfg = dict(settings.CFG_BUILTIN)
cfg['system_info']['distro'] = dtype
paths = helpers.Paths(cfg['system_info']['paths'])
distro_cls = distros.fetch(dtype)
- distro = distro_cls(dtype, cfg['system_info'], paths)
if def_user:
- distro.default_user = def_user
- if def_groups:
- distro.default_user_groups = def_groups
+ cfg['system_info']['default_user'] = def_user.copy()
+ distro = distro_cls(dtype, cfg['system_info'], paths)
return distro
def _norm(self, cfg, distro):
@@ -71,7 +79,7 @@ class TestUGNormalize(MockerTestCase):
self.assertEquals({}, users)
def test_users_simple_dict(self):
- distro = self._make_distro('ubuntu', 'bob')
+ distro = self._make_distro('ubuntu', bcfg)
ug_cfg = {
'users': {
'default': True,
@@ -95,7 +103,7 @@ class TestUGNormalize(MockerTestCase):
self.assertIn('bob', users)
def test_users_simple_dict_no(self):
- distro = self._make_distro('ubuntu', 'bob')
+ distro = self._make_distro('ubuntu', bcfg)
ug_cfg = {
'users': {
'default': False,
@@ -137,7 +145,7 @@ class TestUGNormalize(MockerTestCase):
self.assertEquals({'default': False}, users['bob'])
def test_users_old_user(self):
- distro = self._make_distro('ubuntu', 'bob')
+ distro = self._make_distro('ubuntu', bcfg)
ug_cfg = {
'user': 'zetta',
'users': 'default'
@@ -185,7 +193,7 @@ class TestUGNormalize(MockerTestCase):
self.assertEquals({}, groups)
def test_users_dict_default_additional(self):
- distro = self._make_distro('ubuntu', 'bob')
+ distro = self._make_distro('ubuntu', bcfg)
ug_cfg = {
'users': [
{'name': 'default', 'blah': True}
@@ -201,7 +209,7 @@ class TestUGNormalize(MockerTestCase):
users['bob']['default'])
def test_users_dict_extract(self):
- distro = self._make_distro('ubuntu', 'bob')
+ distro = self._make_distro('ubuntu', bcfg)
ug_cfg = {
'users': [
'default',
@@ -228,7 +236,7 @@ class TestUGNormalize(MockerTestCase):
self.assertEquals(config, expected_config)
def test_users_dict_default(self):
- distro = self._make_distro('ubuntu', 'bob')
+ distro = self._make_distro('ubuntu', bcfg)
ug_cfg = {
'users': [
'default',
diff --git a/tests/unittests/test_filters/__init__.py b/tests/unittests/test_filters/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/unittests/test_filters/__init__.py
diff --git a/tests/unittests/test_filters/test_launch_index.py b/tests/unittests/test_filters/test_launch_index.py
index 7ca7cbb6..773bb312 100644
--- a/tests/unittests/test_filters/test_launch_index.py
+++ b/tests/unittests/test_filters/test_launch_index.py
@@ -1,6 +1,6 @@
import copy
-import helpers as th
+from tests.unittests import helpers
import itertools
@@ -18,7 +18,7 @@ def count_messages(root):
return am
-class TestLaunchFilter(th.ResourceUsingTestCase):
+class TestLaunchFilter(helpers.ResourceUsingTestCase):
def assertCounts(self, message, expected_counts):
orig_message = copy.deepcopy(message)
diff --git a/tests/unittests/test_handler/__init__.py b/tests/unittests/test_handler/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/unittests/test_handler/__init__.py
diff --git a/tests/unittests/test_handler/test_handler_ca_certs.py b/tests/unittests/test_handler/test_handler_ca_certs.py
index d3df5c50..d73c9fa9 100644
--- a/tests/unittests/test_handler/test_handler_ca_certs.py
+++ b/tests/unittests/test_handler/test_handler_ca_certs.py
@@ -77,7 +77,7 @@ class TestConfig(MockerTestCase):
"""Test that a single cert gets passed to add_ca_certs."""
config = {"ca-certs": {"trusted": ["CERT1"]}}
- self.mock_add(self.paths, ["CERT1"])
+ self.mock_add(["CERT1"])
self.mock_update()
self.mocker.replay()
@@ -87,7 +87,7 @@ class TestConfig(MockerTestCase):
"""Test that multiple certs get passed to add_ca_certs."""
config = {"ca-certs": {"trusted": ["CERT1", "CERT2"]}}
- self.mock_add(self.paths, ["CERT1", "CERT2"])
+ self.mock_add(["CERT1", "CERT2"])
self.mock_update()
self.mocker.replay()
@@ -97,7 +97,7 @@ class TestConfig(MockerTestCase):
"""Test remove_defaults works as expected."""
config = {"ca-certs": {"remove-defaults": True}}
- self.mock_remove(self.paths)
+ self.mock_remove()
self.mock_update()
self.mocker.replay()
@@ -116,8 +116,8 @@ class TestConfig(MockerTestCase):
"""Test remove_defaults is not called when config value is False."""
config = {"ca-certs": {"remove-defaults": True, "trusted": ["CERT1"]}}
- self.mock_remove(self.paths)
- self.mock_add(self.paths, ["CERT1"])
+ self.mock_remove()
+ self.mock_add(["CERT1"])
self.mock_update()
self.mocker.replay()
@@ -136,7 +136,7 @@ class TestAddCaCerts(MockerTestCase):
"""Test that no certificate are written if not provided."""
self.mocker.replace(util.write_file, passthrough=False)
self.mocker.replay()
- cc_ca_certs.add_ca_certs(self.paths, [])
+ cc_ca_certs.add_ca_certs([])
def test_single_cert(self):
"""Test adding a single certificate to the trusted CAs."""
@@ -149,7 +149,7 @@ class TestAddCaCerts(MockerTestCase):
"\ncloud-init-ca-certs.crt", omode="ab")
self.mocker.replay()
- cc_ca_certs.add_ca_certs(self.paths, [cert])
+ cc_ca_certs.add_ca_certs([cert])
def test_multiple_certs(self):
"""Test adding multiple certificates to the trusted CAs."""
@@ -163,7 +163,7 @@ class TestAddCaCerts(MockerTestCase):
"\ncloud-init-ca-certs.crt", omode="ab")
self.mocker.replay()
- cc_ca_certs.add_ca_certs(self.paths, certs)
+ cc_ca_certs.add_ca_certs(certs)
class TestUpdateCaCerts(MockerTestCase):
@@ -198,4 +198,4 @@ class TestRemoveDefaultCaCerts(MockerTestCase):
"ca-certificates ca-certificates/trust_new_crts select no")
self.mocker.replay()
- cc_ca_certs.remove_default_ca_certs(self.paths)
+ cc_ca_certs.remove_default_ca_certs()
diff --git a/tests/unittests/test_handler/test_handler_yum_add_repo.py b/tests/unittests/test_handler/test_handler_yum_add_repo.py
new file mode 100644
index 00000000..c7fcddc4
--- /dev/null
+++ b/tests/unittests/test_handler/test_handler_yum_add_repo.py
@@ -0,0 +1,68 @@
+from cloudinit import helpers
+from cloudinit import util
+
+from cloudinit.config import cc_yum_add_repo
+
+from tests.unittests import helpers
+
+import logging
+
+from StringIO import StringIO
+
+import configobj
+
+LOG = logging.getLogger(__name__)
+
+
+class TestConfig(helpers.FilesystemMockingTestCase):
+ def setUp(self):
+ super(TestConfig, self).setUp()
+ self.tmp = self.makeDir(prefix="unittest_")
+
+ def test_bad_config(self):
+ cfg = {
+ 'yum_repos': {
+ 'epel-testing': {
+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
+ # Missing this should cause the repo not to be written
+ #'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
+ 'enabled': False,
+ 'gpgcheck': True,
+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
+ 'failovermethod': 'priority',
+ },
+ },
+ }
+ self.patchUtils(self.tmp)
+ cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
+ with self.assertRaises(IOError):
+ util.load_file("/etc/yum.repos.d/epel_testing.repo")
+
+ def test_write_config(self):
+ cfg = {
+ 'yum_repos': {
+ 'epel-testing': {
+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
+ 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
+ 'enabled': False,
+ 'gpgcheck': True,
+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
+ 'failovermethod': 'priority',
+ },
+ },
+ }
+ self.patchUtils(self.tmp)
+ cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, [])
+ contents = util.load_file("/etc/yum.repos.d/epel_testing.repo")
+ contents = configobj.ConfigObj(StringIO(contents))
+ expected = {
+ 'epel_testing': {
+ 'name': 'Extra Packages for Enterprise Linux 5 - Testing',
+ 'failovermethod': 'priority',
+ 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL',
+ 'enabled': '0',
+ 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch',
+ 'gpgcheck': '1',
+ }
+ }
+ self.assertEquals(expected, dict(contents))
diff --git a/tests/unittests/test_runs/__init__.py b/tests/unittests/test_runs/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/unittests/test_runs/__init__.py
diff --git a/tests/unittests/test_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py
new file mode 100644
index 00000000..60ef812a
--- /dev/null
+++ b/tests/unittests/test_runs/test_simple_run.py
@@ -0,0 +1,80 @@
+import os
+
+from tests.unittests import helpers
+
+from cloudinit.settings import (PER_INSTANCE)
+from cloudinit import stages
+from cloudinit import util
+
+
+class TestSimpleRun(helpers.FilesystemMockingTestCase):
+ def _patchIn(self, root):
+ self.restore()
+ self.patchOS(root)
+ self.patchUtils(root)
+
+ def _pp_root(self, root, repatch=True):
+ self.restore()
+ for (dirpath, dirnames, filenames) in os.walk(root):
+ print(dirpath)
+ for f in filenames:
+ joined = os.path.join(dirpath, f)
+ if os.path.islink(joined):
+ print("f %s - (symlink)" % (f))
+ else:
+ print("f %s" % (f))
+ for d in dirnames:
+ joined = os.path.join(dirpath, d)
+ if os.path.islink(joined):
+ print("d %s - (symlink)" % (d))
+ else:
+ print("d %s" % (d))
+ if repatch:
+ self._patchIn(root)
+
+ def test_none_ds(self):
+ new_root = self.makeDir()
+ self.replicateTestRoot('simple_ubuntu', new_root)
+ cfg = {
+ 'datasource_list': ['None'],
+ 'write_files': [
+ {
+ 'path': '/etc/blah.ini',
+ 'content': 'blah',
+ 'permissions': 0755,
+ },
+ ],
+ 'cloud_init_modules': ['write-files'],
+ }
+ cloud_cfg = util.yaml_dumps(cfg)
+ util.ensure_dir(os.path.join(new_root, 'etc', 'cloud'))
+ util.write_file(os.path.join(new_root, 'etc',
+ 'cloud', 'cloud.cfg'), cloud_cfg)
+ self._patchIn(new_root)
+
+ # Now start verifying whats created
+ initer = stages.Init()
+ initer.read_cfg()
+ initer.initialize()
+ self.assertTrue(os.path.exists("/var/lib/cloud"))
+ for d in ['scripts', 'seed', 'instances', 'handlers', 'sem', 'data']:
+ self.assertTrue(os.path.isdir(os.path.join("/var/lib/cloud", d)))
+
+ initer.fetch()
+ iid = initer.instancify()
+ self.assertEquals(iid, 'iid-datasource-none')
+ initer.update()
+ self.assertTrue(os.path.islink("var/lib/cloud/instance"))
+
+ initer.cloudify().run('consume_userdata',
+ initer.consume_userdata,
+ args=[PER_INSTANCE],
+ freq=PER_INSTANCE)
+
+ mods = stages.Modules(initer)
+ (which_ran, failures) = mods.run_section('cloud_init_modules')
+ self.assertTrue(len(failures) == 0)
+ self.assertTrue(os.path.exists('/etc/blah.ini'))
+ self.assertIn('write-files', which_ran)
+ contents = util.load_file('/etc/blah.ini')
+ self.assertEquals(contents, 'blah')
diff --git a/tools/Z99-cloud-locale-test.sh b/tools/Z99-cloud-locale-test.sh
index 8ad485e8..4012beeb 100755
--- a/tools/Z99-cloud-locale-test.sh
+++ b/tools/Z99-cloud-locale-test.sh
@@ -22,7 +22,7 @@ locale_warn() {
case "$w1" in
locale:) bad_names="${bad_names} ${w4}";;
*)
- key=${w1%%=*}
+ key=${w1%%\=*}
val=${w1#*=}
val=${val#\"}
val=${val%\"}
@@ -31,7 +31,7 @@ locale_warn() {
done
for bad in $bad_names; do
for var in ${vars}; do
- [ "${bad}" = "${var%=*}" ] || continue
+ [ "${bad}" = "${var%\=*}" ] || continue
value=${var#*=}
[ "${bad_lcs#* ${value}}" = "${bad_lcs}" ] &&
bad_lcs="${bad_lcs} ${value}"
diff --git a/tools/hacking.py b/tools/hacking.py
index 11163df3..26a07c53 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -66,8 +66,8 @@ def cloud_import_alphabetical(physical_line, line_number, lines):
# handle import x
# use .lower since capitalization shouldn't dictate order
split_line = import_normalize(physical_line.strip()).lower().split()
- split_previous = import_normalize(lines[line_number - 2]
- ).strip().lower().split()
+ split_previous = import_normalize(lines[line_number - 2])
+ split_previous = split_previous.strip().lower().split()
# with or without "as y"
length = [2, 4]
if (len(split_line) in length and len(split_previous) in length and
diff --git a/tools/run-pep8 b/tools/run-pep8
index ea46c117..ad55d420 100755
--- a/tools/run-pep8
+++ b/tools/run-pep8
@@ -1,6 +1,6 @@
#!/bin/bash
-ci_files='cloud*.py cloudinit/*.py cloudinit/config/*.py'
+ci_files='cloudinit/*.py cloudinit/config/*.py'
test_files=$(find tests -name "*.py")
def_files="$ci_files $test_files"
diff --git a/tools/run-pylint b/tools/run-pylint
index 7ef44ac5..b74efda9 100755
--- a/tools/run-pylint
+++ b/tools/run-pylint
@@ -6,23 +6,16 @@ else
files=( "$@" );
fi
+RC_FILE="pylintrc"
+if [ ! -f $RC_FILE ]; then
+ RC_FILE="../pylintrc"
+fi
+
cmd=(
pylint
- --reports=n
- --include-ids=y
- --max-line-length=79
-
+ --rcfile=$RC_FILE
--disable=R
--disable=I
-
- --disable=W0142 # Used * or ** magic
- --disable=W0511 # TODO/FIXME note
- --disable=W0702 # No exception type(s) specified
- --disable=W0703 # Catch "Exception"
-
- --disable=C0103 # Invalid name
- --disable=C0111 # Missing docstring
-
"${files[@]}"
)