Repository: chaostoolkit/chaostoolkit

Issue:
Add a new argument to specify the path to the config file
While the default value is enough for generic cases, as soon as you want to embed the toolkit anywhere else, it is helpful to be able to provide a different path.


Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index 0de8567..1dacbf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -114,4 +114,5 @@ output.png
 chaos-report.json
 discovery.json
 journal.json
-chaostoolkit.log
\ No newline at end of file
+chaostoolkit.log
+.pytest_cache/
\ No newline at end of file
diff --git a/chaostoolkit/cli.py b/chaostoolkit/cli.py
index 22df1cb..53b68b9 100644
--- a/chaostoolkit/cli.py
+++ b/chaostoolkit/cli.py
@@ -15,7 +15,7 @@ from chaoslib.experiment import ensure_experiment_is_valid, load_experiment,\
     run_experiment
 from chaoslib.notification import notify, DiscoverFlowEvent, InitFlowEvent, \
     RunFlowEvent, ValidateFlowEvent
-from chaoslib.settings import load_settings
+from chaoslib.settings import load_settings, CHAOSTOOLKIT_CONFIG_PATH
 from chaoslib.types import Activity, Discovery, Experiment, Journal
 import click
 from click_plugins import with_plugins
@@ -61,10 +61,13 @@ def encoder(o: object) -> str:
               help='Disable logging to file entirely.')
 @click.option('--log-file', default="chaostoolkit.log", show_default=True,
               help="File path where to write the command's log.")
+@click.option('--settings', default=CHAOSTOOLKIT_CONFIG_PATH,
+              show_default=True, help="Path to the settings file.")
 @click.pass_context
 def cli(ctx: click.Context, verbose: bool = False,
         no_version_check: bool = False, change_dir: str = None,
-        no_log_file: bool = False, log_file: str = "chaostoolkit.log"):
+        no_log_file: bool = False, log_file: str = "chaostoolkit.log",
+        settings: str = CHAOSTOOLKIT_CONFIG_PATH):
     if verbose:
         logzero.loglevel(logging.DEBUG, update_custom_handlers=False)
         fmt = "%(color)s[%(asctime)s %(levelname)s] "\
@@ -90,6 +93,10 @@ def cli(ctx: click.Context, verbose: bool = False,
     logger.debug("#" * 79)
     logger.debug("Running command '{}'".format(subcommand))
 
+    ctx.obj = {}
+    ctx.obj["settings_path"] = click.format_filename(settings)
+    logger.debug("Using settings file '{}'".format(ctx.obj["settings_path"]))
+
     if not no_version_check:
         check_newer_version(command=subcommand)
 
@@ -106,11 +113,12 @@ def cli(ctx: click.Context, verbose: bool = False,
 @click.option('--no-validation', is_flag=True,
               help='Do not validate the experiment before running.')
 @click.argument('path', type=click.Path(exists=True))
-def run(path: str, journal_path: str = "./journal.json", dry: bool = False,
-        no_validation: bool = False) -> Journal:
+@click.pass_context
+def run(ctx: click.Context, path: str, journal_path: str = "./journal.json",
+        dry: bool = False, no_validation: bool = False) -> Journal:
     """Run the experiment given at PATH."""
     experiment = load_experiment(click.format_filename(path))
-    settings = load_settings()
+    settings = load_settings(ctx.obj["settings_path"])
 
     notify(settings, RunFlowEvent.RunStarted, experiment)
 
@@ -139,9 +147,10 @@ def run(path: str, journal_path: str = "./journal.json", dry: bool = False,
 
 @cli.command()
 @click.argument('path', type=click.Path(exists=True))
-def validate(path: str) -> Experiment:
+@click.pass_context
+def validate(ctx: click.Context, path: str) -> Experiment:
     """Validate the experiment at PATH."""
-    settings = load_settings()
+    settings = load_settings(ctx.obj["settings_path"])
     experiment = load_experiment(click.format_filename(path))
     try:
         notify(settings, ValidateFlowEvent.ValidateStarted, experiment)
@@ -166,11 +175,13 @@ def validate(path: str) -> Experiment:
               help='Path where to save the the discovery outcome.',
               show_default=True)
 @click.argument('package')
-def discover(package: str, discovery_path: str = "./discovery.json",
+@click.pass_context
+def discover(ctx: click.Context, package: str,
+             discovery_path: str = "./discovery.json",
              no_system_info: bool = False,
              no_install: bool = False) -> Discovery:
     """Discover capabilities and experiments."""
-    settings = load_settings()
+    settings = load_settings(ctx.obj["settings_path"])
     try:
         notify(settings, DiscoverFlowEvent.DiscoverStarted, package)
         discovery = disco(
@@ -198,12 +209,13 @@ def discover(package: str, discovery_path: str = "./discovery.json",
 @click.option('--experiment-path', default="./experiment.json",
               help='Path where to save the experiment.',
               show_default=True)
-def init(discovery_path: str = "./discovery.json",
+@click.pass_context
+def init(ctx: click.Context, discovery_path: str = "./discovery.json",
          experiment_path: str = "./experiment.json") -> Experiment:
     """
     Initialize a new experiment from discovered capabilities.
     """
-    settings = load_settings()
+    settings = load_settings(ctx.obj["settings_path"])
     notify(settings, InitFlowEvent.InitStarted)
     click.secho(
         "You are about to create an experiment.\n"


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..c5b1387
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+import tempfile
+import logging
+
+import logzero
+import pytest
+
+
+@pytest.fixture(scope='function')
+def log_file():
+    with tempfile.NamedTemporaryFile() as f:
+        yield f
diff --git a/tests/fixtures/fake_settings.yaml b/tests/fixtures/fake_settings.yaml
new file mode 100644
index 0000000..7db360d
--- /dev/null
+++ b/tests/fixtures/fake_settings.yaml
@@ -0,0 +1,5 @@
+notifications:
+- channel: test
+  module: chaosslack.notification
+  token: XYZ
+  type: plugin
\ No newline at end of file
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 336a146..83a68ab 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+import os
+
+from chaoslib.settings import CHAOSTOOLKIT_CONFIG_PATH
 import click
 from click.testing import CliRunner
 
@@ -28,3 +31,34 @@ def test_path_to_experiment_description_is_mandatory():
     assert result.exit_code == 2
     assert result.exception
     assert 'Error: Invalid value for "path": Path "invalid.jsn" does not exist.' in result.output
+
+
+def test_default_settings_file(log_file):
+    runner = CliRunner()
+    exp_path = os.path.join(
+        os.path.dirname(__file__), 'fixtures', 'well-formed-experiment.json')
+    result = runner.invoke(cli, [
+        '--log-file', log_file.name, 'run', exp_path])
+    assert result.exit_code == 1
+
+    log_file.seek(0)
+    log = log_file.read().decode('utf-8')
+    message = "Using settings file '{}'".format(CHAOSTOOLKIT_CONFIG_PATH)
+    assert message in log
+
+
+def test_specify_settings_file(log_file):
+    runner = CliRunner()
+    settings_path = os.path.join(
+        os.path.dirname(__file__), 'fixtures', 'fake_settings.yaml')
+    exp_path = os.path.join(
+        os.path.dirname(__file__), 'fixtures', 'well-formed-experiment.json')
+    result = runner.invoke(cli, [
+        '--log-file', log_file.name, '--settings', settings_path, 'run',
+        exp_path])
+    assert result.exit_code == 1
+
+    log_file.seek(0)
+    log = log_file.read().decode('utf-8')
+    message = "Using settings file '{}'".format(settings_path)
+    assert message in log