From 0049b6e7bc7f52fd442f1b8902294ca8c379df5c Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 20 Sep 2012 17:26:42 -0700 Subject: Remove the need to have 'default_user' and 'default_user_groups' groups be hard coded into the distro class, instead let that set of configuration be located in the config file where it should be specified instead. --- cloudinit/distros/__init__.py | 12 ++++-------- cloudinit/distros/fedora.py | 3 +-- cloudinit/distros/ubuntu.py | 5 +---- config/cloud.cfg | 17 ++++++++++++++++- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 3e9d934d..cdb72b5a 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -44,10 +44,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 @@ -61,7 +58,6 @@ class Distro(object): user = self.get_default_user() groups = self.get_default_user_groups() - if not user: raise NotImplementedError("No Default user") @@ -71,12 +67,12 @@ class Distro(object): 'home': "/home/%s" % user, 'shell': "/bin/bash", 'lock_passwd': True, - 'gecos': "%s%s" % (user[0:1].upper(), user[1:]), + 'gecos': user.title(), 'sudo': "ALL=(ALL) NOPASSWD:ALL", } if groups: - user_dict['groups'] = groups + user_dict['groups'] = ",".join(groups) self.create_user(**user_dict) @@ -212,10 +208,10 @@ class Distro(object): return False def get_default_user(self): - return self.default_user + return self.get_option('default_user') def get_default_user_groups(self): - return self.default_user_groups + return self.get_option('default_user_groups') def create_user(self, name, **kwargs): """ diff --git a/cloudinit/distros/fedora.py b/cloudinit/distros/fedora.py index 9f76a116..c777845d 100644 --- a/cloudinit/distros/fedora.py +++ b/cloudinit/distros/fedora.py @@ -28,5 +28,4 @@ LOG = logging.getLogger(__name__) class Distro(rhel.Distro): - distro_name = 'fedora' - default_user = 'ec2-user' + pass diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index 22f8c2c5..428db6ad 100644 --- a/cloudinit/distros/ubuntu.py +++ b/cloudinit/distros/ubuntu.py @@ -28,8 +28,5 @@ LOG = logging.getLogger(__name__) class Distro(debian.Distro): + pass - distro_name = 'ubuntu' - default_user = 'ubuntu' - default_user_groups = ("adm,audio,cdrom,dialout,floppy,video," - "plugdev,dip,netdev,sudo") diff --git a/config/cloud.cfg b/config/cloud.cfg index b3411d11..fbf53bd5 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 @@ -71,6 +73,19 @@ 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: ubuntu + default_user_groups: + - adm + - audio + - cdrom + - dialout + - floppy + - video + - plugdev + - dip + - netdev + - sudo # Other config here will be given to the distro class and/or path classes paths: cloud_dir: /var/lib/cloud/ -- cgit v1.2.3 From e8a10a41d22876d555084def823817337d9c2a80 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 20 Sep 2012 17:56:22 -0700 Subject: Use only util methods for reading/loading/appending/peeking at files since it is likely soon that we will add a new way of adjusting the root of files read, also it is useful for debugging to track what is being read/written in a central fashion. --- cloudinit/distros/__init__.py | 3 +-- cloudinit/sources/DataSourceAltCloud.py | 19 ++++++++----------- cloudinit/sources/DataSourceConfigDrive.py | 20 +++++++++----------- cloudinit/sources/DataSourceMAAS.py | 6 ++---- cloudinit/sources/DataSourceOVF.py | 5 ++--- cloudinit/util.py | 10 ++++++++++ 6 files changed, 32 insertions(+), 31 deletions(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 3e9d934d..f6aa8d99 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -343,8 +343,7 @@ class Distro(object): 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 diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py index 69c376a5..d7e1204f 100644 --- a/cloudinit/sources/DataSourceAltCloud.py +++ b/cloudinit/sources/DataSourceAltCloud.py @@ -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 b8154367..b477560c 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -227,19 +227,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 @@ -255,8 +255,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: @@ -270,7 +269,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']. @@ -385,8 +384,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/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py index c568d365..d166e9e3 100644 --- a/cloudinit/sources/DataSourceMAAS.py +++ b/cloudinit/sources/DataSourceMAAS.py @@ -301,9 +301,7 @@ if __name__ == "__main__": 'token_secret': args.tsec, 'consumer_secret': args.csec} if args.config: - import yaml - with open(args.config) as fp: - cfg = yaml.safe_load(fp) + cfg = util.read_conf(args.config) if 'datasource' in cfg: cfg = cfg['datasource']['MAAS'] for key in creds.keys(): @@ -312,7 +310,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/util.py b/cloudinit/util.py index 33da73eb..18000301 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -952,6 +952,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() @@ -1281,6 +1287,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) -- cgit v1.2.3 From f31a4e80aaaafca79eb6c808c81a6067f0c493b8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 26 Sep 2012 16:29:50 -0700 Subject: Add a new example test that will patch utils and os functions so that they can be 'retargeted' to a temporary directory, which allows us the ability to run a full set of cloud-init stages. Neat things: 1. All cloud-init code is unchanged (as long as it goes through the utils functions for most functionality) 2. Allows for a natural way to setup a temporary directory then patch the new directory as the new 'root' and then run cloud-init stages and then check the contents of what was placed as desired. --- .../roots/simple_ubuntu/etc/networks/interfaces | 3 + tests/unittests/helpers.py | 106 +++++++++++++++++++++ tests/unittests/test_runs/test_simple_run.py | 89 +++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 tests/data/roots/simple_ubuntu/etc/networks/interfaces create mode 100644 tests/unittests/test_runs/test_simple_run.py 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/helpers.py b/tests/unittests/helpers.py index d0f09e70..d7aebdee 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -1,8 +1,42 @@ import os +import mocker + 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 first argument +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 +74,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_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py new file mode 100644 index 00000000..33dd3ca2 --- /dev/null +++ b/tests/unittests/test_runs/test_simple_run.py @@ -0,0 +1,89 @@ +from mocker import MockerTestCase + +import mocker +import sys +import os + +# Allow running this test individually +top_dir = os.path.join(os.path.dirname(__file__), os.pardir, "helpers.py") +top_dir = os.path.abspath(top_dir) +if os.path.exists(top_dir): + sys.path.insert(0, os.path.dirname(top_dir)) + + +import helpers + +from cloudinit import util +from cloudinit import stages + +from cloudinit.settings import (PER_INSTANCE) + + +class TestSimpleRunDistro(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")) + + (ran, results) = 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(os.path.exists('/etc/blah.ini')) + self.assertIn('write-files', which_ran) + contents = util.load_file('/etc/blah.ini') + self.assertEquals(contents, 'blah') -- cgit v1.2.3 From 4a5d5a044db9e77b931fb4df93f7c01b02021f44 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 26 Sep 2012 16:40:07 -0700 Subject: Fixup some pylint warnings. --- tests/unittests/helpers.py | 8 +++----- tests/unittests/test_filters/test_launch_index.py | 12 ++++++++++-- tests/unittests/test_runs/test_simple_run.py | 14 ++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index d7aebdee..7b829f24 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -1,7 +1,5 @@ import os -import mocker - from mocker import MockerTestCase from cloudinit import helpers as ch @@ -84,7 +82,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): 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): + 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) @@ -122,13 +120,13 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): # Handle subprocess calls func = getattr(util, 'subp') - def nsubp(*args, **kwargs): + def nsubp(*_args, **_kwargs): return ('', '') setattr(util, 'subp', nsubp) self.patched_funcs.append((util, 'subp', func)) - def null_func(*args, **kwargs): + def null_func(*_args, **_kwargs): return None for f in ['chownbyid', 'chownbyname']: diff --git a/tests/unittests/test_filters/test_launch_index.py b/tests/unittests/test_filters/test_launch_index.py index 7ca7cbb6..1e9b9053 100644 --- a/tests/unittests/test_filters/test_launch_index.py +++ b/tests/unittests/test_filters/test_launch_index.py @@ -1,6 +1,14 @@ import copy +import os +import sys -import helpers as th +top_dir = os.path.join(os.path.dirname(__file__), os.pardir, "helpers.py") +top_dir = os.path.abspath(top_dir) +if os.path.exists(top_dir): + sys.path.insert(0, os.path.dirname(top_dir)) + + +import helpers import itertools @@ -18,7 +26,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_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py index 33dd3ca2..7f646b54 100644 --- a/tests/unittests/test_runs/test_simple_run.py +++ b/tests/unittests/test_runs/test_simple_run.py @@ -1,6 +1,3 @@ -from mocker import MockerTestCase - -import mocker import sys import os @@ -19,7 +16,7 @@ from cloudinit import stages from cloudinit.settings import (PER_INSTANCE) -class TestSimpleRunDistro(helpers.FilesystemMockingTestCase): +class TestSimpleRun(helpers.FilesystemMockingTestCase): def _patchIn(self, root): self.restore() self.patchOS(root) @@ -76,13 +73,14 @@ class TestSimpleRunDistro(helpers.FilesystemMockingTestCase): initer.update() self.assertTrue(os.path.islink("var/lib/cloud/instance")) - (ran, results) = initer.cloudify().run('consume_userdata', - initer.consume_userdata, - args=[PER_INSTANCE], - freq=PER_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') -- cgit v1.2.3 From b541f738616349a028c5e54754ea83e439d82734 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 26 Sep 2012 16:42:15 -0700 Subject: Adjust comment. --- tests/unittests/helpers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 7b829f24..d5df580b 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -22,8 +22,7 @@ def rebase_path(old_path, new_base): return path -# Can work on anything that takes a path -# as first argument +# 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) -- cgit v1.2.3 From f255d068c5d4251762b83467d1927ab72da57482 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 28 Sep 2012 18:39:46 -0700 Subject: Ensure that the directory where the sudoers file is being added actually exists before it is written into and ensure that the directory is included in the main sudoers file. --- cloudinit/distros/__init__.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 86ab557c..11422644 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -296,6 +296,38 @@ 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 = '' + 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() + mtch = re.search(r"#includedir\s+(.*)$", line) + if not mtch: + continue + included_dir = mtch.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) + except IOError as e: + util.logexc(LOG, "Failed to write %s" % sudo_base, e) + raise e + util.ensure_dir(path, 0440) + def write_sudo_rules(self, user, rules, @@ -311,9 +343,10 @@ 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: -- cgit v1.2.3 From 9e7320df7a6fb6f1e9a5401735c471467b1fe365 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 28 Sep 2012 20:32:00 -0700 Subject: Dir should be 0755, not 0440 --- cloudinit/distros/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 11422644..301eeed2 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -326,7 +326,7 @@ class Distro(object): except IOError as e: util.logexc(LOG, "Failed to write %s" % sudo_base, e) raise e - util.ensure_dir(path, 0440) + util.ensure_dir(path, 0755) def write_sudo_rules(self, user, -- cgit v1.2.3 From 9a252b3a41ad757587cba729b31079c04b253faa Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 28 Sep 2012 21:04:00 -0700 Subject: Update the log statement used here to be a little more relevant. --- cloudinit/distros/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 301eeed2..500147c6 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -248,7 +248,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: -- cgit v1.2.3 From e2b85e2087b796c7a130ee20f688dbd5d161739f Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 30 Sep 2012 13:47:00 -0700 Subject: Ensure that the include dir starts the line and is not a part of a comment or other part of the line. --- cloudinit/distros/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 500147c6..cdae1add 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -305,10 +305,10 @@ class Distro(object): found_include = False for line in sudoers_contents.splitlines(): line = line.strip() - mtch = re.search(r"#includedir\s+(.*)$", line) - if not mtch: + include_match = re.search(r"^#includedir\s+(.*)$", line) + if not include_match: continue - included_dir = mtch.group(1).strip() + included_dir = include_match.group(1).strip() if not included_dir: continue included_dir = os.path.abspath(included_dir) -- cgit v1.2.3 From e59ce224c45191a0ecceb113c9e5b8e6d9dcdab1 Mon Sep 17 00:00:00 2001 From: Avishai Ish-Shalom Date: Wed, 10 Oct 2012 18:27:28 +0200 Subject: Fixed typo on dict value reference LP: #1065116 --- cloudinit/user_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) -- cgit v1.2.3 From 923f5c70fbff04ff538a5df17c300a9f39a85180 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 23 Oct 2012 06:18:24 -0400 Subject: fix pep8/pylint --- cloudinit/distros/__init__.py | 2 +- tests/unittests/test_datasource/test_configdrive.py | 5 ++--- tests/unittests/test_distros/test_user_data_normalize.py | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index abd6cc48..cac3ed7a 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -424,7 +424,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 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/test_user_data_normalize.py b/tests/unittests/test_distros/test_user_data_normalize.py index f27af826..8f0d8896 100644 --- a/tests/unittests/test_distros/test_user_data_normalize.py +++ b/tests/unittests/test_distros/test_user_data_normalize.py @@ -14,6 +14,7 @@ bcfg = { 'groups': ["foo"] } + class TestUGNormalize(MockerTestCase): def _make_distro(self, dtype, def_user=None): -- cgit v1.2.3 From 182bf60757b76f86ffb4e5ee9aebcc8ce0fcc74f Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 23 Oct 2012 06:35:58 -0400 Subject: tweak default ubuntu user --- config/cloud.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/cloud.cfg b/config/cloud.cfg index efa0fa36..f6c9065a 100644 --- a/config/cloud.cfg +++ b/config/cloud.cfg @@ -76,9 +76,6 @@ system_info: # Default user name + that default users groups (if added/used) 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] -- cgit v1.2.3 From 179f82a9719c2850e465f7f06221978f3be15ffc Mon Sep 17 00:00:00 2001 From: Thomas Hervé Date: Tue, 23 Oct 2012 12:57:26 +0200 Subject: Restart landscape at the end of the configuration --- cloudinit/config/cc_landscape.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py index 331559f4..56ab0ce3 100644 --- a/cloudinit/config/cc_landscape.py +++ b/cloudinit/config/cc_landscape.py @@ -84,6 +84,7 @@ def handle(_name, cfg, cloud, log, _args): log.debug("Wrote landscape config file to %s", lsc_client_fn) util.write_file(LS_DEFAULT_FILE, "RUN=1\n") + util.subp(["service", "landscape-client", "restart"]) def merge_together(objs): -- cgit v1.2.3 From 2953d9d448fde3af19fc96ae00f41066f510d6fd Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 23 Oct 2012 07:54:40 -0700 Subject: No need for the get default users groups function when its provided by the get user function. --- cloudinit/distros/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 6ef63442..11a72da1 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -175,9 +175,6 @@ class Distro(object): def get_default_user(self): return self.get_option('default_user') - def get_default_user_groups(self): - return self.get_option('default_user_groups') - def create_user(self, name, **kwargs): """ Creates users for the system using the GNU passwd tools. This -- cgit v1.2.3 From c508fa0c4159f93df077a2d46ed481fd5d8c8803 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 6 Nov 2012 15:28:29 -0500 Subject: tools/Z99-cloud-locale-test.sh: avoid warning when shell is zsh LP: #1073077 --- ChangeLog | 2 ++ tools/Z99-cloud-locale-test.sh | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index d03c0293..1243ce7f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ - 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) 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/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}" -- cgit v1.2.3 From 7d027a031a3649ac04965a09cd26563ac9d760fd Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 6 Nov 2012 14:13:30 -0800 Subject: Fix the case where a unknown type is seen and it has contents which are in unicode which seems to cause python to blow-up when this happens since 'string-escape' doesn't work on unicode (at least in 2.6). LP: #1075756 --- cloudinit/handlers/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py index 99caed1f..e843eb75 100644 --- a/cloudinit/handlers/__init__.py +++ b/cloudinit/handlers/__init__.py @@ -171,7 +171,11 @@ 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")) + try: + details = "'%s...'" % (start.encode("string-escape")) + except TypeError: + # Unicode doesn't support string-escape... + details = "'%s...'" % (start) if ctype == NOT_MULTIPART_TYPE: LOG.warning("Unhandled non-multipart (%s) userdata: %s", ctype, details) -- cgit v1.2.3 From 7ec0ef04b975eb5b4c40f7ae746d706585c73a02 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 6 Nov 2012 14:24:19 -0800 Subject: Use a method instead + at least attempt the unicode-escape path. --- cloudinit/handlers/__init__.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py index e843eb75..d847f331 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: + text = "'%s...'" % (text.encode("string-escape")) + except TypeError: + try: + # Unicode doesn't support string-escape... + text = "'%s...'" % (text.encode('unicode-escape')) + except TypeError: + # Give up... + text = "'%s...'" % (text) + return text + + def walker_callback(pdata, ctype, filename, payload): if ctype in PART_CONTENT_TYPES: walker_handle_handler(pdata, ctype, filename, payload) @@ -171,11 +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) - try: - details = "'%s...'" % (start.encode("string-escape")) - except TypeError: - # Unicode doesn't support string-escape... - details = "'%s...'" % (start) + details = _escape_string(start) if ctype == NOT_MULTIPART_TYPE: LOG.warning("Unhandled non-multipart (%s) userdata: %s", ctype, details) -- cgit v1.2.3 From 9850895442afe55079cecf4fd96fe8430ed960ea Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 6 Nov 2012 14:27:56 -0800 Subject: Do the append after escape. --- cloudinit/handlers/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py index d847f331..8d6dcd4d 100644 --- a/cloudinit/handlers/__init__.py +++ b/cloudinit/handlers/__init__.py @@ -162,14 +162,14 @@ def _extract_first_or_bytes(blob, size): def _escape_string(text): try: - text = "'%s...'" % (text.encode("string-escape")) + return text.encode("string-escape") except TypeError: try: # Unicode doesn't support string-escape... - text = "'%s...'" % (text.encode('unicode-escape')) + return text.encode('unicode-escape') except TypeError: # Give up... - text = "'%s...'" % (text) + pass return text @@ -184,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 = _escape_string(start) + details = "'%s...'" % (_escape_string(start)) if ctype == NOT_MULTIPART_TYPE: LOG.warning("Unhandled non-multipart (%s) userdata: %s", ctype, details) -- cgit v1.2.3