Repository: MatterMiners/tardis

Issue:
AttributeError: 'str' object has no attribute 'run_command'
Trying out a site with `Slurm` adapter and `SSHExecutor`I always receive the following error:
```
File "/opt/cobald/lib64/python3.6/site-packages/tardis/adapters/sites/slurm.py", line 156, in handle_exceptions
yield
File "/opt/cobald/lib64/python3.6/site-packages/tardis/agents/siteagent.py", line 15, in deploy_resource
resource_attributes=resource_attributes
File "/opt/cobald/lib64/python3.6/site-packages/tardis/adapters/sites/slurm.py", line 100, in deploy_resource
result = await self._executor.run_command(request_command)
AttributeError: 'str' object has no attribute 'run_command'
```
Any idea what might go wrong here?

Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=h1) Report
> Merging [#119](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=desc) into [master](https://codecov.io/gh/MatterMiners/tardis/commit/60d00ae8a6adc720903632ef823941418210ccd7?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/MatterMiners/tardis/pull/119/graphs/tree.svg?width=650&token=BKeuDMxoaj&height=150&src=pr)](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #119      +/-   ##
==========================================
+ Coverage    97.6%   97.61%   +<.01%     
==========================================
  Files          39       39              
  Lines        1421     1424       +3     
==========================================
+ Hits         1387     1390       +3     
  Misses         34       34
```


| [Impacted Files](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tardis/configuration/configuration.py](https://codecov.io/gh/MatterMiners/tardis/pull/119/diff?src=pr&el=tree#diff-dGFyZGlzL2NvbmZpZ3VyYXRpb24vY29uZmlndXJhdGlvbi5weQ==) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=footer). Last update [60d00ae...20b478b](https://codecov.io/gh/MatterMiners/tardis/pull/119?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/source/executors/executors.rst b/docs/source/executors/executors.rst
index 2bb46bb..711d444 100644
--- a/docs/source/executors/executors.rst
+++ b/docs/source/executors/executors.rst
@@ -7,9 +7,13 @@ Executors
 .. container:: left-col
 
     All executors can be loaded using YAML tags using the (`!Tag`) syntax. More details are available in the
-    `PyYAML documentation`_
+    `PyYAML documentation`_.
+
+    Alternatively you can also use the legacy `COBalD object initialisation syntax`_ to construct executors.
+    But it is discouraged.
 
     .. _PyYAML documentation: https://pyyaml.org/wiki/PyYAMLDocumentation
+    .. _COBalD object initialisation syntax: https://cobald.readthedocs.io/en/latest/source/daemon/config.html#object-references
 
 Shell Executor
 --------------
@@ -26,6 +30,12 @@ Shell Executor
 
       !ShellExecutor
 
+    .. rubric:: Example configuration (`COBalD` legacy object initialisation)
+
+    .. code-block:: yaml
+
+        __type__: tardis.utilities.executors.shellexecutor.ShellExecutor
+
 SSH Executor
 ------------
 
@@ -49,3 +59,12 @@ SSH Executor
         client_keys:
           - /opt/tardis/ssh/tardis
 
+    .. rubric:: Example configuration (`COBalD` legacy object initialisation)
+
+    .. code-block:: yaml
+
+        __type__: tardis.utilities.executors.sshexecutor.SSHExecutor
+        host: login.dorie.somewherein.de
+        username: clown
+        client_keys:
+          - /opt/tardis/ssh/tardis
diff --git a/tardis/configuration/configuration.py b/tardis/configuration/configuration.py
index b02bc0f..f5ea52d 100644
--- a/tardis/configuration/configuration.py
+++ b/tardis/configuration/configuration.py
@@ -6,22 +6,26 @@ from ..utilities.attributedict import convert_to_attribute_dict
 from ..utilities.executors import *  # noqa: F403, F401
 from ..utilities.simulators import *  # noqa: F403, F401
 
+from cobald.daemon.config.mapping import Translator
+
 from base64 import b64encode
 import os
 import yaml
 
 
-def encode_user_data(obj):
+def translate_config(obj):
     if isinstance(obj, AttributeDict):
         for key, value in obj.items():
-            if key == "user_data":
+            if key == "user_data":  # base64 encode user data
                 with open(os.path.join(os.getcwd(), obj[key]), "rb") as f:
                     obj[key] = b64encode(f.read())
+            elif key == "__type__":  # do legacy object initialisation
+                return Translator().translate_hierarchy(obj)
             else:
-                obj[key] = encode_user_data(value)
+                obj[key] = translate_config(value)
         return obj
     elif isinstance(obj, list):
-        return [encode_user_data(item) for item in obj]
+        return [translate_config(item) for item in obj]
     else:
         return obj
 
@@ -42,5 +46,5 @@ class Configuration(Borg):
         """
         with open(config_file, "r") as config_file:
             self._shared_state.update(
-                encode_user_data(convert_to_attribute_dict(yaml.safe_load(config_file)))
+                translate_config(convert_to_attribute_dict(yaml.safe_load(config_file)))
             )


Test patch:
diff --git a/tests/configuration_t/Sites.yml b/tests/configuration_t/Sites.yml
index 8c5a8c9..4274fc6 100644
--- a/tests/configuration_t/Sites.yml
+++ b/tests/configuration_t/Sites.yml
@@ -1,8 +1,17 @@
 Sites:
   - name: EXOSCALE
     adapter: CloudStack
+  - name: SLURM
+    adapter: Slurm
 
 EXOSCALE:
   MachineTypeConfiguration:
     Micro:
-      user_data: tests/data/exoscale.ini
\ No newline at end of file
+      user_data: tests/data/exoscale.ini
+SLURM:
+  executor:
+    __type__: tardis.utilities.executors.sshexecutor.SSHExecutor
+    host: somehost.de
+    username: someuser
+    client_keys:
+      - "where the private key is"
diff --git a/tests/configuration_t/test_configuration.py b/tests/configuration_t/test_configuration.py
index 9c0ed59..81d1155 100644
--- a/tests/configuration_t/test_configuration.py
+++ b/tests/configuration_t/test_configuration.py
@@ -1,4 +1,5 @@
 from tardis.configuration.configuration import Configuration
+from tardis.utilities.executors.sshexecutor import SSHExecutor
 
 from unittest import TestCase
 import os
@@ -24,22 +25,37 @@ class TestConfiguration(TestCase):
         self.configuration1.load_config(os.path.join(self.test_path, "Sites.yml"))
         self.assertEqual(id(self.configuration1.Sites), id(self.configuration2.Sites))
         self.assertEqual(
-            self.configuration1.Sites, [dict(name="EXOSCALE", adapter="CloudStack")]
+            self.configuration1.Sites,
+            [
+                dict(name="EXOSCALE", adapter="CloudStack"),
+                dict(name="SLURM", adapter="Slurm"),
+            ],
         )
         self.assertEqual(
             self.configuration2.CloudStackAIO,
             {"api_key": "asdfghjkl", "api_secret": "qwertzuiop"},
         )
 
-    def test_base64_encoded_user_data(self):
-        result = (
+    def test_translate_config(self):
+        b64_result = (
             b"I2Nsb3VkLWNvbmZpZwoKd3JpdGVfZmlsZXM6CiAgLSBwYXRoOiAvZXRjL2huc2NpY2xvdWQvc2l0ZS1pZC5jZmcKICAgIGNvbn",
             b"RlbnQ6IHwKICAgICAgRXhvc2NhbGUKICAgIHBlcm1pc3Npb25zOiAnMDY0NCcK",
         )
         self.configuration1.load_config(os.path.join(self.test_path, "Sites.yml"))
         self.assertEqual(
             self.configuration1.EXOSCALE.MachineTypeConfiguration.Micro.user_data,
-            b"".join(result),
+            b"".join(b64_result),
+        )
+
+        executor = self.configuration1.SLURM.executor
+        self.assertIsInstance(executor, SSHExecutor)
+        self.assertEqual(
+            executor._parameters,
+            dict(
+                host="somehost.de",
+                username="someuser",
+                client_keys=["where the private key is"],
+            ),
         )
 
     def test_access_missing_attribute(self):