def setUp(self): self.nulecule_base = Nulecule_Base(dryrun=True) self.tmpdir = tempfile.mkdtemp(prefix="atomicapp-test", dir="/tmp") self.artifact_dir = os.path.dirname( __file__) + '/docker_artifact_test/' self.plugin = Plugin() self.plugin.load_plugins()
class TestNuleculeBase(unittest.TestCase): def setUp(self): self.nulecule_base = Nulecule_Base(dryrun = True) self.tmpdir = tempfile.mkdtemp(prefix = "atomicapp-test", dir = "/tmp") self.plugin = Plugin() self.plugin.load_plugins() def tearDown(self): pass def create_temp_file(self): return tempfile.mktemp(prefix = "test-config", dir = self.tmpdir) def prepare_provider(self, data): self.nulecule_base.loadAnswers(data) provider_class = self.plugin.getProvider(self.nulecule_base.provider) config = self.nulecule_base.getValues(skip_asking=True) provider = provider_class(config, self.tmpdir, dryrun = False) return provider @mock.patch.object(KubernetesProvider, '_call', mock_provider_call) def test_provider_config_exist(self): provider_config_path = self.create_temp_file() mock_content = "%s_%s" % (MOCK_CONTENT, "_unchanged") with open(provider_config_path, "w") as fp: fp.write(mock_content) data = {'general': {'namespace': 'testing', 'provider': 'kubernetes', 'providerconfig': provider_config_path}} provider = self.prepare_provider(data) self.assertEqual(provider.config_file, provider_config_path) provider.checkConfigFile() with open(provider_config_path, "r") as fp: self.assertEqual(fp.read(), mock_content) @mock.patch("kubernetes.KubernetesProvider._call", mock_provider_call) def test_provider_check_config_generation(self): path = self.create_temp_file() data = {'general': {'namespace': 'testing', 'provider': 'kubernetes', 'providerconfig': path}} provider = self.prepare_provider(data) provider.checkConfigFile() with open(path, "r") as fp: self.assertEqual(fp.read(), MOCK_CONTENT) def test_provider_check_config_fail(self): path = self.create_temp_file() data = {'general': {'namespace': 'testing', 'provider': 'openshift'}} provider = self.prepare_provider(data) self.assertRaises(ProviderFailedException, provider.checkConfigFile)
class TestDockerProviderBase(unittest.TestCase): # Create a temporary directory for our setup as well as load the required providers def setUp(self): self.nulecule_base = Nulecule_Base(dryrun=True) self.tmpdir = tempfile.mkdtemp(prefix="atomicapp-test", dir="/tmp") self.artifact_dir = os.path.dirname( __file__) + '/docker_artifact_test/' self.plugin = Plugin() self.plugin.load_plugins() def tearDown(self): pass # Lets prepare the docker provider with pre-loaded configuration def prepare_provider(self, data): self.nulecule_base.loadAnswers(data) provider_class = self.plugin.getProvider(self.nulecule_base.provider) config = self.nulecule_base.getValues(skip_asking=True) provider = provider_class(config, self.tmpdir, dryrun=True) return provider # Test deploying multiple artifacts within docker def test_multiple_artifact_load(self): data = {'general': {'namespace': 'test', 'provider': 'docker'}} provider = self.prepare_provider(data) provider.init() provider.artifacts = [ self.artifact_dir + 'hello-world-one', self.artifact_dir + 'hello-world-two', self.artifact_dir + 'hello-world-three' ] # Mock the effects of 'docker ps -a'. As if each deployment adds the container to the host mock_container_list = mock.Mock(side_effect=[ ["atomic_default_e9b9a7bfe8f9"], ["atomic_default_e9b9a7bfe8f9", "atomic_test_e9b9a7bfe8f9"], [ "atomic_default_e9b9a7bfe8f9", "atomic_test_e9b9a7bfe8f9", "atomic_test_e9b9a7bfe8f9" ] ]) with mock.patch("docker.DockerProvider._get_containers", mock_container_list): provider.deploy() # Patch in a general container list and make sure it fails if there is already a container with the same name @mock.patch("docker.DockerProvider._get_containers", mock_name_get_call) def test_namespace_name_check(self): data = {'general': {'namespace': 'default', 'provider': 'docker'}} provider = self.prepare_provider(data) provider.init() provider.artifacts = [self.artifact_dir + 'hello-world-one'] with pytest.raises(ProviderFailedException): provider.deploy()
def __init__(self, config, basepath, graph, provider, dryrun): self.plugin = Plugin() self.config = config self.basepath = basepath self.graph = graph self.dryrun = dryrun # We initialize the provider in order to gather provider-specific # information p = self.plugin.getProvider(provider) self.provider = p(config, basepath, dryrun) self.provider.init()
class TestDockerProviderBase(unittest.TestCase): # Create a temporary directory for our setup as well as load the required providers def setUp(self): self.nulecule_base = Nulecule_Base(dryrun = True) self.tmpdir = tempfile.mkdtemp(prefix = "atomicapp-test", dir = "/tmp") self.artifact_dir = os.path.dirname(__file__) + '/docker_artifact_test/' self.plugin = Plugin() self.plugin.load_plugins() def tearDown(self): pass # Lets prepare the docker provider with pre-loaded configuration def prepare_provider(self, data): self.nulecule_base.loadAnswers(data) provider_class = self.plugin.getProvider(self.nulecule_base.provider) config = self.nulecule_base.getValues(skip_asking=True) provider = provider_class(config, self.tmpdir, dryrun = True) return provider # Test deploying multiple artifacts within docker def test_multiple_artifact_load(self): data = {'general': {'namespace': 'test', 'provider': 'docker'}} provider = self.prepare_provider(data) provider.init() provider.artifacts = [ self.artifact_dir + 'hello-world-one', self.artifact_dir + 'hello-world-two', self.artifact_dir + 'hello-world-three' ] # Mock the effects of 'docker ps -a'. As if each deployment adds the container to the host mock_container_list = mock.Mock(side_effect = [ ["atomic_default_e9b9a7bfe8f9"], ["atomic_default_e9b9a7bfe8f9", "atomic_test_e9b9a7bfe8f9"], ["atomic_default_e9b9a7bfe8f9", "atomic_test_e9b9a7bfe8f9", "atomic_test_e9b9a7bfe8f9"] ]) with mock.patch("docker.DockerProvider._get_containers", mock_container_list): provider.deploy() # Patch in a general container list and make sure it fails if there is already a container with the same name @mock.patch("docker.DockerProvider._get_containers", mock_name_get_call) def test_namespace_name_check(self): data = {'general': {'namespace': 'default', 'provider': 'docker'}} provider = self.prepare_provider(data) provider.init() provider.artifacts = [self.artifact_dir + 'hello-world-one'] with pytest.raises(ProviderFailedException): provider.deploy()
def test_getProvider(self): """ Test if getProvider is returning appropriate classes to the corresponding keys. """ p = Plugin() docker_mock = mock.Mock() kubernetes_mock = mock.Mock() # keep some mock objects in place of the actual corresponding # classes, getProvider reads from `plugins` dict. p.plugins = { 'docker': docker_mock, 'kubernetes': kubernetes_mock, } self.assertEqual(p.getProvider('docker'), docker_mock) self.assertEqual(p.getProvider('kubernetes'), kubernetes_mock) # if non-existent key provided self.assertEqual(p.getProvider('some_random'), None)
class Requirements: """ Requirements will search what is currently being used under the requirements section of Nulecule and deploy in accordance to the graph variables as well as whether or not said requirement exists within the provider. The REQUIREMENTS_FUNCTIONS dictionary maps all current requirement names to the function names for each provider. For example, the persistentVolume requirement in Nulecule is mapped as the persistent_storage function within each provider. Requirements tries to be as modular as possible. """ def __init__(self, config, basepath, graph, provider, dryrun): self.plugin = Plugin() self.plugin.load_plugins() self.config = config self.basepath = basepath self.graph = graph self.dryrun = dryrun # We initialize the provider in order to gather provider-specific # information p = self.plugin.getProvider(provider) self.provider = p(config, basepath, dryrun) self.provider.init() def run(self): self._exec("run") def stop(self): self._exec("stop") # Find if the requirement does not exist within REQUIREMENT_FUNCTIONS def _find_requirement_function_name(self, key): logger.debug("Checking if %s matches any of %s" % (key, REQUIREMENT_FUNCTIONS)) if key in REQUIREMENT_FUNCTIONS.keys(): return REQUIREMENT_FUNCTIONS[key] raise RequirementFailedException("Requirement %s does not exist." % key) # We loop through the given requirements graph and # execute each passed requirement def _exec(self, action): for req in self.graph: key_name = req.keys()[0] requirement_function = self._find_requirement_function_name( key_name) # Check to see if the function exists in the provider, # if it does not: warn the user try: requirement = getattr(self.provider, requirement_function) except AttributeError: logger.warning( "Requirement %s does not exist within %s. Skipping." % (requirement_function, self.provider)) continue # Run the requirement function requirement(req[key_name], action)
class NuleculeBase(object): """ This is the base class for Nulecule and NuleculeComponent in atomicapp.nulecule.base. """ def __init__(self, basepath, params, namespace): self.plugin = Plugin() self.basepath = basepath self.params = params or [] self.namespace = namespace def load(self): pass def load_config(self, config, ask=False, skip_asking=False): """ Load config data. Sets the loaded config data to self.config. Args: config (dict): Initial config data ask (bool): When True, ask for values for a param from user even if the param has a default value skip_asking (bool): When True, skip asking for values for params with missing values and set the value as None Returns: None """ self.config = config for param in self.params: value = config.get(param[NAME_KEY], scope=self.namespace, ignore_sources=["defaults"]) if value is None: if ask or (not skip_asking and param.get(DEFAULTNAME_KEY) is None): cockpit_logger.info("%s is missing in answers.conf." % param[NAME_KEY]) value = config.get(param[NAME_KEY], scope=self.namespace) or Utils.askFor( param[NAME_KEY], param, self.namespace ) else: value = param.get(DEFAULTNAME_KEY) config.set(param[NAME_KEY], value, source="runtime", scope=self.namespace) def get_provider(self, provider_key=None, dry=False): """ Get provider key and provider instance. Args: provider_key (str or None): Name of provider dry (bool): Do not make change to the host system while True Returns: tuple: (provider key, provider instance) """ # If provider_key isn't provided via CLI, let's grab it the configuration if provider_key is None: provider_key = self.config.get("provider", scope=GLOBAL_CONF) provider_class = self.plugin.getProvider(provider_key) if provider_class is None: raise NuleculeException( "Invalid Provider - '{}', provided in " "answers.conf (choose from {})".format(provider_key, ", ".join(PROVIDERS)) ) return provider_key, provider_class(self.config.context(), self.basepath, dry) def run(self, provider_key=None, dry=False): raise NotImplementedError def stop(self, provider): raise NotImplementedError def fetch(self): raise NotImplementedError def uninstall(self): raise NotImplementedError
# -*- coding: utf-8 -*- from atomicapp.constants import (GLOBAL_CONF, DEFAULT_PROVIDER, DEFAULT_ANSWERS) from atomicapp.utils import Utils from atomicapp.plugin import Plugin plugin = Plugin() plugin.load_plugins() class NuleculeBase(object): """ This is the base class for Nulecule and NuleculeComponent in atomicapp.nulecule.base. """ def __init__(self, basepath, params, namespace): self.basepath = basepath self.params = params or [] self.namespace = namespace def load(self): pass def load_config(self, config=None, ask=False, skip_asking=False): """ Load config data. Sets the loaded config data to self.config. Args: config (dict): Initial config data ask (bool): When True, ask for values for a param from user even if the param has a default value
class Requirements: """ Requirements will search what is currently being used under the requirements section of Nulecule and deploy in accordance to the graph variables as well as whether or not said requirement exists within the provider. The REQUIREMENTS_FUNCTIONS dictionary maps all current requirement names to the function names for each provider. For example, the persistentVolume requirement in Nulecule is mapped as the persistent_storage function within each provider. Requirements tries to be as modular as possible. """ def __init__(self, config, basepath, graph, provider, dryrun): self.plugin = Plugin() self.config = config self.basepath = basepath self.graph = graph self.dryrun = dryrun # We initialize the provider in order to gather provider-specific # information p = self.plugin.getProvider(provider) self.provider = p(config, basepath, dryrun) self.provider.init() def run(self): self._exec("run") def stop(self): self._exec("stop") # Find if the requirement does not exist within REQUIREMENT_FUNCTIONS def _find_requirement_function_name(self, key): logger.debug("Checking if %s matches any of %s" % (key, REQUIREMENT_FUNCTIONS)) if key in REQUIREMENT_FUNCTIONS.keys(): return REQUIREMENT_FUNCTIONS[key] raise RequirementFailedException("Requirement %s does not exist." % key) # We loop through the given requirements graph and # execute each passed requirement def _exec(self, action): for req in self.graph: key_name = req.keys()[0] requirement_function = self._find_requirement_function_name(key_name) # Check to see if the function exists in the provider, # if it does not: warn the user try: requirement = getattr(self.provider, requirement_function) except AttributeError: logger.warning( "Requirement %s does not exist within %s. Skipping." % (requirement_function, self.provider)) continue # Run the requirement function requirement(req[key_name], action)
class NuleculeBase(object): """ This is the base class for Nulecule and NuleculeComponent in atomicapp.nulecule.base. """ def __init__(self, basepath, params, namespace): self.plugin = Plugin() self.basepath = basepath self.params = params or [] self.namespace = namespace def load(self): pass def load_config(self, config, ask=False, skip_asking=False): """ Load config data. Sets the loaded config data to self.config. Args: config (dict): Initial config data ask (bool): When True, ask for values for a param from user even if the param has a default value skip_asking (bool): When True, skip asking for values for params with missing values and set the value as None Returns: None """ for param in self.params: value = config.get(self.namespace, {}).get(param[NAME_KEY]) or config.get(GLOBAL_CONF, {}).get( param[NAME_KEY] ) if value is None and (ask or (not skip_asking and param.get(DEFAULTNAME_KEY) is None)): cockpit_logger.info("%s is missing in answers.conf." % param[NAME_KEY]) value = Utils.askFor(param[NAME_KEY], param, self.namespace) elif value is None: value = param.get(DEFAULTNAME_KEY) if config.get(self.namespace) is None: config[self.namespace] = {} config[self.namespace][param[NAME_KEY]] = value self.config = config def merge_config(self, to_config, from_config): """ Merge values from from_config to to_config. If value for a key in a group in to_config is missing, then only set it's value from corresponding key in the same group in from_config. Args: to_config (dict): Dictionary to merge config into from_config (dict): Dictionary to merge config from Returns: None """ for group, group_vars in from_config.items(): to_config[group] = to_config.get(group) or {} for key, value in (group_vars or {}).items(): if to_config[group].get(key) is None: to_config[group][key] = value def get_context(self): """ Get context data from config data for rendering an artifact. """ context = {} context.update(self.config.get(GLOBAL_CONF) or {}) context.update(self.config.get(self.namespace) or {}) return context def get_provider(self, provider_key=None, dry=False): """ Get provider key and provider instance. Args: provider_key (str or None): Name of provider dry (bool): Do not make change to the host system while True Returns: tuple: (provider key, provider instance) """ # If provider_key isn't provided via CLI, let's grab it the configuration if provider_key is None: provider_key = self.config.get(GLOBAL_CONF)[PROVIDER_KEY] provider_class = self.plugin.getProvider(provider_key) if provider_class is None: raise NuleculeException( "Invalid Provider - '{}', provided in " "answers.conf (choose from {})".format(provider_key, ", ".join(PROVIDERS)) ) return provider_key, provider_class(self.get_context(), self.basepath, dry) def run(self, provider_key=None, dry=False): raise NotImplementedError def stop(self, provider): raise NotImplementedError def fetch(self): raise NotImplementedError def uninstall(self): raise NotImplementedError
def __init__(self, basepath, params, namespace): self.plugin = Plugin() self.basepath = basepath self.params = params or [] self.namespace = namespace
def __init__(self, basepath, params, namespace): self.plugin = Plugin() self.plugin.load_plugins() self.basepath = basepath self.params = params or [] self.namespace = namespace
def setUp(self): self.nulecule_base = Nulecule_Base(dryrun = True) self.tmpdir = tempfile.mkdtemp(prefix = "atomicapp-test", dir = "/tmp") self.artifact_dir = os.path.dirname(__file__) + '/docker_artifact_test/' self.plugin = Plugin() self.plugin.load_plugins()
class TestKubernetesProviderBase(unittest.TestCase): def setUp(self): self.nulecule_base = Nulecule_Base(dryrun=True) self.tmpdir = tempfile.mkdtemp(prefix="atomicapp-test", dir="/tmp") self.plugin = Plugin() self.plugin.load_plugins() def tearDown(self): pass def create_temp_file(self): return tempfile.mktemp(prefix="test-config", dir=self.tmpdir) def prepare_provider(self, data): self.nulecule_base.loadAnswers(data) provider_class = self.plugin.getProvider(self.nulecule_base.provider) config = self.nulecule_base.getValues(skip_asking=True) provider = provider_class(config, self.tmpdir, dryrun=False) return provider @mock.patch.object(KubernetesProvider, '_call', mock_provider_call) def test_provider_config_exist(self): provider_config_path = self.create_temp_file() mock_content = "%s_%s" % (MOCK_CONTENT, "_unchanged") with open(provider_config_path, "w") as fp: fp.write(mock_content) data = { 'general': { 'namespace': 'testing', 'provider': 'kubernetes', 'providerconfig': provider_config_path } } provider = self.prepare_provider(data) self.assertEqual(provider.config_file, provider_config_path) provider.checkConfigFile() with open(provider_config_path, "r") as fp: self.assertEqual(fp.read(), mock_content) @mock.patch("kubernetes.KubernetesProvider._call", mock_provider_call) def test_provider_check_config_generation(self): path = self.create_temp_file() data = { 'general': { 'namespace': 'testing', 'provider': 'kubernetes', 'providerconfig': path } } provider = self.prepare_provider(data) provider.checkConfigFile() with open(path, "r") as fp: self.assertEqual(fp.read(), MOCK_CONTENT) def test_provider_check_config_fail(self): path = self.create_temp_file() data = {'general': {'namespace': 'testing', 'provider': 'openshift'}} provider = self.prepare_provider(data) self.assertRaises(ProviderFailedException, provider.checkConfigFile)
class NuleculeBase(object): """ This is the base class for Nulecule and NuleculeComponent in atomicapp.nulecule.base. """ def __init__(self, basepath, params, namespace): self.plugin = Plugin() self.basepath = basepath self.params = params or [] self.namespace = namespace def load(self): pass def load_config(self, config, ask=False, skip_asking=False): """ Load config data. Sets the loaded config data to self.config. Args: config (dict): Initial config data ask (bool): When True, ask for values for a param from user even if the param has a default value skip_asking (bool): When True, skip asking for values for params with missing values and set the value as None Returns: None """ self.config = config for param in self.params: value = config.get(param[NAME_KEY], scope=self.namespace, ignore_sources=['defaults']) if value is None: if ask or (not skip_asking and param.get(DEFAULTNAME_KEY) is None): cockpit_logger.info( "%s is missing in answers.conf." % param[NAME_KEY]) value = config.get(param[NAME_KEY], scope=self.namespace) \ or Utils.askFor(param[NAME_KEY], param, self.namespace) else: value = param.get(DEFAULTNAME_KEY) config.set(param[NAME_KEY], value, source='runtime', scope=self.namespace) def get_provider(self, provider_key=None, dry=False): """ Get provider key and provider instance. Args: provider_key (str or None): Name of provider dry (bool): Do not make change to the host system while True Returns: tuple: (provider key, provider instance) """ # If provider_key isn't provided via CLI, let's grab it the configuration if provider_key is None: provider_key = self.config.get('provider', scope=GLOBAL_CONF) provider_class = self.plugin.getProvider(provider_key) if provider_class is None: raise NuleculeException("Invalid Provider - '{}', provided in " "answers.conf (choose from {})" .format(provider_key, ', ' .join(PROVIDERS))) return provider_key, provider_class( self.config.context(), self.basepath, dry) def run(self, provider_key=None, dry=False): raise NotImplementedError def stop(self, provider): raise NotImplementedError def fetch(self): raise NotImplementedError def uninstall(self): raise NotImplementedError
def setUp(self): self.nulecule_base = Nulecule_Base(dryrun=True) self.tmpdir = tempfile.mkdtemp(prefix="atomicapp-test", dir="/tmp") self.plugin = Plugin() self.plugin.load_plugins()
def setUp(self): self.nulecule_base = Nulecule_Base(dryrun = True) self.tmpdir = tempfile.mkdtemp(prefix = "atomicapp-test", dir = "/tmp") self.plugin = Plugin() self.plugin.load_plugins()
class NuleculeBase(object): """ This is the base class for Nulecule and NuleculeComponent in atomicapp.nulecule.base. """ def __init__(self, basepath, params, namespace): self.plugin = Plugin() self.plugin.load_plugins() self.basepath = basepath self.params = params or [] self.namespace = namespace def load(self): pass def load_config(self, config, ask=False, skip_asking=False): """ Load config data. Sets the loaded config data to self.config. Args: config (dict): Initial config data ask (bool): When True, ask for values for a param from user even if the param has a default value skip_asking (bool): When True, skip asking for values for params with missing values and set the value as None Returns: None """ for param in self.params: value = config.get(self.namespace, {}).get(param[NAME_KEY]) or \ config.get(GLOBAL_CONF, {}).get(param[NAME_KEY]) if value is None and (ask or (not skip_asking and param.get(DEFAULTNAME_KEY) is None)): cockpit_logger.info("%s is missing in answers.conf." % param[NAME_KEY]) value = Utils.askFor(param[NAME_KEY], param) elif value is None: value = param.get(DEFAULTNAME_KEY) if config.get(self.namespace) is None: config[self.namespace] = {} config[self.namespace][param[NAME_KEY]] = value self.config = config def merge_config(self, to_config, from_config): """ Merge values from from_config to to_config. If value for a key in a group in to_config is missing, then only set it's value from corresponding key in the same group in from_config. Args: to_config (dict): Dictionary to merge config into from_config (dict): Dictionary to merge config from Returns: None """ for group, group_vars in from_config.items(): to_config[group] = to_config.get(group) or {} for key, value in (group_vars or {}).items(): if to_config[group].get(key) is None: to_config[group][key] = value def get_context(self): """ Get context data from config data for rendering an artifact. """ context = {} context.update(self.config.get(GLOBAL_CONF) or {}) context.update(self.config.get(self.namespace) or {}) return context def get_provider(self, provider_key=None, dry=False): """ Get provider key and provider instance. Args: provider_key (str or None): Name of provider dry (bool): Do not make change to the host system while True Returns: tuple: (provider key, provider instance) """ # If provider_key isn't provided via CLI, let's grab it the configuration if provider_key is None: provider_key = self.config.get(GLOBAL_CONF)[PROVIDER_KEY] provider_class = self.plugin.getProvider(provider_key) if provider_class is None: raise NuleculeException("Invalid Provider - '{}', provided in " "answers.conf (choose from {})".format( provider_key, ', '.join(PROVIDERS))) return provider_key, provider_class(self.get_context(), self.basepath, dry) def run(self, provider_key=None, dry=False): raise NotImplementedError def stop(self, provider): raise NotImplementedError def fetch(self): raise NotImplementedError def uninstall(self): raise NotImplementedError