Repository: golemfactory/dapp-manager (language: python)

Issue:
Integrate with the dapp-runner
1. Use the "real" dapp-runner
2. Fix the interface of the `tests/assets/mock_dapp_runner.py` so that it's exactly the same (if there's anything to fix there)


Discussion / hints:

Fix patch:
diff --git a/cli_sample_usage.sh b/cli_sample_usage.sh
new file mode 100755
index 0000000..2c1501f
--- /dev/null
+++ b/cli_sample_usage.sh
@@ -0,0 +1,46 @@
+function dm () {
+    python3 -m yagna_dapp_manager "$@"
+}
+
+echo "1. Current apps"
+dm list
+
+APP_ID_1=$(DAPP_RUNNER_EXEC=tests/assets/mock_dapp_runner.py dm start --config ttt.yml zzz.yml)
+echo "2. Started app $APP_ID_1"
+
+echo "3. Current apps"
+dm list
+
+APP_ID_2=$(DAPP_RUNNER_EXEC=tests/assets/mock_dapp_runner.py dm start --config ttt.yml zzz.yml)
+echo "4. Started app $APP_ID_2"
+
+echo "5. Current apps"
+dm list
+
+sleep 1
+
+echo "6. Print state file for $APP_ID_1"
+dm raw-state --app-id $APP_ID_1
+
+echo "7. Print data file for $APP_ID_1"
+dm raw-data --app-id $APP_ID_1
+
+sleep 2
+
+echo "8. Print state file for $APP_ID_1"
+dm raw-state --app-id $APP_ID_1
+
+echo "9. Print data file for $APP_ID_1"
+dm raw-data --app-id $APP_ID_1
+
+echo "10. Print data file for $APP_ID_2"
+dm raw-data --app-id $APP_ID_2
+
+echo "11. Stopping $APP_ID_1 with default timeout (should succeed -> app_id printed)"
+dm stop --app-id $APP_ID_1
+
+echo "12. Stopping $APP_ID_2 with short   timeout (should fail -> no print)"
+dm stop --app-id $APP_ID_2 --timeout 1
+
+echo "13. Killing  $APP_ID_2"
+dm kill --app-id $APP_ID_2
diff --git a/yagna_dapp_manager/__main__.py b/yagna_dapp_manager/__main__.py
new file mode 100644
index 0000000..50ac88c
--- /dev/null
+++ b/yagna_dapp_manager/__main__.py
@@ -0,0 +1,88 @@
+from pathlib import Path
+from typing import Tuple
+
+import click
+
+from .dapp_manager import DappManager
+
+
+def _with_app_id(wrapped_func):
+    wrapped_func = click.option(
+        "--app-id",
+        type=str,
+        required=True,
+        help="ID of an existing distributed application.",
+    )(wrapped_func)
+    return wrapped_func
+
+
+@click.group()
+def _cli():
+    pass
+
+
+@_cli.command()
+@click.option(
+    "--config",
+    "-c",
+    required=True,
+    type=Path,
+    help="Path to the file containing yagna-specific config.",
+)
+@click.argument(
+    "descriptors",
+    nargs=-1,
+    required=True,
+    type=Path,
+)
+def start(descriptors: Tuple[Path], *, config: Path):
+    dapp = DappManager.start(*descriptors, config=config)
+    print(dapp.app_id)
+
+
+@_cli.command()
+def list():
+    app_ids = DappManager.list()
+    if app_ids:
+        print("\n".join(app_ids))
+
+
+@_cli.command()
+@click.option(
+    "--timeout",
+    "-t",
+    type=int,
+    default=10,
+    help="Specify a shutdown timeout in seconds. Successful shutdown is indicated by the app_id print",
+)
+@_with_app_id
+def stop(*, app_id: str, timeout: int):
+    dapp = DappManager(app_id)
+    if dapp.stop(timeout):
+        print(app_id)
+
+
+@_cli.command()
+@_with_app_id
+def kill(*, app_id):
+    dapp = DappManager(app_id)
+    dapp.kill()
+    print(app_id)
+
+
+@_cli.command()
+@_with_app_id
+def raw_state(*, app_id):
+    dapp = DappManager(app_id)
+    print(dapp.raw_status())
+
+
+@_cli.command()
+@_with_app_id
+def raw_data(*, app_id):
+    dapp = DappManager(app_id)
+    print(dapp.raw_data())
+
+
+if __name__ == "__main__":
+    _cli()
diff --git a/yagna_dapp_manager/dapp_starter.py b/yagna_dapp_manager/dapp_starter.py
index 54374eb..c00390f 100644
--- a/yagna_dapp_manager/dapp_starter.py
+++ b/yagna_dapp_manager/dapp_starter.py
@@ -1,3 +1,4 @@
+import os
 from subprocess import Popen, DEVNULL
 from typing import List
 from pathlib import Path
@@ -22,5 +23,24 @@ def start(self) -> None:
         self.storage.save_pid(proc.pid)
 
     def _get_command(self):
+        return [self._executable()] + self._cli_args()
+
+    def _cli_args(self) -> List[str]:
+        """Return the dapp-runner CLI command and args."""
         # TODO: https://github.com/golemfactory/dapp-manager/issues/5
-        raise NotImplementedError
+        args = ["start"]
+        args += ["--config", str(self.config.resolve())]
+        args += ["--data", str(self.storage.data_file.resolve())]
+        args += ["--state", str(self.storage.status_file.resolve())]
+        args += [str(d.resolve()) for d in self.descriptors]
+        return args
+
+    def _executable(self) -> str:
+        """Return the "dapp-runner" executable - either set by the env variable or the default.
+
+        Env variable is intended mostly for the testing/debugging purposes."""
+        executable = os.environ["DAPP_RUNNER_EXEC"]
+        if not executable:
+            # TODO: https://github.com/golemfactory/dapp-manager/issues/5
+            raise NotImplementedError
+        return executable


Test patch:
diff --git a/tests/assets/mock_dapp_runner.py b/tests/assets/mock_dapp_runner.py
new file mode 100755
index 0000000..9c8bf73
--- /dev/null
+++ b/tests/assets/mock_dapp_runner.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+# Script that has a similar interface to the dapp-runner,
+# but only prints some things to the screen/files and waits forever.
+# Useful for testing.
+
+from pathlib import Path
+from time import sleep
+from typing import Tuple
+from itertools import count
+from random import random
+
+import click
+
+
+@click.group()
+def _cli():
+    pass
+
+
+@_cli.command()
+@click.option(
+    "--data",
+    "-d",
+    required=True,
+    type=Path,
+    help="Path to the data file.",
+)
+@click.option(
+    "--state",
+    "-s",
+    required=True,
+    type=Path,
+    help="Path to the state file.",
+)
+@click.option(
+    "--config",
+    "-c",
+    required=True,
+    type=Path,
+    help="Path to the file containing yagna-specific config.",
+)
+@click.argument(
+    "descriptors",
+    nargs=-1,
+    required=True,
+    type=Path,
+)
+def start(
+    descriptors: Tuple[Path],
+    **kwargs,
+):
+    print("mock_dapp_runner start", descriptors, kwargs)
+
+    data_file = open(kwargs["data"], "w", buffering=1)
+    state_file = open(kwargs["state"], "w", buffering=1)
+
+    try:
+        for i in count(1):
+            state_file.write(f"Running for {i} seconds\n")
+            if i == 3:
+                data_file.write(f"Important data received: {random()}\n")
+            sleep(1)
+    except KeyboardInterrupt:
+        state_file.write("Shutting down\n")
+        sleep(3)
+        state_file.write("Graceful shutdown finished\n")
+        data_file.close()
+        state_file.close()
+
+
+if __name__ == "__main__":
+    _cli()