Repository: opsdroid/opsdroid

Issue:
Add --load-config flag
Need a flag for installing local skill dependencies when running a dockerized app. Should call the install new requirements.txt from skills loaded by `path` without running the bot


Discussion:
This flag basically needs to tell opsdroid to exit after running the [load method](https://github.com/opsdroid/opsdroid/blob/master/opsdroid/__main__.py#L121).

This would allow you to install all modules and their dependancies without running opsdroid itself. 
Can I take this one?
Hello @betojulio that would be great, please go ahead and submit a PR with this issue, if you need any help with anything you can ask us in [gitter](https://gitter.im/opsdroid/developers)
Cool, thanks...

On Thu, Mar 14, 2019 at 7:51 AM Fábio Rosado <notifications@github.com>
wrote:

> Hello @betojulio <https://github.com/betojulio> that would be great,
> please go ahead and submit a PR with this issue, if you need any help with
> anything you can ask us in gitter <https://gitter.im/opsdroid/developers>
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/opsdroid/opsdroid/issues/244#issuecomment-472863429>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AGlG8VDHZb1qccl32Ip5xwQDR78_mcBgks5vWlP2gaJpZM4Ppox9>
> .
>


Resolving patch:
diff --git a/opsdroid/cli/config.py b/opsdroid/cli/config.py
--- a/opsdroid/cli/config.py
+++ b/opsdroid/cli/config.py
@@ -3,11 +3,12 @@
 import click
 
 from opsdroid.cli.utils import (
+    build_config,
     edit_files,
-    warn_deprecated_cli_option,
-    validate_config,
     list_all_modules,
     path_option,
+    validate_config,
+    warn_deprecated_cli_option,
 )
 from opsdroid.const import EXAMPLE_CONFIG_FILE
 
@@ -125,3 +126,31 @@ def list_modules(ctx):
 
     """
     list_all_modules(ctx, ctx.obj, "config")
+
+
+@config.command()
+@click.pass_context
+@click.option(
+    "--verbose",
+    "verbose",
+    is_flag=True,
+    help="Turns logging level to debug to see all logging messages.",
+)
+def build(ctx, verbose):
+    """Load configuration, load modules and install dependencies.
+
+    This function loads the configuration and install all necessary
+    dependencies defined on a `requirements.txt` file inside the module.
+    If the flag `--verbose` is passed the logging level will be set as debug and
+    all logs will be shown to the user.
+
+
+    Args:
+        ctx (:obj:`click.Context`): The current click cli context.
+        verbose (bool): set the logging level to debug.
+
+    Returns:
+        int: the exit code. Always returns 0 in this case.
+
+    """
+    build_config(ctx, {"path": ctx.obj, "verbose": verbose}, "config")
diff --git a/opsdroid/cli/utils.py b/opsdroid/cli/utils.py
--- a/opsdroid/cli/utils.py
+++ b/opsdroid/cli/utils.py
@@ -1,6 +1,7 @@
 """Utilities for the opsdroid CLI commands."""
 
 import click
+import contextlib
 import gettext
 import os
 import logging
@@ -20,6 +21,8 @@
 )
 from opsdroid.helper import get_config_option
 from opsdroid.loader import Loader
+from opsdroid.logging import configure_logging
+
 
 _LOGGER = logging.getLogger("opsdroid")
 
@@ -226,3 +229,44 @@ def list_all_modules(ctx, path, value):
                 )
 
     ctx.exit(0)
+
+
+def build_config(ctx, params, value):
+    """Load configuration, load modules and install dependencies.
+
+    This function loads the configuration and install all necessary
+    dependencies defined on a `requirements.txt` file inside the module.
+    If the flag `--verbose` is passed the logging level will be set as debug and
+    all logs will be shown to the user.
+
+
+    Args:
+        ctx (:obj:`click.Context`): The current click cli context.
+        params (dict): a dictionary of all parameters pass to the click
+            context when invoking this function as a callback.
+        value (string): the value of this parameter after invocation.
+            It is either "config" or "log" depending on the program
+            calling this function.
+
+    Returns:
+        int: the exit code. Always returns 0 in this case.
+
+    """
+    click.echo("Opsdroid will build modules from config.")
+    path = params.get("path")
+
+    with contextlib.suppress(Exception):
+        check_dependencies()
+
+        config = load_config_file([path] if path else DEFAULT_CONFIG_LOCATIONS)
+
+        if params["verbose"]:
+            config["logging"] = {"level": "debug"}
+            configure_logging(config)
+
+        with OpsDroid(config=config) as opsdroid:
+
+            opsdroid.loader.load_modules_from_config(config)
+
+            click.echo(click.style("SUCCESS:", bg="green", bold=True), nl=False)
+            click.echo(" Opsdroid modules successfully built from config.")


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -266,3 +266,48 @@ def test_config_validate_from_path(self):
             self.assertTrue(click_echo.called)
             self.assertFalse(opsdroid_load.called)
             self.assertEqual(result.exit_code, 0)
+
+    def test_config_build_from_path(self):
+        with mock.patch.object(click, "echo") as click_echo, mock.patch(
+            "opsdroid.configuration.load_config_file"
+        ) as opsdroid_load:
+            runner = CliRunner()
+            from opsdroid.cli import config
+
+            result = runner.invoke(
+                config,
+                [
+                    "-f",
+                    os.path.abspath("tests/configs/full_valid.yaml"),
+                    "build",
+                    "--verbose",
+                ],
+            )
+            self.assertTrue(click_echo.called)
+            self.assertFalse(opsdroid_load.called)
+            self.assertEqual(result.exit_code, 0)
+
+    def test_config_build(self):
+        with mock.patch.object(click, "echo") as click_echo, mock.patch(
+            "opsdroid.configuration.load_config_file"
+        ) as opsdroid_load:
+            runner = CliRunner()
+            from opsdroid.cli.config import build
+
+            result = runner.invoke(build, [])
+            self.assertTrue(click_echo.called)
+            self.assertFalse(opsdroid_load.called)
+            self.assertEqual(result.exit_code, 0)
+
+    def test_config_build_exception(self):
+        with mock.patch.object(click, "echo") as click_echo, mock.patch(
+            "opsdroid.configuration.load_config_file"
+        ), mock.patch("opsdroid.loader") as mock_load:
+
+            mock_load.load_modules_from_config.side_effect = Exception()
+            runner = CliRunner()
+            from opsdroid.cli.config import build
+
+            result = runner.invoke(build, [])
+            self.assertTrue(click_echo.called)
+            self.assertEqual(result.exit_code, 0)