Bläddra i källkod

ResourcesManager now check the version of a module

nico 8 år sedan
förälder
incheckning
480067dcc6

+ 2 - 0
Tests/modules/dna.yml

@@ -0,0 +1,2 @@
+# empty, just for testing file exist
+

+ 1 - 0
Tests/modules/install.yml

@@ -0,0 +1 @@
+# empty, just for testing file exist

+ 10 - 0
Tests/modules/test_invalid_dna.yml

@@ -0,0 +1,10 @@
+---
+  name: "neuron_test"
+  type: "non existing"
+  author: "Kalliope project team"
+
+  kalliope_supported_version:
+    - 0.4.0
+
+  tags:
+    - "test"

+ 10 - 0
Tests/modules/test_valid_dna.yml

@@ -0,0 +1,10 @@
+---
+  name: "neuron_test"
+  type: "neuron"
+  author: "Kalliope project team"
+
+  kalliope_supported_version:
+    - 0.4.0
+
+  tags:
+    - "test"

+ 107 - 0
Tests/test_dna_loader.py

@@ -0,0 +1,107 @@
+import os
+import unittest
+
+from kalliope.core.ConfigurationManager.DnaLoader import DnaLoader
+from kalliope.core.Models.Dna import Dna
+
+
+class TestDnaLoader(unittest.TestCase):
+
+    def setUp(self):
+        if "/Tests" in os.getcwd():
+            self.dna_test_file = "modules/test_valid_dna.yml"
+        else:
+            self.dna_test_file = "Tests/modules/test_valid_dna.yml"
+
+    def tearDown(self):
+        pass
+
+    def test_get_yaml_config(self):
+
+        expected_result = {'kalliope_supported_version': ['0.4.0'],
+                           'author': 'Kalliope project team',
+                           'type': 'neuron',
+                           'name': 'neuron_test',
+                           'tags': ['test']}
+
+        dna_file_content = DnaLoader(self.dna_test_file).get_yaml_config()
+
+        self.assertEqual(dna_file_content, expected_result)
+
+    def test_get_dna(self):
+
+        expected_result = Dna()
+        expected_result.name = "neuron_test"
+        expected_result.module_type = "neuron"
+        expected_result.tags = ['test']
+        expected_result.author = 'Kalliope project team'
+        expected_result.kalliope_supported_version = ['0.4.0']
+
+        dna_to_test = DnaLoader(self.dna_test_file).get_dna()
+
+        self.assertTrue(dna_to_test.__eq__(expected_result))
+
+    def test_load_dna(self):
+        # test with a valid DNA file
+        dna_to_test = DnaLoader(self.dna_test_file)._load_dna()
+
+        self.assertTrue(isinstance(dna_to_test, Dna))
+
+        # test with a non valid DNA file
+        if "/Tests" in os.getcwd():
+            dna_invalid_test_file = "modules/test_invalid_dna.yml"
+        else:
+            dna_invalid_test_file = "Tests/modules/test_invalid_dna.yml"
+
+        self.assertIsNone(DnaLoader(dna_invalid_test_file)._load_dna())
+
+    def test_check_dna(self):
+        # check with valid DNA file
+        test_dna = {'kalliope_supported_version': ['0.4.0'],
+                    'author': 'Kalliope project team',
+                    'type': 'neuron',
+                    'name': 'neuron_test',
+                    'tags': ['test']}
+
+        self.assertTrue(DnaLoader(file_path=self.dna_test_file)._check_dna_file(test_dna))
+
+        # invalid DNA file, no name
+        test_dna = {'kalliope_supported_version': ['0.4.0'],
+                    'author': 'Kalliope project team',
+                    'type': 'neuron',
+                    'tags': ['test']}
+
+        self.assertFalse(DnaLoader(file_path=self.dna_test_file)._check_dna_file(test_dna))
+
+        # invalid DNA file, no type
+        test_dna = {'kalliope_supported_version': ['0.4.0'],
+                    'author': 'Kalliope project team',
+                    'name': 'neuron_test',
+                    'tags': ['test']}
+
+        self.assertFalse(DnaLoader(file_path=self.dna_test_file)._check_dna_file(test_dna))
+
+        # invalid DNA, wrong type
+        test_dna = {'kalliope_supported_version': ['0.4.0'],
+                    'author': 'Kalliope project team',
+                    'type': 'doesnotexist',
+                    'name': 'neuron_test',
+                    'tags': ['test']}
+
+        self.assertFalse(DnaLoader(file_path=self.dna_test_file)._check_dna_file(test_dna))
+
+        # invalid DNA, no kalliope_supported_version
+        test_dna = {'author': 'Kalliope project team',
+                    'type': 'neuron',
+                    'name': 'neuron_test',
+                    'tags': ['test']}
+        self.assertFalse(DnaLoader(file_path=self.dna_test_file)._check_dna_file(test_dna))
+
+        # invalid DNA, kalliope_supported_version empty
+        test_dna = {'kalliope_supported_version': [],
+                    'author': 'Kalliope project team',
+                    'type': 'neuron',
+                    'name': 'neuron_test',
+                    'tags': ['test']}
+
+        self.assertFalse(DnaLoader(file_path=self.dna_test_file)._check_dna_file(test_dna))

+ 152 - 0
Tests/test_resources_manager.py

@@ -0,0 +1,152 @@
+import os
+import unittest
+
+from mock import mock
+
+from kalliope import ResourcesManager
+from kalliope.core.Models import Resources
+from kalliope.core.Models.Dna import Dna
+
+
+class TestResourcesmanager(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_is_settings_ok(self):
+        # -----------------
+        # valid resource
+        # -----------------
+        # valid neuron
+        valid_resource = Resources()
+        valid_resource.neuron_folder = "/path"
+        dna = Dna()
+        dna.module_type = "neuron"
+        self.assertTrue(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # valid stt
+        valid_resource = Resources()
+        valid_resource.stt_folder = "/path"
+        dna = Dna()
+        dna.module_type = "stt"
+        self.assertTrue(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # valid tts
+        valid_resource = Resources()
+        valid_resource.tts_folder = "/path"
+        dna = Dna()
+        dna.module_type = "tss"
+        self.assertTrue(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # valid trigger
+        valid_resource = Resources()
+        valid_resource.trigger_folder = "/path"
+        dna = Dna()
+        dna.module_type = "trigger"
+        self.assertTrue(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # -----------------
+        # invalid resource
+        # -----------------
+        # valid neuron
+        valid_resource = Resources()
+        valid_resource.neuron_folder = None
+        dna = Dna()
+        dna.module_type = "neuron"
+        self.assertFalse(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # valid stt
+        valid_resource = Resources()
+        valid_resource.stt_folder = None
+        dna = Dna()
+        dna.module_type = "stt"
+        self.assertFalse(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # valid tts
+        valid_resource = Resources()
+        valid_resource.tts_folder = None
+        dna = Dna()
+        dna.module_type = "tts"
+        self.assertFalse(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+        # valid trigger
+        valid_resource = Resources()
+        valid_resource.trigger_folder = None
+        dna = Dna()
+        dna.module_type = "trigger"
+        self.assertFalse(ResourcesManager.is_settings_ok(valid_resource, dna))
+
+    def test_is_repo_ok(self):
+        # valid repo
+        if "/Tests" in os.getcwd():
+            dna_file_path = "modules/dna.yml"
+            install_file_path = "modules/install.yml"
+        else:
+            dna_file_path = "Tests/modules/dna.yml"
+            install_file_path = "Tests/modules/install.yml"
+        self.assertTrue(ResourcesManager.is_repo_ok(dna_file_path=dna_file_path, install_file_path=install_file_path))
+
+        # missing dna
+        if "/Tests" in os.getcwd():
+            dna_file_path = ""
+            install_file_path = "modules/install.yml"
+        else:
+            dna_file_path = "T"
+            install_file_path = "Tests/modules/install.yml"
+        self.assertFalse(ResourcesManager.is_repo_ok(dna_file_path=dna_file_path, install_file_path=install_file_path))
+
+        # missing install
+        if "/Tests" in os.getcwd():
+            dna_file_path = "modules/dna.yml"
+            install_file_path = ""
+        else:
+            dna_file_path = "Tests/modules/dna.yml"
+            install_file_path = ""
+        self.assertFalse(ResourcesManager.is_repo_ok(dna_file_path=dna_file_path, install_file_path=install_file_path))
+
+    def test_get_target_folder(self):
+        # test get neuron folder
+        resources = Resources()
+        resources.neuron_folder = '/var/tmp/test/resources'
+        self.assertEqual(ResourcesManager._get_target_folder(resources, "neuron"), "/var/tmp/test/resources")
+
+        # test get stt folder
+        resources = Resources()
+        resources.stt_folder = '/var/tmp/test/resources'
+        self.assertEqual(ResourcesManager._get_target_folder(resources, "stt"), "/var/tmp/test/resources")
+
+        # test get tts folder
+        resources = Resources()
+        resources.tts_folder = '/var/tmp/test/resources'
+        self.assertEqual(ResourcesManager._get_target_folder(resources, "tts"), "/var/tmp/test/resources")
+
+        # test get trigger folder
+        resources = Resources()
+        resources.trigger_folder = '/var/tmp/test/resources'
+        self.assertEqual(ResourcesManager._get_target_folder(resources, "trigger"), "/var/tmp/test/resources")
+
+        # test get non existing resource
+        resources = Resources()
+        self.assertIsNone(ResourcesManager._get_target_folder(resources, "not_existing"))
+
+    def test_check_supported_version(self):
+        # version ok
+        current_version = '0.4.0'
+        supported_version = ['0.4.0', '0.3.0', '0.2.0']
+
+        self.assertTrue(ResourcesManager._check_supported_version(current_version=current_version,
+                                                                  supported_versions=supported_version))
+
+        # version non ok, useer does not confir
+        current_version = '0.4.0'
+        supported_version = ['0.3.0', '0.2.0']
+
+        with mock.patch('kalliope.Utils.query_yes_no', return_value=True):
+            self.assertTrue(ResourcesManager._check_supported_version(current_version=current_version,
+                                                                      supported_versions=supported_version))
+
+        with mock.patch('kalliope.Utils.query_yes_no', return_value=False):
+            self.assertFalse(ResourcesManager._check_supported_version(current_version=current_version,
+                                                                       supported_versions=supported_version))

+ 1 - 0
install/files/python_requirements.txt

@@ -15,3 +15,4 @@ mock==2.0.0
 Flask-Testing==0.6.1
 apscheduler==3.3.0
 GitPython==2.1.1
+packaging>=16.8

+ 2 - 1
kalliope/__init__.py

@@ -98,7 +98,8 @@ def main():
             parameters = {
                 "git_url": args.git_url
             }
-            ResourcesManager("install", **parameters)
+            res_manager = ResourcesManager(**parameters)
+            res_manager.install()
 
 
 def configure_logging(debug=None):

+ 93 - 0
kalliope/core/ConfigurationManager/DnaLoader.py

@@ -0,0 +1,93 @@
+from kalliope.core import Utils
+from kalliope.core.ConfigurationManager import YAMLLoader
+from kalliope.core.Models.Dna import Dna
+
+
+class InvalidDNAException(Exception):
+    pass
+
+VALID_DNA_MODULE_TYPE = ["neuron", "stt", "tts", "trigger"]
+
+
+class DnaLoader(object):
+
+    def __init__(self, file_path):
+        """
+        Load a DNA file and check the content of this one
+        :param file_path: path the the DNA file to load
+        """
+        self.file_path = file_path
+        if self.file_path is None:
+            raise InvalidDNAException("[DnaLoader] You must set a file file")
+
+        self.yaml_config = YAMLLoader.get_config(self.file_path)
+        self.dna = self._load_dna()
+
+    def get_yaml_config(self):
+        """
+        Class Methods which loads default or the provided YAML file and return it as a String
+        :return: The loaded DNA YAML file
+        :rtype: String
+        """
+        return self.yaml_config
+
+    def get_dna(self):
+        """
+        Return the loaded DNA object if this one is valid
+        :return:
+        """
+        return self.dna
+
+    def _load_dna(self):
+        """
+        retur a DNA object from a loaded yaml file
+        :return:
+        """
+        new_dna = None
+        if self._check_dna_file(self.yaml_config):
+            new_dna = Dna()
+            new_dna.name = self.yaml_config["name"]
+            new_dna.module_type = self.yaml_config["type"]
+            new_dna.author = self.yaml_config["author"]
+            new_dna.kalliope_supported_version = self.yaml_config["kalliope_supported_version"]
+            new_dna.tags = self.yaml_config["tags"]
+
+        return new_dna
+
+    @staticmethod
+    def _check_dna_file(dna_file):
+        """
+        Check the content of a DNA file
+        :param dna_file: the dna to check
+        :return: True if ok, False otherwise
+        """
+        success_loading = True
+        if "name" not in dna_file:
+            Utils.print_danger("The DNA of does not contains a \"name\" tag")
+            success_loading = False
+
+        if "type" not in dna_file:
+            Utils.print_danger("The DNA of does not contains a \"type\" tag")
+            success_loading = False
+
+        else:
+            # we have a type, check that is a valid one
+            if dna_file["type"] not in VALID_DNA_MODULE_TYPE:
+                Utils.print_danger("The DNA type %s is not valid" % dna_file["type"])
+                Utils.print_danger("The DNA type must be one of the following: %s" % VALID_DNA_MODULE_TYPE)
+                success_loading = False
+
+        if "kalliope_supported_version" not in dna_file:
+            Utils.print_danger("The DNA of does not contains a \"kalliope_supported_version\" tag")
+            success_loading = False
+        else:
+            # kalliope_supported_version must be a non empty list
+            if not isinstance(dna_file["kalliope_supported_version"], list):
+                Utils.print_danger("kalliope_supported_version is not a list")
+                success_loading = False
+            else:
+                if not dna_file["kalliope_supported_version"]:
+                    Utils.print_danger("kalliope_supported_version cannot be empty")
+                    success_loading = False
+
+        return success_loading

+ 3 - 3
kalliope/core/ConfigurationManager/SettingLoader.py

@@ -558,9 +558,9 @@ class SettingLoader(object):
                     raise SettingInvalidException("The path %s does not exist on the system" % trigger_folder)
 
             if neuron_folder is None \
-                and stt_folder is None \
-                and tts_folder is None \
-                and trigger_folder is None:
+                    and stt_folder is None \
+                    and tts_folder is None \
+                    and trigger_folder is None:
                 raise SettingInvalidException("No required folder has been provided in the setting resource_directory. "
                                               "Define : \'neuron\' or/and \'stt\' or/and \'tts\' or/and \'trigger\'")
 

+ 40 - 0
kalliope/core/Models/Dna.py

@@ -0,0 +1,40 @@
+
+
+class Dna(object):
+
+    def __init__(self, name=None, module_type=None, author=None, kalliope_supported_version=None, tags=None):
+        self.name = name
+        self.module_type = module_type  # type is a reserved python
+        self.author = author
+        self.kalliope_supported_version = kalliope_supported_version
+        self.tags = tags
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object
+
+        :return: A dict of name and parameters
+        :rtype: Dict
+        """
+        return {
+            'name': self.name,
+            'type': self.module_type,
+            'author': self.author,
+            'kalliope_supported_version': self.kalliope_supported_version,
+            'tags': self.tags
+        }
+
+    def __str__(self):
+        return "Dna: name: %s, " \
+               "type: %s, " \
+               "author: %s, " \
+               "kalliope_supported_version: %s, " \
+               "tags: %s" % (self.name, self.module_type, self.author, self.kalliope_supported_version, self.tags)
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 4 - 1
kalliope/core/Models/Settings.py

@@ -1,4 +1,5 @@
 import platform
+from kalliope._version import version_str as current_kalliope_version
 
 
 class Settings(object):
@@ -20,7 +21,8 @@ class Settings(object):
                  cache_path=None,
                  default_synapse=None,
                  resources=None,
-                 machine=None):
+                 machine=None,
+                 kalliope_version=None):
 
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
@@ -35,6 +37,7 @@ class Settings(object):
         self.default_synapse = default_synapse
         self.resources = resources
         self.machine = platform.machine()   # can be x86_64 or armv7l
+        self.kalliope_version = current_kalliope_version
 
     def __eq__(self, other):
         """

+ 128 - 119
kalliope/core/ResourcesManager.py

@@ -1,16 +1,16 @@
 import getpass
-import os
-
 import logging
-
+import os
 import shutil
+
 from git import Repo
-from kalliope.core.Models import Neuron
+from packaging import version
 
-from kalliope import Utils
-from kalliope.core.ConfigurationManager import YAMLLoader
 from kalliope.core.ConfigurationManager import SettingLoader
+from kalliope.core.ConfigurationManager.DnaLoader import DnaLoader
+from kalliope.core.Models import Neuron
 from kalliope.core.NeuronLauncher import NeuronLauncher
+from kalliope.core.Utils import Utils
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -32,16 +32,22 @@ TYPE_STT = "stt"
 TYPE_TRIGGER = "trigger"
 
 
+class ResourcesManagerException(Exception):
+    pass
+
+
 class ResourcesManager(object):
-    def __init__(self, action, **kwargs):
+    def __init__(self, **kwargs):
+        """
+        This class is used to manage community resources.
+        :param kwargs:
+            git-url: the url of the module to clone and install
+        """
         super(ResourcesManager, self).__init__()
         # get settings
         sl = SettingLoader()
         self.settings = sl.settings
 
-        # action to perform (delete, install, update)
-        self.action = action
-
         # in case of update or install, url where
         self.git_url = kwargs.get('git_url', None)
 
@@ -49,84 +55,58 @@ class ResourcesManager(object):
         self.tmp_path = LOCAL_TMP_FOLDER + TMP_GIT_FOLDER
         self.dna_file_path = self.tmp_path + os.sep + DNA_FILE_NAME
         self.install_file_path = self.tmp_path + os.sep + INSTALL_FILE_NAME
-        self.dna_file = None
-
-        if self.action == "install":
-            self.install()
+        self.dna = None
 
     def install(self):
         """
         Module installation method.
         """
-        if self.is_settings_ok(resources=self.settings.resources,
-                               folder_path=self.settings.resources.neuron_folder):
-            # first, we clone the repo
-            self._clone_repo(path=self.tmp_path,
-                             git_url=self.git_url)
-
-            # check the content of the cloned repo
-            if self.is_repo_ok(dna_file_path=self.dna_file_path,
-                               install_file_path=self.install_file_path):
-
-                # Load the dna.yml file
-                self._set_dna_file()
-                if self._check_dna(dna_file=self.dna_file,
-                                   tmp_path=self.tmp_path):
-
-                    # Let's find the target folder depending the type
-                    module_type = self.dna_file["type"].lower()
-                    target_folder = self._get_target_folder(resources=self.settings.resources,
-                                                            module_type=module_type)
-                    if target_folder is not None:
-                        # let's move the tmp folder in the right folder and get a new path for the module
-                        module_name = self.dna_file["name"].lower()
-                        target_path = self._rename_temp_folder(name=module_name,
-                                                               target_folder=target_folder,
-                                                               tmp_path=self.tmp_path)
-
-                        # if the target_path exists, then run the install file within the new repository
-                        if target_path is not None:
-                            self.install_file_path = target_path + os.sep + INSTALL_FILE_NAME
-                            self.run_ansible_playbook_module(install_file_path=self.install_file_path)
-                            Utils.print_success("Module: %s installed" % module_name)
-
-    def _set_dna_file(self):
-        """
-        load the dna file from the module.
-        :return: set loading
-        """
-        # get the content of the DNA file
-        self.dna_file = YAMLLoader().get_config(self.dna_file_path)
-        logger.debug("[ResourcesManager] DNA file content: " + str(self.dna_file))
-
-
-    @staticmethod
-    def _check_dna(dna_file, tmp_path):
-        """
-        Check the dna_file values
-        :param dna_file: the dna_file to check
-        :param tmp_path: the temporary file path of the repo
-        :return: True if ok, False otherwise
-        """
-        success_loading = True
-        if "name" not in dna_file:
-            Utils.print_danger("The DNA of does not contains a \"name\" tag")
-            shutil.rmtree(tmp_path)
-            success_loading = False
-
-        if "type" not in dna_file:
-            Utils.print_danger("The DNA of does not contains a \"type\" tag")
-            shutil.rmtree(tmp_path)
-            success_loading = False
-
-        return success_loading
+        # first, we clone the repo
+        self._clone_repo(path=self.tmp_path,
+                         git_url=self.git_url)
+
+        # check the content of the cloned repo
+        if self.is_repo_ok(dna_file_path=self.dna_file_path,
+                           install_file_path=self.install_file_path):
+
+            # Load the dna.yml file
+            self.dna = DnaLoader(self.dna_file_path).get_dna()
+            if self.dna is not None:
+                logger.debug("[ResourcesManager] DNA file content: " + str(self.dna))
+                if self.is_settings_ok(resources=self.settings.resources, dna=self.dna):
+                    # the dna file is ok, check the supported version
+                    if self._check_supported_version(current_version=self.settings.kalliope_version,
+                                                     supported_versions=self.dna.kalliope_supported_version):
+
+                        # Let's find the target folder depending the type
+                        module_type = self.dna.module_type.lower()
+                        target_folder = self._get_target_folder(resources=self.settings.resources,
+                                                                module_type=module_type)
+                        if target_folder is not None:
+                            # let's move the tmp folder in the right folder and get a new path for the module
+                            module_name = self.dna.name.lower()
+                            target_path = self._rename_temp_folder(name=self.dna.name.lower(),
+                                                                   target_folder=target_folder,
+                                                                   tmp_path=self.tmp_path)
+
+                            # if the target_path exists, then run the install file within the new repository
+                            if target_path is not None:
+                                self.install_file_path = target_path + os.sep + INSTALL_FILE_NAME
+                                self.run_ansible_playbook_module(install_file_path=self.install_file_path)
+                                Utils.print_success("Module: %s installed" % module_name)
+                else:
+                    logger.debug("[ResourcesManager] installation cancelled, deleting temp repo %s"
+                                 % str(self.tmp_path))
+                    shutil.rmtree(self.tmp_path)
 
     @staticmethod
-    def is_settings_ok(resources, folder_path):
+    def is_settings_ok(resources, dna):
         """
         Test if required settings files in config of Kalliope are ok.
+        The resource object must not be empty
+        Check id the use have set the an installation path in his settings for the target module type
         :param resources: the Resources model
-        :param folder_path: the folder associate to the resource
+        :param dna: DNA info about the module to install
         :return:
         """
         settings_ok = True
@@ -135,12 +115,27 @@ class ResourcesManager(object):
             logger.debug(message)
             Utils.print_danger(message)
             settings_ok = False
-
-        if folder_path is None:
-            message = "No folder %s set in settings, cannot install." % folder_path
-            logger.debug(message)
-            Utils.print_danger(message)
-            settings_ok = False
+        else:
+            if dna.module_type == "neuron" and resources.neuron_folder is None:
+                message = "Resources folder for neuron installation not set in settings, cannot install."
+                logger.debug(message)
+                Utils.print_danger(message)
+                settings_ok = False
+            if dna.module_type == "stt" and resources.stt_folder is None:
+                message = "Resources folder for stt installation not set in settings, cannot install."
+                logger.debug(message)
+                Utils.print_danger(message)
+                settings_ok = False
+            if dna.module_type == "tts" and resources.tts_folder is None:
+                message = "Resources folder for tts installation not set in settings, cannot install."
+                logger.debug(message)
+                Utils.print_danger(message)
+                settings_ok = False
+            if dna.module_type == "trigger" and resources.trigger_folder is None:
+                message = "Resources folder for trigger installation not set in settings, cannot install."
+                logger.debug(message)
+                Utils.print_danger(message)
+                settings_ok = False
 
         return settings_ok
 
@@ -150,13 +145,13 @@ class ResourcesManager(object):
         Check if the git cloned repo is fine to be installed
         :return: True if repo is ok to be installed, False otherwise
         """
+        Utils.print_info("Checking repository...")
         repo_ok = True
         # check that a install.yml file is present
         if not os.path.exists(install_file_path):
             Utils.print_danger("Missing %s file" % INSTALL_FILE_NAME)
             repo_ok = False
 
-        Utils.print_info("Checking repository...")
         if not os.path.exists(dna_file_path):
             Utils.print_danger("Missing %s file" % DNA_FILE_NAME)
             repo_ok = False
@@ -167,46 +162,31 @@ class ResourcesManager(object):
     def _get_target_folder(resources, module_type):
         """
         Return the folder from the resources and given a module type
-        :param resources: Resource
+        :param resources: Resource object
+        :type resources: Resources
         :param module_type: type of the module
         :return: path of the folder
         """
-        folder_path = None
-        message = "Does this type really exists ? No %s folder set in settings, cannot install." % module_type
-
+        # dict to get the path behind a type of resource
+        module_type_converter = {
+            TYPE_NEURON: resources.neuron_folder,
+            TYPE_STT: resources.stt_folder,
+            TYPE_TTS: resources.tts_folder,
+            TYPE_TRIGGER: resources.trigger_folder
+        }
         # Let's find the right path depending of the type
-        if module_type == TYPE_NEURON:
-            if resources.neuron_folder is not None:
-                folder_path = resources.neuron_folder
-            else:
-                message = "No %s folder set in settings, cannot install." % TYPE_NEURON
-
-        elif module_type == TYPE_STT:
-            if resources.stt_folder is not None:
-                folder_path = resources.stt_folder
-            else:
-                message = "No %s folder set in settings, cannot install." % TYPE_STT
-
-        elif module_type == TYPE_TTS:
-            if resources.tts_folder is not None:
-                folder_path = resources.stt_folder
-            else:
-                message = "No %s folder set in settings, cannot install." % TYPE_TTS
-
-        elif module_type == TYPE_TRIGGER:
-            if resources.trigger_folder is not None:
-                folder_path = resources.trigger_folder
-            else:
-                message = "No %s folder set in settings, cannot install." % TYPE_TRIGGER
-
+        try:
+            folder_path = module_type_converter[module_type]
+        except KeyError:
+            folder_path = None
         # No folder_path has been found
+        message = "No %s folder set in settings, cannot install." % module_type
         if folder_path is None:
             logger.debug(message)
             Utils.print_danger(message)
 
         return folder_path
 
-
     @staticmethod
     def _clone_repo(path, git_url):
         """
@@ -214,7 +194,7 @@ class ResourcesManager(object):
         :return:
         """
         # clone the repo
-        logger.debug("GIT clone into folder: %s" % path)
+        logger.debug("[ResourcesManager] GIT clone into folder: %s" % path)
         Utils.print_info("Cloning repository...")
         # if the folder already exist we remove it
         if os.path.exists(path):
@@ -230,7 +210,7 @@ class ResourcesManager(object):
         Return the name of the path to install
         :return: path to install, None if already exists
         """
-
+        logger.debug("[ResourcesManager] Rename temp folder")
         new_absolute_neuron_path = target_folder + os.sep + name
         try:
             os.rename(tmp_path, new_absolute_neuron_path)
@@ -239,19 +219,18 @@ class ResourcesManager(object):
             # the folder already exist
             Utils.print_warning("The module %s already exist in the path %s" % (name, target_folder))
             # remove the cloned repo
+            logger.debug("[ResourcesManager] Deleting temp folder %s" % str(tmp_path))
             shutil.rmtree(tmp_path)
 
-
     @staticmethod
     def run_ansible_playbook_module(install_file_path):
         """
         Run the install.yml file through an Ansible playbook using the dedicated neuron !
 
         :param install_file_path: the path of the Ansible playbook to run.
-        :param target_path:
-        :param name:
         :return:
         """
+        logger.debug("[ResourcesManager] Run ansible playbook")
         Utils.print_info("Starting neuron installation")
         # ask the sudo password
         pswd = getpass.getpass('Sudo password:')
@@ -264,3 +243,33 @@ class ResourcesManager(object):
         neuron = Neuron(name="ansible_playbook", parameters=ansible_neuron_parameters)
         NeuronLauncher.start_neuron(neuron)
 
+    @staticmethod
+    def _check_supported_version(current_version, supported_versions):
+        """
+        The dna file contains supported Kalliope version for the module to install.
+        Check if supported versions are match the current installed version. If not, ask the user to confirm the
+        installation anyway
+        :param current_version: current version installed of Kalliope. E.g 0.4.0
+        :param supported_versions: list of supported version
+        :return: True if the version is supported or user has confirmed the installation
+        """
+        logger.debug("[ResourcesManager] Current installed version of Kalliope: %s" % str(current_version))
+        logger.debug("[ResourcesManager] Module supported version: %s" % str(supported_versions))
+
+        supported_version_found = False
+        for supported_version in supported_versions:
+            if version.parse(current_version) == version.parse(supported_version):
+                # we found the exact version
+                supported_version_found = True
+                break
+
+        if not supported_version_found:
+            # we ask the user if we want to install the module even if the version doesn't match
+            Utils.print_info("Current installed version of Kalliope: %s" % current_version)
+            Utils.print_info("Module supported versions: %s" % str(supported_versions))
+            Utils.print_warning("The neuron seems to be not supported by your current version of Kalliope")
+            supported_version_found = Utils.query_yes_no("install it anyway?")
+            logger.debug("[ResourcesManager] install it anyway user answer: %s" % supported_version_found)
+
+        logger.debug("[ResourcesManager] check_supported_version: %s" % str(supported_version_found))
+        return supported_version_found

+ 34 - 0
kalliope/core/Utils/Utils.py

@@ -3,6 +3,8 @@ import os
 import inspect
 import imp
 
+import sys
+
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
@@ -183,3 +185,35 @@ class Utils(object):
                 return file_path_to_test
             else:
                 return None
+
+    @staticmethod
+    def query_yes_no(question, default="yes"):
+        """Ask a yes/no question via raw_input() and return their answer.
+
+        "question" is a string that is presented to the user.
+        "default" is the presumed answer if the user just hits <Enter>.
+            It must be "yes" (the default), "no" or None (meaning
+            an answer is required of the user).
+
+        The "answer" return value is True for "yes" or False for "no".
+        """
+        valid = {"yes": True, "y": True, "ye": True,
+                 "no": False, "n": False}
+        if default is None:
+            prompt = " [y/n] "
+        elif default == "yes":
+            prompt = " [Y/n] "
+        elif default == "no":
+            prompt = " [y/N] "
+        else:
+            raise ValueError("invalid default answer: '%s'" % default)
+
+        while True:
+            Utils.print_warning(question + prompt)
+            choice = raw_input().lower()
+            if default is not None and choice == '':
+                return valid[default]
+            elif choice in valid:
+                return valid[choice]
+            else:
+                Utils.print_warning("Please respond with 'yes' or 'no' or 'y' or 'n').\n")

+ 1 - 0
kalliope/core/__init__.py

@@ -3,5 +3,6 @@ from kalliope.core.OrderListener import OrderListener
 from kalliope.core.ShellGui import ShellGui
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.Utils import FileManager
+from kalliope.core.ResourcesManager import ResourcesManager
 
 

+ 2 - 1
setup.py

@@ -73,7 +73,8 @@ setup(
         'mock==2.0.0',
         'Flask-Testing==0.6.1',
         'apscheduler==3.3.0',
-        'GitPython==2.1.1'
+        'GitPython==2.1.1',
+        'packaging>=16.8'
     ],