We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow usage of env vars in config
The configuration should be parsed for environment variables when loaded. This would allow for secrets like api keys to be kept outside of the opsdroid configuration.

#### Example

```yaml
connectors:
  - name: slack
    default-room: '#general'
    bot-name: "opsdroid"
    icon-emoji: ":robot:"
    api-token: "$SLACK_API_KEY"
```
In this example `$SLACK_API_KEY` would be replaced with the contents of the environment variable of the same name.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `opsdroid/loader.py`
Content:
```
1 """Class for loading in modules to OpsDroid."""
2 
3 import logging
4 import os
5 import sys
6 import shutil
7 import subprocess
8 import importlib
9 import yaml
10 from opsdroid.const import (
11     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,
12     DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,
13     DEFAULT_MODULE_DEPS_PATH)
14 
15 
16 _LOGGER = logging.getLogger(__name__)
17 
18 
19 class Loader:
20     """Class to load in config and modules."""
21 
22     def __init__(self, opsdroid):
23         """Create object with opsdroid instance."""
24         self.opsdroid = opsdroid
25         self.modules_directory = None
26         self.current_import_config = None
27         _LOGGER.debug("Loaded loader")
28 
29     @staticmethod
30     def import_module(config):
31         """Import module namespace as variable and return it."""
32         try:
33             module = importlib.import_module(
34                 config["module_path"] + "." + config["name"])
35             _LOGGER.debug("Loaded " + config["type"] + ": " +
36                           config["module_path"])
37             return module
38         except ImportError as error:
39             _LOGGER.debug("Failed to load " + config["type"] +
40                           " " + config["module_path"] + "." + config["name"])
41             _LOGGER.debug(error)
42 
43         try:
44             module = importlib.import_module(
45                 config["module_path"])
46             _LOGGER.debug("Loaded " + config["type"] + ": " +
47                           config["module_path"])
48             return module
49         except ImportError as error:
50             _LOGGER.debug("Failed to load " + config["type"] +
51                           " " + config["module_path"])
52             _LOGGER.debug(error)
53 
54         _LOGGER.error("Failed to load " + config["type"] +
55                       " " + config["module_path"])
56         return None
57 
58     @staticmethod
59     def check_cache(config):
60         """Remove module if 'no-cache' set in config."""
61         if "no-cache" in config \
62                 and config["no-cache"]:
63             _LOGGER.debug("'no-cache' set, removing " + config["install_path"])
64             if os.path.isdir(config["install_path"]):
65                 shutil.rmtree(config["install_path"])
66             if os.path.isfile(config["install_path"] + ".py"):
67                 os.remove(config["install_path"] + ".py")
68 
69     def build_module_path(self, path_type, config):
70         """Generate the module path from name and type."""
71         if path_type == "import":
72             return MODULES_DIRECTORY + "." + config["type"] + \
73                         "." + config["name"]
74         elif path_type == "install":
75             return self.modules_directory + "/" + config["type"] + \
76                         "/" + config["name"]
77 
78     @staticmethod
79     def git_clone(git_url, install_path, branch):
80         """Clone a git repo to a location and wait for finish."""
81         process = subprocess.Popen(["git", "clone", "-b", branch,
82                                     git_url, install_path], shell=False,
83                                    stdout=subprocess.PIPE,
84                                    stderr=subprocess.PIPE)
85         for output in process.communicate():
86             if output != "":
87                 for line in output.splitlines():
88                     _LOGGER.debug(str(line).strip())
89         process.wait()
90 
91     @staticmethod
92     def pip_install_deps(requirements_path):
93         """Pip install a requirements.txt file and wait for finish."""
94         process = subprocess.Popen(["pip", "install",
95                                     "--target={}".format(
96                                         DEFAULT_MODULE_DEPS_PATH),
97                                     "--ignore-installed",
98                                     "-r", requirements_path],
99                                    shell=False,
100                                    stdout=subprocess.PIPE,
101                                    stderr=subprocess.PIPE)
102         for output in process.communicate():
103             if output != "":
104                 for line in output.splitlines():
105                     _LOGGER.debug(str(line).strip())
106         process.wait()
107 
108     @staticmethod
109     def create_default_config(config_path):
110         """Create a default config file based on the included example."""
111         _LOGGER.info("Creating %s.", config_path)
112         config_dir, _ = os.path.split(config_path)
113         if not os.path.isdir(config_dir):
114             os.makedirs(config_dir)
115         shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)
116         return config_path
117 
118     @staticmethod
119     def _reload_modules(modules):
120         for module in modules:
121             importlib.reload(module["module"])
122 
123     def load_config_file(self, config_paths):
124         """Load a yaml config file from path."""
125         config_path = ""
126         for possible_path in config_paths:
127             if not os.path.isfile(possible_path):
128                 _LOGGER.debug("Config file " + possible_path +
129                               " not found")
130             else:
131                 config_path = possible_path
132                 break
133 
134         if not config_path:
135             _LOGGER.info("No configuration files found.")
136             config_path = self.create_default_config(DEFAULT_CONFIG_PATH)
137 
138         try:
139             with open(config_path, 'r') as stream:
140                 _LOGGER.info("Loaded config from %s", config_path)
141                 return yaml.load(stream)
142         except yaml.YAMLError as error:
143             self.opsdroid.critical(error, 1)
144         except FileNotFoundError as error:
145             self.opsdroid.critical(str(error), 1)
146 
147     def setup_modules_directory(self, config):
148         """Create and configure the modules directory."""
149         module_path = os.path.expanduser(
150             config.get("module-path", DEFAULT_MODULES_PATH))
151         sys.path.append(module_path)
152 
153         if not os.path.isdir(module_path):
154             os.makedirs(module_path, exist_ok=True)
155 
156         self.modules_directory = os.path.join(module_path, MODULES_DIRECTORY)
157 
158         # Create modules directory if doesn't exist
159         if not os.path.isdir(self.modules_directory):
160             os.makedirs(self.modules_directory)
161 
162     def load_modules_from_config(self, config):
163         """Load all module types based on config."""
164         _LOGGER.debug("Loading modules from config")
165 
166         self.setup_modules_directory(config)
167 
168         connectors, databases, skills = None, None, None
169 
170         if 'databases' in config.keys():
171             databases = self._load_modules('database', config['databases'])
172         else:
173             _LOGGER.warning("No databases in configuration")
174 
175         if 'skills' in config.keys():
176             skills = self._load_modules('skill', config['skills'])
177             self.opsdroid.skills = []
178             self._reload_modules(skills)
179         else:
180             self.opsdroid.critical(
181                 "No skills in configuration, at least 1 required", 1)
182 
183         if 'connectors' in config.keys():
184             connectors = self._load_modules('connector', config['connectors'])
185         else:
186             self.opsdroid.critical(
187                 "No connectors in configuration, at least 1 required", 1)
188 
189         return connectors, databases, skills
190 
191     def _load_modules(self, modules_type, modules):
192         """Install and load modules."""
193         _LOGGER.debug("Loading " + modules_type + " modules")
194         loaded_modules = []
195 
196         if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):
197             os.makedirs(DEFAULT_MODULE_DEPS_PATH)
198         sys.path.append(DEFAULT_MODULE_DEPS_PATH)
199 
200         for module in modules:
201 
202             # Set up module config
203             config = module
204             config = {} if config is None else config
205             config["name"] = module["name"]
206             config["type"] = modules_type
207             config["module_path"] = self.build_module_path("import", config)
208             config["install_path"] = self.build_module_path("install", config)
209             if "branch" not in config:
210                 config["branch"] = DEFAULT_MODULE_BRANCH
211 
212             # Remove module for reinstall if no-cache set
213             self.check_cache(config)
214 
215             # Install module
216             self._install_module(config)
217 
218             # Import module
219             self.current_import_config = config
220             module = self.import_module(config)
221             if module is not None:
222                 loaded_modules.append({
223                     "module": module,
224                     "config": config})
225             else:
226                 _LOGGER.error(
227                     "Module " + config["name"] + " failed to import")
228 
229         return loaded_modules
230 
231     def _install_module(self, config):
232         # pylint: disable=R0201
233         """Install a module."""
234         _LOGGER.debug("Installing " + config["name"])
235 
236         if os.path.isdir(config["install_path"]) or \
237                 os.path.isfile(config["install_path"] + ".py"):
238             # TODO Allow for updating or reinstalling of modules
239             _LOGGER.debug("Module " + config["name"] +
240                           " already installed, skipping")
241             return
242 
243         if "path" in config:
244             self._install_local_module(config)
245         else:
246             self._install_git_module(config)
247 
248         if os.path.isdir(config["install_path"]):
249             _LOGGER.debug("Installed " + config["name"] +
250                           " to " + config["install_path"])
251         else:
252             _LOGGER.debug("Install of " + config["name"] + " failed")
253 
254         # Install module dependancies
255         if os.path.isfile(config["install_path"] + "/requirements.txt"):
256             self.pip_install_deps(config["install_path"] +
257                                   "/requirements.txt")
258 
259     def _install_git_module(self, config):
260         """Install a module from a git repository."""
261         if config is not None and "repo" in config:
262             git_url = config["repo"]
263         else:
264             git_url = DEFAULT_GIT_URL + config["type"] + \
265                         "-" + config["name"] + ".git"
266 
267         if any(prefix in git_url for prefix in ["http", "https", "ssh"]):
268             # TODO Test if url or ssh path exists
269             # TODO Handle github authentication
270             _LOGGER.debug("Cloning from remote repository")
271             self.git_clone(git_url, config["install_path"],
272                            config["branch"])
273         else:
274             if os.path.isdir(git_url):
275                 _LOGGER.debug("Cloning from local repository")
276                 self.git_clone(git_url, config["install_path"],
277                                config["branch"])
278             else:
279                 _LOGGER.debug("Could not find local git repo " + git_url)
280 
281     @staticmethod
282     def _install_local_module(config):
283         """Install a module from a local path."""
284         installed = False
285         config["path"] = os.path.expanduser(config["path"])
286 
287         installdir, _ = os.path.split(config["install_path"])
288         if not os.path.isdir(installdir):
289             os.makedirs(installdir, exist_ok=True)
290 
291         if os.path.isdir(config["path"]):
292             shutil.copytree(config["path"], config["install_path"])
293             installed = True
294 
295         if os.path.isfile(config["path"]):
296             os.makedirs(config["install_path"], exist_ok=True)
297             shutil.copyfile(config["path"], config["install_path"] +
298                             "/__init__.py")
299             installed = True
300 
301         if not installed:
302             _LOGGER.error("Failed to install from " + config["path"])
303
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -6,6 +6,7 @@
 import shutil
 import subprocess
 import importlib
+import re
 import yaml
 from opsdroid.const import (
     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,
@@ -135,6 +136,17 @@
             _LOGGER.info("No configuration files found.")
             config_path = self.create_default_config(DEFAULT_CONFIG_PATH)
 
+        env_var_pattern = re.compile(r'^\$([A-Z_]*)$')
+        yaml.add_implicit_resolver("!envvar", env_var_pattern)
+
+        def envvar_constructor(loader, node):
+            """Yaml parser for env vars."""
+            value = loader.construct_scalar(node)
+            [env_var] = env_var_pattern.match(value).groups()
+            return os.environ[env_var]
+
+        yaml.add_constructor('!envvar', envvar_constructor)
+
         try:
             with open(config_path, 'r') as stream:
                 _LOGGER.info("Loaded config from %s", config_path)

{"golden_diff": "diff --git a/opsdroid/loader.py b/opsdroid/loader.py\n--- a/opsdroid/loader.py\n+++ b/opsdroid/loader.py\n@@ -6,6 +6,7 @@\n import shutil\n import subprocess\n import importlib\n+import re\n import yaml\n from opsdroid.const import (\n     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,\n@@ -135,6 +136,17 @@\n             _LOGGER.info(\"No configuration files found.\")\n             config_path = self.create_default_config(DEFAULT_CONFIG_PATH)\n \n+        env_var_pattern = re.compile(r'^\\$([A-Z_]*)$')\n+        yaml.add_implicit_resolver(\"!envvar\", env_var_pattern)\n+\n+        def envvar_constructor(loader, node):\n+            \"\"\"Yaml parser for env vars.\"\"\"\n+            value = loader.construct_scalar(node)\n+            [env_var] = env_var_pattern.match(value).groups()\n+            return os.environ[env_var]\n+\n+        yaml.add_constructor('!envvar', envvar_constructor)\n+\n         try:\n             with open(config_path, 'r') as stream:\n                 _LOGGER.info(\"Loaded config from %s\", config_path)\n", "issue": "Allow usage of env vars in config\nThe configuration should be parsed for environment variables when loaded. This would allow for secrets like api keys to be kept outside of the opsdroid configuration.\r\n\r\n#### Example\r\n\r\n```yaml\r\nconnectors:\r\n  - name: slack\r\n    default-room: '#general'\r\n    bot-name: \"opsdroid\"\r\n    icon-emoji: \":robot:\"\r\n    api-token: \"$SLACK_API_KEY\"\r\n```\r\nIn this example `$SLACK_API_KEY` would be replaced with the contents of the environment variable of the same name.\n", "before_files": [{"content": "\"\"\"Class for loading in modules to OpsDroid.\"\"\"\n\nimport logging\nimport os\nimport sys\nimport shutil\nimport subprocess\nimport importlib\nimport yaml\nfrom opsdroid.const import (\n    DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,\n    DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,\n    DEFAULT_MODULE_DEPS_PATH)\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass Loader:\n    \"\"\"Class to load in config and modules.\"\"\"\n\n    def __init__(self, opsdroid):\n        \"\"\"Create object with opsdroid instance.\"\"\"\n        self.opsdroid = opsdroid\n        self.modules_directory = None\n        self.current_import_config = None\n        _LOGGER.debug(\"Loaded loader\")\n\n    @staticmethod\n    def import_module(config):\n        \"\"\"Import module namespace as variable and return it.\"\"\"\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(error)\n\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"])\n            _LOGGER.debug(error)\n\n        _LOGGER.error(\"Failed to load \" + config[\"type\"] +\n                      \" \" + config[\"module_path\"])\n        return None\n\n    @staticmethod\n    def check_cache(config):\n        \"\"\"Remove module if 'no-cache' set in config.\"\"\"\n        if \"no-cache\" in config \\\n                and config[\"no-cache\"]:\n            _LOGGER.debug(\"'no-cache' set, removing \" + config[\"install_path\"])\n            if os.path.isdir(config[\"install_path\"]):\n                shutil.rmtree(config[\"install_path\"])\n            if os.path.isfile(config[\"install_path\"] + \".py\"):\n                os.remove(config[\"install_path\"] + \".py\")\n\n    def build_module_path(self, path_type, config):\n        \"\"\"Generate the module path from name and type.\"\"\"\n        if path_type == \"import\":\n            return MODULES_DIRECTORY + \".\" + config[\"type\"] + \\\n                        \".\" + config[\"name\"]\n        elif path_type == \"install\":\n            return self.modules_directory + \"/\" + config[\"type\"] + \\\n                        \"/\" + config[\"name\"]\n\n    @staticmethod\n    def git_clone(git_url, install_path, branch):\n        \"\"\"Clone a git repo to a location and wait for finish.\"\"\"\n        process = subprocess.Popen([\"git\", \"clone\", \"-b\", branch,\n                                    git_url, install_path], shell=False,\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE)\n        for output in process.communicate():\n            if output != \"\":\n                for line in output.splitlines():\n                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def pip_install_deps(requirements_path):\n        \"\"\"Pip install a requirements.txt file and wait for finish.\"\"\"\n        process = subprocess.Popen([\"pip\", \"install\",\n                                    \"--target={}\".format(\n                                        DEFAULT_MODULE_DEPS_PATH),\n                                    \"--ignore-installed\",\n                                    \"-r\", requirements_path],\n                                   shell=False,\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE)\n        for output in process.communicate():\n            if output != \"\":\n                for line in output.splitlines():\n                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def create_default_config(config_path):\n        \"\"\"Create a default config file based on the included example.\"\"\"\n        _LOGGER.info(\"Creating %s.\", config_path)\n        config_dir, _ = os.path.split(config_path)\n        if not os.path.isdir(config_dir):\n            os.makedirs(config_dir)\n        shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)\n        return config_path\n\n    @staticmethod\n    def _reload_modules(modules):\n        for module in modules:\n            importlib.reload(module[\"module\"])\n\n    def load_config_file(self, config_paths):\n        \"\"\"Load a yaml config file from path.\"\"\"\n        config_path = \"\"\n        for possible_path in config_paths:\n            if not os.path.isfile(possible_path):\n                _LOGGER.debug(\"Config file \" + possible_path +\n                              \" not found\")\n            else:\n                config_path = possible_path\n                break\n\n        if not config_path:\n            _LOGGER.info(\"No configuration files found.\")\n            config_path = self.create_default_config(DEFAULT_CONFIG_PATH)\n\n        try:\n            with open(config_path, 'r') as stream:\n                _LOGGER.info(\"Loaded config from %s\", config_path)\n                return yaml.load(stream)\n        except yaml.YAMLError as error:\n            self.opsdroid.critical(error, 1)\n        except FileNotFoundError as error:\n            self.opsdroid.critical(str(error), 1)\n\n    def setup_modules_directory(self, config):\n        \"\"\"Create and configure the modules directory.\"\"\"\n        module_path = os.path.expanduser(\n            config.get(\"module-path\", DEFAULT_MODULES_PATH))\n        sys.path.append(module_path)\n\n        if not os.path.isdir(module_path):\n            os.makedirs(module_path, exist_ok=True)\n\n        self.modules_directory = os.path.join(module_path, MODULES_DIRECTORY)\n\n        # Create modules directory if doesn't exist\n        if not os.path.isdir(self.modules_directory):\n            os.makedirs(self.modules_directory)\n\n    def load_modules_from_config(self, config):\n        \"\"\"Load all module types based on config.\"\"\"\n        _LOGGER.debug(\"Loading modules from config\")\n\n        self.setup_modules_directory(config)\n\n        connectors, databases, skills = None, None, None\n\n        if 'databases' in config.keys():\n            databases = self._load_modules('database', config['databases'])\n        else:\n            _LOGGER.warning(\"No databases in configuration\")\n\n        if 'skills' in config.keys():\n            skills = self._load_modules('skill', config['skills'])\n            self.opsdroid.skills = []\n            self._reload_modules(skills)\n        else:\n            self.opsdroid.critical(\n                \"No skills in configuration, at least 1 required\", 1)\n\n        if 'connectors' in config.keys():\n            connectors = self._load_modules('connector', config['connectors'])\n        else:\n            self.opsdroid.critical(\n                \"No connectors in configuration, at least 1 required\", 1)\n\n        return connectors, databases, skills\n\n    def _load_modules(self, modules_type, modules):\n        \"\"\"Install and load modules.\"\"\"\n        _LOGGER.debug(\"Loading \" + modules_type + \" modules\")\n        loaded_modules = []\n\n        if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):\n            os.makedirs(DEFAULT_MODULE_DEPS_PATH)\n        sys.path.append(DEFAULT_MODULE_DEPS_PATH)\n\n        for module in modules:\n\n            # Set up module config\n            config = module\n            config = {} if config is None else config\n            config[\"name\"] = module[\"name\"]\n            config[\"type\"] = modules_type\n            config[\"module_path\"] = self.build_module_path(\"import\", config)\n            config[\"install_path\"] = self.build_module_path(\"install\", config)\n            if \"branch\" not in config:\n                config[\"branch\"] = DEFAULT_MODULE_BRANCH\n\n            # Remove module for reinstall if no-cache set\n            self.check_cache(config)\n\n            # Install module\n            self._install_module(config)\n\n            # Import module\n            self.current_import_config = config\n            module = self.import_module(config)\n            if module is not None:\n                loaded_modules.append({\n                    \"module\": module,\n                    \"config\": config})\n            else:\n                _LOGGER.error(\n                    \"Module \" + config[\"name\"] + \" failed to import\")\n\n        return loaded_modules\n\n    def _install_module(self, config):\n        # pylint: disable=R0201\n        \"\"\"Install a module.\"\"\"\n        _LOGGER.debug(\"Installing \" + config[\"name\"])\n\n        if os.path.isdir(config[\"install_path\"]) or \\\n                os.path.isfile(config[\"install_path\"] + \".py\"):\n            # TODO Allow for updating or reinstalling of modules\n            _LOGGER.debug(\"Module \" + config[\"name\"] +\n                          \" already installed, skipping\")\n            return\n\n        if \"path\" in config:\n            self._install_local_module(config)\n        else:\n            self._install_git_module(config)\n\n        if os.path.isdir(config[\"install_path\"]):\n            _LOGGER.debug(\"Installed \" + config[\"name\"] +\n                          \" to \" + config[\"install_path\"])\n        else:\n            _LOGGER.debug(\"Install of \" + config[\"name\"] + \" failed\")\n\n        # Install module dependancies\n        if os.path.isfile(config[\"install_path\"] + \"/requirements.txt\"):\n            self.pip_install_deps(config[\"install_path\"] +\n                                  \"/requirements.txt\")\n\n    def _install_git_module(self, config):\n        \"\"\"Install a module from a git repository.\"\"\"\n        if config is not None and \"repo\" in config:\n            git_url = config[\"repo\"]\n        else:\n            git_url = DEFAULT_GIT_URL + config[\"type\"] + \\\n                        \"-\" + config[\"name\"] + \".git\"\n\n        if any(prefix in git_url for prefix in [\"http\", \"https\", \"ssh\"]):\n            # TODO Test if url or ssh path exists\n            # TODO Handle github authentication\n            _LOGGER.debug(\"Cloning from remote repository\")\n            self.git_clone(git_url, config[\"install_path\"],\n                           config[\"branch\"])\n        else:\n            if os.path.isdir(git_url):\n                _LOGGER.debug(\"Cloning from local repository\")\n                self.git_clone(git_url, config[\"install_path\"],\n                               config[\"branch\"])\n            else:\n                _LOGGER.debug(\"Could not find local git repo \" + git_url)\n\n    @staticmethod\n    def _install_local_module(config):\n        \"\"\"Install a module from a local path.\"\"\"\n        installed = False\n        config[\"path\"] = os.path.expanduser(config[\"path\"])\n\n        installdir, _ = os.path.split(config[\"install_path\"])\n        if not os.path.isdir(installdir):\n            os.makedirs(installdir, exist_ok=True)\n\n        if os.path.isdir(config[\"path\"]):\n            shutil.copytree(config[\"path\"], config[\"install_path\"])\n            installed = True\n\n        if os.path.isfile(config[\"path\"]):\n            os.makedirs(config[\"install_path\"], exist_ok=True)\n            shutil.copyfile(config[\"path\"], config[\"install_path\"] +\n                            \"/__init__.py\")\n            installed = True\n\n        if not installed:\n            _LOGGER.error(\"Failed to install from \" + config[\"path\"])\n", "path": "opsdroid/loader.py"}], "after_files": [{"content": "\"\"\"Class for loading in modules to OpsDroid.\"\"\"\n\nimport logging\nimport os\nimport sys\nimport shutil\nimport subprocess\nimport importlib\nimport re\nimport yaml\nfrom opsdroid.const import (\n    DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,\n    DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,\n    DEFAULT_MODULE_DEPS_PATH)\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass Loader:\n    \"\"\"Class to load in config and modules.\"\"\"\n\n    def __init__(self, opsdroid):\n        \"\"\"Create object with opsdroid instance.\"\"\"\n        self.opsdroid = opsdroid\n        self.modules_directory = None\n        self.current_import_config = None\n        _LOGGER.debug(\"Loaded loader\")\n\n    @staticmethod\n    def import_module(config):\n        \"\"\"Import module namespace as variable and return it.\"\"\"\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(error)\n\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"])\n            _LOGGER.debug(error)\n\n        _LOGGER.error(\"Failed to load \" + config[\"type\"] +\n                      \" \" + config[\"module_path\"])\n        return None\n\n    @staticmethod\n    def check_cache(config):\n        \"\"\"Remove module if 'no-cache' set in config.\"\"\"\n        if \"no-cache\" in config \\\n                and config[\"no-cache\"]:\n            _LOGGER.debug(\"'no-cache' set, removing \" + config[\"install_path\"])\n            if os.path.isdir(config[\"install_path\"]):\n                shutil.rmtree(config[\"install_path\"])\n            if os.path.isfile(config[\"install_path\"] + \".py\"):\n                os.remove(config[\"install_path\"] + \".py\")\n\n    def build_module_path(self, path_type, config):\n        \"\"\"Generate the module path from name and type.\"\"\"\n        if path_type == \"import\":\n            return MODULES_DIRECTORY + \".\" + config[\"type\"] + \\\n                        \".\" + config[\"name\"]\n        elif path_type == \"install\":\n            return self.modules_directory + \"/\" + config[\"type\"] + \\\n                        \"/\" + config[\"name\"]\n\n    @staticmethod\n    def git_clone(git_url, install_path, branch):\n        \"\"\"Clone a git repo to a location and wait for finish.\"\"\"\n        process = subprocess.Popen([\"git\", \"clone\", \"-b\", branch,\n                                    git_url, install_path], shell=False,\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE)\n        for output in process.communicate():\n            if output != \"\":\n                for line in output.splitlines():\n                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def pip_install_deps(requirements_path):\n        \"\"\"Pip install a requirements.txt file and wait for finish.\"\"\"\n        process = subprocess.Popen([\"pip\", \"install\",\n                                    \"--target={}\".format(\n                                        DEFAULT_MODULE_DEPS_PATH),\n                                    \"--ignore-installed\",\n                                    \"-r\", requirements_path],\n                                   shell=False,\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE)\n        for output in process.communicate():\n            if output != \"\":\n                for line in output.splitlines():\n                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def create_default_config(config_path):\n        \"\"\"Create a default config file based on the included example.\"\"\"\n        _LOGGER.info(\"Creating %s.\", config_path)\n        config_dir, _ = os.path.split(config_path)\n        if not os.path.isdir(config_dir):\n            os.makedirs(config_dir)\n        shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)\n        return config_path\n\n    @staticmethod\n    def _reload_modules(modules):\n        for module in modules:\n            importlib.reload(module[\"module\"])\n\n    def load_config_file(self, config_paths):\n        \"\"\"Load a yaml config file from path.\"\"\"\n        config_path = \"\"\n        for possible_path in config_paths:\n            if not os.path.isfile(possible_path):\n                _LOGGER.debug(\"Config file \" + possible_path +\n                              \" not found\")\n            else:\n                config_path = possible_path\n                break\n\n        if not config_path:\n            _LOGGER.info(\"No configuration files found.\")\n            config_path = self.create_default_config(DEFAULT_CONFIG_PATH)\n\n        env_var_pattern = re.compile(r'^\\$([A-Z_]*)$')\n        yaml.add_implicit_resolver(\"!envvar\", env_var_pattern)\n\n        def envvar_constructor(loader, node):\n            \"\"\"Yaml parser for env vars.\"\"\"\n            value = loader.construct_scalar(node)\n            [env_var] = env_var_pattern.match(value).groups()\n            return os.environ[env_var]\n\n        yaml.add_constructor('!envvar', envvar_constructor)\n\n        try:\n            with open(config_path, 'r') as stream:\n                _LOGGER.info(\"Loaded config from %s\", config_path)\n                return yaml.load(stream)\n        except yaml.YAMLError as error:\n            self.opsdroid.critical(error, 1)\n        except FileNotFoundError as error:\n            self.opsdroid.critical(str(error), 1)\n\n    def setup_modules_directory(self, config):\n        \"\"\"Create and configure the modules directory.\"\"\"\n        module_path = os.path.expanduser(\n            config.get(\"module-path\", DEFAULT_MODULES_PATH))\n        sys.path.append(module_path)\n\n        if not os.path.isdir(module_path):\n            os.makedirs(module_path, exist_ok=True)\n\n        self.modules_directory = os.path.join(module_path, MODULES_DIRECTORY)\n\n        # Create modules directory if doesn't exist\n        if not os.path.isdir(self.modules_directory):\n            os.makedirs(self.modules_directory)\n\n    def load_modules_from_config(self, config):\n        \"\"\"Load all module types based on config.\"\"\"\n        _LOGGER.debug(\"Loading modules from config\")\n\n        self.setup_modules_directory(config)\n\n        connectors, databases, skills = None, None, None\n\n        if 'databases' in config.keys():\n            databases = self._load_modules('database', config['databases'])\n        else:\n            _LOGGER.warning(\"No databases in configuration\")\n\n        if 'skills' in config.keys():\n            skills = self._load_modules('skill', config['skills'])\n            self.opsdroid.skills = []\n            self._reload_modules(skills)\n        else:\n            self.opsdroid.critical(\n                \"No skills in configuration, at least 1 required\", 1)\n\n        if 'connectors' in config.keys():\n            connectors = self._load_modules('connector', config['connectors'])\n        else:\n            self.opsdroid.critical(\n                \"No connectors in configuration, at least 1 required\", 1)\n\n        return connectors, databases, skills\n\n    def _load_modules(self, modules_type, modules):\n        \"\"\"Install and load modules.\"\"\"\n        _LOGGER.debug(\"Loading \" + modules_type + \" modules\")\n        loaded_modules = []\n\n        if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):\n            os.makedirs(DEFAULT_MODULE_DEPS_PATH)\n        sys.path.append(DEFAULT_MODULE_DEPS_PATH)\n\n        for module in modules:\n\n            # Set up module config\n            config = module\n            config = {} if config is None else config\n            config[\"name\"] = module[\"name\"]\n            config[\"type\"] = modules_type\n            config[\"module_path\"] = self.build_module_path(\"import\", config)\n            config[\"install_path\"] = self.build_module_path(\"install\", config)\n            if \"branch\" not in config:\n                config[\"branch\"] = DEFAULT_MODULE_BRANCH\n\n            # Remove module for reinstall if no-cache set\n            self.check_cache(config)\n\n            # Install module\n            self._install_module(config)\n\n            # Import module\n            self.current_import_config = config\n            module = self.import_module(config)\n            if module is not None:\n                loaded_modules.append({\n                    \"module\": module,\n                    \"config\": config})\n            else:\n                _LOGGER.error(\n                    \"Module \" + config[\"name\"] + \" failed to import\")\n\n        return loaded_modules\n\n    def _install_module(self, config):\n        # pylint: disable=R0201\n        \"\"\"Install a module.\"\"\"\n        _LOGGER.debug(\"Installing \" + config[\"name\"])\n\n        if os.path.isdir(config[\"install_path\"]) or \\\n                os.path.isfile(config[\"install_path\"] + \".py\"):\n            # TODO Allow for updating or reinstalling of modules\n            _LOGGER.debug(\"Module \" + config[\"name\"] +\n                          \" already installed, skipping\")\n            return\n\n        if \"path\" in config:\n            self._install_local_module(config)\n        else:\n            self._install_git_module(config)\n\n        if os.path.isdir(config[\"install_path\"]):\n            _LOGGER.debug(\"Installed \" + config[\"name\"] +\n                          \" to \" + config[\"install_path\"])\n        else:\n            _LOGGER.debug(\"Install of \" + config[\"name\"] + \" failed\")\n\n        # Install module dependancies\n        if os.path.isfile(config[\"install_path\"] + \"/requirements.txt\"):\n            self.pip_install_deps(config[\"install_path\"] +\n                                  \"/requirements.txt\")\n\n    def _install_git_module(self, config):\n        \"\"\"Install a module from a git repository.\"\"\"\n        if config is not None and \"repo\" in config:\n            git_url = config[\"repo\"]\n        else:\n            git_url = DEFAULT_GIT_URL + config[\"type\"] + \\\n                        \"-\" + config[\"name\"] + \".git\"\n\n        if any(prefix in git_url for prefix in [\"http\", \"https\", \"ssh\"]):\n            # TODO Test if url or ssh path exists\n            # TODO Handle github authentication\n            _LOGGER.debug(\"Cloning from remote repository\")\n            self.git_clone(git_url, config[\"install_path\"],\n                           config[\"branch\"])\n        else:\n            if os.path.isdir(git_url):\n                _LOGGER.debug(\"Cloning from local repository\")\n                self.git_clone(git_url, config[\"install_path\"],\n                               config[\"branch\"])\n            else:\n                _LOGGER.debug(\"Could not find local git repo \" + git_url)\n\n    @staticmethod\n    def _install_local_module(config):\n        \"\"\"Install a module from a local path.\"\"\"\n        installed = False\n        config[\"path\"] = os.path.expanduser(config[\"path\"])\n\n        installdir, _ = os.path.split(config[\"install_path\"])\n        if not os.path.isdir(installdir):\n            os.makedirs(installdir, exist_ok=True)\n\n        if os.path.isdir(config[\"path\"]):\n            shutil.copytree(config[\"path\"], config[\"install_path\"])\n            installed = True\n\n        if os.path.isfile(config[\"path\"]):\n            os.makedirs(config[\"install_path\"], exist_ok=True)\n            shutil.copyfile(config[\"path\"], config[\"install_path\"] +\n                            \"/__init__.py\")\n            installed = True\n\n        if not installed:\n            _LOGGER.error(\"Failed to install from \" + config[\"path\"])\n", "path": "opsdroid/loader.py"}]}