Repository: flyingcircusio/batou

Issue:
improve error message
I am trying to setup a local batou deployment (obviously to debug another problem), and with just a `dev.cfg` like 

```
[environment]
connect_method = local
```

I get this error message:
```
============================================ Configuring model ... =============================================

ERROR: Unexpected exception
        Traceback (most recent call last):
        File "/home/jugmac00/Projects/batou_buildout3/.batou/f2666082/lib/python3.10/site-packages/batou/deploy.py", line 228, in main
    step()
        File "/home/jugmac00/Projects/batou_buildout3/.batou/f2666082/lib/python3.10/site-packages/batou/deploy.py", line 98, in configure
    self.connections[0].join()
        IndexError: list index out of range
===================================== DEPLOYMENT FAILED (during configure) =====================================
```

The fix was to add a hosts declaration with some components... like
```
[hosts]
localhost =
    demo
```

The error message could be improved by including both what went wrong (e.g. missing hosts section/missing components...) and what to do to fix the problem (e.g. please do this and that)


Discussion:

Patch:
diff --git a/examples/errorsnohost/.batou.json b/examples/errorsnohost/.batou.json
new file mode 100644
index 00000000..66163b26
--- /dev/null
+++ b/examples/errorsnohost/.batou.json
@@ -0,0 +1,5 @@
+{
+    "migration": {
+        "version": 2400
+    }
+}
diff --git a/examples/errorsnohost/appenv b/examples/errorsnohost/appenv
new file mode 120000
index 00000000..fd4ab101
--- /dev/null
+++ b/examples/errorsnohost/appenv
@@ -0,0 +1,1 @@
+../../appenv.py
\ No newline at end of file
diff --git a/examples/errorsnohost/batou b/examples/errorsnohost/batou
new file mode 120000
index 00000000..1a3c4893
--- /dev/null
+++ b/examples/errorsnohost/batou
@@ -0,0 +1,1 @@
+appenv
\ No newline at end of file
diff --git a/examples/errorsnohost/environments/errorsnohost/environment.cfg b/examples/errorsnohost/environments/errorsnohost/environment.cfg
new file mode 100644
index 00000000..53ee8dd0
--- /dev/null
+++ b/examples/errorsnohost/environments/errorsnohost/environment.cfg
@@ -0,0 +1,2 @@
+[environment]
+connect_method = local
diff --git a/examples/errorsnohost/requirements.lock b/examples/errorsnohost/requirements.lock
new file mode 100644
index 00000000..2473b96d
--- /dev/null
+++ b/examples/errorsnohost/requirements.lock
@@ -0,0 +1,17 @@
+# appenv-requirements-hash: a12a4aceadd6cbfbc0c654d8540fe14587d0fee8084431a7fe37a5ca9cea5260
+-e ../../
+ConfigUpdater==3.1.1
+Jinja2==3.0.1
+MarkupSafe==2.0.1
+PyYAML==6.0.1
+certifi==2022.6.15
+charset-normalizer==2.0.12
+execnet==1.9.0
+idna==3.3
+importlib-metadata==4.8.3
+py==1.11.0
+remote-pdb==2.1.0
+requests==2.27.1
+typing_extensions==4.1.1
+urllib3==1.26.11
+zipp==3.6.0
diff --git a/examples/errorsnohost/requirements.txt b/examples/errorsnohost/requirements.txt
new file mode 100644
index 00000000..172801a6
--- /dev/null
+++ b/examples/errorsnohost/requirements.txt
@@ -0,0 +1,2 @@
+# appenv-python-preference: 3.6,3.7,3.8,3.9
+-e ../../
diff --git a/src/batou/deploy.py b/src/batou/deploy.py
index e2dc8ed5..c8e62d1c 100644
--- a/src/batou/deploy.py
+++ b/src/batou/deploy.py
@@ -7,7 +7,11 @@ import time
 import traceback
 from concurrent.futures import ThreadPoolExecutor
 
-from batou import ReportingException, SilentConfigurationError
+from batou import (
+    ConfigurationError,
+    ReportingException,
+    SilentConfigurationError,
+)
 from batou._output import TerminalBackend, output
 
 from .environment import Environment
@@ -98,7 +102,6 @@ class ConfigureErrors(ReportingException):
 
 
 class Deployment(object):
-
     _upstream = None
 
     def __init__(
@@ -111,7 +114,6 @@ class Deployment(object):
         predict_only=False,
         provision_rebuild=False,
     ):
-
         self.environment = Environment(
             environment, timeout, platform, provision_rebuild=provision_rebuild
         )
@@ -205,6 +207,11 @@ class Deployment(object):
             all_reporting_hostnames.add(reporting_hostname)
             all_errors.extend([(reporting_hostname, e) for e in errors])
             # collects all errors into (reporting_hostname, error) tuples
+        # if there are no connections, then we append a ConfigurationError
+        if not self.connections:
+            raise ConfigurationError.from_context(
+                "No host found in environment."
+            )
         # if there are no errors, we're done
         if not all_errors:
             return


Test patch:
diff --git a/src/batou/tests/test_deploy.py b/src/batou/tests/test_deploy.py
index 4da14a6a..de28fa86 100644
--- a/src/batou/tests/test_deploy.py
+++ b/src/batou/tests/test_deploy.py
@@ -60,3 +60,39 @@ ERROR: Secrets section for unknown component found
 ======================= DEPLOYMENT FAILED (during load) ========================
 """
     )  # noqa: E501 line too long
+
+
+def test_main_fails_if_no_host_in_environment(capsys):
+    os.chdir("examples/errorsnohost")
+
+    from batou.deploy import main
+
+    with pytest.raises(SystemExit) as r:
+        main(
+            environment="errorsnohost",
+            platform=None,
+            timeout=None,
+            dirty=False,
+            consistency_only=False,
+            predict_only=False,
+            jobs=None,
+            provision_rebuild=False,
+        )
+
+    assert r.value.code == 1
+
+    out, err = capsys.readouterr()
+    assert err == ""
+    assert out == Ellipsis(
+        """\
+batou/2... (cpython 3...)
+================================== Preparing ===================================
+main: Loading environment `errorsnohost`...
+main: Verifying repository ...
+main: Loading secrets ...
+================== Connecting hosts and configuring model ... ==================
+
+ERROR: No host found in environment.
+====================== DEPLOYMENT FAILED (during connect) ======================
+"""
+    )  # noqa: E501 line too long