def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['localhost'], } self.settings.roledefs = { 'role': ['localhost'], }
class TestJinjaFilters(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['localhost'], } self.settings.roledefs = { 'role': ['localhost'], } def test_built_in_filters(self): """ Generated templates that use built-in filters have the correct values. """ conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/jinjafilters/builtin'), lambda _: { 'bar': [1, 2, 3], 'pivot': 2, 'foo': { 'key1': 'foo1', 'key2': 'foo2', }, 'key': 'key2', }) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) eq_('foo2', tmp_dir.read('generated/localhost/foo.txt')) eq_("['+2+', '+3+', '+1+']", tmp_dir.read('generated/localhost/bar/bar.txt')) def test_user_filters(self): """ Generated templates that use user-defined filters have the correct values. """ def multiply(value, mult): return value * mult with JinjaFilters(multiply): conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/jinjafilters/user'), lambda _: {'foo': 'foo'}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) eq_('foofoofoo', tmp_dir.read('generated/localhost/foo.txt'))
def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "test1": ["host1", "host2", "host3"], "test2": ["host2", "host3"], "test3": [] } self.settings.roledefs = { "role1": ["host1", "host2", "host3"], "role2": ["host2", "host3"], "role3": [], }
class TestJinjaFilters(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['localhost'], } self.settings.roledefs = { 'role': ['localhost'], } def test_built_in_filters(self): """ Generated templates that use built-in filters have the correct values. """ conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/jinjafilters/builtin'), lambda _: { 'bar': [1, 2, 3], 'pivot': 2, 'foo': { 'key1': 'foo1', 'key2': 'foo2', }, 'key': 'key2', }) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) eq_('foo2', tmp_dir.read('generated/localhost/foo.txt')) eq_("['+2+', '+3+', '+1+']", tmp_dir.read('generated/localhost/bar/bar.txt')) def test_user_filters(self): """ Generated templates that use user-defined filters have the correct values. """ def multiply(value, mult): return value * mult with JinjaFilters(multiply): conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/jinjafilters/user'), lambda _: {'foo': 'foo'}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) eq_('foofoofoo', tmp_dir.read('generated/localhost/foo.txt'))
def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "environment": ["host"], } self.settings.roledefs = { "role": ["host"], } self.settings.componentdefs = { "role": ["component"], } self.component = self.settings.for_env( "environment").components().next()
def test_load_from_empty_module(self): """ Loading empty state results in empty dictionaries. """ self.settings = Settings.load_from_module(join(self.dir_name, "empty.py")) for key in Settings.KEYS: eq_({}, getattr(self.settings, key, {}))
def generate_tasks(settings_path=None): """ Autogenerate `env` tasks for all defined environments. """ def create_task(settings, environment): def select_environment(*roles): if hasattr(env, "environmentdef" ) and env.environmentdef.name != environment: abort("Environment already defined as '{}'".format( env.environmentdef.name)) # Do not select hosts here. # # If `fab` is run with multiple hosts, this task will be run multiple # times, overwriting the value of "env.environmentdef". By not selecting hosts # here, we ensure that the same environmentdef will be loaded each # time. See also confab.iter:iter_conffiles env.environmentdef = settings.for_env(environment).with_roles( *roles) return select_environment settings = Settings.load_from_module(settings_path) for environment in settings.environmentdefs.iterkeys(): _add_task( environment, create_task(settings, environment), "Set environment to '{environment}'".format( environment=environment)) return settings
def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "test1": ["host1", "host2", "host3"], "test2": ["host2", "host3"], "test3": [] } self.settings.roledefs = { "role1": ["host1", "host2", "host3"], "role2": ["host2", "host3"], "role3": [], } self.settings.componentdefs = { "role1": ["comp1", "compgroup"], "compgroup": ["comp2", "comp3"], }
def generate_tasks(settings_path=None): """ Autogenerate `env` tasks for all defined environments. """ def create_task(settings, environment): def select_environment(*roles): if hasattr(env, "environmentdef") and env.environmentdef.name != environment: abort("Environment already defined as '{}'".format(env.environmentdef.name)) # Do not select hosts here. # # If `fab` is run with multiple hosts, this task will be run multiple # times, overwriting the value of "env.environmentdef". By not selecting hosts # here, we ensure that the same environmentdef will be loaded each # time. See also confab.iter:iter_conffiles env.environmentdef = settings.for_env(environment).with_roles(*roles) return select_environment settings = Settings.load_from_module(settings_path) for environment in settings.environmentdefs.iterkeys(): _add_task(environment, create_task(settings, environment), "Set environment to '{environment}'".format(environment=environment)) return settings
def test_extension_paths(self): """ Test loading of templates and data from extension entry points. """ settings_ = Settings.load_from_dict( dict(environmentdefs={'any': ['host1']}, roledefs={'role1': ['host1']})) # use an empty dir as the user's templates dir to make sure # only the test templates and data are loaded with TempDir() as tmp_dir: makedirs(join(tmp_dir.path, 'templates')) makedirs(join(tmp_dir.path, 'data')) # mock entry point loading to return one entry point with the test templates mock_entry_point = Mock() mock_entry_point.load.return_value = lambda: join( dirname(__file__), 'extension') with patch('confab.iter.iter_entry_points', Mock(return_value=[mock_entry_point])): with settings(environmentdef=settings_.for_env('any')): for conffiles in iter_conffiles(tmp_dir.path): conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt'))
def test_load_from_empty_dict(self): """ Loading empty state results in empty dictionaries. """ self.settings = Settings.load_from_dict({}) for key in Settings.KEYS: eq_({}, getattr(self.settings, key, {}))
def test_multiple_directories(self): """ Generate templates for roles with components where templates and data are in multiple directories. """ settings = Settings.load_from_dict( dict(environmentdefs={'any': ['host1']}, roledefs={ 'role1': ['host1'], 'role2': ['host1'] }, componentdefs={'role1': ['comp1', 'comp2']})) subdirs = ['roles', 'components'] template_dirs = map( lambda d: join(dirname(__file__), 'templates/multidir', d), subdirs) data_dirs = map(lambda d: join(dirname(__file__), 'data/multidir', d), subdirs) with TempDir() as tmp_dir: for host_and_role in settings.for_env('any').all(): conffiles = ConfFiles( host_and_role, FileSystemEnvironmentLoader(*template_dirs), DataLoader(data_dirs)) conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt')) self.assertEquals('bar', tmp_dir.read('generated/host1/bar.txt')) self.assertEquals('baz', tmp_dir.read('generated/host1/baz.conf'))
def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['localhost'], } self.settings.roledefs = { 'role': ['localhost'], }
def test_load_from_example_module(self): """ Loading non-empty module results in expected dictionaries. """ self.settings = Settings.load_from_module(join(self.dir_name, "example.py")) eq_(["host1", "host2"], self.settings.environmentdefs["environment1"]) eq_(["host1"], self.settings.roledefs["role1"]) eq_(["component1"], self.settings.componentdefs["role1"])
def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "environment": ["host"], } self.settings.roledefs = { "role": ["host"], } self.settings.componentdefs = { "role": ["component"], } self.component = self.settings.for_env("environment").components().next()
class TestHostDefinition(TestCase): """ Test host iteration. """ def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "test1": ["host1", "host2", "host3"], "test2": ["host2", "host3"], "test3": [] } self.settings.roledefs = { "role1": ["host1", "host2", "host3"], "role2": ["host2", "host3"], "role3": [], } self.settings.componentdefs = { "role1": ["comp1", "compgroup"], "compgroup": ["comp2", "comp3"], } def test_iter_hosts_roles(self): """ Test iteration over an environment's hosts to get roles. """ def get_hosts_roles(envdef): return {hostdef.host: [host_and_role.role for host_and_role in hostdef.roles()] for hostdef in envdef.hosts()} eq_({"host1": ["role1"], "host2": ["role1", "role2"], "host3": ["role1", "role2"]}, get_hosts_roles(self.settings.for_env("test1"))) eq_({"host2": ["role1", "role2"], "host3": ["role1", "role2"]}, get_hosts_roles(self.settings.for_env("test2"))) eq_({}, get_hosts_roles(self.settings.for_env("test3"))) def test_iter_hosts_components(self): """ Test iteration over an environment's hosts to get components. """ def get_hosts_components(envdef): return {hostdef.host: [compdef.name for compdef in hostdef.components()] for hostdef in envdef.hosts()} eq_({"host1": ["comp1", "comp2", "comp3"], "host2": ["comp1", "comp2", "comp3", "role2"], "host3": ["comp1", "comp2", "comp3", "role2"]}, get_hosts_components(self.settings.for_env("test1"))) eq_({"host2": ["comp1", "comp2", "comp3", "role2"], "host3": ["comp1", "comp2", "comp3", "role2"]}, get_hosts_components(self.settings.for_env("test2"))) eq_({}, get_hosts_components(self.settings.for_env("test3")))
def main(): """ Diagnostics command line entry point. """ try: # Parse and validate arguments parser, options, arguments = parse_options() settings = Settings.load_from_module(options.directory) except Exception as e: parser.error(e) descriptions = make_conffile_descriptions(settings, options.environment, options.hosts.split(",") if options.hosts else [], options.roles.split(",") if options.roles else []) print_conffiles(descriptions)
def test_load_from_dict(self): """ Loading empty state results in empty dictionaries. """ dct = { "environmentdefs": { "environment1": ["host1", "host2"], }, "roledefs": { "role1": ["host1"], }, "componentdefs": { "role1": ["component1"], } } self.settings = Settings.load_from_dict(dct) eq_(["host1", "host2"], self.settings.environmentdefs["environment1"]) eq_(["host1"], self.settings.roledefs["role1"]) eq_(["component1"], self.settings.componentdefs["role1"])
def main(): """ Diagnostics command line entry point. """ try: # Parse and validate arguments parser, options, arguments = parse_options() configure_output(verbosity=options.verbosity, quiet=options.quiet) settings = Settings.load_from_module(options.directory) except Exception as e: parser.error(e) table = make_table(settings, options.environment, options.hosts.split(",") if options.hosts else [], options.roles.split(",") if options.roles else []) print(table)
def test_extension_paths(self): """ Test loading of templates and data from extension entry points. """ settings_ = Settings.load_from_dict(dict(environmentdefs={'any': ['host1']}, roledefs={'role1': ['host1']})) # use an empty dir as the user's templates dir to make sure # only the test templates and data are loaded with TempDir() as tmp_dir: makedirs(join(tmp_dir.path, 'templates')) makedirs(join(tmp_dir.path, 'data')) # mock entry point loading to return one entry point with the test templates mock_entry_point = Mock() mock_entry_point.load.return_value = lambda: join(dirname(__file__), 'extension') with patch('confab.iter.iter_entry_points', Mock(return_value=[mock_entry_point])): with settings(environmentdef=settings_.for_env('any')): for conffiles in iter_conffiles(tmp_dir.path): conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt'))
def load_environmentdef(environment, settings_path=None, hosts=None, roles=None): """ Load settings, construct an environment definition, and save in Fabric env as ``confab`` for use by subsequent confab tasks. :param environment: environment name :param settings_path: path to settings module :param hosts: comma delimited host list :param roles: comma delimited role list """ settings_ = Settings.load_from_module(settings_path) # Normalize and resolve hosts to roles mapping selected_hosts = hosts.split(",") if hosts else [] selected_roles = roles.split(",") if roles else [] env.environmentdef = settings_.for_env(environment) env.environmentdef = env.environmentdef.with_hosts(*selected_hosts) env.environmentdef = env.environmentdef.with_roles(*selected_roles) return env.environmentdef
def load_environmentdef(environment, settings_path=None, hosts=None, roles=None): """ Load settings, construct an environment definition, and save in Fabric env as "confab" for use by subsequent confab tasks. :param environment: environment name :param settings_path: path to settings module :param hosts: comma delimited host list :param roles: comma delimited role list """ settings_ = Settings.load_from_module(settings_path) # Normalize and resolve hosts to roles mapping selected_hosts = hosts.split(",") if hosts else [] selected_roles = roles.split(",") if roles else [] env.environmentdef = settings_.for_env(environment) env.environmentdef = env.environmentdef.with_hosts(*selected_hosts) env.environmentdef = env.environmentdef.with_roles(*selected_roles) return env.environmentdef
def test_multiple_directories(self): """ Generate templates for roles with components where templates and data are in multiple directories. """ settings = Settings.load_from_dict(dict(environmentdefs={'any': ['host1']}, roledefs={'role1': ['host1'], 'role2': ['host1']}, componentdefs={'role1': ['comp1', 'comp2']})) subdirs = ['roles', 'components'] template_dirs = map(lambda d: join(dirname(__file__), 'templates/multidir', d), subdirs) data_dirs = map(lambda d: join(dirname(__file__), 'data/multidir', d), subdirs) with TempDir() as tmp_dir: for host_and_role in settings.for_env('any').all(): conffiles = ConfFiles(host_and_role, FileSystemEnvironmentLoader(*template_dirs), DataLoader(data_dirs)) conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt')) self.assertEquals('bar', tmp_dir.read('generated/host1/bar.txt')) self.assertEquals('baz', tmp_dir.read('generated/host1/baz.conf'))
class TestComponents(TestCase): """ Tests for component selection. """ def setUp(self): self.settings = Settings() def test_components_for_role(self): """ Test key matching within componentdefs. """ self.settings.environmentdefs = { "env": ["host1", "host2"], } self.settings.roledefs = { "role1": ["host1"], "role2": ["host2"], "role3": ["host2"], } # if there are no components, roles are treated like components eq_(["role3", "role2", "role1"], map(lambda c: c.name, self.settings.for_env("env").components())) self.settings.componentdefs = { "role1": ["comp1", "compgroup"], "role2": ["comp2", "comp3"], "compgroup": ["comp3", "comp4"] } eq_(["comp1", "comp3", "comp4"], map(lambda c: c.name, self.settings.for_env("env").with_roles("role1").components())) eq_(["comp2", "comp3"], map(lambda c: c.name, self.settings.for_env("env").with_roles("role2").components())) eq_(["role3"], map(lambda c: c.name, self.settings.for_env("env").with_roles("role3").components())) def test_componentdefs_cycle(self): """ Fail if componentdefs has cycles. """ self.settings.environmentdefs = { "env": ["host1"], } self.settings.roledefs = { "role1": ["host1"], } self.settings.componentdefs = { "role1": ["compgroup"], "compgroup": ["role1"], } with self.assertRaises(Exception): map(lambda c: c.name, self.settings.for_env("env").with_roles("role1").components()) def test_componentdefs_cycle2(self): """ Fail if componentdefs has cycles. """ self.settings.environmentdefs = { "env": ["host1"], } self.settings.roledefs = { "role1": ["host1"], } self.settings.componentdefs = { "role1": ["compgroup1"], "compgroup1": ["compgroup2"], "compgroup2": ["compgroup1"], } with self.assertRaises(Exception): map(lambda c: c.name, self.settings.for_env("env").with_roles("role1").components()) def test_multiple_paths(self): """ Fail if componentdefs has multiple paths to a leaf component. """ self.settings.environmentdefs = { "env": ["host1"], } self.settings.roledefs = { "role1": ["host1"], } self.settings.componentdefs = { "role1": ["comp1", "compgroup"], "compgroup": ["comp1"], } with self.assertRaises(Exception): map(lambda c: c.name, self.settings.for_env("env").with_roles("role1").components())
class TestListing(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['host'], } self.settings.roledefs = { 'role': ['host'], } def test_get_conf_files(self): """ Generating conf files finds all templates in the package and generates their names properly. """ conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar'}) eq_(2, len(conffiles.conffiles)) names = map(lambda x: x.name, conffiles.conffiles) self.assertTrue('foo.txt' in names) self.assertTrue('bar/bar.txt' in names) def test_undefined(self): """ Raise an error if a template value is undefined. """ with self.assertRaises(UndefinedError): ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {}) def test_filter_func(self): """ Passing a filter_func limits which templates are generated. """ with Options(filter_func=lambda file_name: file_name != 'foo.txt'): conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar'}) eq_(1, len(conffiles.conffiles)) names = map(lambda x: x.name, conffiles.conffiles) self.assertTrue('bar/bar.txt' in names) def test_same_component_for_different_roles(self): """ Two roles using the same component on the same host. """ self.settings.roledefs = { 'role1': ['host'], 'role2': ['host'], } self.settings.componentdefs = { 'role1': ['comp'], 'role2': ['comp'], } environment_loader = PackageEnvironmentLoader('confab.tests', 'templates/validate') # use data that will create different conffiles for the same # component in the two roles. conffiles = ConfFiles( self.settings.for_env('any').with_roles('role1').all().next(), environment_loader, lambda comp: {'foo': 'role1'}) eq_(1, len(conffiles.conffiles)) ok_('role1.txt' == conffiles.conffiles[0].name) conffiles = ConfFiles( self.settings.for_env('any').with_roles('role2').all().next(), environment_loader, lambda comp: {'foo': 'role2'}) eq_(1, len(conffiles.conffiles)) ok_('role2.txt' == conffiles.conffiles[0].name) def test_warn_no_conffiles(self): """ Warn when a role doesn't have any configuration files. """ with Options(filter_func=lambda _: False): with catch_warnings(record=True) as captured_warnings: conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {}) eq_(0, len(conffiles.conffiles)) eq_(1, len(captured_warnings))
class TestHooks(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "environment": ["host"], } self.settings.roledefs = { "role": ["host"], } self.settings.componentdefs = { "role": ["component"], } self.component = self.settings.for_env("environment").components().next() def test_add_remove_hook(self): """ Load additional configuration data via hook. * Test adding new hook * Test removing hook """ def test_hook(host): return {'data': {'num_cores': 4}} testhook = Hook(test_hook) local_hooks = HookRegistry() local_hooks.add_hook('host', testhook) ok_(testhook in local_hooks._hooks['host']) local_hooks.remove_hook('host', testhook) ok_(testhook not in local_hooks._hooks['host']) def test_hook_override_data(self): """ Test that data loaded via hook overwrites data loaded via config. """ def test_hook(role): return {'data': {'role': 'role2'}} with ScopeAndHooks(('host', Hook(test_hook))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role2', 'environment': 'environment', 'host': 'host'}) def test_data_override_hook(self): """ Test that data loaded via hook will be overwritten by data loaded later via config. """ def test_hook(role): return {'data': {'environment': 'environment2'}} with ScopeAndHooks(('role', Hook(test_hook))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host'}) def test_hook_load_order(self): """ Test that hooks overwrite each other based on order they are defined. """ def test_hook1(host): return {'data': {'host': 'host1'}} def test_hook2(host): return {'data': {'host': 'host2'}} with ScopeAndHooks(('host', Hook(test_hook1)), ('host', Hook(test_hook2))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host2'}) def test_filter_func(self): """ Test that hooks only run if the filter_func returns true """ def test_hook1(host): return {'data': {'host': 'host1'}} def test_hook2(host): return {'data': {'host': 'host2'}} with ScopeAndHooks(('host', Hook(test_hook1)), ('host', Hook(test_hook2, lambda componentdef: False))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host1'})
class TestGenerate(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['localhost'], } self.settings.roledefs = { 'role': ['localhost'], } def test_generate(self): """ Generated templates have the correct values. """ conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar', 'foo': 'foo'}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) # foo.txt is populated with 'foo' eq_('foo', tmp_dir.read('generated/localhost/foo.txt')) # bar.txt is populated with 'bar' and path is substituted eq_('bar', tmp_dir.read('generated/localhost/bar/bar.txt')) def test_unicode(self): """ Generated templates with unicode data. """ conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar', 'foo': u'\xc5\xae'}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) # foo.txt is populated with u'\xc5\xae' eq_(u'\xc5\xae', tmp_dir.read('generated/localhost/foo.txt')) # bar.txt is populated with 'bar' and path is substituted eq_('bar', tmp_dir.read('generated/localhost/bar/bar.txt')) def test_undefined(self): """ An exception is raised if a template value is undefined. """ conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar'}) with TempDir() as tmp_dir: with self.assertRaises(UndefinedError): conffiles.generate(tmp_dir.path) def test_should_render(self): """ Passing a mime_type_func controls whether templates are rendered. """ with Options(should_render=lambda mime_type: False): conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar', 'foo': 'foo'}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) # templates not rendered (though paths are) eq_('{{foo}}', tmp_dir.read('generated/localhost/foo.txt')) eq_('{{bar}}', tmp_dir.read('generated/localhost/bar/bar.txt')) def test_binary_template(self): """ Confab copies binary config files verbatim to generated folder. """ templates_dir = join(dirname(__file__), 'templates/binary') conffiles = ConfFiles(self.settings.for_env('any').all().next(), FileSystemEnvironmentLoader(templates_dir), lambda _: {}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) ok_(filecmp.cmp(join(tmp_dir.path, 'generated/localhost/test.png'), join(templates_dir, 'role/test.png'))) def test_components(self): """ Generate templates for roles with components. """ self.settings.environmentdefs = { 'any': ['host1'], } self.settings.roledefs = { 'role1': ['host1'], 'role2': ['host1'], 'role3': ['host1'], } self.settings.componentdefs = { 'role1': ['comp1'], 'role2': ['compgroup'], 'compgroup': ['comp2', 'comp3'], } with TempDir() as tmp_dir: for host_and_role in self.settings.for_env('any').all(): conffiles = ConfFiles(host_and_role, PackageEnvironmentLoader('confab.tests', 'templates/components'), DataLoader(join(dirname(__file__), 'data/components'))) conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt')) self.assertEquals('bar', tmp_dir.read('generated/host1/bar/bar.txt')) self.assertEquals('baz', tmp_dir.read('generated/host1/baz.conf')) def test_multiple_directories(self): """ Generate templates for roles with components where templates and data are in multiple directories. """ settings = Settings.load_from_dict(dict(environmentdefs={'any': ['host1']}, roledefs={'role1': ['host1'], 'role2': ['host1']}, componentdefs={'role1': ['comp1', 'comp2']})) subdirs = ['roles', 'components'] template_dirs = map(lambda d: join(dirname(__file__), 'templates/multidir', d), subdirs) data_dirs = map(lambda d: join(dirname(__file__), 'data/multidir', d), subdirs) with TempDir() as tmp_dir: for host_and_role in settings.for_env('any').all(): conffiles = ConfFiles(host_and_role, FileSystemEnvironmentLoader(*template_dirs), DataLoader(data_dirs)) conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt')) self.assertEquals('bar', tmp_dir.read('generated/host1/bar.txt')) self.assertEquals('baz', tmp_dir.read('generated/host1/baz.conf'))
class TestListing(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['host'], } self.settings.roledefs = { 'role': ['host'], } def test_get_conf_files(self): """ Generating conf files finds all templates in the package and generates their names properly. """ conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar'}) eq_(2, len(conffiles.conffiles)) names = map(lambda x: x.name, conffiles.conffiles) self.assertTrue('foo.txt' in names) self.assertTrue('bar/bar.txt' in names) def test_undefined(self): """ Raise an error if a template value is undefined. """ with self.assertRaises(UndefinedError): ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {}) def test_filter_func(self): """ Passing a filter_func limits which templates are generated. """ with Options(filter_func=lambda file_name: file_name != 'foo.txt'): conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar'}) eq_(1, len(conffiles.conffiles)) names = map(lambda x: x.name, conffiles.conffiles) self.assertTrue('bar/bar.txt' in names) def test_same_component_for_different_roles(self): """ Two roles using the same component on the same host. """ self.settings.roledefs = { 'role1': ['host'], 'role2': ['host'], } self.settings.componentdefs = { 'role1': ['comp'], 'role2': ['comp'], } environment_loader = PackageEnvironmentLoader('confab.tests', 'templates/validate') # use data that will create different conffiles for the same # component in the two roles. conffiles = ConfFiles(self.settings.for_env('any').with_roles('role1').all().next(), environment_loader, lambda comp: {'foo': 'role1'}) eq_(1, len(conffiles.conffiles)) ok_('role1.txt' == conffiles.conffiles[0].name) conffiles = ConfFiles(self.settings.for_env('any').with_roles('role2').all().next(), environment_loader, lambda comp: {'foo': 'role2'}) eq_(1, len(conffiles.conffiles)) ok_('role2.txt' == conffiles.conffiles[0].name) def test_warn_no_conffiles(self): """ Warn when a role doesn't have any configuration files. """ with Options(filter_func=lambda _: False): with catch_warnings(record=True) as captured_warnings: conffiles = ConfFiles(self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {}) eq_(0, len(conffiles.conffiles)) eq_(1, len(captured_warnings))
class TestHostAndRole(TestCase): """ Test host and role resolution. """ def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "test1": ["host1", "host2", "host3"], "test2": ["host2", "host3"], "test3": [] } self.settings.roledefs = { "role1": ["host1", "host2", "host3"], "role2": ["host2", "host3"], "role3": [], } def test_resolve_only_environment(self): """ Specifying only an environment, returns all of its hosts and roles. """ eq_({"host1": ["role1"], "host2": ["role1", "role2"], "host3": ["role1", "role2"]}, self.settings.for_env("test1").host_roles) def test_resolve_only_empty_environment(self): """ Specifying an empty environment generates a warning. """ with catch_warnings(record=True) as caught_warnings: eq_({}, self.settings.for_env("test3").host_roles) eq_(1, len(caught_warnings)) def test_resolve_host_without_roles(self): """ Explicit hosts return all of their roles. """ eq_({"host1": ["role1"]}, self.settings.for_env("test1").with_hosts("host1").host_roles) eq_({"host2": ["role1", "role2"]}, self.settings.for_env("test1").with_hosts("host2").host_roles) def test_resolve_hosts_without_roles(self): """ Explicit host list returns all hosts and all of their roles. """ eq_({"host1": ["role1"], "host2": ["role1", "role2"]}, self.settings.for_env("test1").with_hosts("host1", "host2").host_roles) def test_resolve_role_without_hosts(self): """ Explicit role returns all hosts in environment with that role. """ eq_({"host1": ["role1"], "host2": ["role1"], "host3": ["role1"]}, self.settings.for_env("test1").with_roles("role1").host_roles) def test_resolve_roles_without_hosts(self): """ Explicit role list returns all hosts in environment with any of those roles. """ eq_({"host1": ["role1"], "host2": ["role1", "role2"], "host3": ["role1", "role2"]}, self.settings.for_env("test1").with_roles("role1", "role2").host_roles) eq_({"host2": ["role1", "role2"], "host3": ["role1", "role2"]}, self.settings.for_env("test2").with_roles("role1", "role2").host_roles) def test_resolve_unknown_role_without_hosts(self): """ Explicit role matching no hosts returns empty mapping. """ eq_({}, self.settings.for_env("test1").with_roles("role3").host_roles) def test_resolve_host_with_role(self): """ Explicit host and role mappings return host and role. """ eq_({"host1": ["role1"]}, self.settings.for_env("test1").with_hosts("host1").with_roles("role1").host_roles) def test_resolve_hosts_with_role(self): """ Explicit hosts and role mappings return role for all hosts. """ eq_({"host1": ["role1"], "host2": ["role1"]}, self.settings.for_env("test1").with_hosts("host1", "host2").with_roles("role1").host_roles) def test_resolve_host_with_roles(self): """ Explicit host and roles mappings return all roles applicable for host. """ eq_({"host1": ["role1"]}, self.settings.for_env("test1").with_hosts("host1").with_roles("role1", "role2").host_roles) eq_({"host2": ["role1", "role2"]}, self.settings.for_env("test1").with_hosts("host2").with_roles("role1", "role2").host_roles) def test_resolve_hosts_with_roles(self): """ Explicit hosts and roles mappings return all roles applicable for hosts. """ eq_({"host1": ["role1"], "host2": ["role1", "role2"]}, self.settings.for_env("test1").with_hosts("host1", "host2").with_roles("role1", "role2").host_roles)
class TestHooks(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "environment": ["host"], } self.settings.roledefs = { "role": ["host"], } self.settings.componentdefs = { "role": ["component"], } self.component = self.settings.for_env( "environment").components().next() def test_add_remove_hook(self): """ Load additional configuration data via hook. * Test adding new hook * Test removing hook """ def test_hook(host): return {'data': {'num_cores': 4}} testhook = Hook(test_hook) local_hooks = HookRegistry() local_hooks.add_hook('host', testhook) ok_(testhook in local_hooks._hooks['host']) local_hooks.remove_hook('host', testhook) ok_(testhook not in local_hooks._hooks['host']) def test_hook_override_data(self): """ Test that data loaded via hook overwrites data loaded via config. """ def test_hook(role): return {'data': {'role': 'role2'}} with ScopeAndHooks(('host', Hook(test_hook))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_( loader(self.component)['data'], { 'default': 'default', 'component': 'component', 'role': 'role2', 'environment': 'environment', 'host': 'host' }) def test_data_override_hook(self): """ Test that data loaded via hook will be overwritten by data loaded later via config. """ def test_hook(role): return {'data': {'environment': 'environment2'}} with ScopeAndHooks(('role', Hook(test_hook))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_( loader(self.component)['data'], { 'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host' }) def test_hook_load_order(self): """ Test that hooks overwrite each other based on order they are defined. """ def test_hook1(host): return {'data': {'host': 'host1'}} def test_hook2(host): return {'data': {'host': 'host2'}} with ScopeAndHooks(('host', Hook(test_hook1)), ('host', Hook(test_hook2))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_( loader(self.component)['data'], { 'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host2' }) def test_filter_func(self): """ Test that hooks only run if the filter_func returns true """ def test_hook1(host): return {'data': {'host': 'host1'}} def test_hook2(host): return {'data': {'host': 'host2'}} with ScopeAndHooks( ('host', Hook(test_hook1)), ('host', Hook(test_hook2, lambda componentdef: False))): loader = DataLoader(join(dirname(__file__), 'data/order')) eq_( loader(self.component)['data'], { 'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host1' })
class TestData(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { "environment": ["host"], } self.settings.roledefs = { "role": ["host"], } self.settings.componentdefs = { "role": ["component"], } self.component = self.settings.for_env("environment").components().next() def test_data_templates(self): """ Data modules can be templates. This test loads the "bar.py_tmpl" file as a Jinja template and converts it into a Python module. In the process of resolving this template, the foo.py module is included (defining "foo") and the "baz.py" macro is evaluated (defining "baz"). """ data = import_configuration('bar', join(dirname(__file__), 'data/templates')) eq_(data, {'foo': 'foo', 'bar': 'bar', 'baz': {'n': 42}}) def test_load_order(self): """ Data modules are always loaded in the same order: default, component, role, environment, then host. """ loader = DataLoader(join(dirname(__file__), 'data/order')) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host'}) def test_custom_data_modules_load_order(self): """ Defining custom data modules does not affect load order. """ loader = DataLoader(join(dirname(__file__), 'data/order'), data_modules=reversed(DataLoader.ALL)) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host'}) def test_custom_data_modules_selection(self): """ Defining custom data modules may select specific modules to load. """ loader = DataLoader(join(dirname(__file__), 'data/order'), data_modules=['component', 'host']) eq_(loader(self.component)['data'], {'role': 'component', 'component': 'component', 'environment': 'component', 'host': 'host'}) def test_nested_configuration_files(self): ''' Load configuration data from nested folder structure. ''' loader = DataLoader(join(dirname(__file__), 'data/nested')) eq_(loader(self.component)['data'], {'default': 'default', 'component': 'component', 'role': 'role', 'environment': 'environment', 'host': 'host'}) def test_missing_data_module(self): """ If a data module does not exist, it is ignored. """ loader = DataLoader(join(dirname(__file__), 'data/missing'), data_modules=['component']) # no module named component eq_(None, loader(self.component).get('data')) def test_broken_data_module(self): """ If a data module has a broken import, an import error is raised. """ loader = DataLoader(join(dirname(__file__), 'data/broken'), data_modules=['component']) with self.assertRaises(ImportError): loader(self.component).get('data') def test_broken_data_template(self): """ If a data template has a broken import, an import error is raised. """ loader = DataLoader(join(dirname(__file__), 'data/broken'), data_modules=['host']) with self.assertRaises(ImportError): loader(self.component).get('data') def test_data_callables(self): """ Data callables are applied when merging. """ loader = DataLoader(join(dirname(__file__), 'data/callables')) data = loader(self.component) eq_(data['appended'], ['default', 'environment']) eq_(data['prepended'], ['environment', 'default']) eq_(data['unique'], ['default']) eq_(data['rotated'], ['pivot', 'itemB', 'itemA'])
class TestGenerate(TestCase): def setUp(self): self.settings = Settings() self.settings.environmentdefs = { 'any': ['localhost'], } self.settings.roledefs = { 'role': ['localhost'], } def test_generate(self): """ Generated templates have the correct values. """ conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: { 'bar': 'bar', 'foo': 'foo' }) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) # foo.txt is populated with 'foo' eq_('foo', tmp_dir.read('generated/localhost/foo.txt')) # bar.txt is populated with 'bar' and path is substituted eq_('bar', tmp_dir.read('generated/localhost/bar/bar.txt')) def test_unicode(self): """ Generated templates with unicode data. """ conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: { 'bar': 'bar', 'foo': u'\xc5\xae' }) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) # foo.txt is populated with u'\xc5\xae' eq_(u'\xc5\xae', tmp_dir.read('generated/localhost/foo.txt')) # bar.txt is populated with 'bar' and path is substituted eq_('bar', tmp_dir.read('generated/localhost/bar/bar.txt')) def test_undefined(self): """ An exception is raised if a template value is undefined. """ conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: {'bar': 'bar'}) with TempDir() as tmp_dir: with self.assertRaises(UndefinedError): conffiles.generate(tmp_dir.path) def test_should_render(self): """ Passing a mime_type_func controls whether templates are rendered. """ with Options(should_render=lambda mime_type: False): conffiles = ConfFiles( self.settings.for_env('any').all().next(), PackageEnvironmentLoader('confab.tests', 'templates/default'), lambda _: { 'bar': 'bar', 'foo': 'foo' }) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) # templates not rendered (though paths are) eq_('{{foo}}', tmp_dir.read('generated/localhost/foo.txt')) eq_('{{bar}}', tmp_dir.read('generated/localhost/bar/bar.txt')) def test_binary_template(self): """ Confab copies binary config files verbatim to generated folder. """ templates_dir = join(dirname(__file__), 'templates/binary') conffiles = ConfFiles( self.settings.for_env('any').all().next(), FileSystemEnvironmentLoader(templates_dir), lambda _: {}) with TempDir() as tmp_dir: conffiles.generate(tmp_dir.path) ok_( filecmp.cmp(join(tmp_dir.path, 'generated/localhost/test.png'), join(templates_dir, 'role/test.png'))) def test_components(self): """ Generate templates for roles with components. """ self.settings.environmentdefs = { 'any': ['host1'], } self.settings.roledefs = { 'role1': ['host1'], 'role2': ['host1'], 'role3': ['host1'], } self.settings.componentdefs = { 'role1': ['comp1'], 'role2': ['compgroup'], 'compgroup': ['comp2', 'comp3'], } with TempDir() as tmp_dir: for host_and_role in self.settings.for_env('any').all(): conffiles = ConfFiles( host_and_role, PackageEnvironmentLoader('confab.tests', 'templates/components'), DataLoader(join(dirname(__file__), 'data/components'))) conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt')) self.assertEquals('bar', tmp_dir.read('generated/host1/bar/bar.txt')) self.assertEquals('baz', tmp_dir.read('generated/host1/baz.conf')) def test_multiple_directories(self): """ Generate templates for roles with components where templates and data are in multiple directories. """ settings = Settings.load_from_dict( dict(environmentdefs={'any': ['host1']}, roledefs={ 'role1': ['host1'], 'role2': ['host1'] }, componentdefs={'role1': ['comp1', 'comp2']})) subdirs = ['roles', 'components'] template_dirs = map( lambda d: join(dirname(__file__), 'templates/multidir', d), subdirs) data_dirs = map(lambda d: join(dirname(__file__), 'data/multidir', d), subdirs) with TempDir() as tmp_dir: for host_and_role in settings.for_env('any').all(): conffiles = ConfFiles( host_and_role, FileSystemEnvironmentLoader(*template_dirs), DataLoader(data_dirs)) conffiles.generate(tmp_dir.path) self.assertEquals('foo', tmp_dir.read('generated/host1/foo.txt')) self.assertEquals('bar', tmp_dir.read('generated/host1/bar.txt')) self.assertEquals('baz', tmp_dir.read('generated/host1/baz.conf'))
def setUp(self): self.dir_name = join(dirname(__file__), "data/default") self.settings = Settings()
class TestEnvironment(TestCase): """ Tests for environment selection. """ def setUp(self): self.dir_name = join(dirname(__file__), "data/default") self.settings = Settings() def test_no_environments(self): """ Fail if there is no environments defined. """ with self.assertRaises(Exception): self.settings.for_env("local") def test_unknown_environments(self): """ Fail if an environment is unknown. """ self.settings.environmentdefs = { "foo": ["bar"], } self.settings.roledefs = { "baz": ["bar"], } with self.assertRaises(Exception): self.settings.for_env("local") def test_environment(self): """ Lookup by environments works. """ self.settings.environmentdefs = { "foo": ["bar", "baz"], "bar": ["foo"], } self.settings.roledefs = { "role": ["foo", "bar", "baz"], } eq_({"bar": ["role"], "baz": ["role"]}, self.settings.for_env("foo").host_roles) eq_({"foo": ["role"]}, self.settings.for_env("bar").host_roles) def test_host_without_roles(self): """ Fail if an environment host has no roles """ self.settings.environmentdefs = { "foo": ["bar"], } self.settings.roledefs = { } with self.assertRaises(Exception): self.self.settings.for_env("foo") def test_select_hosts(self): """ Selecting specific hosts works. """ self.settings.environmentdefs = { "foo": ["bar", "baz"], } self.settings.roledefs = { "role": ["bar", "baz"], } eq_({"bar": ["role"]}, self.settings.for_env("foo").with_hosts("bar").host_roles) eq_({"bar": ["role"], "baz": ["role"]}, self.settings.for_env("foo").with_hosts("bar", "baz").host_roles) def test_select_unknown_hosts(self): """ Fail if a selected host is not known. """ self.settings.environmentdefs = { "foo": ["bar", "baz"], "other": ["blah"] } self.settings.roledefs = { "role": ["bar", "baz", "blah"], } with self.assertRaises(Exception): self.self.settings.for_env("foo").with_hosts("bad") with self.assertRaises(Exception): self.self.settings.for_env("foo").with_hosts("blah") def test_select_role(self): """ Selecting specific roles works. """ self.settings.environmentdefs = { "foo": ["bar", "baz"], } self.settings.roledefs = { "role1": ["bar", "baz"], "role2": ["bar"], } eq_({"bar": ["role1"], "baz": ["role1"]}, self.settings.for_env("foo").with_roles("role1").host_roles) eq_({"bar": ["role2"]}, self.settings.for_env("foo").with_roles("role2").host_roles) def test_select_unknown_role(self): """ Fail if a selected role is not known. """ self.settings.environmentdefs = { "foo": ["bar", "baz"], } self.settings.roledefs = { "role1": ["bar"], "role2": ["baz"], } with self.assertRaises(Exception): self.self.settings.for_env("foo").with_roles("bad") def test_select_roles_and_hosts(self): """ Test key matching within roledefs. """ self.settings.environmentdefs = { "foo": ["bar", "baz"], } self.settings.roledefs = { "role1": ["bar"], "role2": ["baz"], } eq_({"baz": ["role2"]}, self.settings.for_env("foo").with_hosts("bar", "baz").with_roles("role2").host_roles)
def setUp(self): self.settings = Settings()